feignClient + feign-from实现文件上传

作者
2024-08-30阅读 706

最近折腾了好久的feign文件上传,记录一下。重点是在这期间碰到了很奇怪的问题。

先说feign的文件上传

本身这个文件上传不难,网上一找一大把,大都是类似的代码。

说一下我这个的场景:我这个是发邮件需要带着附件。整个过程中出了文件,还有发件人邮箱,主题等的邮件参数

我就直接贴代码了

一、服务端

关键点解释:

(1)这个必须要加的:consumes = MediaType.MULTIPART_FORM_DATA_VALUE

(2)我注释的代码是用来查看文件的。因为我在测试的时候,设置了文件名叫test.jpg,这就导致了我用@RequestPart(value = "attachmentFile")的时候获取不到。这里我打印了一下文件,其中的part.getName()就是能用的参数名。这个如果碰到了文件是null等类似的问题,可以尝试着这么调查一下。

@PostMapping(value = "/sendMail", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public boolean sendMail(@RequestParam("to") String to,
                            @RequestParam("title") String title,
                            @RequestParam("content") String content,
                            @RequestParam(value = "attachmentName",required = false) String attachmentName,
                            @RequestPart(value = "attachmentFile",required = false) MultipartFile attachmentFile) throws Exception {
//        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
//        Collection<Part> parts = request.getParts();
//        for (Iterator<Part> iterator = parts.iterator(); iterator.hasNext();) {
//            Part part = iterator.next();
//            System.out.println("-----类型名称------->"+part.getName());
//            System.out.println("-----类型------->"+part.getContentType());
//            System.out.println("-----提交的类型名称------->"+part.getSubmittedFileName());
//            System.out.println("----流-------->"+part.getInputStream());
//        }
        sendMailService.sendMail(to, title, content, attachmentName, attachmentFile);
        log.info("邮件发送服务,接收到的信息:to:{},title:{},content:{},attachmentName:{}", to, title, content, attachmentName);
        return true;
    }

二、客户端

1、添加maven引用

版本的使用:

我用springboot1.x(feign)的时候,我用的是3.3.0

springboot2.x(openfeign)的时候,我用的是3.8.0。在github上看了。3.8.0的版本,已经抛弃spring-cloud-starter-feign了。

spring-cloud-starter-feign is a deprecated dependency and it always uses the OpenFeign's 9.* versions.

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>io.github.openfeign.form</groupId>
            <artifactId>feign-form</artifactId>
            <version>3.3.0</version>
        </dependency>

3、备注

1、feignclient的代码。这个地方需要注意的是MultipartSupportConfig这个内部类的使用

(1)如果是springboot1.X版本,请查看我的另一边文章。这个地方容易出问题

(2)如果是springboot2.X版本,随便用

import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.cloud.netflix.feign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;

@ConditionalOnClass(FeignClient.class)
@FeignClient(value = "iov-openservice", configuration = OpenServiceMailFeignService.MultipartSupportConfig.class)
@RequestMapping("/srv010600601/v0.0.1")
public interface OpenServiceMailFeignService {

    /**
     * 带附件邮件发送
     * @param attachmentName
     * @param to
     * @param title
     * @param content
     * @param attachmentFile
     * @return ResponseMessage
     */
    @PostMapping(value = "/sendMail", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    @ResponseBody
    boolean sendMail(@RequestParam("to") String to,
                     @RequestParam("title") String title,
                     @RequestParam("content") String content,
                     @RequestParam(value = "attachmentName",required = false) String attachmentName,
                     @RequestPart(value = "attachmentFile",required = false) MultipartFile attachmentFile);

    /**
     * feignclient为了支持文件上传,需要配置SpringFormEncoder
     * 其中的@Component注解是为了在多个同名feignclient时configuration不产生覆盖
     * @param
     * @return {}
    */
    @Component
    class MultipartSupportConfig {

        @Bean("multipartFormEncoder")
        @Primary
        @Scope("prototype")
        public Encoder multipartFormEncoder() {
            return new SpringFormEncoder(new SpringEncoder(new ObjectFactory<HttpMessageConverters>() {
                @Override
                public HttpMessageConverters getObject() throws BeansException {
                    return new HttpMessageConverters(new RestTemplate().getMessageConverters());
                }
            }));
        }

        @Bean("multipartLoggerLevel")
        public feign.Logger.Level multipartLoggerLevel() {
            return feign.Logger.Level.FULL;

        }
    }
}

2、MultipartSupportConfig类除了可以当做是内部类,还可以单独作为类使用。以下是单独的配置

(1)@Configuration这个注解可以不用的

import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import org.springframework.beans.BeansException;
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;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.web.client.RestTemplate;

@Configuration
public class MultipartSupportConfig {

    @Bean("multipartFormEncoder")
    @Primary
    @Scope("prototype")
    public Encoder multipartFormEncoder() {
        return new SpringFormEncoder(new SpringEncoder(new ObjectFactory<HttpMessageConverters>() {
            @Override
            public HttpMessageConverters getObject() throws BeansException {
                return new HttpMessageConverters(new RestTemplate().getMessageConverters());
            }
        }));
    }

    @Bean("multipartLoggerLevel")
    public feign.Logger.Level multipartLoggerLevel() {
        return feign.Logger.Level.FULL;

    }

}

3、我的测试代码

用这个测试代码注意的地方,就是生成文件的时候FileItem fileItem = new DiskFileItem("attachmentFile",

这个变量名就是我上面说的服务器端接收的变量名。设置的时候要注意。

import openservice.utils.ResponseMessage;
import openservice.mail.feign.OpenServiceMailFeignService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.nio.file.Files;
import java.util.UUID;

@Slf4j
@RestController
@RequestMapping("/samplemail")
public class SampleMailController {

    @Autowired
    private OpenServiceMailFeignService client;


    /**
     * 带附件邮件发送
//     * @param to 收件人
//     * @param title 主题
//     * @param content 邮件内容
//     * @param attachmentName 附件名称
//     * @param attachmentFile 附件
     * @return {boolean}
     */
    @PostMapping("/sendMail")
    public void sendMail() throws IOException {

        String to = "xxx@xxx.com";
        String title = "测试";
        String content = "12312313啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊";
        String attachmentName = "aslkdfjaslkwenjian.png";
        MultipartFile attachmentFile = null;

        String filePath = "E:aaaimageannere43e26feaaa603f1498b6ea48066d2d21e293237.jpg";
        File file = new File(filePath);
        // 需要导入commons-fileupload的包
        FileItem fileItem = new DiskFileItem("attachmentFile", Files.probeContentType(file.toPath()),false,file.getName(),(int)file.length(),file.getParentFile());
        byte[] buffer = new byte[4096];
        int n;
        try (InputStream inputStream = new FileInputStream(file); OutputStream os = fileItem.getOutputStream()){
            while ( (n = inputStream.read(buffer,0,4096)) != -1){
                os.write(buffer,0,n);
            }
            //也可以用IOUtils.copy(inputStream,os);
            MultipartFile multipartFile = new CommonsMultipartFile(fileItem);
            System.out.println(multipartFile.getName());
            attachmentFile = multipartFile;
        }catch (IOException e){
            e.printStackTrace();
        }

        client.sendMail(to, title, content, attachmentName, attachmentFile);
    }

}



全部评论