修改经过Spring Gateway的表单中的Json数据

背景

使用Spring Cloud Gateway作为网关时有时候一个请求是既包含excel又包含json的表单数据,出于各种层面考虑网关需要获取并更新其中的json数据

依赖

  • Spring Boot版本:2.7.15
  • Hutool: 5.8.21
  • Java: 11

实现逻辑

实现分为2个部分

  1. 使用上文提到的ModifyRequestBodyGatewayFilterFactory类来修改请求体,这样最后就不用我们手动包装
  2. 核心service通过将表单转为String,然后根据其中的boundary进行分割,提取修改json报文部分后再进行组装

注意:示例代码的核心service处理的表单内容只是2个,Json数据的key指定为json,另一个excel文件流

自定义filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Component
@Slf4j
public class RequestModifyFilter implements GlobalFilter, Ordered {
@Autowired
private ModifyRequestBodyGatewayFilterFactory modifyRequestBodyFilter;
@Autowired
private JsonRequestBodyRewriteService jsonRequestBodyRewriteService;
@Autowired
private FormDataRequestBodyRewriteService formDataRequestBodyRewriteService;

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {
// 纯json报文处理逻辑
return modifyRequestBodyFilter
.apply(
new ModifyRequestBodyGatewayFilterFactory.Config()
.setRewriteFunction(byte[].class, byte[].class, jsonRequestBodyRewriteService))
.filter(exchange, chain);
} else if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(mediaType)) {
// form表单数据处理
return modifyRequestBodyFilter
.apply(
new ModifyRequestBodyGatewayFilterFactory.Config()
.setRewriteFunction(byte[].class, byte[].class, formDataRequestBodyRewriteService))
.filter(exchange, chain);
} else {
return filter(exchange, chain);
}

}

@Override
public int getOrder() {
return OrderConstant.REQUEST_MODIFY_FILTER.getOrder();
}
}

核心service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@Service
@Slf4j
public class FormDataRequestBodyRewriteService implements RewriteFunction<byte[], byte[]> {
private final String BOUNDARY_PREFIX_IN_CONTENT_TYPE = "----WebKitFormBoundary";
private final String BOUNDARY_PREFIX_IN_FORM_DATA = "------WebKitFormBoundary";
private final String BOUNDARY_SUFFIX = "--\r\n";

@Override
public Publisher<byte[]> apply(ServerWebExchange exchange, byte[] body) {
String finalResultString = "";

// 将表单转为字符串格式从而根据boundary分割表单数据。注意这里不能用默认编码
String request = StrUtil.str(body, StandardCharsets.ISO_8859_1);

// 获取boundary的随机字符信息
String contentType = exchange.getRequest().getHeaders().getContentType().toString();
String randomStr = contentType.substring(contentType.indexOf(BOUNDARY_PREFIX_IN_CONTENT_TYPE) + BOUNDARY_PREFIX_IN_CONTENT_TYPE.length());

// 这里和前端约定json数据的表单key为json
String keyPart = "^\r\nContent-Disposition: form-data; name=\"json\"";
Pattern r = Pattern.compile(keyPart);

// 根据表单内分割线进行分割。并通过关键段落keyPart来找到目标json数据
String[] split = request.split(BOUNDARY_PREFIX_IN_FORM_DATA + randomStr);
for (int x = 0; x < split.length - 1; x++) {
Matcher m = r.matcher(split[x]);
if (m.find()) {
// 找到了json报文部分数据
String originalJsonString = split[x];

// 找到 JSON 数据的起始和结束位置
int startIndex = originalJsonString.indexOf("{\"");
int endIndex = originalJsonString.indexOf("\"}") + 2;
// 提取 JSON 数据
String jsonData = originalJsonString.substring(startIndex, endIndex);
log.info("原始报文为:{}", jsonData);

JSONObject jsonObject = JSONUtil.parseObj(jsonData);
jsonObject.set("empId", "2345");
jsonObject.set("department", "Engineering");
String modifiedString = originalJsonString.substring(0, startIndex) + jsonObject + originalJsonString.substring(endIndex);
log.info("修改后报文为:{}", modifiedString);

// 重新组装split数组
finalResultString = finalResultString + modifiedString + BOUNDARY_PREFIX_IN_FORM_DATA + randomStr;
} else {
// 重组表单数据
finalResultString = finalResultString + split[x] + BOUNDARY_PREFIX_IN_FORM_DATA + randomStr;
}
}

// 补上最后一截数据
finalResultString = finalResultString + BOUNDARY_SUFFIX;

return Mono.just(finalResultString.getBytes(StandardCharsets.ISO_8859_1));
}
}

相关代码

https://github.com/eastcukt/demo-gatway

其他

核心service获取表单中的json数据逻辑挺复杂,根本原因是没有合适的方法进行对象转换,如果有像使用@RequestPart(value = “json”)注解一样方便的方法将会非常方便也不用自己截取,各位大佬有更方便的方法感谢分享一下

参考

https://blog.csdn.net/qq_36966137/article/details/128536391