Springboot+freemaker+eureka+fegin实现多文件上传,完整demo
可能一般用fegin实现文件上传的不多,但这也算是一个文件上传方式吧,如果用到了,可以考虑借鉴一下,直接上代码好了
eureka
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.demo</groupId> <artifactId>fileeureka</artifactId> <version>0.0.1-SNAPSHOT</version> <name>fileeureka</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
启动类
这个服务器中只有这一个类
package com.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class FileEurekaApp { public static void main(String[] args) { SpringApplication.run(FileEurekaApp.class, args); } }
配置
server.port=9999 spring.application.name=fileeureka eureka.instance.hostname=127.0.0.1 eureka.instance.preferIpAddress=true eureka.client.registerWithEureka=false eureka.client.fetchRegistry=false eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
文件服务端
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.demo</groupId> <artifactId>fileserver</artifactId> <version>0.0.1-SNAPSHOT</version> <name>fileserver</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency> <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> <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.5</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR1</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-bom</artifactId> <version>Dysprosium-SR25</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
工具类
package com.demo.utils; import com.google.gson.Gson; public class GsonUtils<T> { private static final Gson gson = new Gson(); public static <T> String object2Json(T t){ return gson.toJson(t); } public static <T> T jsonStr2Object(String json, Class<T> clazz){ return gson.fromJson(json,clazz); } }
启动类
package com.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class FileServerApp { public static void main(String[] args) { SpringApplication.run(FileServerApp.class, args); } }
文件接收处理类
package com.demo.rpc; import com.demo.utils.GsonUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import java.io.*; @RestController @RequestMapping("/rpc") public class FileRpcService { private Logger logger = LoggerFactory.getLogger(FileRpcService.class); @Autowired private HttpServletRequest request; @RequestMapping(value = "uploadFile", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}) public String uploadFile(@RequestParam("file") MultipartFile[] files) { logger.info("file size: {}", files==null ?0:files.length); for(MultipartFile file: files){ BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(file.getInputStream())); String line = null; StringBuilder sb = new StringBuilder(); while((line = reader.readLine())!=null){ sb.append(line); line = null; } logger.info("fileName: {}, fileContent: {}", file.getOriginalFilename(), sb.toString()); } catch (IOException e) { throw new RuntimeException(e); }finally { if(reader!=null){ try { reader.close(); } catch (IOException e) { throw new RuntimeException(e); } } } } try { // 用于测试,如果不知道接收用什么文件名,可以打印出来 logger.info("********** parts: {}", GsonUtils.object2Json(request.getParts())); } catch (IOException e) { throw new RuntimeException(e); } catch (ServletException e) { throw new RuntimeException(e); } return "success"; } }
配置
server.port=8888 spring.application.name=fileserver eureka.client.serviceUrl.defaultZone=http://127.0.0.1:9999/eureka/ eureka.instance.prefer-ip-address=true
文件上传客户端
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.demo</groupId> <artifactId>fileclient</artifactId> <version>0.0.1-SNAPSHOT</version> <name>fileclient</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency> <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>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
页面
src/mian/resources/templates/index.ftl
<!DOCTYPE html> <html> <head> <title>Home page</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <form action="/upload" method = "post", enctype="multipart/form-data"> <!-- <input type="input" name="title"/> <br/>--> <input type="file" name="file"/> <br/> <input type="file" name="file"/> <br/> <input type="submit" name="submit"/> </form> </body> </html>
启动类
package com.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableFeignClients @EnableDiscoveryClient public class FileClientApp { public static void main(String[] args) { SpringApplication.run(FileClientApp.class, args); } }
接收前端传递的文件控制器
package com.demo.freemaker.controller; import com.demo.freemaker.feign.FileFeignClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; @Controller public class TController { @Autowired private FileFeignClient fileFeignClient; @RequestMapping("/") public String toIndex(){ return "index"; } @ResponseBody @RequestMapping(value = "/upload", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}) public void upload(@RequestParam("file") MultipartFile[] file){ fileFeignClient.uploadFile(file); } }
fegin文件上传接口
package com.demo.freemaker.feign; import com.demo.freemaker.config.FeignMultiPartFileConfig; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.multipart.MultipartFile; @FeignClient(name="FILESERVER", contextId = "myFileFeign", configuration = FeignMultiPartFileConfig.class) public interface FileFeignClient { @RequestMapping(value="/rpc/uploadFile", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}) String uploadFile(@RequestPart(value="files", required = false )MultipartFile[] files); }
文件上传自定义配置
package com.demo.freemaker.config; 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.slf4j.Logger; import org.slf4j.LoggerFactory; 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.web.multipart.MultipartFile; import java.lang.reflect.Type; import java.util.*; @Configuration public class FeignMultiPartFileConfig { @Autowired private ObjectFactory<HttpMessageConverters> messageConverters; @Bean public Encoder feignFormEncoder() { return new CustomEncoder(new SpringEncoder(messageConverters)); } class CustomEncoder extends FormEncoder { private Logger logger = LoggerFactory.getLogger(CustomEncoder.class); public CustomEncoder(Encoder encoder) { super(encoder); MultipartFormContentProcessor processor = (MultipartFormContentProcessor) getContentProcessor(ContentType.MULTIPART); processor.addFirstWriter(new SpringSingleMultipartFileWriter()); processor.addFirstWriter(new SpringManyMultipartFilesWriter()); } @Override public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { processMultipartFile(object, bodyType, template); } private void processMultipartFile(Object object, Type bodyType, RequestTemplate template) { if (bodyType != null && bodyType.equals(MultipartFile.class)) { MultipartFile file = (MultipartFile) object; Map<String, Object> stringObjectMap = Collections.singletonMap(file.getName(), object); super.encode(stringObjectMap, MAP_STRING_WILDCARD, template); } else if (bodyType != null && bodyType.equals(MultipartFile[].class)) { MultipartFile[] file = (MultipartFile[]) object; if (file != null) { Map<String, Object> stringObjectMap = Collections.singletonMap(file.length == 0 ? "" : file[0].getName(), object); // file[0].getName(): file, stringObjectMap: {file=[Lorg.springframework.web.multipart.MultipartFile;@750c26f5}, // 这里可能是导致服务端必须以file名称接收文件数组的原因 super.encode(stringObjectMap, MAP_STRING_WILDCARD, template); } } else { super.encode(object, bodyType, template); } } } }
配置文件
server.port=8080 eureka.client.serviceUrl.defaultZone=http://127.0.0.1:9999/eureka/ eureka.instance.prefer-ip-address=true ## 非必须 spring.main.allow-bean-definition-overriding=true
运行结果
到此,全部结束,但这里需要注意:
1、feign客户端上传的文件,使用requestparam定义的名称在文件接收服务端不一定有用(本例中,fegin客户端使用的files上传文件,文件服务端只能用file接收,不能用files,files接收不到文件),文件接收服务端接收的为文件的名称,从测试结果看,该名称与前端传递过来的name相同,如果不确定名称,可以通过打印request.getParts()中的内容来确定