小言_互联网的博客

SpringFeign 携带复杂参数 进行文件上传

575人阅读  评论(0)

 

网上百度了很多办法均太过简单,大多都是携带几个简单的参数和一个File对象,如果将File和其他参数封装到一个对象或者DTO内就不行了 

网上的例子 MultipartFile 

Feign 无法直接传递文件参数,需要在client端引入几个依赖  

io.github.openfeign.form:feign-form:3.0.3 

io.github.openfeign.form:feign-form-spring:3.0.3

1. 创建服务端

方式与普通的文件上传方法一致


  
  1. @RestController
  2. @RequestMapping( "/producer/upload")
  3. class UploadProducer {
  4. @PostMapping(value = '/upload', consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
  5. String upload( @RequestPart(value = "file") MultipartFile file) {
  6. // ...
  7. return file .originalFilename
  8. }
  9. }

2. 创建client

2.1 需要在客户端引入以下依赖


  
  1. io .github .openfeign .form :feign-form :3.0.3
  2. io .github .openfeign .form :feign-form-spring :3.0.3

2.2 定义client接口


  
  1. @FeignClient(name = 'upload', url = '${upload.base-url}', path = '/producer/upload')
  2. interface UploadClient {
  3. @RequestMapping(value = '/upload', method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE
  4. , produces = MediaType.APPLICATION_JSON_VALUE)
  5. String upload( @RequestPart( "file") MultipartFile file)
  6. }

2.3 添加配置文件

划重点


  
  1. @Configuration
  2. class MultipartSupportConfig {
  3. @Autowired
  4. private ObjectFactory<HttpMessageConverters> messageConverters
  5. // new一个form编码器,实现支持form表单提交
  6. @Bean
  7. Encoder feignFormEncoder() {
  8. return new SpringFormEncoder(new SpringEncoder(messageConverters))
  9. }
  10. }

3. 创建Controller,调用client接口


  
  1. @RestController
  2. @RequestMapping( "/")
  3. class RecordController {
  4. @Autowired
  5. private UploadClient uploadClient
  6. @RequestMapping(value = '/upload', method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
  7. String upload( @RequestParam( "file") MultipartFile file) {
  8. return uploadClient .uploade(file)
  9. }
  10. }

可以看到网上的例子只能传递简单的表单参数 下面我有种需求 如果携带一个对象呢? 对象信息如下


  
  1. package com.smdk.dsminio.vo;
  2. import lombok.Data;
  3. import lombok.ToString;
  4. import org.springframework.web.multipart.MultipartFile;
  5. import java.io.Serializable;
  6. /**
  7. * @author 神秘的凯
  8. *date 2020-03-17 17:16
  9. */
  10. @Data
  11. @ToString
  12. public class MultipartFileParam implements Serializable {
  13. public static final long serialVersionUID= 1L;
  14. // 用户id
  15. private String uid;
  16. //任务ID
  17. private String id;
  18. //总分片数量
  19. private int chunks;
  20. //当前为第几块分片
  21. private int chunk;
  22. //当前分片大小
  23. private long size = 0L;
  24. //文件名
  25. private String name;
  26. //分片对象
  27. private MultipartFile file;
  28. // MD5
  29. private String md5;
  30. //BucketID
  31. private Long bucketId;
  32. //文件夹ID
  33. private Long parentFolderId;
  34. }

 如果这样直接上传Spring的解析器解析不到MultipartFileParam 自定义对象会直接报错 另外Spring只能解析自带的Multipart 对象

报错信息

%s is not a type supported by this encoder.....省略

那么如果传递自定义的参数对象呢 那就是重写Spring的对象解析器 代码如下

Feign 调用端代码


  
  1. package com.smdk.dsminio.apiservice;
  2. import com.smdk.dsminio.config.FeignSupportConfig;
  3. import com.smdk.dsminio.utils.AjaxResult;
  4. import com.smdk.dsminio.vo.MultipartFileParam;
  5. import org.springframework.cloud.openfeign.FeignClient;
  6. import org.springframework.http.MediaType;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. import org.springframework.web.bind.annotation.RequestMethod;
  9. @FeignClient(value = "OSS-STORAGE-SERVICE",configuration = FeignSupportConfig.class)
  10. public interface FileService {
  11. /**
  12. * produces 用于指定返回类型为JSON格式
  13. * consumes 用于指定生产者数据请求类型
  14. * @param multipartFileParam
  15. * @return
  16. */
  17. @RequestMapping(value = "//OSS-STORAGE-SERVICE-$SERVER_ID/api/uploadFileInfo", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE},consumes = MediaType.MULTIPART_FORM_DATA_VALUE,method = RequestMethod.POST)
  18. AjaxResult uploadFileInfo(MultipartFileParam multipartFileParam);
  19. }

服务端 


  
  1. package com.smdk.dsminio.controller;
  2. import cn.hutool.log.StaticLog;
  3. import com.smdk.dsminio.redis.RedisUtil;
  4. import com.smdk.dsminio.service.FileStorageService;
  5. import com.smdk.dsminio.utils.AjaxResult;
  6. import com.smdk.dsminio.utils.Constants;
  7. import com.smdk.dsminio.vo.MultipartFileParam;
  8. import org.apache.commons.fileupload.servlet.ServletFileUpload;
  9. import org.apache.commons.io.FileUtils;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
  12. import org.springframework.http.MediaType;
  13. import org.springframework.web.bind.annotation.*;
  14. import javax.servlet.http.HttpServletRequest;
  15. import java.io.File;
  16. import java.io.IOException;
  17. import java.util.LinkedList;
  18. import java.util.List;
  19. /**
  20. * 默认控制层
  21. * Created by 神秘的凯 on 22020/11/11.
  22. * version 1.0
  23. */
  24. @RestController
  25. @RequestMapping(value = "/api")
  26. public class FileDiskStorageController {
  27. @Autowired
  28. private FileStorageService fileStorageService;
  29. /**
  30. * 上传文件
  31. *
  32. * @param param
  33. * @param request
  34. * @return
  35. * @throws Exception
  36. */
  37. @RequestMapping(value = "/uploadFileInfo", method = RequestMethod.POST)
  38. public AjaxResult uploadFileInfo(MultipartFileParam multipartFileParam) {
  39. StaticLog.info( "上传文件start。");
  40. try {
  41. // 方法1
  42. //storageService.uploadFileRandomAccessFile(param);
  43. // 方法2 这个更快点
  44. boolean uploadResult= fileStorageService.uploadFileByMappedByteBuffer(multipartFileParam);
  45. if (uploadResult){
  46. return AjaxResult.success(uploadResult, "上传完毕");
  47. } else {
  48. return AjaxResult.fail( "分片上传中...正在上传第"+multipartFileParam.getChunk()+ "块文件块,剩余"+(multipartFileParam.getChunks()-multipartFileParam.getChunk())+ "个分块");
  49. }
  50. } catch (IOException e) {
  51. e.printStackTrace();
  52. StaticLog.error( "文件上传失败。{}", multipartFileParam.toString());
  53. return AjaxResult.fail( "文件上传失败");
  54. }
  55. }
  56. }

核心重写feignFormEncoder 方法类 下面的的路由配置请忽略


  
  1. package com.smdk.dsminio.config;
  2. import com.smdk.dsminio.vo.MultipartFileParam;
  3. import feign.Request;
  4. import feign.RequestInterceptor;
  5. import feign.RequestTemplate;
  6. import feign.codec.EncodeException;
  7. import feign.codec.Encoder;
  8. import feign.form.spring.SpringFormEncoder;
  9. import org.springframework.context.annotation.Bean;
  10. import org.springframework.context.annotation.Primary;
  11. import org.springframework.context.annotation.Scope;
  12. import java.io.IOException;
  13. import java.lang.reflect.Type;
  14. import static java.lang.String.format;
  15. public class FeignSupportConfig{
  16. //转换参数请求模型封装
  17. @Bean
  18. @Primary
  19. @Scope("prototype")
  20. public Encoder feignFormEncoder() {
  21. return new SpringFormEncoder( new Encoder() {
  22. @Override
  23. public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
  24. if (bodyType == String.class) {
  25. template.body(Request.Body.bodyTemplate(object.toString(), null));
  26. } else if (bodyType == byte[].class) {
  27. template.body(Request.Body.encoded(( byte[]) object, null));
  28. } else if (bodyType == MultipartFileParam.class) {
  29. MultipartFileParam multipartFileParam = (MultipartFileParam) object;
  30. try {
  31. template.body(Request.Body.encoded(multipartFileParam.getFile().getBytes(), null));
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. }
  35. }
  36. else if (object != null) {
  37. throw new EncodeException(format( "%s is not a type supported by this encoder.", object.getClass()));
  38. }
  39. }
  40. });
  41. }
  42. @Bean
  43. public feign.Logger. Level multipartLoggerLevel() {
  44. return feign.Logger.Level.FULL;
  45. }
  46. //路由重构
  47. @Bean
  48. public RequestInterceptor cloudContextInterceptor() {
  49. return new RequestInterceptor() {
  50. @Override
  51. public void apply(RequestTemplate template) {
  52. String url = template.url();
  53. if (url.contains( "$SERVER_ID")) {
  54. url = url.replace( "$SERVER_ID", route(template));
  55. template.uri(url);
  56. }
  57. if (url.startsWith( "//")) {
  58. url = "http:" + url;
  59. template.target(url);
  60. template.uri( "");
  61. }
  62. }
  63. private CharSequence route(RequestTemplate template) {
  64. // TODO 你的路由算法在这里
  65. return "01";
  66. }
  67. };
  68. }
  69. }

 可以看到我加了个判断  if (bodyType == MultipartFileParam.class) 进行自己封装的对象解析

大工告成  另外说下这个问题 品读了Spring的源码才搞定的  在此之前各种尝试和百度已经测试过无数前辈提供的方法 搞了两天才搞定这个bug 哎! 两天啊!!!!!!!!!!!!

 

 


转载:https://blog.csdn.net/fujiakai/article/details/109821141
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场