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()方法,也會報錯