Spring参数绑定 适应多种提交方式(二)

背景

一点点补充之前没有考虑到的细节

Validation

自定义参数注入 会导致 Validation 失效

获得到对象后 手动调用 validate(obj);

如下 validate方法

List

上次写的逻辑 对简单参数做了处理

但是 如果前端传递的是List就无法处理了

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
/*
请求参数 JSON
{
"resultCode":"b",
"orders": [
{
"sequence": "4",
"superviseBankXh": "10000222"
},
{
"sequence": "3",
"superviseBankXh": "10000223"
},
{
"sequence": "2",
"superviseBankXh": "10000224"
},
{
"sequence": "1",
"superviseBankXh": "10000225"
}
]
}
*/
@PostMapping("/jgzhyxjsz")
public R jgzhyxjsz(List<JgzhyxjszDTO> orders){
return R.data(merchantMngmtService.jgzhyxjsz(orders));
}

后台如果不想用 @RequestBody 并且封装一个对象去接收的话

而是这样直接就能获取到 那就必须实现一个 List 的自定义参数转换器

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
@Slf4j
@Component
public class HandlerArrayResolver implements HandlerMethodArgumentResolver {

@Autowired
private Validator validator;

@Override
public boolean supportsParameter(MethodParameter parameter) {
return Collection.class.isAssignableFrom(parameter.getParameterType());
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container,
NativeWebRequest nativeWebRequest, WebDataBinderFactory factory) throws Exception {
ContentCachingRequestWrapper request = nativeWebRequest.getNativeRequest(ContentCachingRequestWrapper.class);
Object result = null;
//获取 List<?> 的泛型信息
ParameterizedType genericSuperclass = (ParameterizedType) parameter.getMethod().getGenericParameterTypes()[parameter.getParameterIndex()];
Type type = genericSuperclass.getActualTypeArguments()[0];

String contentType = request.getHeader(HttpHeaders.CONTENT_TYPE);
if(request.getBodySize() > 0 && (request.isRewiteBody() || contentType == null || contentType.startsWith(MediaType.APPLICATION_JSON_VALUE))) {
String queryBody = request.getQueryBody();
try {
if(queryBody.strip().startsWith("[")) {
result = JSONObject.parseArray(queryBody,Class.forName(type.getTypeName()));
}//支持多个对象嵌套
else if(queryBody.indexOf("\"" + parameter.getParameterName() + "\"") != -1) {
String tempStr = JSONObject.parseObject(queryBody).getString(parameter.getParameterName());
result = JSONObject.parseArray(tempStr,Class.forName(type.getTypeName()));
}
}catch (Exception e) {
log.error("参数注入增强-JSON转换失败");
}
}//if

return validate(parameter,result);
}//method

private Object validate(MethodParameter parameter,Object obj) {
//无需校验
if(parameter.getParameterAnnotation(Validated.class) == null && parameter.getParameterAnnotation(Valid.class) == null) return obj;

if(obj == null) throw new Exception("参数不能为null", 500);

Set<ConstraintViolation<Object>> validateResult = validator.validate(obj);
if(validateResult.size() > 0)
throw new MysdException(validateResult.iterator().next().getPropertyPath() +":"+ validateResult.iterator().next().getMessage(), 500);
return obj;
}
}//class

参数没有放在QueryBody 中

之前考虑的都是 参数放到QueryBody 如果对方把请求参数写在 QueryParam中

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115

