Spring Cloud Feign MultipartFile文件上传踩坑之路总结

一、前端文件上传

文件上传组件用的是ant-design的a-upload组件,我的界面如下所示:

文件上传请求API:
FileUtils.js

import axios from "axios"const uploadApi = ({file, URL, onUploadProgress}) => {const formData = new FormData()formData.append('file', file)return axios.post(URL, formData, {headers:{'Content-type': 'multipart/form-data',},onUploadProgress // 上传进度回调函数 onUploadProgress(ev))}) }export default uploadApi;

需要注意的只有FileUtils.js定义的uploadApi请求函数,其中URL为后端请求接口(“/imageConvert/upload”),文件上传方法必须定义为POST,在headers加入’Content-type’: ‘multipart/form-data’,后端即可用@RequestParam或者@RequestPart + MultipartFile 来接受文件。
FileUpload.vue(无关紧要,用法大致相同,看你自己需求,这里只是提供一个参考范例)

// 自定义文件上传公共函数// e - 上传组件返回的上传实例,里面包括 file,和一些组件方法// e.file - 上传的文件实例对象const customUpload = e => {let curFile = fileList.value.filter(item => item.uid == e.file.uid)[0]curFile.status = 'uploading'uploadApi({file: e.file,URL: '/imageConvert/upload',// uid: 'admin',// 需要更改为用户id,待修改onUploadProgress: ev => {// ev - axios 上传进度实例,上传过程触发多次// ev.loaded 当前已上传内容的大小,ev.total - 本次上传请求内容总大小// console.log(ev);const percent = (ev.loaded / ev.total) * 100;// 计算出上传进度,调用组件进度条方法e.onProgress({ percent });}}).then(res => {let curFile = fileList.value.filter(item => item.uid == e.file.uid)[0]curFile.response = res.dataif(res.data.code == 400) {curFile.status = 'error'curFile['error'] = curFile.response.msgconsole.error(`文件${curFile.name}上传失败:${res.data.msg}`)} else {// 通知组件该文件上传成功curFile.status = 'done'curFile.url = res.data.datacurFile.thumbUrl = res.data.dataconsole.log(`文件${curFile.name}上传成功`, curFile.url);}}).catch(err => {let curFile = fileList.value.filter(item => item.uid == e.file.uid)[0]curFile.status = 'error'curFile['error'] = '文件传输失败'console.log('上传失败', err);})}

二、后端处理

后端框架我这里使用的是Spring Cloud,将文件处理统一定义为一个单独模块,通过Feign为其他业务模块提供服务。

服务提供者

Controller
这里注意要在@PostMapping加入MediaType.MULTIPART_FORM_DATA_VALUEMediaType.MULTIPART_FORM_DATA_VALUE,并且参数使用@RequestPart来接受参数

@RefreshScope@RestController@RequestMapping("/oss/file")public class OSSFileController {@Autowiredprivate IOSSService ossService;/** * 文件上传,入参可以根据具体业务进行添加 * @param file 文件 * @return 响应结果 */@PostMapping( value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)public String uploadFile(@RequestPart("file") MultipartFile file, @RequestParam("storagePath") String storagePath) {return ossService.uploadFile(file, storagePath);}}

Service(文件存储方式跟Feign没关系,可忽略)
收到文件后我们将其保存在aliyun-oss文件服务器中:
如何将文件保存在aliyun-oss具体请参考:Spring Boot 集成阿里云 OSS 进行文件存储
或者可以使用file.transferTo(File file)保存至本地

** * OSS服务类 * / @Author: ZenSheep * / @Date: 2023/8/10 16:05 */@Servicepublic class OSSService implements IOSSService {@Autowiredprivate OSS ossClient;@Autowiredprivate OSSConfiguration ossConfiguration;/** * 上传文件到阿里云 OSS 服务器 * 链接:https://help.aliyun.com/document_detail/oss/sdk/java-sdk/upload_object.html" />@Overridepublic String uploadFile(MultipartFile file, String storagePath) {String url = "";try {// UUID生成文件名,防止重复String fileName = "";String baseName = OSSFileUtils.getBaseName(OSSFileUtils.getBaseName(file.getOriginalFilename()));InputStream inputStream = file.getInputStream();// 创建ObjectMetadata,设置用户自定义的元数据以及HTTP头,比如内容长度,ETag等ObjectMetadata objectMetadata = new ObjectMetadata();objectMetadata.setContentLength(inputStream.available());objectMetadata.setCacheControl("no-cache");objectMetadata.setHeader("Pragma", "no-cache");objectMetadata.setContentType(OSSFileUtils.getcontentType(file.getOriginalFilename()));objectMetadata.setContentDisposition("inline;filename=" + baseName);fileName = storagePath + "/" + UUID.randomUUID().toString() + "/"+ file.getOriginalFilename();// 上传文件:调用ossClient的putObject方法完成文件上传,并返回文件名ossClient.putObject(ossConfiguration.getBucketName(), fileName, inputStream, objectMetadata);// 设置签名URL过期时间,单位为毫秒。Date expiration = new Date(new Date().getTime() + 3600 * 1000);// 生成以GET方法访问的签名URL,访客可以直接通过浏览器访问相关内容。url = ossClient.generatePresignedUrl(ossConfiguration.getBucketName(), fileName, expiration).toString();} catch (IOException e) {e.printStackTrace();}return url;}}

Feign

引入依赖:

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>

RemoteFileService
这里同样需要注意:@PostMapping需要加入consumes = MediaType.MULTIPART_FORM_DATA_VALUE,参数传递用@RequestPart(“file”)

import org.springframework.cloud.openfeign.FeignClient;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RequestPart;import org.springframework.web.multipart.MultipartFile;/** * File Feign: 提供File的远程服务 * / @Author: ZenSheep * / @Date: 2023/8/14 18:48 */@FeignClient(name = "opentool-system", contextId="remote-file")public interface RemoteFileService {@PostMapping(value = "/oss/file/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)String uploadFile(@RequestPart("file") MultipartFile file, @RequestParam("storagePath") String storagePath);}

服务消费者

Controller

/** * 图像转换控制类 * / @Author: ZenSheep * / @Date: 2023/8/14 18:59 */@RefreshScope@RestController@RequestMapping("/imageConvert")public class ImageConvertController {@AutowiredIImageConvertService iImageConvertService;@PostMapping("/upload")public R<?> uploadFile(@RequestPart("file") MultipartFile file) {return R.ok(iImageConvertService.uploadFile(file, "ImageConvert/images"));}}

Service(在这里调用feign服务)

/** * 图像转换服务类 * / @Author: ZenSheep * / @Date: 2023/8/14 18:53 */@Servicepublic class ImageConvertService implements IImageConvertService {@Autowiredprivate RemoteFileService remoteFileService;@Overridepublic String uploadFile(MultipartFile file, String storagePath) { return remoteFileService.uploadFile(file, storagePath);}}

ok,到这一步我们的工作就完成了,测试一下:


可以看到我们的文件已经成功上传,并成功保存至目标服务器返回了一个文件存储url,有什么不懂的可以在评论区问我,哪里讲的不对请大佬轻喷,我也是第一次做文件传输。