网上百度了很多办法均太过简单,大多都是携带几个简单的参数和一个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. 创建服务端
方式与普通的文件上传方法一致
@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. 创建client
2.1 需要在客户端引入以下依赖
io.github.openfeign.form:feign-form:3.0.3
io.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 添加配置文件
划重点
@Configuration
class MultipartSupportConfig {
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters
// new一个form编码器,实现支持form表单提交
@Bean
Encoder feignFormEncoder() {
return new SpringFormEncoder(new SpringEncoder(messageConverters))
}
}
3. 创建Controller,调用client接口
@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
@ToString
public 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
本文暂时没有评论,来添加一个吧(●'◡'●)