Spring Feign大文件上传踩坑记

2022-05-10 749 0

Spring Feign大文件上传踩坑记

https://github.com/OpenFeign/feign-form

引入依赖

<dependencies>
  <dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form</artifactId>
    <version>3.8.0</version>
  </dependency>
  <dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form-spring</artifactId>
    <version>3.8.0</version>
  </dependency>
</dependencies>

编写Feign Client

/**
 * @author hushow
 * @Descrption
 * @date 2022/03/23 10:25
 */
@FeignClient(name = "${hushow.feign.service-name:ability-service}", contextId = "ability-lfile", configuration = StorageLocalFeignService.FeignUploadConfig.class)
public interface StorageLocalFeignService {

    class FeignUploadConfig {
        @Bean
        public Encoder springEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
            return new SpringFormEncoder(new SpringEncoder(messageConverters));
        }

        @Bean
        Request.Options feignOptions() {
            return new Request.Options(2000, TimeUnit.SECONDS, -1, TimeUnit.SECONDS, true);
        }
    }

    /**
     * 文件上传
     *  hushowly
     * @param bucketName
     * @param file
     * @param fileName
     * @param relativeUrl
     * @param isPrivate
     * @return
     */
    @PostMapping(value = "/ability/v1/lfile/uploadFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    Result<StorageFileResp> uploadFile(@RequestParam(value = "bucketName", required = true) String bucketName,
                                       @RequestPart(value = "file", required = true) MultipartFile file,
                                       @RequestParam(value = "fileName", required = true) String fileName,
                                       @RequestParam(value = "relativeUrl", required = false) String relativeUrl,
                                       @RequestParam(value = "isPrivate", required = false) Boolean isPrivate);

客户端调用

StreamMultipartFile streamMultipartFile = new StreamMultipartFile(key, fileName, inputStream, fileSize, mimeTypeStr);
Result<StorageFileResp> response = storageLocalFeignService.uploadFile(bucketName, streamMultipartFile, fileName, key, isPrivate);

上传和下载时超时的坑

  • 第一步,FeignUploadConfig配置超时

    @Bean
    Request.Options feignOptions() {
    return new Request.Options(2000, TimeUnit.SECONDS, -1, TimeUnit.SECONDS, true);
    }
  • 结果发现不起作用,调试源码后发现,配置文件中feign超时配置优先级高,通过单独配置上传feign解决问是:

    feign:
    client:
        config:
            default:
                loggerLevel: FULL
                connectTimeout: 2000
                readTimeout: 2000
            ability-lfile:
                loggerLevel: FULL
                connectTimeout: 2000
                readTimeout: -1
  • feign超时配置逻辑源码位置(FeignClientFactoryBean类)

    protected void configureFeign(FeignContext context, Feign.Builder builder) {
        FeignClientProperties properties = this.applicationContext
                .getBean(FeignClientProperties.class);
        if (properties != null) {
            if (properties.isDefaultToProperties()) {
                 # 读取类加载的时候认配置
                configureUsingConfiguration(context, builder);
    
                 # 如果存在使用属性文件中默认配置,则覆盖
                configureUsingProperties(
                        properties.getConfig().get(properties.getDefaultConfig()),
                        builder);
                 # 如果存在属性文件特定上contextId配置则覆盖配置
                configureUsingProperties(properties.getConfig().get(this.contextId),
                        builder);
            }
            else {
                configureUsingProperties(
                        properties.getConfig().get(properties.getDefaultConfig()),
                        builder);
                configureUsingProperties(properties.getConfig().get(this.contextId),
                        builder);
                configureUsingConfiguration(context, builder);
            }
        }
        else {
            configureUsingConfiguration(context, builder);
        }
    }

尝试上传大文件后,出现OOM坑

经分析,feign暂时不支持大文件,github有人上报,至今未修复

https://github.com/OpenFeign/feign-form/issues/101

分析源码后,发现竟读取字节数组到内存中! 本打算自己理写实现,发现feign上层根本未考虑Stream方式设计,所以暂时未打通feign上传方案,使restTemplate方案进行了代替

java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3236)
    at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:118)
    at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93)
    at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:153)
    at java.io.OutputStream.write(OutputStream.java:75)
    at feign.form.multipart.Output.write(Output.java:65)
    at feign.form.multipart.Output.write(Output.java:53)
    at feign.form.multipart.AbstractWriter.write(AbstractWriter.java:37)
    at feign.form.MultipartFormContentProcessor.process(MultipartFormContentProcessor.java:87)
    at feign.form.FormEncoder.encode(FormEncoder.java:105)
    at feign.form.spring.SpringFormEncoder.encode(SpringFormEncoder.java:84)

相关文章

快速实现通用的办公文档在线预览方案
MinIO分布式存储方案预研
Dubbo+Grpc+Spring Boot初体验
vagrant+virtualBox快速部署集群节点
Spring Boot下grpc最佳实践
Mysql Bit类型多状态位在Java中的妙用

发布评论