vue+springboot的文件上传处理

时间:2024-01-23 19:41:35

目录

1.前端:

2.说明


1.示例:

使用前端框架中的上传组件,例如element plus及arco design,要使用自定义的上传属性或者change属性,获取上传的文件信息。

<template>
  <a-drawer :width="600" :visible="drawerVisible" @ok="handleOk" :ok-loading="okLoading" @cancel="handleCancel"
            unmountOnClose>
    <template #title>
      <span> {{ title }} </span>
    </template>
    <a-form ref="formRef" :model="insertForm" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
            label-align="left">
      <a-row :gutter="20">
        <a-col :span="20">
          <a-form-item field="releaseTime" label="发行时间"
                       :rules="[{required: true,message: '发行时间不能为空'}]"
                       :validate-trigger="['change', 'blur']">
            <a-date-picker
                v-model="insertForm.releaseTime"
                style="width: 165px;"
                show-time
                format="YYYY-MM-DD HH:mm"
            />
          </a-form-item>
        </a-col>
      </a-row>
      <a-row :gutter="20">
        <a-col :span="20">
          <a-form-item field="forceType" label="是否强制">
            <a-radio-group :default-value="0"
                           v-model="insertForm.forceType"
                           :options="[{ label: '否', value: 0},{ label: '是', value: 1 }]"/>
          </a-form-item>
        </a-col>
      </a-row>
      <a-row :gutter="20">
        <a-col :span="20">
          <a-form-item field="content" label="更新内容" :rules="[{required: true,message: '更新内容不能为空'},{maxLength:100,message:'内容不能超过100位'},
        {
          validator: (value, cb) => {
            if (!value.trim()) {
              cb('更新内容不能为空')
            } else {
              cb()
            }
          }
        }]"
                       :validate-trigger="['change', 'blur']" >
            <a-textarea v-model="insertForm.content" placeholder="请填写更新内容"/>
          </a-form-item>
        </a-col>
      </a-row>
      <a-row :gutter="20">
        <a-col :span="20">
          <a-form-item field="file" label="附件信息">
            <a-upload
                draggable
                accept=".apk"
                :limit="1"
                :custom-request="customRequest"
                :default-file-list="fileList"
                :show-remove-button="!isUpdate"
            />
          </a-form-item>
        </a-col>
      </a-row>
    </a-form>
  </a-drawer>
</template>

<script lang="ts" setup>
import {ref} from "vue";
import {FormInstance} from '@arco-design/web-vue/es/form';
import {Message} from '@arco-design/web-vue';
import {update, upload, VersionData} from "@/api/appVer";
import {getResult} from "@/utils/api";
import moment from "moment";

// 表单对象
const formRef = ref<FormInstance>();
// 触发更新时间
const emits = defineEmits(['handleQuery']);
// 弹窗对象
const drawerVisible = ref(false);
// 表单数据对象
let insertForm = ref<VersionData>({forceType: 0, versionCode: 1});
// 框展示类型
let title = ref();
// 文件信息
const fileList = ref([] as any)
// 是否是更新的标识
let isUpdate = ref(false);
// 定义文件对象
let fileItem: any = {uid: "0"};
// 确定键loading
const okLoading = ref(false);

// 打开弹窗
const openDialog = async (row: any, type: any) => {
  if (type == "2") {
    insertForm.value = row;
    title.value = "修改";
    isUpdate.value = true;
    // 设置文件信息
    fileItem = {
      uid: row.id,
      name: row.appName,
      url: row.appPath,
    };
    // 设置文件列表
    fileList.value = [fileItem];

  } else {
    title.value = "新增";
    insertForm.value = {forceType: 0, versionCode: 1}
    isUpdate.value = false;
    fileList.value = [];
  }
  drawerVisible.value = true;
};

// 导出方法在父组件中进行使用
defineExpose({openDialog});

