微服务中,跨服务调用上传文件接口,使用feign方式调用
环境:
1. springboot2.2.1.RELEASE;
2.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>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</exclusion>
</exclusions>
</dependency>
问题:多文件上传接口正常调用成功,但是,在控制台输出文件数量显示只收到一个文件
解决:
经排查,客户端收到了多个文件并输出,然后feign调用,需要在feign接口上注入一个配置,经查看,引用的jar中包含一个SpringFormEncoder,源码如下:
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package feign.form.spring;
import static feign.form.ContentType.MULTIPART;
import static java.util.Collections.singletonMap;
import java.lang.reflect.Type;
import java.util.HashMap;
import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import feign.form.FormEncoder;
import feign.form.MultipartFormContentProcessor;
import lombok.val;
import org.springframework.web.multipart.MultipartFile;
/**
* Adds support for {@link MultipartFile} type to {@link FormEncoder}.
*
* @author Tomasz Juchniewicz <tjuchniewicz@gmail.com>
* @since 14.09.2016
*/
public class SpringFormEncoder extends FormEncoder {
/**
* Constructor with the default Feign's encoder as a delegate.
*/
public SpringFormEncoder () {
this(new Encoder.Default());
}
/**
* Constructor with specified delegate encoder.
*
* @param delegate delegate encoder, if this encoder couldn't encode object.
*/
public SpringFormEncoder (Encoder delegate) {
super(delegate);
val processor = (MultipartFormContentProcessor) getContentProcessor(MULTIPART);
processor.addFirstWriter(new SpringSingleMultipartFileWriter());
processor.addFirstWriter(new SpringManyMultipartFilesWriter());
}
@Override
public void encode (Object object, Type bodyType, RequestTemplate template) throws EncodeException {
if (bodyType.equals(MultipartFile[].class)) {
val files = (MultipartFile[]) object;
val data = new HashMap<String, Object>(files.length, 1.F);
for (val file : files) {
data.put(file.getName(), file);
}
super.encode(data, MAP_STRING_WILDCARD, template);
} else if (bodyType.equals(MultipartFile.class)) {
val file = (MultipartFile) object;
val data = singletonMap(file.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;
}
}
在encode中,加入了对文件数组的处理
问题就出现在file.getName(),多个文件时,这个方式获取到的文件名称不变,因此只能获取到一个文件。
至此,重写该方法,如下代码解决:
@Configuration
class FeignMultipartSupportConfig {
@Bean
public Encoder springFormEncoder(ObjectFactory<HttpMessageConverters> messageConvertersObjectFactory) {
// 避免实体作为参数无法接收
SpringEncoder springEncoder = new SpringEncoder(messageConvertersObjectFactory);
return new SpringMultipartEncoder(springEncoder);
}
}
public class SpringMultipartEncoder extends FormEncoder {
public SpringMultipartEncoder() {
this(new Default());
}
public SpringMultipartEncoder(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 {
if (bodyType.equals(MultipartFile[].class)) {
MultipartFile[] files = (MultipartFile[])((MultipartFile[])object);
Map<String, MultipartFile[]> data = Collections.singletonMap("files", files);
super.encode(data, MAP_STRING_WILDCARD, template);
} else if (bodyType.equals(MultipartFile.class)) {
Map<String, Object> data = Collections.singletonMap("file", object);
super.encode(data, MAP_STRING_WILDCARD, template);
} else if (this.isMultipartFileCollection(object)) {
Iterable<?> iterable = (Iterable)object;
HashMap<String, Object> data = new HashMap();
Iterator var6 = iterable.iterator();
while(var6.hasNext()) {
Object item = var6.next();
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;
} else {
Iterable<?> iterable = (Iterable)object;
Iterator<?> iterator = iterable.iterator();
return iterator.hasNext() && iterator.next() instanceof MultipartFile;
}
}
}