使用feign上传文件时提示错误

Could not write request: no suitable HttpMessageConverter found for request type 
[[Lorg.springframework.web.multipart.MultipartFile;] and content type 
[multipart/form-data]"

因为feign本身并不能上传附件,feignClient需要设置为"multipart/form-data", 配合

feign传输单个MultipartFile文件

引入依赖
<!--引入feign-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>1.4.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form</artifactId>
    <version>3.0.3</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form-spring</artifactId>
    <version>3.0.3</version>
</dependency>
配置文件
@Configuration
public class FeignMessageConverterConfig {
    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;

    @Bean
    @Primary
    @Scope("prototype")
    public Encoder feignEncoder() {
        return new SpringFormEncoder(new SpringEncoder(messageConverters));
    }

    @Bean
    public feign.Logger.Level multipartLoggerLevel() {
        return feign.Logger.Level.FULL;
    }
}
feign的接口定义
@FeignClient(value = "file-system", configuration = FeignMultipartSupportConfig.class)
public interface FileSystemClient {
    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    Resp<?> upload(@RequestPart("file") MultipartFile file);
}

额外: file转为MultipartFile

将一个File对象封装成MultipartFile参数,传递给FileSystemClient的upload方法即可。文件名等会自动做URLEncoder。

private MultipartFile getMultipartFile(File file) { 
    final DiskFileItem item = new DiskFileItem("file", MediaType.MULTIPART_FORM_DATA_VALUE, true, file.getName(), 100000000, file.getParentFile());
    try {
        OutputStream os = item.getOutputStream();
        os.write(FileUtils.readFileToByteArray(file));
    } catch (IOException e) {
        e.printStackTrace(); // do nothing!
    }
    return new CommonsMultipartFile(item);
}

feign需要传输多个MultipartFile

如果在feignClient接口定义中使用了MultipartFile[]数组,会异常提示:

Caused by: feign.codec.EncodeException: Could not write request: no suitable HttpMessageConverter found for request type [[Lorg.springframework.web.multipart.MultipartFile;] and content type [multipart/form-data]

因为在低版本的SpringFormEncoder中只判断了multipart对象,没有判断数组.所以需要修改源码

引入依赖
<!--引入feign-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>1.4.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form</artifactId>
    <version>3.0.3</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form-spring</artifactId>
    <version>3.0.3</version>
</dependency>
配置文件
@Configuration
public class MultipartSupportConfig {

    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;

    @Bean
    @Primary
    @Scope("prototype")
    public Encoder feignEncoder() {
        return new SpringMultipartEncoder(new SpringEncoder(messageConverters));
    }
}

/**
 * 多附件上传
 */
public class SpringMultipartEncoder extends SpringFormEncoder {

    public SpringMultipartEncoder () {
        this(new Encoder.Default());
    }

    public SpringMultipartEncoder (Encoder delegate) {
        super(delegate);

        MultipartFormContentProcessor processor = (MultipartFormContentProcessor) getContentProcessor(MULTIPART);
        processor.addWriter(new SpringSingleMultipartFileWriter());
        processor.addWriter(new SpringManyMultipartFilesWriter());
    }

    @Override
    public void encode (Object object, Type bodyType, RequestTemplate template) throws EncodeException {
        if (bodyType.equals(MultipartFile[].class)) {
            MultipartFile[] files = (MultipartFile[]) object;
            Map data = new HashMap<String, Object>(files.length, 1.F);
            for (MultipartFile file : files) {
                if (file == null) {
                    continue;
                }
                data.put(file.getName(), file);
            }
            super.encode(data, MAP_STRING_WILDCARD, template);
        } else if (bodyType.equals(MultipartFile.class)) {
            MultipartFile file = (MultipartFile) object;
            Map data = singletonMap(file.getName(), object);
            super.encode(data, MAP_STRING_WILDCARD, template);
        } else if (isMultipartFileCollection(object)) {
            Iterable iterable = (Iterable<?>) object;
            Map data = new HashMap<String, Object>();
            for (Object item : iterable) {
                MultipartFile file = (MultipartFile) item;
                data.put(file.getName(), file);
            }
            super.encode(data, MAP_STRING_WILDCARD, template);
        } else {
            super.encode(object, bodyType, template);
        }
    }

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

}

自定义的SpringMultipartEncoder判断了MultipartFile[]数组.

定义feignClient
import com.posp.oos.web.srv.config.MultipartSupportConfig;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;

@FeignClient(value = "XPOS-CIF-SRV",configuration = MultipartSupportConfig.class)
public interface CifCustomerPicPoolFeignService {

    @PostMapping(value = "saveCustomerPicPool",consumes = MediaType.MULTIPART_FORM_DATA_VALUE,produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    RSP saveCustomerPicPool(@RequestPart MultipartFile[] files,
                            @RequestParam("customerType") Integer customerType,
                            @RequestParam(value = "mccIds",required = false) String mccIds,
                            @RequestParam(value = "remark",required = false) String remark) ;
}
Controller中接收处理
/**
     * 保存图片
     */
    @PostMapping(value = "saveCustomerPicPool",consumes = MediaType.MULTIPART_FORM_DATA_VALUE,produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public RSP saveCustomerPicPool(@RequestPart(value = "businessLicenseFile",required = false) MultipartFile businessLicenseFile,
                                   @RequestPart(value = "shopTitleFile",required = false) MultipartFile shopTitleFile,
                                   @RequestPart(value = "shopPhotoFile",required = false) MultipartFile shopPhotoFile,
                                   @RequestPart(value = "shopCashFile",required = false) MultipartFile shopCashFile,
                                   @RequestPart(value = "shopWayFile",required = false) MultipartFile shopWayFile,
                                   @RequestParam("customerType") Integer customerType,
                                   @RequestParam(value = "mccIds",required = false) String mccIds,
                                   @RequestParam(value = "remark",required = false) String remark
                                   ) {
        RSP rsp;
        try {
            CifCustomerPicPoolPo po = new CifCustomerPicPoolPo();
            po.setCustomerType(customerType);
            po.setMccIds(mccIds);
            po.setRemark(remark);
            po.setBusinessLicenseFile(businessLicenseFile);
            po.setShopCashFile(shopCashFile);
            po.setShopPhotoFile(shopPhotoFile);
            po.setShopTitleFile(shopTitleFile);
            po.setShopWayFile(shopWayFile);
            Assert.notNull(po, "数据不存在!");
            customerPicPoolService.saveEntity(po);
            rsp = RSP.ok();
        } catch (Exception e) {
            log.error("商户图片池保存失败!", e);
            rsp = RSP.ok().fail("商户图片池保存失败!" + e.getMessage());
        }
        return rsp;
    }

注意到, 这里发送时是MultipartFile[]数组,接收处理时拆出来了.每个使用MultipartFile文件的name来定义的.

额外:

网上有说使用这样的方式

 
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.Map;
 
 
/**
 * @version: 1.00.00
 * @description:
 * @copyright:
 * @company: 
 * @author: panfan
 * @date: 2018/8/13 17:27
 * @history:
 */
public class SpringMultipartEncoder extends FormEncoder {
 
    /**
     * Constructor with the default Feign's encoder as a delegate.
     */
    public SpringMultipartEncoder() {
        this(new Default());
    }
 
 
    /**
     * Constructor with specified delegate encoder.
     * @param delegate delegate encoder, if this encoder couldn't encode object.
     */
    public SpringMultipartEncoder(Encoder delegate) {
        super(delegate);
 
        MultipartFormContentProcessor processor = (MultipartFormContentProcessor) getContentProcessor(ContentType.MULTIPART);
        processor.addWriter(new SpringSingleMultipartFileWriter());
        processor.addWriter(new SpringManyMultipartFilesWriter());
    }
 
 
    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
        // 单MultipartFile判断
        if (bodyType.equals(MultipartFile.class)) {
            MultipartFile file = (MultipartFile) object;
            Map data = Collections.singletonMap(file.getName(), object);
            super.encode(data, MAP_STRING_WILDCARD, template);
        } else if (bodyType.equals(MultipartFile[].class)) {
            // MultipartFile数组处理
            MultipartFile[] file = (MultipartFile[]) object;
            if(file != null) {
                Map data = Collections.singletonMap(file.length == 0 ? "" : file[0].getName(), object);
                super.encode(data, MAP_STRING_WILDCARD, template);
            }
        } else if (isMultipartFileCollection(object)) {
            val iterable = (Iterable<?>) object;
            val data = new HashMap<String, Object>();
            for (val item : iterable) {
                val file = (MultipartFile) item;
                data.put(file.getName(), file);
            }
            super.encode(data, MAP_STRING_WILDCARD, template);
        } else {
            // 其他类型调用父类默认处理方法
            super.encode(object, bodyType, template);
        }
    }

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

如果使用MultipartFile[]数组, 发现所有文件的name变成了同一个.

feignClient定义

/**
 * 文件操作Feign接口
 */
@FeignClient(
        value = "file-service",
        path = "/file",
        fallback = FileOperationClientFallback.class,
        configuration = FeignSupportConfig.class)
public interface IFileOperationClient {

    /**
     * 上传文件
     *
     * @param file         文件
     * @return 文件存储URL
     */
    @PostMapping(value = "/uploadFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    String uploadFile(@RequestPart("file") MultipartFile file);

    /**
     * 批量上传文件
     *
     * @param files        文件数组
     * @return 文件存储URL数组
     */
    @PostMapping(value = "/uploadFiles", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    Map<String, List<String>> uploadFiles(@RequestPart("files") MultipartFile[] files);
}

文章作者: 刘同学
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 刘同学的小站
后端 Java
喜欢就支持一下吧