前后端联合实现多个文件上传

 1、前端 Vue3

CommonApplyBasicInfoForm.vue

<script setup lang="ts" name="CommonApplyBasicInfoForm">
......
// 文件输入实例对象
const fileInputRef = ref<HTMLInputElement | null>(null);
// 选择文件列表
const selectedFiles = ref<FileList | null>(null);
// 通过 FormData 对象实现文件上传
let formData = new FormData();// 选择文件,防抖
const onUploadClick = debounce(() => {// 模拟点击元素if (fileInputRef.value) {// 重置以允许重复选择相同文件fileInputRef.value.value = "";fileInputRef.value.click();}},1000,{ leading: true, trailing: true, maxWait: 1000 }
);// 点击【选择文件】触发,实现多个文件上传
const handleUpload = async (e: Event) => {// 获取文件对象列表const input = e.target as HTMLInputElement;selectedFiles.value = input.files;// 判断文件列表是否为空if (!selectedFiles.value?.length) {ElMessage.warning("请选择至少一个文件");return;}try {// 清空 FormData 表单数据的内容:重新赋值,创建新实例,旧数据被丢弃(完全清空),需要使用 let 声明对象,不能使用 const 声明对象formData = new FormData();// 添加多个文件到 FormDatafor (const file of selectedFiles.value) {formData.append("uploadFiles", file);}// 受理编号长度超过8位if (applyBasicInfo.value.outerApplyId.length > 8) {// 添加主键编号到 FormDataformData.append("key", applyBasicInfo.value.outerApplyId);// 文件数量不超过5份if (selectedFiles.value.length < 5) {// 将文件存储位置添加到 formData 对象中,如:受理基础信息、ApplyBasicInfo、ApplyBasicFile,后端会将文件存储到数据库的 ApplyBasicFile 表中formData.append("storage", "受理基础信息");}// 文件数量不超过6份else if (selectedFiles.value.length < 6) {// 将文件存储位置添加到 formData 对象中// 如:受理基础信息/、ApplyBasicInfo/、ApplyBasicFile/,后端会将文件存储到本地磁盘默认目录下formData.append("storage", "受理基础信息/");}// 文件数量超过6份else {// 将文件存储位置添加到 formData 对象中// 如:受理基础信息/xxx、ApplyBasicInfo/xxx、ApplyBasicFile/xxx,后端会将文件存储到本地磁盘默认目录下的 xxx 目录下formData.append("storage", "受理基础信息/ApplyBasicFile");}// 发送请求,上传多个文件到后端服务器await uploadFilesService(formData);}// 受理编号长度不超过8位else {// 添加受理编号到 FormDataformData.append("outerApplyId", applyBasicInfo.value.outerApplyId);// 发送请求,上传多个文件到后端服务器await applyBasicInfoUploadFilesService(formData);}ElMessage.success("文件上传成功!");// 获取已上传的文件列表await fetchUploadedFiles(applyBasicInfo.value.outerApplyId);} catch (error) {ElMessage.error("文件上传失败!");}
};
......
</script><template>
......<span>选择文件</span><!-- 文件输入元素,不显示,通过点击【选择文件】执行 onUploadClick,模拟点击该元素,从而触发 handleUpload 事件 --><input ref="fileInputRef" type="file" multiple style="display: none" @change="handleUpload" />
......
</template>

applyBasicInfo.ts

import request from "@/utils/request";/*** 上传多个文件,使用 post 发送请求,发送的数据只有:请求体数据(表单数据 formData)* @param formData 表单数据,包含的数据有:多个文件数据(uploadFiles)和 受理编号(outerApplyId) {@link FormData}*/
export const applyBasicInfoUploadFilesService = (formData: FormData) => {return request.post("/applyBasicInfo/uploadFiles", formData, {// 上传文件,需设置 headers 信息,将"Content-Type"设置为"multipart/form-data"headers: {"Content-Type": "multipart/form-data"}});
};

2、后端 Spring boot + Mybatis

控制层:ApplyBasicInfoController.java
package com.weiyu.controller;import com.weiyu.pojo.*;
import com.weiyu.service.ApplyBasicInfoService;
import com.weiyu.service.FileDownloadService;
import com.weiyu.service.FileUploadService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import java.util.List;/*** 受理信息 Controller*/
@Slf4j
@RestController
@RequestMapping("/applyBasicInfo")
public class ApplyBasicInfoController {@Autowiredprivate ApplyBasicInfoService applyBasicInfoService;@Autowiredprivate FileUploadService fileUploadService;/*** 上传多个文件** @param outerApplyId 受理编号* @param uploadFiles  多个文件 {@link List}&lt;{@link MultipartFile}&gt;* @return 统一响应结果 {@link Result}&lt;*&gt;*/@PostMapping("/uploadFiles")public Result<?> uploadFiles(@RequestParam("outerApplyId") String outerApplyId,@RequestParam("uploadFiles") List<MultipartFile> uploadFiles) {log.info("【受理基础信息】,上传多个文件,/applyBasicInfo/uploadFiles," +"outerApplyId = {},uploadFiles = {}", outerApplyId, uploadFiles);if (uploadFiles == null || uploadFiles.isEmpty()) {return Result.error("请选择至少一个文件");}try {for (MultipartFile uploadFile: uploadFiles) {fileUploadService.uploadFile(outerApplyId, "受理基础信息", uploadFile);}return Result.success("多个文件上传成功!");} catch (Exception e) {return Result.error("多个文件上传失败:" + e.getMessage());}}
}
服务层接口实现:FileUploadServiceImpl .java
package com.weiyu.service.impl;import com.weiyu.mapper.FileUploadMapper;
import com.weiyu.pojo.FileData;
import com.weiyu.service.FileUploadService;
import com.weiyu.utils.FileSaveUtils;
import com.weiyu.utils.PublicUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import java.util.List;/*** 文件上传 Service 接口实现*/
@Service
public class FileUploadServiceImpl implements FileUploadService {@Autowiredprivate FileUploadMapper fileUploadMapper;@Autowiredprivate FileSaveUtils fileSaveUtils;/*** 文件上传** @param key        主键* @param storage    文件存储位置(数据库表名 或 文件系统路径 或 实体名称 或 业务名称)* @param uploadFile 上传文件*/@Overridepublic void uploadFile(String key, String storage, MultipartFile uploadFile) {try {// storage 包含路径分隔符 / ,上传文件到本地磁盘中// 如:storage = 质量体系文件/、QualityFile/、ControledFileMain/,上传文件到本地磁盘默认目录下// 如:storage = 质量体系文件/xxx、QualityFile/xxx、ControledFileMain/xxx,上传文件到本地磁盘默认目录中的 xxx 子目录下if (storage.contains("/")) {// 分割处理 storage 的内容List<String> list = List.of(storage.split("/"));// 获取表名String tableName = PublicUtils.mapStorageToTableName(list.get(0));// 文件相对路径名称String fileRelativePathName;// 保存文件到本地磁盘(默认目录)if (list.size() == 1) {// 使用文件存储工具类,将文件保存到本地磁盘fileRelativePathName = fileSaveUtils.saveFileToLocalDisk(uploadFile);}// 保存文件到本地磁盘(默认目录的子目录 xxx)else {fileRelativePathName = fileSaveUtils.saveFileToLocalDisk(uploadFile, list.get(1));}// 通过更新方式,将文件名称保存到数据库if ("ControledFileMain".equalsIgnoreCase(tableName)) {if (key.length() <= 5) {// 使用文件存储工具类,将文件名称保存到数据库fileSaveUtils.saveFileNameToDatabase(fileRelativePathName, tableName, key);} else {// 使用 Mapper,将文件名称保存到数据库fileUploadMapper.saveFileName(key, tableName, fileRelativePathName);}}}// storage 不包含路径分隔符 / ,上传文件到数据库中 或 上传文件到本地磁盘中// 如:storage = 质量体系文件、QualityFile、ControledFileMainelse {// 获取表名String tableName = PublicUtils.mapStorageToTableName(storage);// 文件大小超过3MB,上传文件到本地磁盘中if (uploadFile.getSize() > 1024 * 1024 * 3) {// 使用文件存储工具类,将文件保存到本地磁盘String fileRelativePathName = fileSaveUtils.saveFileToLocalDisk(uploadFile);// 通过更新方式,将文件名称保存到数据库if ("ControledFileMain".equalsIgnoreCase(tableName)) {if (key.length() <= 5) {// 使用文件存储工具类,通过更新方式,将文件名称保存到数据库fileSaveUtils.saveFileNameToDatabase(fileRelativePathName, tableName, key);} else {// 使用 Mapper,通过更新方式,将文件名称保存到数据库fileUploadMapper.saveFileName(key, tableName, fileRelativePathName);}}}// 文件大小不超过3MB,上传文件到数据库中else {FileData fileData = new FileData();fileData.setFileName(uploadFile.getOriginalFilename());fileData.setFileContent(uploadFile.getBytes());// 通过新增方式,将文件增加到数据库if ("ApplyBasicFile".equalsIgnoreCase(tableName)) {// 新增文件到数据库if (key.length() > 8) {// 使用 使用文件存储工具类,通过新增方式,将文件增加到数据库fileSaveUtils.addFileToDatabase(key, tableName, uploadFile);} else {// 使用 Mapper,通过新增方式,将文件增加到数据库fileUploadMapper.addFile(key, tableName, fileData);}}// 通过更新方式,将文件保存到数据库else {// 保存文件到数据库if (key.length() <= 5) {// 使用文件存储工具类,通过更新方式,将文件保存到数据库fileSaveUtils.saveFileToDatabase(uploadFile, tableName, key);} else {// 使用 Mapper,通过更式方式,将文件保存到数据库fileUploadMapper.saveFile(key, tableName, fileData);}}}}} catch (Exception e) {// 抛出运行异常throw new RuntimeException(e.getMessage());}}
}
数据表结构
数据传输对象 DTO:FileData.java
package com.weiyu.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** 文件数据*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FileData {private String fileName;private byte[] fileContent;
}

持久层 Mapper:FileUploadMapper.java

package com.weiyu.mapper;import com.weiyu.pojo.FileData;
import org.apache.ibatis.annotations.Mapper;/*** 文件上传 Mapper*/
@Mapper
public interface FileUploadMapper {/*** 保存文件到数据库** @param key       主键* @param tableName 表名* @param fileData  文件数据*/void saveFile(String key, String tableName, FileData fileData);/*** 保存文件名称到数据库** @param key       主键* @param tableName 表名* @param fileName  文件名称*/void saveFileName(String key, String tableName, String fileName);/*** 增加文件到数据库** @param key       主键* @param tableName 表名* @param fileData  文件数据*/void addFile(String key, String tableName, FileData fileData);
}

持久层数据库sql更新:FileUploadMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.weiyu.mapper.FileUploadMapper"><!--mssql--><!-- 保存文件数据到数据库 --><update id="saveFile"><choose><when test="tableName == 'ControledFileMain'">update ControledFileMain setcfm_ContentFileName = #{fileData.fileName}, cfm_Content = #{fileData.fileContent}, cfm_ContentIsNull = 0where Cfm_BigType = '3' and Cfm_ID = #{key}</when></choose></update><!-- 保存文件名称到数据库 --><update id="saveFileName"><choose><when test="tableName == 'ControledFileMain'">update ControledFileMain setcfm_ContentFileName = #{fileName}, cfm_Content = null, cfm_ContentIsNull = 0where Cfm_BigType = '3' and Cfm_ID = #{key}</when></choose></update><!-- 增加文件数据到数据库 --><insert id="addFile"><choose><when test="tableName == 'ApplyBasicFile'">insert into ApplyBasicFile (ApplyBasicFile_MasterID, ApplyBasicFile_FileName, ApplyBasicFile_File)values (#{key}, #{fileData.fileName}, #{fileData.fileContent})</when></choose></insert>
</mapper>

持久层工具类:FileSaveUtils.java

package com.weiyu.utils;import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.UUID;/*** 文件存储工具类*/
@Component // 通过 @Component 注解,将该工具类交给 ICO 容器管理,使用的时候不需要 new,直接 @Autowired 注入即可
@Slf4j
public class FileSaveUtils {// 获取文件目录@Getter@Value("${upload.localhost.savePath}") // 从配置文件中获取 upload.localhost.savePath 的配置值private String savePath;@Autowiredprivate DataSource dataSource;/*** 增加文件到数据库** @param key       主键* @param tableName 表名* @param file      文件流* @throws SQLException 异常,SQL类的操作需要抛出异常*/public void addFileToDatabase(String key, String tableName, MultipartFile file) throws SQLException, IOException {log.info("【文件存储工具类】,增加文件到数据库,fileNo = {},tableName = {},file = {}", key, tableName, file);// 参数校验if (file == null || file.isEmpty()) {throw new IllegalArgumentException("文件不能为空");}// 使用白名单校验if (!"ApplyBasicFile".equalsIgnoreCase(tableName)) {throw new IllegalArgumentException("不支持的表名: " + tableName);}if (key == null || key.trim().isEmpty()) {throw new IllegalArgumentException("文件编号不能为空");}String sql = "";if ("ApplyBasicFile".equalsIgnoreCase(tableName)) {// 编写 sqlsql = " insert into ApplyBasicFile (ApplyBasicFile_MasterID, ApplyBasicFile_FileName, ApplyBasicFile_File)" +" values (?, ?, ? )";}// 使用 try-with-Resources 自动管理资源try (// 通过数据源,获取连接Connection connection = dataSource.getConnection();// 装载 sql,PreparedStatement 是 Java JDBC API 中的一个重要接口,用于执行预编译的SQL语句。它比普通的Statement更高效、更安全,特别是在需要多次执行相似SQL语句时。PreparedStatement ps = connection.prepareStatement(sql);// 读取文件流InputStream inputStream = file.getInputStream()) {if ("ApplyBasicFile".equalsIgnoreCase(tableName)) {// 设置参数ps.setString(1, key);ps.setString(2, file.getOriginalFilename());if (key.length() <= 5) {ps.setBlob(3, inputStream);} else {ps.setBinaryStream(3, inputStream);}}// 执行 sql,执行更新,返回受影响的行数ps.executeUpdate();} catch (SQLException | IOException e) {// ❌ 禁止使用 printStackTrace 在控制台输出异常的详细堆栈跟踪信息// e.printStackTrace();// ✅ 规范日志记录:使用日志框架记录完整异常堆栈(参数 e 包含异常的详细堆栈跟踪信息)log.error("异常错误 {}", e.getMessage(), e); // 记录错误消息和详细堆栈跟踪信息// 重新抛出异常throw e;}// 无需 finally 块,资源会自动关闭}
}

3、应用效果

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

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

相关文章

软考高级--系统架构设计师--综合知识真题解析

系列文章目录 文章目录系列文章目录一、2019年真题二、2020年真题三、2021年真题四、2022年真题总结一、2019年真题 二、2020年真题 三、2021年真题 四、2022年真题 总结

“帕萨特B5钳盘式制动器结构设计三维PROE模型7张CAD图纸PDF图“

摘 要本文首先对汽车制动器原理和对各种各样的制动器进行分析,详细地阐述了各类制动器的结构,工作原理和优缺点。再根据轿车的车型和结构选择了适合的方案。根据市场上同系列车型的车大多数是滑钳盘式制动器,而且滑动钳式盘式制动器结构简单,性能居中,设计规范,所以我选择滑动…

SQL注入6----(其他注入手法)

一.前言 本章节来介绍一下其他的注入手法&#xff0c;也就是非常规注入手法&#xff0c;来和大家介绍一下 二.加密注入 前端提交的有些数据是加密之后&#xff0c;到了后台在解密&#xff0c;然后再进行数据库查询等相关操作的&#xff0c;那么既然如 此我们也应该将注入语句…

visual studio2022 配置 PCL 1.13.1

PCL库下载 下载链接&#xff1a; https://github.com/PointCloudLibrary/pcl/releases 下载这两个。 PCL库安装 运行.exe文件进行安装。 环境变量勾第二个&#xff08;其实无所谓&#xff0c;反正还要添加别的环境变量&#xff0c;这里没选之后加也一样&#xff09;。 安装…

金融学-货币理论

前言 前面学习了什么是货币供给&#xff0c;货币供给的决定以及联邦储备体系在货币供给中所起的作用。现在我们要开始探讨经济中货币供给在决定价格水平与全部商品和劳务(总供给)中的作用。关于货币对经济影响的研究&#xff0c;称为货币理论(monetarythe-ory) 货币数量论 古典…

Visio绘图——给多边形增加连接线

每次在画项目框图和各类爪图的时候&#xff0c;连接线是最烦人的&#xff0c;虽然选择的是折线&#xff0c;单往往事与愿违。 下面就记录一下&#xff0c;如何查找各类连接线。 1、先展开左侧菜单栏&#xff0c;点击如下所示的“&#xff1e;”2、在展开的界面&#xff0c;再次…

【开题答辩全过程】以 付费自习室系统小程序为例,包含答辩的问题和答案

个人简介一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。感谢大家的…

开疆智能Profinet转EtherCAT网关连接TR-Electronic传感器配置案例

本案例是通过开疆智能研发的Profinet转EtherCAT网关将传感器数据传送到PLC&#xff0c;由于两边设备采用协议不同&#xff0c;故而使用网关进行转换。网关配置&#xff1a;打开网关配置软件“EtherCAT Manager”并新建项目。根据不通网关型号也可选择ModbusTCP&#xff0c;Ethe…

VSCode中使用Markdown

文章目录1. 背景2. 安装插件3. 基础写作与预览4. 生成PDF文档5. 插入代码6. 插入图片7. 小结1. 背景 编程技术人员&#xff0c;很多人写作习惯用Markdown格式吧。 首先Markdown很简单&#xff0c;第二它的层次结构特别清晰&#xff0c;再然后它对嵌入图片、代码的支持很优秀。…

2024全栈技术栈选型指南

前后端技术栈选择现代前后端技术栈选择需兼顾市场需求与个人兴趣。前端领域React、Vue、Angular形成三足鼎立&#xff0c;React在大型项目占比达58%&#xff0c;Vue在小中型企业更受欢迎。TypeScript采用率年增长25%&#xff0c;已成为工程化标配。后端技术呈现多元化趋势&…

Spring Boot 项目文件上传安全与优化:OSS、MinIO、Nginx 分片上传实战

在实际的 Web 项目中&#xff0c;文件上传是一个常见需求&#xff1a;用户上传头像、企业后台上传资料、视频平台上传大文件等等。然而&#xff0c;文件上传也是最容易引发安全风险的功能之一&#xff0c;比如恶意脚本上传、木马文件伪装、存储空间消耗攻击。同时&#xff0c;当…

智能安防:以AI重塑安全新边界

传统安防依赖人力监控与简单报警&#xff0c;效率低下且易遗漏风险。随着人工智能、物联网及大数据技术的融合&#xff0c;智能安防正重新定义安全管理的范式&#xff0c;从被动响应转向主动预警&#xff0c;成为智慧城市与数字化生活的重要基石。智能安防的核心是人工智能视觉…

【AI】【强化学习】强化学习算法总结、资料汇总、个人理解

前言&#xff1a;自己学习西湖大学赵老师的课、youtube系列的课程相关比较重要的内容&#xff0c;后续不断再进行完善。 YouTube Serrano.academy rlhf讲的很好 合集最后一个没看 强化学习第四章 police没一步需要无穷&#xff0c;值迭代只需要一步 收敛不一样 值迭代:原因在于…

一键掌控三线资源:极简 Shell 脚本实现 CPU·磁盘·内存可视化巡检

目录 前言 数值型 for 循环 语法格式 示例&#xff1a;打印 1 到 5 示例&#xff1a;打印5次Hello World 示例&#xff1a;计算 1 到 100 的累加和 遍历型 for 循环 语法格式 示例&#xff1a;遍历字符串列表 示例&#xff1a;遍历数组 示例&#xff1a;遍历文件列表…

数据结构:创建堆(或者叫“堆化”,Heapify)

目录 最直观的思路 更优化的思路&#xff08;自底向上的构建&#xff09; 第一步&#xff1a;重新审视问题 第二步&#xff1a;找到规律&#xff0c;形成策略 用一个实例来推演 第三步&#xff1a;编写代码 总结与分析 我们来深入探讨“创建堆”&#xff08;或者叫“堆化…

基于 GPT-OSS 的成人自考口语评测 API 开发全记录

1️⃣ 需求与指标 在项目启动前&#xff0c;我们设定了核心指标&#xff1a; 字错率&#xff08;WER&#xff09;< 5%响应延迟 < 800 ms高可用、可扩展 这些指标将贯穿整个开发和测试流程。 2️⃣ 数据准备 准备训练数据是关键步骤&#xff0c;我们使用了 1k 条自考口…

Linux初始——基础指令篇

Linux常用指令pwdlscdtouchmkdirrmmancpmvcatmorelesswhichwhereisaliasgrepfilezip/unzip 指令rzsztarpwd 在xshell中输入pwd并回车&#xff0c;将输出当前用户所存在的目录位置 可看到当前用户是在/home/hhw这个目录下 ls 在xshell中输入ls会显示当前目录所包含的文件 其中…

Vue-24-利用Vue3的element-plus库实现树形结构数据展示

文章目录 1 项目启动 1.1 创建和启动项目(vite+vue) 1.2 清理不需要的代码 1.3 下载必备的依赖(element-plus) 1.4 完整引入并注册(main.sj) 1.5 设置@别名(vite.config.js) 2 el-tree树形控件 2.1 TreeComponents.vue 2.1.1 模板部分 2.1.2 类型定义(Tree) 2.1.3 树形数据(dat…

Kubernetes 部署与发布完全指南:从 Pod 到高级发布策略

引言:告别手动,拥抱声明式 在传统的部署流程中,我们常常需要手动执行一系列命令:SSH 到服务器、拉取新代码、编译、重启服务、检查日志、处理错误…这个过程不仅繁琐低效,而且极易出错,难以保证环境的一致性。 Kubernetes 彻底改变了这一切。它通过一种 “声明式” 的模…

支持向量机核心知识总结

一、核心基础概念核心目标&#xff1a;在样本空间中找到划分超平面&#xff0c;将不同类别样本分开&#xff0c;且该超平面对训练样本局部扰动的 “容忍性” 最优&#xff08;即抗干扰能力强&#xff09;。超平面定义超平面是 n 维空间中的 n-1 维子空间&#xff0c;是 SVM 分类…