【前后端的那些事】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.hqwc.cn/news/413888.html

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

相关文章

Maven《二》-- Maven的安装与配置(亲测成功版)

目录 &#x1f436;2.1 Maven的安装条件 &#x1f436;2.2 Maven安装步骤 1. 检查本地%JAVA_HOME% 2. 解压maven 3. 配置maven的环境变量 4. 校验maven是否配置成功 5. 配置本地仓库 &#x1f436;2.3 Idea配置本地Maven软件 &#x1f436;2.1 Maven的安装条件 各个工具…

three.js 缓动算法.easing(渐入相机动画)

效果&#xff1a;淡入&#xff0c;靠近物体 代码&#xff1a; <template><div><el-container><el-main><div class"box-card-left"><div id"threejs" style"border: 1px solid red"></div><div c…

基于网络爬虫的天气数据分析

二、网络爬虫设计 网络爬虫原理 网络爬虫是一种自动化程序&#xff0c;用于从互联网上获取数据。其工作原理可以分为以下几个步骤&#xff1a; 定义起始点&#xff1a;网络爬虫首先需要定义一个或多个起始点&#xff08;URL&#xff09;&#xff0c;从这些起始点开始抓取数据…

Spring | Spring中的Bean--下

Spring中的Bean: 4.Bean的生命周期5.Bean的配装配式 ( 添加Bean到IOC容器的方式 依赖注入的方式 )5.1 基于XML的配置5.2 基于Annotation (注解) 的装配 (更常用&#xff09;5.3 自动装配 4.Bean的生命周期 Spring容器可以管理 singleton作用域的Bean的生命周期&#xff0c;在此…

DBA技术栈(三):MySQL 性能影响因素

文章目录 前言一、影响MySQL性能的因素1.1 商业上的需求1.2 应用架构规划1.3 查询语句使用方式1.4 Schema的设计1.5 硬件环境 总结 前言 大部分人都一致认为一个数据库应用系统&#xff08;这里的数据库应用系统概指所有使用数据库的系统&#xff09;的性能瓶颈最容易出现在数…

Go 中 slice 的 In 功能实现探索

文章目录 遍历二分查找map key性能总结 之前在知乎看到一个问题&#xff1a;为什么 Golang 没有像 Python 中 in 一样的功能&#xff1f;于是&#xff0c;搜了下这个问题&#xff0c;发现还是有不少人有这样的疑问。 补充&#xff1a;本文写于 2019 年。GO 现在已经支持泛型&am…

TDengine 企业级功能:存储引擎对多表低频场景优化工作分享

在去年 8 月份发布的 3.1.0.0 版本中&#xff0c;TDengine 进行了一系列重要的企业级功能更新&#xff0c;其中包括对多表低频场景写入性能的大幅优化。这一优化工作为有此需求的用户提供了更大的便捷性和易用性。在本文中&#xff0c;TDengine 的资深研发将对此次优化工作进行…

Spring-BeanPostProcessor PostConstruct init InitializingBean 执行顺序

执行顺序探究 新建一个对象用于测试 Component public class Student implements InitializingBean {private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name name;}public int getAge() {return age;}pu…

从matlab的fig图像文件中提取数据

这里用的是openfig&#xff08;&#xff09;函数打开的fig文件 →→→【matlab 中 fig 数据提取】 很简洁 →→→【MATLAB提取 .fig 文件中的数据】 这个给出了包含多个曲线的情况 →→→【提取matlab fig文件里的数据和legend】 chatgpt给出的方法 打开fig文件并保存数据 我的…

《Python数据分析技术栈》第01章 02 Jupyter入门(Getting started with Jupyter notebooks)

02 Jupyter入门&#xff08;Getting started with Jupyter notebooks&#xff09; 《Python数据分析技术栈》第01章 02 Jupyter入门&#xff08;Getting started with Jupyter notebooks&#xff09; Before we discuss the essentials of Jupyter notebooks, let us discuss…

Java JVM 堆、栈、方法区详解

目录 1. 栈 2. 堆 3. 方法区 4. 本地方法栈 5. 程序计数器 首先来看一下JVM运行时数据区有哪些。 1. 栈 在介绍JVM栈之前&#xff0c;先了解一下 栈帧 概念。 栈帧&#xff1a;一个栈帧随着一个方法的调用开始而创建&#xff0c;这个方法调用完成而销毁。栈帧内存放者方…

Java导出Excel并合并单元格

需求&#xff1a;需要在导出excel时合并指定的单元格 ruoyi excel 项目基于若伊框架二次开发&#xff0c;本着能用现成的就不自己写的原则&#xff0c;先是尝试了Excel注解中needMerge属性 /*** 是否需要纵向合并单元格,应对需求:含有list集合单元格)*/public boolean needMer…