# 版本要求

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