1、前端 Vue3
QualityFileInfoDialog.vue
<script setup lang="ts" name="QualityFile">
......
// 上传,防抖
const onUploadClick = debounce(() => {// 模拟点击元素if (fileInputRef.value) {// 重置以允许重复选择相同文件fileInputRef.value.value = "";fileInputRef.value.click();}},1000,{ leading: true, trailing: true, maxWait: 1000 }
);// 点击【上传】触发,实现 SQL Server image 类型文件上传
const handleUpload = async (e: Event) => {// 清空 FormData 表单数据的内容:重新赋值,创建新实例,旧数据被丢弃(完全清空),需要使用 let 声明对象,不能使用 const 声明对象formData = new FormData();// 获取文件对象const input = e.target as HTMLInputElement;if (!input.files?.length) return;const file = input.files[0];// 校验文件大小if (file.size > 1024 * 1024 * 10) {ElMessage.warning("文件大小不能超过10MB");return;}if (file) {// 获取文件名的扩展名(后缀)extension.value = getExtension(file.name);if (upperCase(extension.value) === upperCase("pdf")) {// 将文件对象 file 添加到 formData 对象中,uploadFile 需要与后端接口中接收文件的参数名一致,如果不一致,则后端需要指定参数名,如 @RequestPart("uploadFile") MultipartFile fileformData.append("uploadFile", file);// 无需点击确定,直接发送请求,上传文件到数据库,实现 SQL Server image 类型文件上传await qualityFileUploadFileWithPutService(qualityFileObj.value.fileNo, formData);} else {// 将文件对象 file 添加到 formData 对象中,uploadFile 需要与后端接口中接收文件的参数名一致,如果不一致,则后端需要指定参数名,如 @RequestPart("uploadFile") MultipartFile fileformData.append("uploadFile", file);// 将普通对象 qualityFileObj 的 fileNo 属性添加到 formData 对象中formData.append("fileNo", qualityFileObj.value.fileNo);// 无需点击确定,直接发送请求,上传文件到数据库,实现 SQL Server image 类型文件上传await qualityFileUploadFileService(formData);}// 点击【上传/重传】选择文件后,上传文件完成,通知父组件更新文件路径名称和是否空内容的操作emit("upload-file-complete", file.name);// 同步更新表单数据qualityFileObj.value.filePathname = file.name;qualityFileObj.value.isNullContent = false;}
};
......
</script><template>
......<el-table-column label="操作" width="150" header-align="center" align="center" fixed="right"><template #default="scope"><BasePreventReClickButton type="primary" plain :loading="false" @click="onUploadClick">{{qualityFileObj.isNullContent ? "上传" : "重传"}}</BasePreventReClickButton>></template></el-table-column>
......
</template>
qualityFile.ts
import request from "@/utils/request";
import type { IQualityFile, IQualityFileQueryObj } from "@/views/resources/QualityFile/types";/*** 上传质量体系文件,实现 SQL Server image 类型文件上传,使用 put 发送请求,发送的数据有:请求体数据(文件数据 uploadFile),请求参数数据(文件编号 fileNo)* @param fileNo 文件编号(可能包含特殊字符如 /)* @param formData 表单数据,包含的数据只有:文件数据(uploadFile)* @returns*/
export const qualityFileUploadFileWithPutService = (fileNo: string, formData: FormData) => {// 发送请求,发送的数据有:请求体数据(文件数据 uploadFile),请求参数数据(文件编号 fileNo)return request.put("/resources/qualityFile/uploadFile", formData, {params: {fileNo: fileNo},// 上传文件,需设置 headers 信息,将"Content-Type"设置为"multipart/form-data"headers: {"Content-Type": "multipart/form-data"}});
};/*** 上传质量体系文件,实现 SQL Server image 类型文件上传,使用 post 发送请求,发送的数据只有:请求体数据(文件数据 uploadFile)* @param formData 表单数据,包含的数据有:文件数据(uploadFile)和 文件编号(fileNo) {@link FormData}* @returns*/
export const qualityFileUploadFileService = (formData: FormData) => {return request.post("/resources/qualityFile/uploadFile", formData, {// 上传文件,需设置 headers 信息,将"Content-Type"设置为"multipart/form-data"headers: {"Content-Type": "multipart/form-data"}});
};
fileUtils.ts
/*** 获取文件名的扩展名(后缀)* @param filename 文件名* @returns 扩展名(后缀)*/
export const getExtension = (filename: string) => {// 方法1:使用split()和pop(),通过将文件名按点号(.)分割成数组,取最后一个元素作为后缀名。// const parts = filename.split(".");// return parts.length > 1 ? parts.pop() : "";// 方法2:使用lastIndexOf()和substring(),更健壮的方式是定位最后一个点号的位置后截取字符串,避免多重点号的误判// const lastDotIndex = filename.lastIndexOf(".");// return lastDotIndex !== -1 ? filename.substring(lastDotIndex + 1) : "";// 方法3:使用slice()和lastIndexOf()// const lastDotIndex = filename.lastIndexOf(".");// return lastDotIndex !== -1 ? filename.slice(lastDotIndex + 1) : "";// 方法4:使用正则表达式const match = filename.match(/\.([a-zA-Z0-9]+)$/);return match ? match[1] : "";
};/*** 获取URL中的文件名的扩展名(后缀)* @param url url* @returns 扩展名(后缀)*/
export const getExtensionFromURL = (url: string) => {const pathname = new URL(url).pathname;return getExtension(pathname);
};
2、后端 Spring boot + Mybatis
控制层:QualityFileController.java
package com.weiyu.controller;import com.alibaba.fastjson.JSON;
import com.weiyu.anno.Debounce;
import com.weiyu.pojo.QualityFile;
import com.weiyu.pojo.QualityFileDTO;
import com.weiyu.pojo.QualityFileQueryDTO;
import com.weiyu.pojo.Result;
import com.weiyu.service.QualityFileService;
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.io.IOException;
import java.util.List;/*** 质量体系文件 Controller*/
@RestController
@RequestMapping("/resources/qualityFile")
@Slf4j
public class QualityFileController {@Autowiredprivate QualityFileService qualityFileService;/*** 质量体系文件上传,实现 SQL Server image 类型文件上传,使用 @PutMapping 接收请求* MultipartFile参数名称说明:* 因为前端使用 formData.append("uploadFile", file) 用的参数名称是 uploadFile* 后端这里使用 uploadFile 与前端一致,可以不使用 @RequestPart("uploadFile"),也可以使用* @param fileNo 文件编号(可能包含特殊字符如 /)* @param uploadFile 上传文件 {@link MultipartFile}* @apiNote 本接口使用防抖机制,3s 内重复请求会被忽略*/@PutMapping("/uploadFile")@Debounce(key = "/resources/qualityFile/uploadFile", value = 3000)public Result<?> uploadFile(@RequestParam String fileNo, MultipartFile uploadFile) {try {log.info("【质量体系文件】,上传,实现 SQL Server image 类型文件上传,使用 @PutMapping 接收请求," +"/resources/qualityFile/uploadFile,fileNo = {},uploadFile = {}", fileNo, uploadFile);qualityFileService.uploadFile(fileNo, uploadFile);return Result.success("文件上传成功!");} catch (Exception e) {return Result.success("文件上传失败:" + e.getMessage());}}/*** 质量体系文件上传,实现 SQL Server image 类型文件上传,使用 @PostMapping 接收请求* MultipartFile参数名称说明:* 因为前端使用 formData.append("uploadFile", file) 用的参数名称是 uploadFile* 后端这里使用 uploadFile 与前端一致,可以不使用 @RequestPart("uploadFile"),也可以使用* String参数名称说明:* 因为前端使用 formData.append("fileNo", qualityFileObj.value.fileNo) 用的参数名称是 fileNo* 后端这里使用 fileNo 与前端一致,可以不使用 @RequestPart("fileNo"),也可以使用* @param fileNo 文件编号(可能包含特殊字符如 /)* @param uploadFile 上传文件 {@link MultipartFile}* @apiNote 本接口使用防抖机制,3s 内重复请求会被忽略*/@PostMapping("/uploadFile")@Debounce(key = "/resources/qualityFile/uploadFile", value = 3000)public Result<?> uploadFile(MultipartFile uploadFile, String fileNo) {try {log.info("【质量体系文件】,上传,实现 SQL Server image 类型文件上传,使用 @PostMapping 接收请求," +"/resources/qualityFile/uploadFile,uploadFile = {},fileNo = {}", uploadFile, fileNo);qualityFileService.uploadFile(fileNo, uploadFile);return Result.success("文件上传成功!");} catch (Exception e) {return Result.success("文件上传失败:" + e.getMessage());}}
}
服务层接口实现:QualityFileServiceImpl .java
package com.weiyu.service.impl;import com.weiyu.mapper.QualityFileMapper;
import com.weiyu.pojo.FileData;
import com.weiyu.pojo.QualityFile;
import com.weiyu.pojo.QualityFileQueryDTO;
import com.weiyu.service.QualityFileService;
import com.weiyu.utils.FileDownloadUtil;
import jakarta.validation.constraints.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;/*** 质量体系文件 Service 接口实现*/
@Service
public class QualityFileServiceImpl implements QualityFileService {@Autowiredprivate QualityFileMapper qualityFileMapper;/*** 上传质量体系文件** @param fileNo 文件编号* @param uploadFile 上传文件*/@Overridepublic void uploadFile(String fileNo, MultipartFile uploadFile) throws IOException {FileData fileData = new FileData();fileData.setFileName(uploadFile.getOriginalFilename());fileData.setFileContent(uploadFile.getBytes());// todo: 如果是大文件(超过10MB)保存到文件系统,数据库只保存文件路径;否则保存到数据库// 保存文件到数据库qualityFileMapper.saveFile(fileNo, fileData);}
}
数据表结构数据传输对象 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;
}
持久层:QualityFileMapper.java
package com.weiyu.mapper;import com.weiyu.pojo.FileData;
import com.weiyu.pojo.QualityFile;
import com.weiyu.pojo.QualityFileQueryDTO;
import org.apache.ibatis.annotations.Mapper;import java.util.List;/*** 质量体系文件 Mapper*/
@Mapper
public interface QualityFileMapper {/*** 保存质量体系文件数据到数据库* @param fileNo 文件编号* @param fileData 上传文件*/void saveFile(String fileNo, FileData fileData);
}
持久层数据库sql更新:QualityFileMapper.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.QualityFileMapper"><!--mssql--><!-- 保存质量体系文件数据到数据库 --><update id="saveFile">update ControledFileMain setcfm_ContentFileName = #{fileData.fileName}, cfm_Content = #{fileData.fileContent}, cfm_ContentIsNull = 0where Cfm_BigType = '3' and Cfm_ID = #{fileNo}</update>
</mapper>
3、应用效果
文件名称支持空格和加号