一、技术栈
1、前端 Vue3 + Element Plus + TypeSprict
2、后端 Spring Boot 3.2.12 + Mybatis Plus
3、模型特点
3.1、表格展示列表数据
3.2、行点击,弹出对话框
3.3、前端使用 FormData 提交混合表单数据,包含普通字段和文件字段
3.4、文件对应数据库结构类型为 image
3.5、Spring MVC 注解 @RequestPart、@ModelAttribute
2、应用效果





3、Vue3 前端
对话框表单 QualityFileInfoDialog.vue
TypeScript
<script setup lang="ts" name="QualityFileInfoDialog">
......
// 上传,防抖
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 表单数据的内容
// FormData对象不能直接通过 console.log(formData)输出完整内容(控制台仅显示 FormData {}的抽象表示)
// console.log("formData = ", formData);
// 使用 Object.fromEntries()将 FormData 转为普通对象,再通过 console.log 打印完整内容
// const data = Object.fromEntries(formData.entries());
// console.log(data);
// 清空 FormData 表单数据的内容:遍历删除所有字段
// formData.keys() 返回的迭代器会实时跟踪 FormData的当前状态。当调用 delete(key) 后,迭代器的内部指针可能因数据变化而跳过后续键。
// 先将将迭代器转为静态数组,避免动态变化的影响
// 使用 Array.from() 将迭代器转为静态数组
// const keys = Array.from(formData.keys());
// 使用 扩展运算符 将迭代器转为静态数组
// const keys = [...formData.keys()];
// for (let key of keys) {
// formData.delete(key);
// }
// 清空 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 (props.isNew) {
if (upperCase(extension.value) === upperCase("pdf")) {
// 将文件对象 file 添加到 formData 对象中,uploadFile 需要与后端接口中接收文件的参数名一致,如果不一致,则后端需要指定参数名,如 @RequestPart("uploadFile") MultipartFile file
formData.append("uploadFile", file);
} else if (upperCase(extension.value) === upperCase("xls") || upperCase(extension.value) === upperCase("xlsx")) {
// 将文件对象 file 添加到 formData 对象中,uploadFile 需要与后端接口中接收文件的参数名一致,如果不一致,则后端需要指定参数名,如 @RequestPart("uploadFile") MultipartFile file
formData.append("uploadFile", file);
// 将普通对象 qualityFileObj 的属性添加到 formData 对象中
formData.append("fileNo", qualityFileObj.value.fileNo);
formData.append("fileName", qualityFileObj.value.fileName);
formData.append("edition", qualityFileObj.value.edition as string);
formData.append("orderNo", qualityFileObj.value.orderNo as string);
formData.append("issueDept", qualityFileObj.value.issueDept as string);
formData.append("issueDate", qualityFileObj.value.issueDate as string);
formData.append("smallCategory", qualityFileObj.value.smallCategory as string);
formData.append("detailCategory", qualityFileObj.value.detailCategory as string);
formData.append("modifyRecord", qualityFileObj.value.modifyRecord as string);
formData.append("remark", qualityFileObj.value.remark as string);
} else {
// 将文件对象 file 添加到 formData 对象中,uploadFile 需要与后端接口中接收文件的参数名一致,如果不一致,则后端需要指定参数名,如 @RequestPart("uploadFile") MultipartFile file
formData.append("uploadFile", file);
// 将普通对象 qualityFileObj 转换为 json 字符串 添加到 formData 对象中
formData.append("qualityFile", JSON.stringify(qualityFileObj.value));
}
}
// 查改模式
else {
if (upperCase(extension.value) === upperCase("pdf")) {
// 将文件对象 file 添加到 formData 对象中,uploadFile 需要与后端接口中接收文件的参数名一致,如果不一致,则后端需要指定参数名,如 @RequestPart("uploadFile") MultipartFile file
formData.append("uploadFile", file);
// 无需点击确定,直接发送请求,上传文件到数据库,实现 SQL Server image 类型文件上传
await qualityFileUploadFileWithPutService(qualityFileObj.value.fileNo, formData);
} else {
// 将文件对象 file 添加到 formData 对象中,uploadFile 需要与后端接口中接收文件的参数名一致,如果不一致,则后端需要指定参数名,如 @RequestPart("uploadFile") MultipartFile file
formData.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;
}
};
// 确定
const onConfirmClick = async () => {
// 检查
if (!check()) {
return;
}
// 新增模式
if (props.isNew) {
if (upperCase(extension.value) === upperCase("pdf")) {
// 发送请求,使用 put 发送请求,发送的数据有:请求体数据(文件数据 file),请求参数数据(普通对象数据 qualityFile)
await qualityFileAddAttachUploadFileWithPutService(qualityFileObj.value, formData);
} else if (upperCase(extension.value) === upperCase("xls") || upperCase(extension.value) === upperCase("xlsx")) {
// 发送请求,使用 patch 发送请求,通过请求体发送表单数据 formData,表单数据,包含的数据有:文件数据(uploadFile)和
// 普通对象的属性数据(fileNo、fileName、edition、orderNo、issueDept、issueDate、smallCategory、detailCategory、modifyRecord、remark)
await qualityFileAddAttachUploadFileWithPatchService(formData);
} else {
// 发送请求,使用 post 发送请求,通过请求体发送表单数据 formData,表单数据,包含的数据有:文件数据(uploadFile)和 普通对象的json字符串数据(qualityFile)
await qualityFileAddAttachUploadFileService(formData);
}
}
// 查改模式
else {
// 两个对象不相同,需要更新数据;如果两个对象相同(所有属性值都相同),不需要更新数据
if (!isEqual(props.qualityFileInfo, qualityFileObj.value)) {
// 点击【确定】,确定质量体系文件信息,通知父组件执行相应的操作
emit("confirm-quality-file", qualityFileObj.value);
}
}
dialogVisible.value = false;
};
......
</script>
<template>
<el-dialog
class="quality-file-dialog"
title="基础信息"
width="640px"
top="0vh"
center
style="border-radius: 10px"
v-model="dialogVisible"
:close-on-press-escape="true"
:close-on-click-modal="false"
:show-close="true"
@close="onCancelClick">
<template #default>
<el-form :model="qualityFileObj" label-width="auto" style="margin: 8px 16px">
<el-row :gutter="10">
<el-col :span="12">
<el-form-item label="序号" label-position="right">
<el-input v-model="qualityFileObj.orderNo" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="文件编号" label-position="right">
<el-input v-model="qualityFileObj.fileNo" clearable :disabled="!props.isNew" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="24">
<el-form-item label="文件名称" label-position="right">
<el-input v-model="qualityFileObj.fileName" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item label="版本号" label-position="right">
<el-input v-model="qualityFileObj.edition" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="修改记录" label-position="right">
<el-input v-model="qualityFileObj.modifyRecord" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item label="所属小类" label-position="right">
<el-select
v-model="qualityFileObj.smallCategory"
placeholder="请选择"
clearable
@clear="handleSmallCategoryClear">
<el-option v-for="item in fileSmallCategoryList" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属类别" label-position="right">
<el-select
v-model="qualityFileObj.detailCategory"
placeholder="请选择"
clearable
filterable
default-first-option>
<el-option v-for="item in fileDetailCategoryList" :label="item.label" :value="item.label" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item label="发布部门" label-position="right">
<el-select
v-model="qualityFileObj.issueDept"
placeholder="请选择"
clearable
filterable
allow-create
default-first-option
:value-on-clear="``">
<el-option v-for="item in departmentList" :label="item.deptName" :value="item.deptName" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发布日期" label-position="right">
<el-date-picker
v-model="qualityFileObj.issueDate"
type="date"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="24">
<el-form-item label="备注" label-position="right">
<el-input v-model="qualityFileObj.remark" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="15">
<el-form-item label="附件" label-position="right">
<el-input class="input-readonly" v-model="qualityFileObj.filePathname" readonly />
</el-form-item>
</el-col>
<el-col :span="9">
<el-form-item label="" label-position="right">
<BasePreventReClickButton type="primary" plain :loading="false" @click="onUploadClick">{{
qualityFileObj.isNullContent ? "上传" : "重传"
}}</BasePreventReClickButton>
<BasePreventReClickButton
:loading="false"
:disabled="qualityFileObj.isNullContent"
@click="onDownloadClick"
>下载</BasePreventReClickButton
>
<BasePreventReClickButton
type="danger"
plain
:loading="false"
:disabled="qualityFileObj.isNullContent"
@click="onClearClick"
>清除</BasePreventReClickButton
>
<!-- 文件输入元素,不显示,通过点击按钮【上传/重传】执行 onUploadClick,模拟点击该元素,从而触发 handleUpload 事件 -->
<input ref="fileInputRef" type="file" style="display: none" @change="handleUpload" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<!-- 模态框底部插槽,就算没有内容,也要写一个空的插槽,否则会影响布局 -->
<template #footer>
<div class="footer-div">
<BasePreventReClickButton class="btn" type="primary" @click="onConfirmClick">确定</BasePreventReClickButton>
<el-button class="btn" @click="onCancelClick">取消</el-button>
</div>
</template>
</el-dialog>
</template>
<style scoped lang="scss">
.el-form {
margin: 0 15px;
}
// 设置等同于disabled的样式效果,背景色 #f5f7fa,字体颜色 #c0c4cc
// 设置类名为 input-readonly 的元素的背景颜色
.input-readonly :deep(.el-input__wrapper) {
background-color: #f5f7fa;
}
// 设置只读的input的字体颜色,不使用类名,使用类属性选择器(.类名[属性名])
:deep(.el-input__inner[readonly]) {
color: #c0c4cc;
}
</style>
qualityFile.ts
TypeScript
import request from "@/utils/request";
import type { IQualityFile, IQualityFileQueryObj } from "@/views/resources/QualityFile/types";
/**
* 新增质量体系文件信息,附带上传文件,使用 put 发送请求,发送的数据有:请求体数据(文件数据 uploadFile),请求参数数据(普通对象数据 qualityFile)
* @param qualityFile 质量体系文件信息 {@link IQualityFile}
* @param formData 表单数据,包含的数据只有:文件数据(uploadFile) {@link FormData}
* @returns
*/
export const qualityFileAddAttachUploadFileWithPutService = (qualityFile: IQualityFile, formData: FormData) => {
// 发送请求,发送的数据有:请求体数据(文件数据 uploadFile),请求参数数据(普通对象数据 qualityFile)
return request.put("/resources/qualityFile/addAttachUploadFile", formData, {
params: qualityFile,
// 上传文件,需设置 headers 信息,将"Content-Type"设置为"multipart/form-data"
headers: {
"Content-Type": "multipart/form-data"
}
});
};
/**
* 新增质量体系文件信息,附带上传文件,使用 patch 发送请求,通过请求体发送表单数据 formData
* @param formData 表单数据,包含的数据有:文件数据(uploadFile)和
* 普通对象的属性数据(fileNo、fileName、edition、orderNo、issueDept、issueDate、smallCategory、detailCategory、modifyRecord、remark) {@link FormData}
* @returns
*/
export const qualityFileAddAttachUploadFileWithPatchService = (formData: FormData) => {
return request.patch("/resources/qualityFile/addAttachUploadFile", formData, {
// 上传文件,需设置 headers 信息,将"Content-Type"设置为"multipart/form-data"
headers: {
"Content-Type": "multipart/form-data"
}
});
};
/**
* 新增质量体系文件信息,附带上传文件,使用 post 发送请求,通过请求体发送表单数据 formData
* @param formData 表单数据,包含的数据有:文件数据(uploadFile)和 普通对象的json字符串数据(qualityFile) {@link FormData}
* @returns
*/
export const qualityFileAddAttachUploadFileService = (formData: FormData) => {
return request.post("/resources/qualityFile/addAttachUploadFile", formData, {
// 上传文件,需设置 headers 信息,将"Content-Type"设置为"multipart/form-data"
headers: {
"Content-Type": "multipart/form-data"
}
});
};
4、Spring Boot 后端
DTO
QualityFile.java
java
package com.weiyu.pojo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
/**
* 质量体系文件
*/
@Schema(description = "质量体系文件实体")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class QualityFile {
@Schema(description = "文件唯一标识 id", example = "1")
private Integer id;
@Schema(description = "序号", example = "A00")
private String orderNo;
@Schema(description = "文件编号", example = "CZCDC/QM-2024-A0")
private String fileNo;
@Schema(description = "文件名称", example = "质量手册(封面)")
private String fileName;
@Schema(description = "版本号", example = "第9版")
private String edition;
@Schema(description = "修改记录", example = "第9版第0次修改")
private String modifyRecord;
@Schema(description = "所属小类", example = "3003")
private String smallCategory;
@Schema(description = "所属类别", example = "第九版")
private String detailCategory;
@Schema(description = "发布部门", example = "质量管理科")
private String issueDept;
@Schema(description = "发布日期", example = "2025-08-16")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate issueDate;
@Schema(description = "备注", example = " ")
private String remark;
@Schema(description = "文件路径名称", example = "CZCDC∕QM-2018-B2 4.2 人员vVv+DW=dw.doc")
private String filePathname;
@Schema(description = "是否空内容", example = "true")
private Boolean isNullContent;
}
QualityFileDTO.java
java
package com.weiyu.pojo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
/**
* 质量体系文件 DTO
*/
@Schema(description = "质量体系文件 DTO")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class QualityFileDTO {
@Schema(description = "序号", example = "A00")
private String orderNo;
@Schema(description = "文件编号", example = "CZCDC/QM-2024-A0")
private String fileNo;
@Schema(description = "文件名称", example = "质量手册(封面)")
private String fileName;
@Schema(description = "版本号", example = "第9版")
private String edition;
@Schema(description = "修改记录", example = "第9版第0次修改")
private String modifyRecord;
@Schema(description = "所属小类", example = "3003")
private String smallCategory;
@Schema(description = "所属类别", example = "第九版")
private String detailCategory;
@Schema(description = "发布部门", example = "质量管理科")
private String issueDept;
@Schema(description = "发布日期", example = "2025-08-16")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate issueDate;
@Schema(description = "备注", example = " ")
private String remark;
}
控制层 QualityFileController.java
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 {
@Autowired
private QualityFileService qualityFileService;
/**
* 新增质量体系文件,附带上传文件,使用 @PutMapping 接收请求,@ModelAttribute 接收参数
* @ ModelAttribute是 Spring MVC 中处理复杂数据绑定和模型管理的核心注解,尤其适合表单操作和共享数据场景,能显著减少样板代码
* 将 HTTP 请求参数(如表单字段、查询参数)自动绑定到 Java 对象的属性上
* 示例说明:
* 如果查询参数有fileNo为A1,表单字段也有fileNo为B2,那么qualityFile中的fileNo为B2,A1,即先获取表单字段,再获取查询参数,中间使用逗号分隔
* MultipartFile参数名称说明:
* 因为前端使用 formData.append("uploadFile", file) 用的参数名称是 uploadFile
* 后端这里使用 uploadFile 与前端一致,可以不使用 @RequestPart("uploadFile"),也可以使用
* @param qualityFile 质量体系文件 {@link QualityFile}
* @param uploadFile 上传文件 {@link MultipartFile}
*/
@PutMapping("/addAttachUploadFile")
public Result<?> addAttachUploadFile(@ModelAttribute QualityFile qualityFile,
MultipartFile uploadFile) throws IOException {
log.info("【质量体系文件】,新增附带上传文件,使用 @PutMapping 接收请求,@ModelAttribute 接收参数," +
"/resources/qualityFile/addAttachUploadFile,qualityFile = {},uploadFile = {}", qualityFile, uploadFile);
qualityFileService.addAttachUploadFile(qualityFile, uploadFile);
return Result.success();
}
/**
* 新增质量体系文件,附带上传文件,使用 @PatchMapping 接收请求,@ModelAttribute 接收参数
* @ ModelAttribute是 Spring MVC 中处理复杂数据绑定和模型管理的核心注解,尤其适合表单操作和共享数据场景,能显著减少样板代码
* 将 HTTP 请求参数(如表单字段、查询参数)自动绑定到 Java 对象的属性上
* 示例说明:
* 如果查询参数有fileNo为A1,表单字段也有fileNo为B2,那么qualityFile中的fileNo为B2,A1,即先获取表单字段,再获取查询参数,中间使用逗号分隔
* MultipartFile参数名称说明:
* 因为前端使用 formData.append("uploadFile", file) 用的参数名称是 uploadFile
* 后端这里使用 uploadFile 与前端一致,可以不使用 @RequestPart("uploadFile"),也可以使用
* @param qualityFileDTO 质量体系文件 DTO {@link QualityFileDTO}
* @param uploadFile 上传文件 {@link MultipartFile}
*/
@PatchMapping("/addAttachUploadFile")
public Result<?> addAttachUploadFile(@ModelAttribute QualityFileDTO qualityFileDTO,
MultipartFile uploadFile) throws IOException {
log.info("【质量体系文件】,新增附带上传文件,使用 @PatchMapping 接收请求,@ModelAttribute 接收参数," +
"/resources/qualityFile/addAttachUploadFile,qualityFileDTO = {},uploadFile = {}", qualityFileDTO, uploadFile);
QualityFile qualityFile = new QualityFile();
qualityFile.setFileNo(qualityFileDTO.getFileNo());
qualityFile.setFileName(qualityFileDTO.getFileName());
qualityFile.setEdition(qualityFileDTO.getEdition());
qualityFile.setOrderNo(qualityFileDTO.getOrderNo());
qualityFile.setIssueDept(qualityFileDTO.getIssueDept());
qualityFile.setIssueDate(qualityFileDTO.getIssueDate());
qualityFile.setSmallCategory(qualityFileDTO.getSmallCategory());
qualityFile.setDetailCategory(qualityFileDTO.getDetailCategory());
qualityFile.setModifyRecord(qualityFileDTO.getModifyRecord());
qualityFile.setRemark(qualityFileDTO.getRemark());
qualityFileService.addAttachUploadFile(qualityFile, uploadFile);
return Result.success();
}
/**
* 新增质量体系文件,附带上传文件,使用 @PostMapping 接收请求,@RequestPart 接收参数
* @ RequestPart是 Spring MVC 中处理 multipart 请求的灵活工具,尤其适合文件与结构化数据混合提交的场景,简化了数据绑定与异常处理
* MultipartFile参数名称说明:
* 因为前端使用 formData.append("uploadFile", file) 用的参数名称是 uploadFile
* 后端这里使用 uploadFile 与前端一致,可以使用 @RequestPart("uploadFile"),也可以不使用
* String参数名称说明:
* 因为前端使用 formData.append("qualityFile", JSON.stringify(qualityFileObj.value)) 用的参数名称是 qualityFile
* 后端这里使用 qualityFileJsonString 与前端不一致,必须使用 @RequestPart("qualityFile") 映射参数名称
* @param qualityFileJsonString 质量体系文件json字符串
* @param uploadFile 上传文件 {@link MultipartFile}
*/
@PostMapping("/addAttachUploadFile")
public Result<?> addAttachUploadFile(@RequestPart("qualityFile") String qualityFileJsonString,
@RequestPart("uploadFile") MultipartFile uploadFile) throws IOException {
log.info("【质量体系文件】,新增附带上传文件,使用 @PostMapping 接收请求,@RequestPart 接收参数," +
"/resources/qualityFile/addAttachUploadFile,qualityFileJsonString = {},uploadFile = {}",
qualityFileJsonString, uploadFile);
// json字符串 转换成 java对象(QualityFile)
QualityFile qualityFile = JSON.parseObject(qualityFileJsonString, QualityFile.class);
qualityFileService.addAttachUploadFile(qualityFile, uploadFile);
return Result.success();
}
}
服务层 QualityFileServiceImpl.java
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 {
@Autowired
private QualityFileMapper qualityFileMapper;
/**
* 查询质量体系文件列表
*
* @param queryDTO 查询 DTO
* @return 质量体系文件列表
*/
@Override
public List<QualityFile> query(QualityFileQueryDTO queryDTO) {
if (queryDTO == null) return new ArrayList<>();
return qualityFileMapper.select(queryDTO);
}
/**
* 新增质量体系文件
*
* @param qualityFile 质量体系文件
*/
@Override
public void add(QualityFile qualityFile) {
qualityFileMapper.insert(qualityFile);
}
/**
* 上传质量体系文件
*
* @param fileNo 文件编号
* @param uploadFile 上传文件
*/
@Override
public 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);
}
/**
* 新增质量体系文件,附带上传文件
*
* @param qualityFile 质量体系文件
* @param uploadFile 上传文件
*/
@Override
@Transactional
public void addAttachUploadFile(QualityFile qualityFile, MultipartFile uploadFile) throws IOException {
// 新增质量体系文件
add(qualityFile);
// 上传质量体系文件
uploadFile(qualityFile.getFileNo(), uploadFile);
}
}
Mapper QualityFileMapper.java
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 queryDTO 查询 DTO
* @return 质量体系文件列表
*/
List<QualityFile> select(QualityFileQueryDTO queryDTO);
/**
* 新增质量体系文件
* @param qualityFile 质量体系文件
*/
void insert(QualityFile qualityFile);
/**
* 保存质量体系文件数据到数据库
* @param fileNo 文件编号
* @param fileData 上传文件
*/
void saveFile(String fileNo, FileData fileData);
}
数据库sql操作 QualityFileMapper.xml
XML
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.weiyu.mapper.QualityFileMapper">
<!--mssql-->
<!-- 查询质量体系文件列表 -->
<select id="select" resultType="com.weiyu.pojo.QualityFile">
select
Cfm_OrderID as orderNo, Cfm_ID as fileNo, Cfm_Name as fileName, Cfm_Edition as edition,
Cfm_ModifyRecord as modifyRecord, Cfm_SmallType as smallCategory, Cfm_SampleType as detailCategory,
Cfm_Dept as issueDept, Cfm_IssueDate as issueDate, Cfm_Memo as remark,
cfm_ContentFileName as filePathname, cfm_ContentIsNull as isNullContent
from ControledFileMain
<where>
Cfm_BigType = '3'
<if test="fileNo != null and fileNo != ''">
and Cfm_ID like '%' + #{fileNo} + '%'
</if>
<if test="fileName != null and fileName != ''">
and Cfm_Name like '%' + #{fileName} + '%'
</if>
</where>
order by Cfm_SmallType, Cfm_ID
</select>
<!-- 新增质量体系文件 -->
<insert id="insert">
insert into ControledFileMain(
Cfm_BigType, Cfm_OrderID, Cfm_ID, Cfm_Name, Cfm_Edition,
Cfm_ModifyRecord, Cfm_SmallType, Cfm_SampleType,
Cfm_Dept, Cfm_IssueDate, Cfm_Memo
)
values (
'3', #{orderNo}, #{fileNo}, #{fileName}, #{edition},
#{modifyRecord}, #{smallCategory}, #{detailCategory},
#{issueDept}, #{issueDate}, #{remark}
)
</insert>
<!-- 保存质量体系文件数据到数据库 -->
<update id="saveFile">
update ControledFileMain set
cfm_ContentFileName = #{fileData.fileName}, cfm_Content = #{fileData.fileContent}, cfm_ContentIsNull = 0
where Cfm_BigType = '3' and Cfm_ID = #{fileNo}
</update>
</mapper>
5、数据库表结构(sql server)

sql
CREATE TABLE [dbo].[ControledFileMain](
[Cfm_ID] [varchar](30) NOT NULL,
[Cfm_Name] [varchar](200) NULL,
[Cfm_BigType] [varchar](10) NOT NULL,
[Cfm_SmallType] [varchar](10) NULL,
[Cfm_Dept] [varchar](50) NULL,
[Cfm_Memo] [varchar](100) NULL,
[Cfm_ModifyRecord] [varchar](128) NULL,
[Cfm_Edition] [varchar](50) NULL,
[Cfm_IssueDate] [datetime] NULL,
[Cfm_SampleType] [varchar](50) NULL,
[Cfm_StandardType] [int] NULL,
[Cfm_OrderID] [varchar](10) NULL,
[Cfm_Holder] [varchar](50) NULL,
[cfm_Content] [image] NULL,
[cfm_ContentIsNull] [bit] NULL,
[cfm_ContentFileName] [varchar](255) NULL,
CONSTRAINT [PK_ControledFileMain] PRIMARY KEY CLUSTERED
(
[Cfm_ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO