Vue3+Spring Boot技术栈,前端提交混合表单数据(普通字段+文件字段),上传文件,后端插入数据,将文件保存到数据库

一、技术栈

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