【前后端的那些事】15min快速实现图片上传,预览功能(ElementPlus+Springboot)

文章目录

    • Element Plus + SpringBoot实现图片上传,预览,删除
        • 效果展示
      • 1. 后端代码
        • 1.1 controller
        • 1.2 service
      • 2. 前端代码
        • 2.1 路由创建
        • 2.2 api接口
        • 2.2 文件创建
      • 3. 前端上传组件封装

前言:最近写项目,发现了一些很有意思的功能,想写文章,录视频把这些内容记录下。但这些功能太零碎,如果为每个功能都单独搭建一个项目,这明显不合适。于是我想,就搭建一个项目,把那些我想将的小功能全部整合到一起。实现 搭一次环境,处处使用。

本文主要实现以下功能

  1. 图片上传

环境搭建
文章链接

已录制视频
视频链接

仓库地址
https://github.com/xuhuafeifei/fgbg-font-and-back.git

Element Plus + SpringBoot实现图片上传,预览,删除

效果展示
  • 提交样式
    在这里插入图片描述

  • 放大预览

在这里插入图片描述

  • 成功提交后端
    在这里插入图片描述

  • 访问url

在这里插入图片描述

  • 后端存储
    在这里插入图片描述

  • 根据url下载/访问图片

在这里插入图片描述

1. 后端代码

1.1 controller
import com.fgbg.common.utils.R;
import com.fgbg.demo.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;@RestController
@RequestMapping("/common/file")
public class FileController {@Autowired@Qualifier("localFileService")private FileService fileService;/*** 上传接口*/@RequestMapping("/upload")public R upload(@RequestParam("image") MultipartFile file) throws IOException {String url = fileService.uploadFile(file, UUID.randomUUID().toString().substring(0, 10)+ "-" + file.getOriginalFilename());return R.ok().put("data", url);}/*** 下载接口*/@RequestMapping("/download/{fileName}")public void download(@PathVariable("fileName") String fileName, HttpServletRequest request, HttpServletResponse response) {fileService.downloadFile(fileName, request, response);}/*** 删除接口*/@RequestMapping("/delete")public R deleteFile(@RequestParam String fileName) {boolean flag = fileService.deleteFile(fileName);return R.ok().put("data", flag);}
}
1.2 service

tip: 文件上传存储有多种解决方案,比如minio,阿里云…

笔者考虑到编写容易程度与文章核心解决问题,采用了最原始的存储方法,即本地存储。以后端所在服务器为存储容器,将前端上传的图片以FileIO的形式进行存储。

考虑到有多种存储方式,读者可以实现FileService接口,自行编写impl类,以达到不同的文件存储的具体实现方式

import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public interface FileService {/*** 上传图片, 返回url*/String uploadFile(MultipartFile file, String fileName) throws IOException;/*** 下载图片*/void downloadFile(String fileName, HttpServletRequest request, HttpServletResponse response);/*** 删除图片*/boolean deleteFile(String fileName);
}

impl

import com.fgbg.demo.service.FileService;
import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;/*** 基于本地的文件管理服务*/
@Service("localFileService")
public class LocalFileServiceImpl implements FileService {/*** 文件访问域名(请求下载的接口)*/private static final String DOMAIN = "http://localhost:9005/api_demo/common/file/download/";/*** 文件物理存储位置*/private static final String STORE_DIR = "E:\\B站视频创作\\前后端项目构建-小功能实现\\代码\\backend\\src\\main\\resources\\pict\\";/*** 上传图片, 返回url** @param file* @param fileName*/@Overridepublic String uploadFile(MultipartFile file, String fileName) throws IOException {// 获取文件流InputStream is = file.getInputStream();// 在服务器中存储文件FileUtils.copyInputStreamToFile(is, new File(STORE_DIR + fileName));// 返回图片urlString url = DOMAIN + fileName;System.out.println("文件url: " + url);return url;}/*** 下载图片** @param fileName*/@Overridepublic void downloadFile(String fileName, HttpServletRequest request, HttpServletResponse response) {// 获取真实的文件路径String filePath = STORE_DIR + fileName;System.out.println("++++完整路径为:"+filePath);try {// 下载文件// 设置响应头response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName);// 读取文件内容并写入输出流Files.copy(Paths.get(filePath), response.getOutputStream());response.getOutputStream().flush();} catch (IOException e) {response.setStatus(404);}}/*** 删除图片** @param fileName*/@Overridepublic boolean deleteFile(String fileName) {// 获取真实的文件路径String filePath = STORE_DIR + fileName;System.out.println("++++完整路径为:"+filePath);File file = new File(filePath);return file.delete();}
}

2. 前端代码

2.1 路由创建

/src/router/modules/file.ts

const { VITE_HIDE_HOME } = import.meta.env;
const Layout = () => import("@/layout/index.vue");export default {path: "/file",name: "file",component: Layout,redirect: "/pict",meta: {icon: "homeFilled",title: "文件",rank: 0},children: [{path: "/pict",name: "pict",component: () => import("@/views/file/pict.vue"),meta: {title: "图片",showLink: VITE_HIDE_HOME === "true" ? false : true}}]
} as RouteConfigsTable;
2.2 api接口

tip:

