# 版本要求
FizzGate集成平台v2.1.0或以上版本 (安装教程 (opens new window))
FizzGate集成平台从1.0开始已支持文件上传请求的转发,从2.1.0开始在服务编排功能对form-data上传文件进行了支持,以便进行更复杂的接口编排。
# 实例说明
通常文件上传或图片服务都是一个独立的服务来维护,它只专注于文件的上传和维护,不处理具体的业务逻辑。它会提供一个文件上传接口,上传成功后接口返回文件的URL(或文件ID)供业务方使用,业务方只需要存储文件的URL;下面以修改个人信息场景为例说明:
- 用户可以修改名称,年龄和头像
- 后端服务已有公用的通用文件上传接口,通过multipart/form-data提交, 如:/post/fileUpload
- 后端服务已有保存用户信息接口,如:/post/saveProfile,入参为(userId, name, age, avatarUrl)
为了实现这个功能在往常我们需求编写一个更新个人信息接口,先调用/post/fileUpload上传图片,拿到图片URL后再调用/post/saveProfile接口保存到用户表。
下面以这个场景来编排一个更新个人信息的接口
# 环境准备
创建一个服务来模拟已有的接口,项目代码:https://gitee.com/fizzgate/fizz-examples/fizz-examples-rest-api.git
FileUploadController.java:
package we.controller;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.codec.multipart.FormFieldPart;
import org.springframework.http.codec.multipart.Part;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* Example
*
* @author Francis Dong
*
*/
@RestController
public class FileUploadController {
/**
*
* File upload
*
* @param exchange
* @return
*/
@PostMapping(value = "/post/fileUpload", consumes = "multipart/form-data")
public Object formData(ServerWebExchange exchange) {
Mono<MultiValueMap<String, Part>> m = exchange.getMultipartData();
return m.flatMap(md -> {
// extract non-file form data
Map<String, Object> params = extractFormData(md);
System.out.println(params);
List<FilePart> list = new ArrayList<>();
for (Entry<String, List<Part>> entry : md.entrySet()) {
List<Part> val = entry.getValue();
if (val != null && val.size() > 0) {
val.stream().forEach(part -> {
if (part instanceof FilePart) {
FilePart fp = (FilePart) part;
list.add(fp);
}
});
}
}
if (list != null && list.size() > 0) {
Flux<FilePart> fileParts = Flux.fromIterable(list);
return fileParts.flatMap(fp -> {
String tmpPath = System.getProperty("user.dir") + "/tmp/";
File tmpFolder = new File(tmpPath);
if (!tmpFolder.exists()) {
tmpFolder.mkdirs();
}
String path = tmpPath + fp.filename();
System.out.println(path);
// Return file path
return fp.transferTo(new File(path)).then(Mono.just("/tmp/" + fp.filename()));
}).collectList().flatMap(urls -> {
Map<String, Object> result = new HashMap<>();
result.put("urls", urls);
return Mono.just(result);
});
} else {
return Mono.just(new HashMap<>());
}
});
}
/**
* Save profile
*
* @param exchange
* @return
*/
@PostMapping(value = "/post/saveProfile", consumes = "application/x-www-form-urlencoded")
public Object saveProfile(ServerWebExchange exchange) {
Mono<MultiValueMap<String, String>> m = exchange.getFormData();
return m.flatMap(md -> {
// save profile info ...
System.out.println(md);
Map<String, Object> result = new HashMap<>();
result.put("code", 0);
result.put("message", 0);
Map<String, Object> user = new HashMap<>();
user.put("userId", md.getFirst("userId"));
user.put("name", md.getFirst("name"));
user.put("age", md.getFirst("age"));
user.put("avatarUrl", md.getFirst("avatarUrl"));
result.put("user", user);
return Mono.just(result);
});
}
public static Map<String, Object> extractFormData(MultiValueMap<String, Part> params) {
HashMap<String, Object> m = new HashMap<>();
if (params == null || params.isEmpty()) {
return m;
}
for (Entry<String, List<Part>> entry : params.entrySet()) {
List<Part> val = entry.getValue();
if (val != null && val.size() > 0) {
if (val.size() > 1) {
List<String> formFieldValues = new ArrayList<>();
val.stream().forEach(part -> {
if (part instanceof FormFieldPart) {
FormFieldPart p = (FormFieldPart) part;
formFieldValues.add(p.value());
} else if (part instanceof FilePart) {
FilePart fp = (FilePart) part;
formFieldValues.add(fp.filename());
}
});
if (formFieldValues.size() > 0) {
m.put(entry.getKey(), formFieldValues);
}
} else {
if (val.get(0) instanceof FormFieldPart) {
FormFieldPart p = (FormFieldPart) val.get(0);
m.put(entry.getKey(), p.value());
} else if (val.get(0) instanceof FilePart) {
FilePart fp = (FilePart) val.get(0);
m.put(entry.getKey(), fp.filename());
}
}
}
}
return m;
}
}
通用文件上传接口URL: http://127.0.0.1:8080/post/fileUpload
更新用户信息接口: http://127.0.0.1:8080/post/saveProfile (为了演示form表单的提交方式,接口限制只能使用x-www-form-urlencoded提交方式)
# 编排更新个人信息接口
# 新增接口
菜单位置:服务编辑->接口列表,点击新增
# 配置输入
在配置输入tab可以定义接口的入参和请求头等信息,如果不定义网关不会对接收到的参数做任何校验。
# 配置步骤
因为要先后调用两个接口,需求新增2个步骤. 在步骤1里调用图片上传接口,在步骤2里调用保存用户信息接口。
步骤一:
点击新增HTTP服务,把上传文件服务和保存用户信息的服务添加到系统。
选择刚添加的服务fizz-examples-rest-api,填写上传文件接口路径/post/fileUpload,请求体里选form-data,引用用户输入的图片参数。
步骤二:
添加步骤二,选fizz-examples-rest-api服务,填写保存用户信息接口路径/post/saveProfile,引用步骤一上传文件接口的返回结果和用户输入的姓名和年龄数据。
# 配置输出
配置要返回给前端的响应报文,这里直接引用步骤二的结果
# 测试
配置完接口后,点击测试
# 访问正式接口
发布接口并配置路由后访问URL: http://[网关IP]:8600/proxy/func-test/user/updateProfile