网上百度了很多办法均太过简单,大多都是携带几个简单的参数和一个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
方式与普通的文件上传方法一致
@RestController@RequestMapping("/producer/upload")class UploadProducer { @PostMapping(value = '/upload', consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) String upload(@RequestPart(value = "file") MultipartFile file) { // ... return file.originalFilename }}
2.1 需要在客户端引入以下依赖
io.github.openfeign.form:feign-form:3.0.3io.github.openfeign.form:feign-form-spring:3.0.3
2.2 定义client接口
@FeignClient(name = 'upload', url = '${upload.base-url}', path = '/producer/upload')interface UploadClient { @RequestMapping(value = '/upload', method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE , produces = MediaType.APPLICATION_JSON_VALUE) String upload(@RequestPart("file") MultipartFile file)}
2.3 添加配置文件
划重点
@Configurationclass MultipartSupportConfig { @Autowired private ObjectFactory messageConverters // new一个form编码器,实现支持form表单提交 @Bean Encoder feignFormEncoder() { return new SpringFormEncoder(new SpringEncoder(messageConverters)) }}
@RestController@RequestMapping("/")class RecordController { @Autowired private UploadClient uploadClient @RequestMapping(value = '/upload', method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) String upload(@RequestParam("file") MultipartFile file) { return uploadClient.uploade(file) }}
可以看到网上的例子只能传递简单的表单参数 下面我有种需求 如果携带一个对象呢? 对象信息如下
package com.smdk.dsminio.vo; import lombok.Data;import lombok.ToString;import org.springframework.web.multipart.MultipartFile;import java.io.Serializable; /** * @author 神秘的凯 *date 2020-03-17 17:16 */@Data@ToStringpublic class MultipartFileParam implements Serializable { public static final long serialVersionUID=1L; // 用户id private String uid; //任务ID private String id; //总分片数量 private int chunks; //当前为第几块分片 private int chunk; //当前分片大小 private long size = 0L; //文件名 private String name; //分片对象 private MultipartFile file; // MD5 private String md5; //BucketID private Long bucketId; //文件夹ID private Long parentFolderId;}
如果这样直接上传Spring的解析器解析不到MultipartFileParam 自定义对象会直接报错 另外Spring只能解析自带的Multipart 对象
报错信息
%s is not a type supported by this encoder.....省略
那么如果传递自定义的参数对象呢 那就是重写Spring的对象解析器 代码如下
Feign 调用端代码
package com.smdk.dsminio.apiservice;import com.smdk.dsminio.config.FeignSupportConfig;import com.smdk.dsminio.utils.AjaxResult;import com.smdk.dsminio.vo.MultipartFileParam;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod; @FeignClient(value = "OSS-STORAGE-SERVICE",configuration = FeignSupportConfig.class)public interface FileService { /** * produces 用于指定返回类型为JSON格式 * consumes 用于指定生产者数据请求类型 * @param multipartFileParam * @return */ @RequestMapping(value = "//OSS-STORAGE-SERVICE-$SERVER_ID/api/uploadFileInfo", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE},consumes = MediaType.MULTIPART_FORM_DATA_VALUE,method = RequestMethod.POST) AjaxResult uploadFileInfo(MultipartFileParam multipartFileParam);}
服务端
package com.smdk.dsminio.controller; import cn.hutool.log.StaticLog;import com.smdk.dsminio.redis.RedisUtil;import com.smdk.dsminio.service.FileStorageService;import com.smdk.dsminio.utils.AjaxResult;import com.smdk.dsminio.utils.Constants;import com.smdk.dsminio.vo.MultipartFileParam;import org.apache.commons.fileupload.servlet.ServletFileUpload;import org.apache.commons.io.FileUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest;import java.io.File;import java.io.IOException;import java.util.LinkedList;import java.util.List; /** * 默认控制层 * Created by 神秘的凯 on 22020/11/11. * version 1.0 */@RestController@RequestMapping(value = "/api")public class FileDiskStorageController { @Autowired private FileStorageService fileStorageService; /** * 上传文件 * * @param param * @param request * @return * @throws Exception */ @RequestMapping(value = "/uploadFileInfo", method = RequestMethod.POST) public AjaxResult uploadFileInfo(MultipartFileParam multipartFileParam) { StaticLog.info("上传文件start。"); try { // 方法1 //storageService.uploadFileRandomAccessFile(param); // 方法2 这个更快点 boolean uploadResult= fileStorageService.uploadFileByMappedByteBuffer(multipartFileParam); if (uploadResult){ return AjaxResult.success(uploadResult,"上传完毕"); }else { return AjaxResult.fail("分片上传中...正在上传第"+multipartFileParam.getChunk()+"块文件块,剩余"+(multipartFileParam.getChunks()-multipartFileParam.getChunk())+"个分块"); } } catch (IOException e) { e.printStackTrace(); StaticLog.error("文件上传失败。{}", multipartFileParam.toString()); return AjaxResult.fail("文件上传失败"); } }}
核心重写feignFormEncoder 方法类 下面的的路由配置请忽略
package com.smdk.dsminio.config; import com.smdk.dsminio.vo.MultipartFileParam;import feign.Request;import feign.RequestInterceptor;import feign.RequestTemplate;import feign.codec.EncodeException;import feign.codec.Encoder;import feign.form.spring.SpringFormEncoder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Primary;import org.springframework.context.annotation.Scope;import java.io.IOException;import java.lang.reflect.Type; import static java.lang.String.format;public class FeignSupportConfig{ //转换参数请求模型封装 @Bean @Primary @Scope("prototype") public Encoder feignFormEncoder() { return new SpringFormEncoder(new Encoder() { @Override public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { if (bodyType == String.class) { template.body(Request.Body.bodyTemplate(object.toString(),null)); } else if (bodyType == byte[].class) { template.body(Request.Body.encoded((byte[]) object,null)); }else if (bodyType == MultipartFileParam.class) { MultipartFileParam multipartFileParam = (MultipartFileParam) object; try { template.body(Request.Body.encoded(multipartFileParam.getFile().getBytes(),null)); } catch (IOException e) { e.printStackTrace(); } } else if (object != null) { throw new EncodeException(format("%s is not a type supported by this encoder.", object.getClass())); } } }); } @Bean public feign.Logger.Level multipartLoggerLevel() { return feign.Logger.Level.FULL; } //路由重构 @Bean public RequestInterceptor cloudContextInterceptor() { return new RequestInterceptor() { @Override public void apply(RequestTemplate template) { String url = template.url(); if (url.contains("$SERVER_ID")) { url = url.replace("$SERVER_ID", route(template)); template.uri(url); } if (url.startsWith("//")) { url = "http:" + url; template.target(url); template.uri(""); } } private CharSequence route(RequestTemplate template) { // TODO 你的路由算法在这里 return "01"; } }; }}
可以看到我加了个判断 if (bodyType == MultipartFileParam.class) 进行自己封装的对象解析
大工告成 另外说下这个问题 品读了Spring的源码才搞定的 在此之前各种尝试和百度已经测试过无数前辈提供的方法 搞了两天才搞定这个bug 哎! 两天啊!!!!!!!!!!!!
作者:神秘的凯
原文链接:https://fujiakai.blog.csdn.net/article/details/109821141