// 表单提交
const handleOk = async () => {
  okLoading.value = true;
  const validRes = await formRef.value?.validate();
  // 校验失败直接返回
  if (validRes) {
    okLoading.value = false;
    return;
  }
  // 附件必填
  if (!fileItem.name) {
    Message.error({content: '附件信息不能为空', position: 'top'})
    okLoading.value = false;
    return;
  }
  const formData = new FormData();
  formData.append('file', fileItem.file);
  formData.append("data", JSON.stringify(insertForm.value))
  // 调用新增接口
  let result;

  // 更新
  try {
    if (isUpdate.value) {
      result = await update({
        reqBody: {
          ...insertForm.value,
          idCondition: insertForm.value.id,
          releaseTime: moment(insertForm.value.releaseTime)
        },
        reqHdr: null
      });
    } else {
      result = await upload(formData);
    }
    getResult(result);
  } catch (e) {
    return;
  } finally {
    okLoading.value = false;
  }
  // 重置表单
  formRef.value?.resetFields()
  drawerVisible.value = false;
  // 调用父组件方法进行画面刷新
  emits('handleQuery');
};

// 取消
const handleCancel = () => {
  // 重置表单
  formRef.value?.resetFields()
  drawerVisible.value = false;
}

// 上传文件
const customRequest = (option: any) => {
  const {onSuccess} = option
  fileItem = option.fileItem
  onSuccess();
}
</script>

示例说明:

画面中的表单存在可输入的信息以及要上传附件。上传组件如下:

            <a-upload
                draggable
                accept=".apk"
                :limit="1"
                :custom-request="customRequest"
                :default-file-list="fileList"
                :show-remove-button="!isUpdate"
            />

开启了拖拽上传,设置了可以上传的格式为apk格式,设置了上传文件个数最多上传一个,设置了自定义的上传方法,上传后触发此方法,设置了默认的文件列表,用于修改时进行显示,设置了是否有文件删除按钮,修改时附件信息不可修改。

上传之后,触发了自定义的上传方法,如下:

// 上传文件
const customRequest = (option: any) => {
  const {onSuccess} = option
  fileItem = option.fileItem
  onSuccess();
}

存储了上传文件的信息,并且执行了上传成功的方法,在画面上会显示上传成功的标志,这是为了让画面显示的更好看,否则会显示上传中的标志,对用户有误导。

点击确定按钮,调用后端的接口,进行文件的上传及信息的存储。如下:

// 表单提交
const handleOk = async () => {
  okLoading.value = true;
  const validRes = await formRef.value?.validate();
  // 校验失败直接返回
  if (validRes) {
    okLoading.value = false;
    return;
  }
  // 附件必填
  if (!fileItem.name) {
    Message.error({content: '附件信息不能为空', position: 'top'})
    okLoading.value = false;
    return;
  }
  const formData = new FormData();
  formData.append('file', fileItem.file);
  formData.append("data", JSON.stringify(insertForm.value))
  // 调用新增接口
  let result;

  // 更新
  try {
    if (isUpdate.value) {
      result = await update({
        reqBody: {
          ...insertForm.value,
          idCondition: insertForm.value.id,
          releaseTime: moment(insertForm.value.releaseTime)
        },
        reqHdr: null
      });
    } else {
      result = await upload(formData);
    }
    getResult(result);
  } catch (e) {
    return;
  } finally {
    okLoading.value = false;
  }
  // 重置表单
  formRef.value?.resetFields()
  drawerVisible.value = false;
  // 调用父组件方法进行画面刷新
  emits('handleQuery');
};

首先进行了表单校验及附件信息的校验。

因为要上传文件,所以使用formData,创建formData对象,存储文件对象及表单对象的字符串。新增时直接调用upload方法,进行上传,使用post方式。api文件中的内容如下:

// 上传数据
export async function upload(params: any): Promise<any> {
    return axios.post<ResponseData>("/xxxx/file/uploadInfo", params);
}

