介绍
最近遇到一个钉钉微应用的项目,前端自实现,后端数据操作需要对接其他系统。
总结
总结一下后端需要做的事情。
对前端参数进行校验。比如数值范围,字符串等类型,某些值要小于从其他系统查到的余额等场景。
请求转发。前端传递json类型,要传到某些其他系统中,类型不确定,有的是xml,有的是json类型。
请求响应。收到响应请求时,需要转成json类型。
收到的结构数据中,有字符串是json的类型,需要转成json类型
字段名称需要映射,比如jobNo对应其他系统的T_ABC
以上就是新系统需要做的事情,涉及的接口近百个左右。如果不出意外的场景。正常写代码的话。
需要各种json解析,一对一的解析,拼装请求,http代码,解析响应,返回给前端。看了下demo,一个接口的代码量在100行以上完全不是问题。
大量重复的代码堆积。开启了优化重构之路。
寻找共性
通过对上述接口的分解,不难看出其共性:字段映射,较强大的规则校验,,json转xml。
字段映射
通过配置化json即可。
json和xml的互转
dom4j解析xml实现
规则化json
{“data”: “{\”name\”: \”abc\”}”} -> {“data”:{“name”: “abc”}}
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/**
* 将不规则的json数据解析成序列化json
* @param jsonObject
*/
public void convert2JSONObject(JSONObject jsonObject){
if(jsonObject != null && jsonObject.size() > 0) {
for(String key: jsonObject.keySet()) {
Object object = jsonObject.get(key);
if(object instanceof JSONObject) {
convert2JSONObject(jsonObject.getJSONObject(key));
}else if(object instanceof JSONArray) {
JSONArray array = jsonObject.getJSONArray(key);
for(int i=0 ;i< array.size(); i++) {
convert2JSONObject(array.getJSONObject(i));
}
}else if(object instanceof String) {
String line = object.toString();
if(line.contains("[{")) {
try{
jsonObject.put(key, JSON.parseArray(line));
}catch (Exception e) {
//fixed logger
logger.error("line format to json array fail, line: {}", line);
throw new InvalidArgumentException(String.format("line format to json array fail, line: %s", line));
}
}else if(line.contains("{")) {
try{
jsonObject.put(key, JSON.parseObject(line));
}catch (Exception e) {
//fixed logger
logger.error("line format to json object fail, line: {}", line);
throw new InvalidArgumentException(String.format("line format to json object fail, line: %s", line));
}
}
}
}
}
}
规则解析引擎
定义一个规则类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/***
* 用于流程归档规则校验
*/
@JsonTop(path="data.I_INPUT", type= Constant.OpCode.STRING)
public class ProcessArchiveRule extends Rule{
@UrlCompare(url="http://localhost/name/query?id=", args="id" resp="a.b.c")
@NotNull
@NumberCompare(eq = "1", type=Constant.OpCode.INT)
private String status;
@NumberCompare(eq = "10001", type=Constant.OpCode.INT)
private String pernr;
@Extend
public void extend(String jsonStr){
System.out.println(String.format("jsonStr:",jsonStr));
}
}
配置文件
1checkConfigs=[{"processArchive":"ProcessArchiveRule.class"},{"processApprove":"ProcessApproveRule.class"}]
解释
@JsonTop path代表要解析传参的路径, type代表我要转义的类型string/object/array
下面每个字段都会选择去递归其path下的key进行对比逐个字段上注解的校验。
@NumberCompare 代表对其数值的校验。
@UrlCompare 代表对比的该值需要从其他url中获取,可申明json/xml类型。
@extend 代表是否需要自定义扩展check。比如有注解做不到的场景时,支持扩展。
如此就能完成自动化校验。
规则校验部分代码
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134/***
* 匹配规则bean类
* @param body
*/
public void checkRule(String body){
JSONObject jsonObject = JSONObject.parseObject(body);
String method = jsonObject.getString(Constant.OpCode.METHOD);
StringBuilder sb = new StringBuilder(Constant.OpCode.BEAN_PACKAGE);
sb.append(method.substring(0,1).toUpperCase()).append(method.substring(1,method.length())).append(Constant.OpCode.RULE);
String bean = sb.toString();
//获得类级别的注解对象
try {
List> list = ruleClazzList;
for (int i=0; i
Map beanMap = list.get(i);
for (String key : beanMap.keySet()){
if(key.equals(method)){
Class clazz =Class.forName(bean);
JsonTop jsonTop = (JsonTop) clazz.getAnnotation(JsonTop.class);
String[] tops = jsonTop.path().split("\\.");
loopTopJson(clazz,jsonObject,tops, 0, Constant.OpCode.STRING);
}
}
}
} catch (ClassNotFoundException e) {
logger.error("{} class is ClassCastException", bean);
throw new InvalidArgumentException(String.format("%s class is ClassCastException", bean));
}
}
/**
* 递归获取到rule类注解的path
* @param clazz
* @param jsonObject
* @param tops
* @param index
* @param type
*/
private void loopTopJson(Class clazz,JSONObject jsonObject, String[] tops, int index, String type){
if(index >= tops.length - 1) {
if(Constant.OpCode.STRING.equals(type)) {
logger.info(jsonObject.getString(tops[index]));
validateRule(clazz, jsonObject.getString(tops[index]));
}else if(Constant.OpCode.OBJECT.equals(type)) {
logger.info(jsonObject.getString(tops[index]));
validateRule(clazz, jsonObject.getString(tops[index]));
}
}else{
if (jsonObject.containsKey(tops[index])) {
Object object = jsonObject.get(tops[index]);
if (object instanceof JSONObject) {
loopTopJson(clazz,jsonObject.getJSONObject(tops[index]), tops, index++, type);
} else if (object instanceof JSONArray) {
JSONArray array = jsonObject.getJSONArray(tops[index]);
if(array.size() > 0) {
for(int i=0; i
loopTopJson(clazz,array.getJSONObject(i), tops, index+1, type);
}
}
} else {
Object obj = jsonObject.get(tops[index]);
}
}
}
}
/**
* 验证其json类型
* @param clazz 对应类名
* @param data json数据
*/
private void validateRule(Class clazz, String data){
Object object = JSON.parse(data);
if(object instanceof JSONObject) {
JSONObject jsonObject = JSON.parseObject(data);
validateRule(clazz, jsonObject);
}else if(object instanceof JSONArray) {
JSONArray jsonArray = JSON.parseArray(data);
for(int i=0 ;i
validateRule(clazz, jsonArray.getJSONObject(i));
}
}
}
/***
* 将匹配到的json数据与rule上的注解进行逐一check
* @param clazz 对应类名
* @param data json数据
*/
public void validateRule(Class clazz, JSONObject data){
loopField: for(Field field: clazz.getDeclaredFields()) {
if(field.isAnnotationPresent(NumberCompare.class)) {
if(! data.containsKey(field.getName())) {
logger.info("field:{} not find from data: {}", field.getName(), data.keySet());
continue loopField;
}
NumberCompare number = field.getAnnotation(NumberCompare.class);
String checkEq = number.eq();
String dataField = data.getString(field.getName());
if(number.type().equals(Constant.OpCode.INT)) {
if(Integer.parseInt(checkEq) != Integer.parseInt(dataField)) {
throw new InvalidArgumentException(String.format("%s value is %s, need equals %s", field.getName(), dataField, checkEq));
}
}else if(number.type().equals(Constant.OpCode.DOUBLE)) {
if(Double.parseDouble(checkEq) != Double.parseDouble(dataField)) {
throw new InvalidArgumentException(String.format("%s value is %s, need equals %s", field.getName(), dataField, checkEq));
}
}
}
if (field.isAnnotationPresent(NotNull.class)) {
if(! data.containsKey(field.getName())) {
logger.info("field:{} not find from data: {}", field.getName(), data.keySet());
continue loopField;
}
checkNotNull(data.getString(field.getName()), field.getName());
}
if (field.isAnnotationPresent(UrlCompare.class)) {
if(! data.containsKey(field.getName())) {
logger.info("field:{} not find from data: {}", field.getName(), data.keySet());
continue loopField;
}
UrlCompare url = field.getAnnotation(UrlCompare.class);
//append url annotations
}
}
}
剩下的只是针对不同的接口写不同的Rule类,声明要check的字段,需要check的类型注解即可。实现代码的优化,简化。代码量指数级的下降。当然只针对这一场景。
为其他场景提供一个思路。代码比较简单,coding 3小时基本差不多能fix。
主要涉及到的技术点:自定义注解,递归,反射