上传文件 后台参数偶尔为空_SpringFeign 携带复杂参数,进行文件上传

ee66d6854626bea2b27aad0bd21d8d91.png

网上百度了很多办法均太过简单,大多都是携带几个简单的参数和一个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 哎! 两天啊!!!!!!!!!!!!

461e159b1041cbb7334ab4f63c99cdc3.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值