后端:

package com.kingagroot.info.tianjiannongshi.mvc.controller.file;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.kingagroot.info.common.annotation.log.SysLog;
import com.kingagroot.info.common.domains.standard.netformat.ResponseBean;
import com.kingagroot.info.common.domains.standard.netformat.RtnBean;
import com.kingagroot.info.common.domains.standard.rescodeformat.ResMsg;
import com.kingagroot.info.tianjiannongshi.mvc.persistent.entity.master.base.AppVersionEntity;
import com.kingagroot.info.tianjiannongshi.mvc.service.master.customize.file.SftpService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;

import javax.servlet.http.HttpServletRequest;


/**
 * 文件上传控制层API入口
 *
 * @author Lison 2023/12/11
 */
@Api(value = "/xxxx/file", tags = {"文件上传的控制层API入口"})
@RestController
@RequestMapping("/xxxx/file")
public class SftpController {
    @Autowired
    private SftpService sftpService;

    @SysLog
    @ApiOperation(value = "上传app文件数据接口")
    @PostMapping(value = "/uploadInfo")
    public ResponseBean mb1002(@RequestParam("file") MultipartFile file, @RequestParam(value = "data") String data) {
        RtnBean rtnBean = sftpService.appUpload(file, JSON.parseObject(data,AppVersionEntity.class));
        if(rtnBean.getRsl()) {
            return new ResponseBean("uploadInfo", ResMsg.SUCCESS.getRetCode(), ResMsg.SUCCESS.getRetMsg(), rtnBean.getRtnObj());
        }else {
            return new ResponseBean("uploadInfo", rtnBean.getResMsg().getRetCode(),  rtnBean.getResMsg().getRetMsg(), null);
        }
    }

}

后端使用RequestParam进行参数接口,参数名和前端中formData中的key执行,接收到的表单数据可以使用JSON转换为具体的实体类进行使用。

2.说明

①上传时,请求头中的content-type为multipart/form-data格式的。

②使用formData存储表单中的数据以及文件数据,并作为参数传给后端。

FormData 接口提供了一种表示表单数据的键值对 key/value 的构造方式。主要是用来上传文件。

// 使用append来添加键/值对到表单里面
formData.append('username', 'yang');
// 使用get方法获取key对应的值
formData.get('username');
// 使用delete方法删除键/值对
formData.delete('username');
// 使用set方法,当key存在时,进行更新;key不存在时,进行添加
formData.set('username', 'Chris');
// 使用has方法判断是否存在key
formData.has('username');
//使用entries方法返回一个 iterator对象 ,此对象可以遍历访问FormData中的键值对
var i = formData.entries();
for(var pair of formData.entries()) {
   console.log(pair[0]+ ', '+ pair[1]);
}
// FormData.keys() 该方法返回一个迭代器(iterator),遍历了该 formData 包含的所有key
for (var key of formData.keys()) {
   console.log(key);

// FormData.values() 方法返回一个允许遍历该对象中所有值的 迭代器 。这些值是 USVString 或是Blob 对象。
for (var value of formData.values()) {
   console.log(value);
}

③后端可以使用requestParam注解进行文件及表单数据的接收,接收表单数据要使用String类型接收,然后再通过JSON工具类转换成具体的实体对象进行使用。 当然,后端也可以使用requestPart进行接收。直接将requestParam换成requestPart即可,表单数据还是使用String类型进行接收。

查看其他资料,后端的表单数据也可以用实体类进行接收,按照下面的方法试了一下,报400的错误,没有实现。

https://blog.csdn.net/qq_38076935/article/details/123008493

@RequestParam和@RequestPart的区别-CSDN博客

SpringBoot @RequestPart 同时接收文件和复杂json数据_requestpart同时接收附件和对象-CSDN博客

④注意使用不同组件时,获取上传文件的方式。

⑤修改时,文件列表的回显可以使用组件的默认值类似的属性。