SpringCloud OpenFeign单文件和多文件上传

1、单文件上传

OpenFeign默认不支持文件上传,需要通过引入Feign的扩展包来实现,添加依赖

        <!--Feign上传文件-->
        <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>
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.3</version>
        </dependency>

服务消费方(发送文件)

添加配置类FeignConfiguration

package com.medrd.backgroundmanager.config;

import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignConfiguration {

    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;

    @Bean
    public Encoder feignEncoder() {
        return new SpringFormEncoder(new SpringEncoder(messageConverters));
    }
}

接口消费方(调用),参数接收需要@RequestPart注解,@RequestParam会错,post请求添加consumes = MediaType.MULTIPART_FORM_DATA_VALUE,指定 multipart/form-data

package com.medrd.backgroundmanager.api.common;

import com.medrd.backgroundmanager.service.feign.common.CommonServiceClient;
import com.medrd.common.core.vo.RestResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;

/**
 * 资源上传 API 接口
 *
 * @author Generator
 * @date 2020-09-25 13:44:00
 **/
@Api(tags = "资源上传")
@Log4j2
@RestController
@RequestMapping("/api/common")
public class CommonApi {

    @Resource
    private CommonServiceClient commonServiceClient;

    /**
     * @Description
     * @Anthor Generator
     * @Date 2020/09/25 09:04
     */
    @ApiOperation(value = "上传图片")
    @PostMapping(value = "/uploadImage", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public RestResult uploadImage(@RequestPart("file") MultipartFile file) {
        return commonServiceClient.uploadImage(file);
    }

   /**
     * @Description
     * @Anthor Generator
     * @Date 2020/09/25 09:04
     */
    @ApiOperation(value = "批量上传图片")
    @PostMapping(value = "/uploadImages", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public RestResult uploadImages(@RequestPart("file") MultipartFile[] file) {
        return commonServiceClient.uploadImages(file);
    }
}

开启Feign,并指定配置类

package com.medrd.backgroundmanager.service.feign.common;

import com.medrd.backgroundmanager.config.FeignConfiguration;
import com.medrd.common.client.constants.CommonConstants;
import com.medrd.common.client.service.CommonClient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;


/**
 * @Description: 资源上传 Feign接口
 * @Author: Generator
 * @Date: 2020-09-25 13:44:00
 */
@Component
@FeignClient(value = CommonConstants.COMMON_SERVICE, configuration = FeignConfiguration.class)
public interface CommonServiceClient extends CommonClient {

}

服务提供方(接收文件)

package com.medrd.common.client.service;

import com.medrd.common.core.vo.RestResult;
import io.swagger.annotations.ApiOperation;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

/**
 * @Description: 通用API
 * @Author: medrd
 * @Date: 2020/3/9
 */
@RequestMapping("/commonServiceServer/api/common")
public interface CommonClient {

    /**
     * @Description
     * @Anthor Generator
     * @Date 2020/09/25 09:04
     */
    @ApiOperation(value = "上传图片")
    @PostMapping(value = "/uploadImage", consumes = MediaType.MULTIPART_FORM_DATA_VALUE )
    RestResult uploadImage(@RequestPart("file") MultipartFile file);

   /**
     * @Description
     * @Anthor Generator
     * @Date 2020/09/25 09:04
     */
    @ApiOperation(value = "批量上传图片")
    @PostMapping(value = "/uploadImages", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    RestResult uploadImages(@RequestPart("file") MultipartFile[] file);
}

实现类

package com.medrd.common.server.web.api;

import com.medrd.common.client.config.QiNiuYunProperties;
import com.medrd.common.client.domain.enums.ChronicConstants;
import com.medrd.common.client.domain.po.ResourceEntity;
import com.medrd.common.client.service.CommonClient;
import com.medrd.common.core.generator.KeyGenerator;
import com.medrd.common.core.vo.RestResult;
import com.medrd.common.server.service.ResourceService;
import com.medrd.common.server.service.UploadService;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.util.*;

/**
 * @Description: 通用API
 * @Author: medrd
 * @Date: 2020/3/9
 */
@Log4j2
@RestController
@RequestMapping("/api/common")
public class CommonApi implements CommonClient {

    @Autowired
    private QiNiuYunProperties qiNiuYunProperties;

    @Autowired
    private UploadService uploadService;

    @Autowired
    private ResourceService resourceService;

    @Override
    public RestResult uploadImage(@RequestParam("file") MultipartFile file) {
        MultipartFile[] files = {file};
        return this.uploadImages(files);
    }

    /**
     * @Author: DavidWood
     * @Date: 2020/3/31 15:30
     */
    @Override
    public RestResult uploadImages(@RequestParam("file") MultipartFile[] files) {
        // 上传文件验证--start
        if (files == null || files.length == 0) {
            return RestResult.buildErrorApi("文件为空,请重新上传");
        }
        if (files.length > 5) {
            return RestResult.buildErrorApi("一次最多上传5张图片");
        }
        List<String> imgTypes = Arrays.asList("PNG,JPEG,JPG".split(","));
        for (MultipartFile file : files) {
            String fileName = file.getOriginalFilename();
            String fileType = fileName.substring(fileName.lastIndexOf(".") + 1).toUpperCase();
            if (!imgTypes.contains(fileType)) {
                return RestResult.buildErrorApi(String.format("不支持此类型的图片【%s】", fileType.toLowerCase()));
            }
        }
        // 上传文件验证--end
        List<Map<String, String>> result = new ArrayList<>(5);
        List<ResourceEntity> resourceEntities = new ArrayList<>(5);
        for (MultipartFile file : files) {
            RestResult restResult = uploadService.uploadImage(file);
            if (!"200".equals(restResult.getCode())) {
                return restResult;
            }
            Long id = KeyGenerator.INSTANCE.getInstance().generateKey().longValue();
            String url = (String) restResult.getData();
            ResourceEntity resourceEntity = new ResourceEntity();
            resourceEntity.setId(id);
            resourceEntity.setName(file.getOriginalFilename());
            resourceEntity.setType(this.getFileType(file.getOriginalFilename()));
            resourceEntity.setDomain(qiNiuYunProperties.getDomain());
            resourceEntity.setUri(url.replace(resourceEntity.getDomain(), ""));
            resourceEntity.setUrl(url);

            resourceEntities.add(resourceEntity);

            String resourceId = "" + resourceEntity.getId();
            Map<String, String> resourceMap = new HashMap<>();
            resourceMap.put("resourceId", resourceId);
            resourceMap.put("url", url);
            result.add(resourceMap);
        }
        resourceService.saveAll(resourceEntities);
        return RestResult.buildSuccessApi(result);
    }

    /**
     * @Description: 返回文件类型,结果大写
     * @Author: DavidWood
     * @Date: 2020/3/31 14:16
     * @Return:
     */
    private String getFileType(String fileName) {
        String extendName = fileName.substring(fileName.lastIndexOf(".") + 1);
        String fileType = extendName.toUpperCase();

        for (ChronicConstants.ResourceType item : ChronicConstants.ResourceType.values()) {
            if (item.name().toUpperCase().equals(fileType)) {
                return item.getCode();
            }
        }
        return null;
    }

}

2、多文件上传

多文件上传主要是修改配置类,其他都单文件上传一样,上面也提供了多文件上传的方法。这里主要是说一说配置类

如果用单文件上传的文件,同时传多张图片,只会上传最后一张图片。

网上查了很多资料,用网上说的配置类,都会有问题,索性就研究了一下源码。

SpringFormEncoder 部分源码,网上很多说没有MultipartFile[]类型的判断,我这里用的版本较新,虽然有数组类型的判断,但是因为数组里每个元素file.getName()都是相同,它作为map的键,data前面的值会被覆盖,所以只存在最后一个元素

debug 看最后data的值,data就是消费端通过file接收到的值

最后新建 FeignSpringFormEncoder 类,把源码复制到 FeignSpringFormEncoder 中修改

不知道是不是源码有其他考虑只传了一个对象,这里只需要把数组files传过去就可以了

完整的配置类 

package com.medrd.backgroundmanager.config;

import feign.form.spring.SpringFormEncoder;
import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import feign.form.ContentType;
import feign.form.FormEncoder;
import feign.form.MultipartFormContentProcessor;
import feign.form.spring.SpringManyMultipartFilesWriter;
import feign.form.spring.SpringSingleMultipartFileWriter;
import org.springframework.web.multipart.MultipartFile;

import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;

/**
 * author:czq
 * time: 2020/9/27 15:35
 * description: 多文件上传配置
 **/
public class FeignSpringFormEncoder extends FormEncoder {

    public FeignSpringFormEncoder() {
        this(new Default());
    }

    public FeignSpringFormEncoder(Encoder delegate) {
        super(delegate);
        MultipartFormContentProcessor processor = (MultipartFormContentProcessor) this.getContentProcessor(ContentType.MULTIPART);
        processor.addFirstWriter(new SpringSingleMultipartFileWriter());
        processor.addFirstWriter(new SpringManyMultipartFilesWriter());
    }

    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
        HashMap data;
        if (bodyType.equals(MultipartFile[].class)) {
            MultipartFile[] files = (MultipartFile[]) object;
            data = new HashMap();
            //data添加数组
            if (files != null) {
                data.put(files.length == 0 ? "" : files[0].getName(), files);
            }
            super.encode(data, MAP_STRING_WILDCARD, template);
        } else if (bodyType.equals(MultipartFile.class)) {
            MultipartFile file = (MultipartFile) object;
            data = (HashMap) Collections.singletonMap(file.getName(), object);
            super.encode(data, MAP_STRING_WILDCARD, template);
        } else if (this.isMultipartFileCollection(object)) {
            Iterable<?> iterable = (Iterable) object;
            data = new HashMap();
            Iterator var13 = iterable.iterator();

            while (var13.hasNext()) {
                Object item = var13.next();
                MultipartFile file = (MultipartFile) item;
                data.put(file.getName(), file);
            }
        } else {
            super.encode(object, bodyType, template);
        }

    }

    private boolean isMultipartFileCollection(Object object) {
        if (!(object instanceof Iterable)) {
            return false;
        } else {
            Iterable<?> iterable = (Iterable) object;
            Iterator<?> iterator = iterable.iterator();
            return iterator.hasNext() && iterator.next() instanceof MultipartFile;
        }
    }
}

3、Postman测试

 

成功

后文

之前找了很多多文件配置文件的资料,都报错,后来自己研究代码才发现和自己写的逻辑差不多,但为什么之前有问题呢?

这里测试了一下发现

网上资料:

自己的: 

后面我把自己的也改成 addWriter()方法,也会报错

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值