spring-cloud feign 支持送MultipartFile参数进行文件传输

复制代码
package com.xxx.messageCenter.api;import java.util.List;
import feign.Contract;
import feign.Param;
import feign.RequestLine;
import feign.codec.Encoder;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.multipart.MultipartFile;

@FeignClient(name = "messageCenter", qualifier = "MessageSenderApi", contextId = "MessageSenderApi",
    configuration = {com.xxx.common.feign.FeignExceptionConfiguration.class, HeaderInterceptor.class, MessageSenderApi.FeignSpringFormConfig.class})
public interface MessageSenderApi {

    @RequestLine("POST /messageCenter/sender/sendEmail")
    ResponseWrapper sendEmailWithFile(@Param("singleMessageEventDTO") SingleMessageEventDTO singleMessageEventDTO,
                                      @Param("inlineAttachments") List<MultipartFile> inlineAttachments,
                                      @Param("fileAttachments") List<MultipartFile> fileAttachments);

    class FeignSpringFormConfig {

        @Bean
        public Encoder feignEncoder() {
            return new FeignSpringFormEncoder();
        }
        @Bean
        public Contract feignContract() {
            return new feign.Contract.Default();
        }
    }

}
复制代码
复制代码
package com.xxx.messageCenter.config;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

/**
 * A custom {@link Encoder} that supports Multipart requests. It uses
 * {@link HttpMessageConverter}s like {@link RestTemplate} does.
 *
 * @author Pierantonio Cangianiello
 */
public class FeignSpringFormEncoder implements Encoder {


    private final List<HttpMessageConverter<?>> converters = new RestTemplate().getMessageConverters();

    public static final Charset UTF_8 = Charset.forName("UTF-8");

    public FeignSpringFormEncoder() {
    }

