Spring Cloud Feign MultipartFile文件上传踩坑之路(包含前端文件上传请求、后端文件保存到aliyun-oss文件服务器)

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

一、前端文件上传

文件上传组件用的是ant-design的a-upload组件,我的界面如下所示:
Spring Cloud Feign MultipartFile文件上传踩坑之路(包含前端文件上传请求、后端文件保存到aliyun-oss文件服务器)_第1张图片
文件上传请求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.data
            if(res.data.code == 400) {
                curFile.status = 'error'
                curFile['error'] = curFile.response.msg
                console.error(`文件${curFile.name}上传失败:${res.data.msg}`)
            } else {
                // 通知组件该文件上传成功
                curFile.status = 'done'
                curFile.url = res.data.data
                curFile.thumbUrl = res.data.data
                console.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 {
    @Autowired
    private 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
 */
@Service
public class OSSService implements IOSSService {
    @Autowired
    private OSS ossClient;

    @Autowired
    private OSSConfiguration ossConfiguration;

    /**
     * 上传文件到阿里云 OSS 服务器
     * 链接:https://help.aliyun.com/document_detail/oss/sdk/java-sdk/upload_object.html?spm=5176.docoss/user_guide/upload_object
     *
     * @param file 文件
     * @param storagePath 文件存储路径
     * @return 文件存储完整路径
     */
    @Override
    public 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.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        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 {
    @Autowired
    IImageConvertService 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
 */
@Service
public class ImageConvertService implements IImageConvertService {
    @Autowired
    private RemoteFileService remoteFileService;

    @Override
    public String uploadFile(MultipartFile file, String storagePath) {
       return remoteFileService.uploadFile(file, storagePath);
    }
}

ok,到这一步我们的工作就完成了,测试一下:
Spring Cloud Feign MultipartFile文件上传踩坑之路(包含前端文件上传请求、后端文件保存到aliyun-oss文件服务器)_第2张图片
Spring Cloud Feign MultipartFile文件上传踩坑之路(包含前端文件上传请求、后端文件保存到aliyun-oss文件服务器)_第3张图片
可以看到我们的文件已经成功上传,并成功保存至目标服务器返回了一个文件存储url,有什么不懂的可以在评论区问我,哪里讲的不对请大佬轻喷,我也是第一次做文件传输。

你可能感兴趣的:(软件开发架构平台,spring,cloud,前端,服务器)