@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
@Component
public class TraceFilter extends ActionFilter {

@Override
public void filter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
Long startTime = System.nanoTime();

String contentType = request.getContentType();
log.info("QueryStart_Url(" + request.getRequestURI() + ")_Method("+request.getMethod()+")_Type("+getContentType(contentType)+")_Ip("+getIpAddress(request)+")"+getDevInfo(request.getHeader(HttpHeaders.USER_AGENT)));
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
log.info("QueryString:" + requestWrapper.getQueryString());
log.info("QueryParameter:" + (requestWrapper.getParameterMap().size() !=0 ? JSON.toJSONString(requestWrapper.getParameterMap()):""));
if (requestWrapper.hasFile() && requestWrapper.getBodySize() != 0) {
log.info("QueryBody: 内容是个文件");
} else {
String queryBody = requestWrapper.getQueryBody();
String fmqb = queryBody.replaceAll("\\s+", " ");
log.info("QueryBody:" + fmqb);

//支持 前端用 get 但是数据传递在body中
if(HttpMethod.GET.matches(request.getMethod()) && requestWrapper.getBodySize() != 0) {
queryBody = URLDecoder.decode(queryBody, "UTF-8");
}

if(queryBody != null && queryBody.length() > 3 && queryBody.length() < 999) {
//支持 前端用 JSON 时 后端用 @RequestParam 接收参数
if((contentType == null || contentType.startsWith(MediaType.APPLICATION_JSON_VALUE))
//JSON 必须 没有数组
&& fmqb.indexOf("[") == -1 ) {
//对简单的 RquestBody JSON 数据 放入 RequestParam 封装
JSONObject json = JSONObject.parseObject(queryBody);
for(String key : json.keySet()) {
String value = json.getString(key);
if(StringUtils.isNotEmpty(value) && !value.startsWith("{") && !value.startsWith("["))
requestWrapper.put(key, value);
}//for
}

//支持 前端用 x-www 后端用 参数 或者 JavaBean 接收
if(contentType == null || contentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
String[] params = queryBody.split("&");
for(String param : params) {
String[] par = param.split("=");
if(par.length != 2) break;
requestWrapper.put(par[0], par[1]);
}//for
}
}//if

//支持 前端用 x-www 或 form-data 时 后端用 JavaBean 接收
//或者 body 没有数据时 param有数据 把 param内容放到 body 中
if((requestWrapper.getBodySize() == 0 || HttpMethod.GET.matches(request.getMethod())) &&
(contentType == null ||
contentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|| contentType.startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)
|| contentType.startsWith(MediaType.APPLICATION_JSON_VALUE))) {
Map<String,String> map = requestWrapper.getParameterMapEx();
if(!map.isEmpty()) {
String mapJson = JSON.toJSONString(map)
.replaceAll("\"\\{","{")
.replaceAll("\\}\"", "}")
.replaceAll("\"\\[","[")
.replaceAll("\\]\"", "]")
.replaceAll("\\\\\"", "\"");
requestWrapper.rewriteBody(mapJson);
}
}
}//else

//劫持返回信息
boolean isMeHook = false;
ResponseWrapper proxyResponse;
if (response instanceof ResponseWrapper)
proxyResponse = (ResponseWrapper) response;
else {
proxyResponse = new ResponseWrapper(response);
isMeHook = true;
}
chain.doFilter(requestWrapper, proxyResponse);

if(response != null && (response.getContentType() == null || response.getContentType().startsWith(MediaType.APPLICATION_JSON_VALUE)))
log.info("ResponseBody Secret({}) Detail({})", response instanceof CryptoHttpServletResponse, new String(proxyResponse.getContent()));
else
log.info("ResponseBody Detail({})", "内容未知 type:" + response.getContentType());

Long elapsedTime = DateUtil.nanosToMillis(DateUtil.spendNt(startTime));
log.info("QueryEnd-Url:" + request.getRequestURI() + " elapsedTime(" + elapsedTime + ")");

if(isMeHook)
proxyResponse.finish();
}// method


public static String getContentType(String contentType) {
if(StringUtils.isEmpty(contentType))
return "NONE";
if(contentType.indexOf(";") != -1)
contentType = contentType.split(";")[0];
if(contentType.indexOf("/") != -1)
contentType = contentType.split("/")[1];
return contentType;
}

public static String getDevInfo(String agent) {
...
}

public static String getIpAddress(HttpServletRequest request) {
...
}
}// class

手动把 queryString的参数放到 requestBody 中去

总结

很多人会搞不明白 content-type 的区别

明明说了 用 JSON 请求 参数却放在 param 中

或者需要放在 param 中的参数 放到 body 中 或者参数放到 url 中

如果后端不区分这些 不论 param url body 全部都处理 都可以自由接收

那将极大的降低对接口的工作量

只是有太多情况需要考虑 一点点完善吧~