    /**
     * {@inheritDoc }
     */
    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
        if (isFormRequest(bodyType)) {
            final HttpHeaders multipartHeaders = new HttpHeaders();
            multipartHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
            encodeMultipartFormRequest((Map<String, ?>) object, multipartHeaders, template);
        } else {
            final HttpHeaders jsonHeaders = new HttpHeaders();
            jsonHeaders.setContentType(MediaType.APPLICATION_JSON);
            encodeRequest(object, jsonHeaders, template);
        }
    }

    /**
     * Encodes the request as a multipart form. It can detect a single {@link MultipartFile}, an
     * array of {@link MultipartFile}s, or POJOs (that are converted to JSON).
     *
     * @param formMap
     * @param template
     * @throws EncodeException
     */
    private void encodeMultipartFormRequest(Map<String, ?> formMap, HttpHeaders multipartHeaders, RequestTemplate template) throws EncodeException {
        if (formMap == null) {
            throw new EncodeException("Cannot encode request with null form.");
        }
        LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
        for (Entry<String, ?> entry : formMap.entrySet()) {
            Object value = entry.getValue();
            if (isMultipartFile(value)) {
                map.add(entry.getKey(), encodeMultipartFile((MultipartFile) value));
            } else if (isMultipartFileArray(value)) {
                encodeMultipartFiles(map, entry.getKey(), Arrays.asList((MultipartFile[]) value));
            } else if (isMultipartFileList(value)) {
                encodeMultipartFiles(map, entry.getKey(), (List<MultipartFile>)value);
            } else {
                map.add(entry.getKey(), encodeJsonObject(value));
            }
        }
        encodeRequest(map, multipartHeaders, template);
    }

    private boolean isMultipartFile(Object object) {
        return object instanceof MultipartFile;
    }

    private boolean isMultipartFileArray(Object o) {
        return o != null && o.getClass().isArray() && MultipartFile.class.isAssignableFrom(o.getClass().getComponentType());
    }

    private boolean isMultipartFileList(Object o) {
        return o != null && (List.class.isAssignableFrom(o.getClass()) && ((List<?>) o).size() > 0 && MultipartFile.class.isAssignableFrom(((List<?>) o).get(0).getClass()));
    }

    /**
     * Wraps a single {@link MultipartFile} into a {@link HttpEntity} and sets the
     * {@code Content-type} header to {@code application/octet-stream}
     *
     * @param file
     * @return
     */
    private HttpEntity<?> encodeMultipartFile(MultipartFile file) {
        try {
            HttpHeaders filePartHeaders = new HttpHeaders();
            filePartHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            try {
                String fileContentType = file.getContentType();
                filePartHeaders.setContentType(new MediaType(fileContentType.substring(0, fileContentType.indexOf('/')), fileContentType.substring(fileContentType.indexOf('/') + 1)));
            } catch (Exception e) {
                e.printStackTrace();
            }
            Resource multipartFileResource = new MultipartFileResource(file.getOriginalFilename(), file.getSize(), file.getInputStream());
            return new HttpEntity<>(multipartFileResource, filePartHeaders);
        } catch (IOException ex) {
            throw new EncodeException("Cannot encode request.", ex);
        }
    }

    /**
     * Fills the request map with {@link HttpEntity}s containing the given {@link MultipartFile}s.
     * Sets the {@code Content-type} header to {@code application/octet-stream} for each file.
     *
     * @param map the current request
     * @param name the name of the array field in the multipart form.
     * @param files
     */
    private void encodeMultipartFiles(LinkedMultiValueMap<String, Object> map, String name, List<? extends MultipartFile> files) {
        try {
            for (MultipartFile file : files) {
                HttpHeaders filePartHeaders = new HttpHeaders();
                filePartHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
                try {
                    String fileContentType = file.getContentType();
// 特别处理文件类型,传输的时候一定要是content-type为正确内容,不然都会拿到APPLICATION_OCTET_STREAM,下游如果是用于发送邮件什么的时候,就会不知道是什么文件类型,导致发生了一个乱码文件名 filePartHeaders.setContentType(
new MediaType(fileContentType.substring(0, fileContentType.indexOf('/')), fileContentType.substring(fileContentType.indexOf('/') + 1))); } catch (Exception e) { e.printStackTrace(); } Resource multipartFileResource = new MultipartFileResource(file.getOriginalFilename(), file.getSize(), file.getInputStream()); map.add(name, new HttpEntity<>(multipartFileResource, filePartHeaders)); } } catch (IOException ex) { throw new EncodeException("Cannot encode request.", ex); } } /** * Wraps an object into a {@link HttpEntity} and sets the {@code Content-type} header to * {@code application/json} * * @param o * @return */ private HttpEntity<?> encodeJsonObject(Object o) { HttpHeaders jsonPartHeaders = new HttpHeaders(); jsonPartHeaders.setContentType(MediaType.APPLICATION_JSON); return new HttpEntity<>(o, jsonPartHeaders); } /** * Calls the conversion chain actually used by * {@link RestTemplate}, filling the body of the request * template. * * @param value * @param requestHeaders * @param template * @throws EncodeException */ private void encodeRequest(Object value, HttpHeaders requestHeaders, RequestTemplate template) throws EncodeException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); HttpOutputMessage dummyRequest = new HttpOutputMessageImpl(outputStream, requestHeaders); try { Class<?> requestType = value.getClass(); MediaType requestContentType = requestHeaders.getContentType(); for (HttpMessageConverter<?> messageConverter : converters) { if (messageConverter.canWrite(requestType, requestContentType)) { ((HttpMessageConverter<Object>) messageConverter).write( value, requestContentType, dummyRequest); break; } } } catch (IOException ex) { throw new EncodeException("Cannot encode request.", ex); } HttpHeaders headers = dummyRequest.getHeaders(); if (headers != null) { for (Entry<String, List<String>> entry : headers.entrySet()) { template.header(entry.getKey(), entry.getValue()); } } /* we should use a template output stream... this will cause issues if files are too big, since the whole request will be in memory. */ template.body(outputStream.toByteArray(), UTF_8); } /** * Minimal implementation of {@link HttpOutputMessage}. It's needed to * provide the request body output stream to * {@link HttpMessageConverter}s */ private class HttpOutputMessageImpl implements HttpOutputMessage { private final OutputStream body; private final HttpHeaders headers; public HttpOutputMessageImpl(OutputStream body, HttpHeaders headers) { this.body = body; this.headers = headers; } @Override public OutputStream getBody() throws IOException { return body; } @Override public HttpHeaders getHeaders() { return headers; } } /** * Heuristic check for multipart requests. * * @param type * @return * @see Encoder#MAP_STRING_WILDCARD */ static boolean isFormRequest(Type type) { return MAP_STRING_WILDCARD.equals(type); } /** * Dummy resource class. Wraps file content and its original name. */ static class MultipartFileResource extends InputStreamResource { private final String filename; private final long size; public MultipartFileResource(String filename, long size, InputStream inputStream) { super(inputStream); this.size = size; this.filename = filename; } @Override public String getFilename() { return this.filename; } @Override public InputStream getInputStream() throws IOException, IllegalStateException { return super.getInputStream(); //To change body of generated methods, choose Tools | Templates. } @Override public long contentLength() throws IOException { return size; } } }
复制代码

 content-type枚举类,用于multipartfile传输的时候,设置content-type

复制代码
package com.xxxx.messageCenter.constant;

import lombok.AllArgsConstructor;

@AllArgsConstructor
public enum EmailMimeType {

    APPLICATION_DOC("application/msword"),
    APPLICATION_XLS("application/vnd.ms-excel"),
    APPLICATION_PPT("application/vnd.ms-powerpoint"),
    APPLICATION_ODT("application/vnd.oasis.opendocument.text"),
    APPLICATION_PPTX("application/vnd.openxmlformats-officedocument.presentationml.presentation"),
    APPLICATION_XLSX("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
    APPLICATION_DOCX("application/vnd.openxmlformats-officedocument.wordprocessingml.document"),
    APPLICATION_XML("application/xml"),
    APPLICATION_ZIP("application/zip"),
    APPLICATION_ZSTD("application/zstd"),
    AUDIO_MPEG("audio/mpeg"),
    AUDIO_OGG("audio/ogg"),
    IMAGE_AVIF("image/avif"),
    IMAGE_JPEG_JPG("image/jpeg"),
    IMAGE_PNG("image/png"),
    IMAGE_SVG("image/svg+xml"),
    TEXT_PLAIN("text/plain"),
    TEXT_CSS("text/css"),
    TEXT_CSV("text/csv"),
    TEXT_HTML("text/html"),
    TEXT_XML("text/xml")
    ;

    private String value;

    public String getValue() {
        return value;
    }
}
复制代码

 

 

具体controller上要写清楚

@Override
    @ResponseBody
    @PostMapping(value = "/messageCenter/sender/sendEmail", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ResponseWrapper sendEmailWithFile(@RequestPart("singleMessageEventDTO") SingleMessageEventDTO singleMessageEventDTO, @RequestPart(value = "inlineAttachments", required = false) List<MultipartFile> inlineAttachments, @RequestPart(value = "fileAttachments", required = false) List<MultipartFile> fileAttachments) {
}

 参考自:https://github.com/pcan/feign-client-test

https://cloud.tencent.com/developer/article/1549685

https://blog.csdn.net/manic_pfy/article/details/113699529

https://blog.csdn.net/weixin_30929011/article/details/99120558

https://zhuanlan.zhihu.com/p/134636551

posted @   klm-kain  阅读(1060)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
历史上的今天:
2022-02-23 @EnableWebMvc、WebMvcConfigurationSupport、WebMvcConfigurer、DelegatingWebMvcConfiguration配置彼此影响关系的记录
点击右上角即可分享
微信分享提示