  • 文件上传只能用post
  • 前端部分图片封装为FormData对象
  • 请求头标明"Content-Type": "multipart/form-data"
import { http } from "@/utils/http";
import { R, baseUrlApi } from "./utils";/** upload batch */
export const uploadBatch = (data: FormData) => {return http.request<R<any>>("post", baseUrlApi("common/file/uploadList"), {data,headers: {"Content-Type": "multipart/form-data"}});
};/** upload */
export const upload = (data: FormData) => {return http.request<R<any>>("post", baseUrlApi("common/file/upload"), {data,headers: {"Content-Type": "multipart/form-data"}});
};
2.2 文件创建

/src/views/file/pict.vue

tip:

  • 图片封装为FormData

  • formdata添加图片信息时,使用的是append()方法. append(name: string, value: string | Blob)

  • append的第一个参数,对应的是后端@RequestParam("xxx") MultipartFile file中xxx的值,本文中后端批量上传接口,xxx值为’imageList’

  • Element Plus上传图片,图片数据中都会有一个新的字段数据raw,这个数据我们就理解成文件本身。像后端提交数据提交的也是raw本身,而非其余额外数据

    在这里插入图片描述

  • append第二个参数,提交的是fileList中每个文件元素的raw属性s数据

<template><el-uploadv-model:file-list="fileList"list-type="picture-card"multiple:auto-upload="false":on-preview="handlePictureCardPreview":on-remove="handleRemove"><el-icon><Plus /></el-icon></el-upload><el-dialog v-model="dialogVisible"><img w-full :src="dialogImageUrl" alt="Preview Image" /></el-dialog><el-button @click="submit">提交</el-button>
</template><script lang="ts" setup>
import { ref } from "vue";
import { Plus } from "@element-plus/icons-vue";
import { uploadBatch } from "/src/api/file.ts";
import type { UploadProps } from "element-plus";
import { ElMessage } from "element-plus";const submit = () => {console.log(fileList.value);// 封装formDataconst data = new FormData();// forEach遍历的时fileList.value, 所有element不需要.value去除代理fileList.value.forEach(element => {data.append("imageList", element.raw);});uploadBatch(data).then(res => {console.log(res);if (res.code === 0) {ElMessage.success("上传成功");} else {ElMessage.error("上传失败: " + res.msg);}});
};const fileList = ref();const dialogImageUrl = ref("");
const dialogVisible = ref(false);const handleRemove: UploadProps["onRemove"] = (uploadFile, uploadFiles) => {console.log(uploadFile, uploadFiles);
};const handlePictureCardPreview: UploadProps["onPreview"] = uploadFile => {dialogImageUrl.value = uploadFile.url!;dialogVisible.value = true;
};
</script>

3. 前端上传组件封装

如果没有组件封装需求,那就不需要修改代码。
组件封装视频链接

tip: 提交逻辑交由父组件实现

child.vue

<template><el-uploadv-model:file-list="localFileList"list-type="picture-card"multiple:auto-upload="false":on-preview="handlePictureCardPreview":on-remove="handleRemove"><el-icon><Plus /></el-icon></el-upload><el-dialog v-model="dialogVisible"><img w-full :src="dialogImageUrl" alt="Preview Image" /></el-dialog>
</template><script lang="ts" setup>
import { ref, watch } from "vue";
import { Plus } from "@element-plus/icons-vue";
import type { UploadProps } from "element-plus";// 定义数据
const props = defineProps({fileList: {type: Array,default: () => []}
});// 将父组件的数据拆解为子组件数据
const localFileList = ref(props.fileList);// 监听localFileList, 跟新父组件数据
watch(localFileList,newValue => {emits("update:fileList", newValue);},{deep: true}
);// 定义组件事件, 跟新fileList
const emits = defineEmits(["update:fileList"]);const dialogImageUrl = ref("");
const dialogVisible = ref(false);const handleRemove: UploadProps["onRemove"] = (uploadFile, uploadFiles) => {console.log(uploadFile, uploadFiles);
};const handlePictureCardPreview: UploadProps["onPreview"] = uploadFile => {dialogImageUrl.value = uploadFile.url!;dialogVisible.value = true;
};
</script>

父组件

<script setup lang="ts">
import Child from "./component/child.vue";
import { ref } from "vue";
import { ElMessage } from "element-plus";
import { uploadBatch } from "/src/api/file.ts";const fileList = ref();const submit = () => {console.log(fileList.value);// 封装formDataconst data = new FormData();// forEach遍历的时fileList.value, 所有element不需要.value去除代理fileList.value.forEach(element => {data.append("imageList", element.raw);});uploadBatch(data).then(res => {console.log(res);if (res.code === 0) {ElMessage.success("上传成功");} else {ElMessage.error("上传失败: " + res.msg);}});
};
</script><template><Child v-model:fileList="fileList" /><el-button @click="submit">提交</el-button>
</template><style lang="scss" scoped></style>

效果
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/632845.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络安全产品之认识WEB应用防火墙

随着B/S架构的广泛应用&#xff0c;Web应用的功能越来越丰富&#xff0c;蕴含着越来越有价值的信息&#xff0c;应用程序漏洞被恶意利用的可能性越来越大&#xff0c;因此成为了黑客主要的攻击目标。传统防火墙无法解析HTTP应用层的细节&#xff0c;对规则的过滤过于死板&#…

速盾网络:高防ip是什么

速盾网络&#xff1a;高防IP是什么 在当今信息化社会中&#xff0c;网络安全问题日益突出&#xff0c;各种网络攻击威胁层出不穷。为了保护企业的网络安全&#xff0c;提高网络业务的稳定性&#xff0c;高防IP应运而生。那么&#xff0c;什么是高防IP呢&#xff1f; 高防IP是…

Jackson标签的高阶使用样例--多继承/子类、对象id、JsonIdentityInfo、JsonTypeInfo、JsonSubTypes

1. 背景 最近笔者在开发大数据平台XSailboat 的 数据资产目录 模块。它的其中一个功能是能定义并查看资产数据。我们支持的资产类型不仅有关系数据库表&#xff0c;也支持Kafka主题&#xff0c;hdfs上的文件等。对于Kafka主题&#xff0c;hdfs文件等&#xff0c;它们没有强模式…

CSS Position总结:定位属性的实战技巧

CSS Position总结&#xff1a;定位属性的实战技巧 大家好&#xff0c;我是免费搭建查券返利机器人赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;在今天的文章中&#xff0c;我们将深入研究CSS中一个至关重要的属…

移动云助力智慧交通数智化升级

智慧交通是在整个交通运输领域充分利用物联网、空间感知、云计算、移动互联网等新一代信息技术&#xff0c;综合运用交通科学、系统方法、人工智能、知识挖掘等理论与工具&#xff0c;以全面感知、深度融合、主动服务、科学决策为目标&#xff0c;推动交通运输更安全、更高效、…

软件设计师5--CISC与RISC

软件设计师5--CISC与RISC 考点1&#xff1a;CISC与RISC有什么不同考点2&#xff1a;CISC与RISC比较&#xff0c;分哪些维度例题&#xff1a; 考点1&#xff1a;CISC与RISC有什么不同 考点2&#xff1a;CISC与RISC比较&#xff0c;分哪些维度 例题&#xff1a; 1、以下关于RISC…

微信小程序安卓系统下Input输入内容上移错位问题的解决办法

在较长的表单中&#xff0c;页面可能需要滑动&#xff0c; 在这种情况下&#xff0c;在苹果手机上使用Input显示正常&#xff0c;但是在安卓手机上就会出现输入内容上移错位的问题,严重影响使用 需要设置一个状态控制scroll-view是否允许滑动&#xff0c;当Input获取焦点是&am…

SD-WAN组网设计原则:灵活、安全、高效

在实现按需、灵活和安全的SD-WAN组网方案中&#xff0c;我们必须遵循一系列关键的设计原则&#xff0c;以确保网络的可靠性和效率。通过以下几点设计原则&#xff0c;SD-WAN能够满足企业对灵活性、安全性和高效性的迫切需求。 灵活的Overlay网络互联 SD-WAN通过IP地址在站点之间…

Unicode编码

文章目录 前言一、Unicode &#xff1f;二、前端工程师使用Unicode三、Javascript中处理 Unicode总结 前言 一、Unicode &#xff1f; Unicode 是一种字符编码标准&#xff0c;旨在为世界上所有的字符&#xff08;包括各种语言、符号和特殊字符&#xff09;提供唯一的数字标识…

UniAPP社区论坛项目实战--社区服务 API 接口文档

社区服务 API 接口文档 社区服务 API 接口文档基础相关信息一、广告管理1.1 查询所有广告位1.2 获取一个广告位的广告列表1.3 批量获取广告列表 二、动态管理2.1 批量获取动态列表信息2.2 获取指定 ID 动态详情2.3 创建一条动态2.4 点赞、取消点赞、点赞列表 当前动态2.5 评论指…

深度求索开源国内首个 MoE 大模型 | DeepSeekMoE:在专家混合语言模型中实现终极专家专业化

文章目录 一、前言二、主要内容三、总结 &#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、前言 在大语言模型时代&#xff0c;混合专家模型&#xff08;MoE&#xff09;是一种很有前途的架构&#xff0c;用于在扩展模型参数时管理计算成本。然而&a…

旅游项目day03

1. 前端整合后端发短信接口 2. 注册功能 后端提供注册接口&#xff0c;接受前端传入的参数&#xff0c;创建新的用户对象&#xff0c;保存到数据库。 接口设计&#xff1a; 实现步骤&#xff1a; 手机号码唯一性校验&#xff08;后端一定要再次校验手机号唯一性&#xff09…

Vray渲染效果图材质参数设置

渲染是创造出引人入胜视觉效果的关键步骤&#xff0c;在视觉艺术领域尤为重要。不过&#xff0c;渲染作为一个资源密集型的过程&#xff0c;每当面对它时&#xff0c;我们往往都会遭遇到时间消耗和资源利用的巨大挑战。幸运的是&#xff0c;有几种方法能够帮助我们优化渲染&…

【51单片机】数码管的静态与动态显示(含消影)

数码管在现实生活里是非常常见的设备&#xff0c;例如 这些数字的显示都是数码管的应用。 目录 静态数码管&#xff1a;器件介绍&#xff1a;数码管的使用&#xff1a;译码器的使用&#xff1a;缓冲器&#xff1a; 实现原理&#xff1a;完整代码&#xff1a; 动态数码管&#…

centos 升级 openssh 到9.4p1版本

#参考网站 https://leheavengame.com/article/65a29edae7b7e20d2e1fbf7c 操作步骤 将openssl-1.1.1t.tar.gz openssh-9.4p1.tar.gz 和此脚本&#xff0c; 提前上传到服务器&#xff0c; 如果yum是内网源 &#xff0c;进入 cd /etc/yum.repos.d/ 下&#xff0c;删除无关的源&…

Python语法回答5:文件+进程+异常+枚举

文件 处理文件的基本操作有哪些步骤 打开文件文件处理&#xff08;如读写&#xff09;关闭文件 文件读写的常用函数是什么 读 file.read()file.readline()file.readlines() 写 file.write()file.writelines() 文件读写的常用模式有哪些 wwwbwbrrrbrbaa with语句文件操…

【Bug】.net6 cap总线+rabbitmq延时消息收不到

文章目录 问题问题代码原因解决处理Bug的具体步骤 问题 我有两个服务一个叫05一个叫15 然后用的cap总线rabbitmq 05消息队列发了一条延时消息&#xff0c;到时间了05服务的订阅者能收到 15服务订阅同一个消息的没收到(cap的cashboard)&#xff08;手动requeue05和15都能收到&a…

Redis学习指南(18)-Redis的AOF持久化介绍

Redis是一种高性能的开源键值存储系统&#xff0c;通过将数据存储在内存中以提供快速读写访问。然而&#xff0c;由于内存有限&#xff0c;如果Redis服务器意外崩溃或断电&#xff0c;所有数据将会丢失。为了解决这个问题&#xff0c;Redis提供了一种称为AOF&#xff08;Append…

Apache安全及优化

配置第一台虚拟机 VM1网卡 yum仓库 挂载磁盘 上传3个软件包到/目录 到/目录下进行解压缩 tar xf apr-1.6.2.tar.gz tar xf apr-util-1.6.0.tar.gz tar -xjf httpd-2.4.29.tar.bz2 mv apr-1.6.2 httpd-2.4.29/srclib/apr mv apr-util-1.6…

【Redis】基于Token单点登录

基于Token单点登录 获取验证码 流程图 #mermaid-svg-DLGHgCofEYXVSmI5 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-DLGHgCofEYXVSmI5 .error-icon{fill:#552222;}#mermaid-svg-DLGHgCofEYXVSmI5 .error-text{f…