最近接了个项目,一个中间转发系统
大概需求是根据推送系统的报文,转发给其他的系统,转发前需要验证报文的格式
需要验证如下几种异常:
2:字段类型不匹配,字段值数据类型验证、字段类型验证(字段,对象,数组)
6:字段内容不能为空,字段值非空验证
1:字段长度不合规,字段值长度验证
3:字段找不到匹配内容,即报文含有不需要的节点或者字段
5:字段不存在,即需要该节点或字段,但是实际报文没有
死写不现实
1.接口太多了
2.报文字段未最终确定,会频繁变动
3.验证规则未最终确定
所以决定规则全部动态配,验证也是统一方法处理
目前手头只有报文demo格式,就像这样(实际报文不会这么简单,会出现,数组,对象,各种嵌套)
{
"DATA": [
{
"XXXX": [
{
"FIELD1": "字段说明1",
"FIELD2": "字段说明2"
}
]
}
]
}
最终我决定,将报文节点分成array数组,map对象,field字段三种类型,field里面又分string,int,float,timestamp这几种类型
最终处理的报文验证规则如下(报文demo是怎么转换验证规则的下面会有说明)
{
"DATA": {
"type": "array",
"required": "1",
"name": "DATA",
"memo": "",
"childFileds": {
"FIELD1": {
"field_type": "string",
"field_length": "20",//长度不超过20
"required": "1",//必填
"type": "field",
"name": "FIELD1",
"memo": "字段说明1"
},
"FIELD2": {
"field_type": "string",
"field_length": "20",
"required": "",
"type": "field",
"name": "FIELD2",
"memo": "字段说明2"
}
}
}
}
剩下的就是实际报文跟这个验证规则进行验证处理了,下面贴上java代码
//返回错误信息list,string[]第一位是错误code,第二位是具体错误说明
public static List<String[]> checkDo(String params, String rules) {
List<String[]> errors = new java.util.LinkedList<String[]>();
try {
if (StrKit.isBlank(rules)) {// 不需要验证
return errors;
}
Map<String, Object> data = gson.fromJson(params, Map.class);
Map<String, Map<String, Object>> checkMap = gson.fromJson(rules, Map.class);
checkDo(data, checkMap, errors);
} catch (Exception e) {
e.printStackTrace();
errors.add(new String[] {"7", e.getMessage()});
}
return errors;
}
public static void checkDo(Map<String, Object> data, Map<String, Map<String, Object>> checkMap, List<String[]> errors) {
Set<String> keys = data.keySet();
for(Map.Entry<String, Object> entry : data.entrySet()){
String key = entry.getKey();
Object val = entry.getValue();
Map<String, Object> rc = checkMap.get(key);
if (null == rc) {
if (val instanceof String || val instanceof Number) {// 不需要的字段
errors.add(new String[] {"3", "字段找不到匹配内容:" + key});
} else {// 不需要的节点
errors.add(new String[] {"3", "字段找不到匹配内容:" + key});
}
continue;
}
for(Entry<String, Map<String, Object>> entry2 : checkMap.entrySet()){
String key2 = entry2.getKey();
Map<String, Object> rcc = entry2.getValue();
Integer required = getInt(rcc, "required");
if (!keys.contains(key2) && null != required && required.intValue() == 1) {
errors.add(new String[] {"5", "字段不存在:" + key2});
continue;
}
}
String v_type = getStr(rc, "type");
switch (v_type) {
case "field":// 字段,验证必填、长度
String field_type = getStr(rc, "field_type");
Integer required = getInt(rc, "required");
Integer field_length = getInt(rc, "field_length");
switch (field_type) {
case "string": // 字符串
if (!(null == val || val instanceof String || val instanceof Number)) {
// errors.add(key + ",应为一个字段,不是节点。");
errors.add(new String[] {"2", "字段类型不匹配:" + key});
continue;
}
if (null != required && required.intValue() == 1) {// 必填
if (null == val || "".equals(String.valueOf(val))) {
// errors.add(key + "不能为空");
errors.add(new String[] {"6", "字段内容不能为空:" + key});
continue;
}
}
if (null != field_length) {
if (null != val && String.valueOf(val).length() > field_length) {
// errors.add(key + "超长(最大" + field_length + ")," + val + "长" + String.valueOf(val).length() + "。");
errors.add(new String[] {"1", "字段长度不合规:" + key + ",规定长度" + field_length});
continue;
}
}
break;
case "int":// 数值
if (!(null == val || val instanceof String || val instanceof Number)) {
// errors.add(key + ",应为一个字段,不是节点。");
errors.add(new String[] {"2", "字段类型不匹配:" + key});
continue;
}
Integer v_int = null;
try {
if (null != val) {
v_int = Double.valueOf(String.valueOf(val)).intValue();
}
} catch (Exception e) {
// errors.add(key + ",字段类型错误,应为‘int’");
errors.add(new String[] {"2", "字段类型不匹配:" + key});
continue;
}
if (null != required && required.intValue() == 1) {// 必填
if (null == v_int) {
// errors.add(key + "不能为空");
errors.add(new String[] {"6", "字段内容不能为空:" + key});
continue;
}
}
if (null != field_length) {
if (null != v_int && v_int.toString().length() > field_length) {
// errors.add(key + "超长(最大" + field_length + ")," + val + "长" + String.valueOf(val).length() + "。");
errors.add(new String[] {"1", "字段长度不合规:" + key + ",规定长度" + field_length});
continue;
}
}
break;
case "float":// float
if (!(null == val || val instanceof String || val instanceof Number)) {
// errors.add(key + ",应为一个字段,不是节点。");
errors.add(new String[] {"2", "字段类型不匹配:" + key});
continue;
}
Double v_float = null;
try {
if (null != val) {
v_float = Double.valueOf(String.valueOf(val));
}
} catch (Exception e) {
// errors.add(key + ",字段类型错误,应为‘float’");
errors.add(new String[] {"2", "字段类型不匹配:" + key});
continue;
}
if (null != required && required.intValue() == 1) {// 必填
if (null == v_float) {
// errors.add(key + "不能为空");
errors.add(new String[] {"6", "字段内容不能为空:" + key});
continue;
}
}
if (null != field_length) {
if (null != v_float && v_float.toString().length() > field_length) {
// errors.add(key + "超长(最大" + field_length + ")," + val + "长" + String.valueOf(val).length() + "。");
errors.add(new String[] {"1", "字段长度不合规:" + key + ",规定长度" + field_length});
continue;
}
}
break;
case "timestamp":
if (!(null == val || val instanceof String || val instanceof Number)) {
// errors.add(key + ",应为一个字段,不是节点。");
errors.add(new String[] {"2", "字段类型不匹配:" + key});
continue;
}
Long v_long = null;
try {
if (null != val) {
v_long = Double.valueOf(String.valueOf(val)).longValue();
}
} catch (Exception e) {
// errors.add(key + ",字段类型错误,应为‘timestamp’");
errors.add(new String[] {"2", "字段类型不匹配:" + key});
continue;
}
if (null != required && required.intValue() == 1) {// 必填
if (null == v_long) {
// errors.add(key + "不能为空");
errors.add(new String[] {"6", "字段内容不能为空:" + key});
continue;
}
}
if (null != field_length) {
if (null != v_long && v_long.toString().length() > field_length) {
// errors.add(key + "超长(最大" + field_length + ")," + val + "长" + String.valueOf(val).length() + "。");
errors.add(new String[] {"1", "字段长度不合规:" + key + ",规定长度" + field_length});
continue;
}
}
break;
default:
break;
}
break;
case "array":// 数组
if (!(null == val || val instanceof Collection)) {
// errors.add(key + ",应为一个数组。");
errors.add(new String[] {"2", "字段类型不匹配:" + key});
continue;
}
if (null != rc.get("childFileds") && null != val) {
List<Map<String, Object>> ll = (List<Map<String, Object>>) val;
for (Map<String, Object> m : ll) {
checkDo(m, (Map<String, Map<String, Object>>) rc.get("childFileds"), errors);
}
}
break;
case "map":// 对象
if (!(null == val || val instanceof Map)) {
// errors.add(key + ",应为一个对象。");
errors.add(new String[] {"2", "字段类型不匹配:" + key});
continue;
}
if (null != rc.get("childFileds") && null != val) {
checkDo((Map<String, Object>) val, (Map<String, Map<String, Object>>) rc.get("childFileds"), errors);
}
break;
default:
break;
}
}
}
}
public static final Integer getInt(Map<?, ?> map, String key) {
Object i = map.get(key);
if (null != i && !StrKit.isBlank(String.valueOf(i))) {
return Double.valueOf(String.valueOf(i)).intValue();
}
return null;
}
public static String getStr(Map<?, ?> map, String key) {
Object i = map.get(key);
if (null != i) {
return String.valueOf(i);
}
return null;
}
下面说明下报文规则是怎么处理的
第一步就是把报文demo处理处理成rule的,调用下面的方法返回map
//params 为报文demo,rules为已经保存的验证格式(可以为null)
public static LinkedHashMap<String, Map<String, Object>> rulesAuto(String params, String rules) {
LinkedHashMap<String, Map<String, Object>> retMap = new LinkedHashMap<String, Map<String,Object>>();
Map<String, Object> parm = gson.fromJson(params, LinkedHashMap.class);
Map<String, Map<String, Object>> checkMap = null;
if(!StrKit.isBlank(rules)) {
checkMap = gson.fromJson(rules, Map.class);
}
for(Map.Entry<String, Object> entry : parm.entrySet()){
String mapKey = entry.getKey();
Map<String, Object> cm = null;
if(null != checkMap) {
cm = checkMap.get(mapKey);
}
Object mapValue = entry.getValue();
LinkedHashMap<String, Object> th = new LinkedHashMap<String, Object>();
//th.put("name", mapKey);
if(null != cm) {
th.put("memo", cm.get("memo"));
th.put("required", getInt(cm, "required"));
} else {
th.put("required", 0);
}
if(mapValue instanceof String || mapValue instanceof Number) {//字段
th.put("type", "field");
if(null != cm) {
th.put("memo", cm.get("memo"));
th.put("field_type", cm.get("field_type"));
th.put("field_length", getInt(cm, "field_length"));
} else {
th.put("memo", mapValue);
th.put("field_type", "string");
th.put("field_length", "");
}
retMap.put(mapKey, th);
} else if(mapValue instanceof Collection) {//队列
LinkedHashMap<String, Object> childFileds = new LinkedHashMap<String, Object>();
th.put("type", "array");
th.put("childFileds", childFileds);
retMap.put(mapKey, th);
rulesAutoNext(((List<Map<String, Object>>)mapValue).get(0), (null == cm || !(cm.get("childFileds") instanceof Map)) ? null : (Map<String, Map<String,Object>>)cm.get("childFileds"), childFileds);
} else {//map
LinkedHashMap<String, Object> childFileds = new LinkedHashMap<String, Object>();
th.put("type", "map");
th.put("childFileds", childFileds);
retMap.put(mapKey, th);
rulesAutoNext((Map<String, Object>)mapValue, (null == cm || !(cm.get("childFileds") instanceof Map)) ? null : (Map<String, Map<String,Object>>)cm.get("childFileds"), childFileds);
}
}
return retMap;
}
public static void rulesAutoNext(Map<String, Object> parm, Map<String, Map<String, Object>> checkMap, LinkedHashMap<String, Object> retMap) {
for(Map.Entry<String, Object> entry : parm.entrySet()){
String mapKey = entry.getKey();
Object mapValue = entry.getValue();
Map<String, Object> cm = null;
if(null != checkMap) {
cm = checkMap.get(mapKey);
}
LinkedHashMap<String, Object> th = new LinkedHashMap<String, Object>();
//th.put("name", mapKey);
//th.put("memo", mapKey);
if(null != cm) {
th.put("memo", cm.get("memo"));
th.put("required", getInt(cm, "required"));
} else {
th.put("required", 0);
}
if(mapValue instanceof String || mapValue instanceof Number) {//字段
th.put("type", "field");
if(null != cm) {
th.put("memo", cm.get("memo"));
th.put("field_type", cm.get("field_type"));
th.put("field_length", getInt(cm, "field_length"));
} else {
th.put("memo", mapValue);
th.put("field_type", "string");
th.put("field_length", "");
}
retMap.put(mapKey, th);
} else if(mapValue instanceof Collection) {//队列
LinkedHashMap<String, Object> childFileds = new LinkedHashMap<String, Object>();
th.put("type", "array");
th.put("childFileds", childFileds);
retMap.put(mapKey, th);
rulesAutoNext(((List<Map<String, Object>>)mapValue).get(0), (null == cm || !(cm.get("childFileds") instanceof Map)) ? null : (Map<String, Map<String,Object>>)cm.get("childFileds"), childFileds);
} else {//map
LinkedHashMap<String, Object> childFileds = new LinkedHashMap<String, Object>();
th.put("type", "map");
th.put("childFileds", childFileds);
retMap.put(mapKey, th);
rulesAutoNext((Map<String, Object>)mapValue, (null == cm || !(cm.get("childFileds") instanceof Map)) ? null : (Map<String, Map<String,Object>>)cm.get("childFileds"), childFileds);
}
}
}
第二步在jsp页面处理,客户可以自定义必填、字段类型、长度等等验证
<ul id="content">
</ul>
<button onClick="article_save_submit()">保存</button>
<style>
.required {background-color :yellow;}
</style>
<script type="text/javascript">
var rules = ${rules };//上面java代码处理的map的json格式
$(function() {
$("#content").on("change", ":checkbox", function() {//必填加个背景,黄色
var span = $(this).closest("li").children("font");
if($(this).is(":checked")) {
if(!span.is(".required")) {
span.addClass("required");
}
} else {
span.removeClass("required");
}
})
//初始化tree
initChildDiv(rules, $("#content"));
//ul下li的显示、隐藏效果
$("#content").on("click", ".Hui-iconfont-jianhao", function() {
$(this).parent().children("ul").hide();
$(this).removeClass("Hui-iconfont-jianhao").addClass("Hui-iconfont-add");
})
//ul下li的显示、隐藏效果
$("#content").on("click", ".Hui-iconfont-add", function() {
$(this).parent().children("ul").show();
$(this).removeClass("Hui-iconfont-add").addClass("Hui-iconfont-jianhao");
})
})
//初始化ul,li
function initChildDiv(childFileds, ul) {
var i = 0;
for(var key in childFileds) {
i++;
var type = childFileds[key]['type'];
if(type == 'field') {
var li = $("<li style='margin-left:20px;'>"+i+". <font>" + key + "</font>"
+ "<span><select style='width:100px;margin-left:10px;' name='field_type'><option>string</option><option>int</option><option>float</option><option>timestamp</option></select>"
+ ",长度:<input type='text' class='ll-numberbox' style='width:100px;margin-left:10px;' data-options='min:1,max:5000' name='field_length'/>"
+ ",必填:<input type='checkbox' name='required' value='1'/>"
+ "<span style='display:none;'><input type='text' name='type' disabled readonly/></span>"
+ "<input type='text' name='name' style='display:none;'/>"
+ ",中文描述:<input type='text' name='memo' style='width:300px;'/></span></li>");
var obj = childFileds[key];
obj['name'] = key;
ul.append(li);
li.formFill(childFileds[key]);
} else {
var ul_next = $("<ul><ul>");
var li = $("<li style='margin-left:20px;'><i class='Hui-iconfont Hui-iconfont-jianhao'></i> "+i+". <font>" + key + "</font>"
+ "<span><select name='type' readonly style='width:100px;margin-left:10px;background-color:grey;'><option>array</option><option>map</option><option>field</option></select>"
+ ",必填:<input type='checkbox' name='required' value='1'/>"
+ "<input type='text' name='name' style='display:none;'/>"
+ ",中文描述:<input type='text' name='memo' style='width:300px;'/></span></li>");
var obj = childFileds[key];
obj['name'] = key;
li.append(ul_next);
ul.append(li);
li.formFill(childFileds[key]);
initChildDiv(childFileds[key]['childFileds'], ul_next);
}
}
}
//保存
function article_save_submit() {
var obj = new Object();
$("#content > li").each(function() {
var span = $(this).children("span");
var o = span.formSerialize();
var key = o.name;
obj[o.name] = o;
if(o.type == 'field') {
} else {
var childFileds = {};
rule_($(this).children("ul"), childFileds);
o['childFileds'] = childFileds;
}
})
console.log(obj);
//alert(JSON.stringify(obj))
}
//递归处理ul,li
function rule_(ul, obj) {
var lis = ul.children('li');
$.each(lis, function(i, item) {
var span = $(this).children("span");
var o = span.formSerialize();
var key = o.name;
obj[o.name] = o;
if(o.type == 'field') {
} else {
var childFileds = {};
rule_($(this).children("ul"), childFileds);
o['childFileds'] = childFileds;
}
})
}
//自动把data中的数据根据名称填充到form(支持所有,div,span,a均可)中
//div等中元素序列化成对象
//$("form").formFill(data);
$.fn.extend({
formSerialize : function() {// 将元素中的 input,select,textarea 转成对象
var obj = {};
var txtSelect = $(this).find("input,select,textarea").not(":button,:reset,:submit");
$.each(txtSelect, function(i, item) {
var $this = $(item);
var name = $this.attr("name");
if(name == '' || typeof name == 'undefined') {
return true;
}
var val = $this.val();
if(($this.is(":radio") || $this.is(":checkbox")) && !$this.is(":checked")) {
val = '';
}
if(obj.hasOwnProperty(name)) {
if(val != '') {
obj[name] = obj[name] == '' ? val : obj[name] + "," + val;
}
} else {
obj[name] = val;
}
})
return obj;
},
formFill : function(data) {
var $form = $(this);
var txtSelect = $form.find("input, select, textarea").not(":button,:reset,:submit");
$form.find(":radio, :checkbox").prop("checked", false);
txtSelect.not(":radio,:checkbox").val('');
var names = [];
$.each(txtSelect, function(i, item) {
var $this = $(item);
var name = $this.attr("name");
if(name == ''|| typeof name == 'undefined' || names.contains(name)) {
return true;
}
names.push(name);
var val = "";
if(data.hasOwnProperty(name)) {
val = data[name];
}
if(val != null){
val = val.toString();
} else {
val = '';
}
if($this.is("select")) {
var all = $form.find("select[name='" + name + "']");
if(all.length == 1) {
$this.attr("data-default", val);
$this.val(val);
if($this.val() == val) {
$this.change();
} else {
$this.val('').change();
}
} else {
val = val.split(",");
$.each(all, function(j, jtem) {
$(jtem).val('').change();
$(jtem).attr("data-default", val[j]);
if($(jtem).find("option[value='" + val[j] + "']").length == 0) {
$(jtem).val('').change();
} else {
$(jtem).val(val[j]).change();
}
})
}
} else if ($this.is("textarea")) {
var all = $form.find("textarea[name='" + name + "']");
if(all.length == 1) {
$this.val(val).change();
} else {
val = val.split(",");
$.each(all, function(j, jtem) {
$(jtem).val(val[j]).change();
})
}
} else {
if($this.is(":radio")) {
if(val != '')
$form.find(":radio[name='" + name + "'][value='" + val + "']").prop("checked", true).change();
} else if($this.is(":checkbox")) {
val = val.split(",");
$.each(val, function(m, n) {
if(n != '')
$form.find(":checkbox[name='" + name + "'][value='" + n + "']").prop("checked", true).change();
})
} else {// :text,:email 等等
var all = $form.find("input[name='" + name + "']");
if(all.length == 1) {
$this.val(val).change();
} else {
val = val.split(",");
$.each(all, function(j, jtem) {
$(jtem).val(val[j]).change();
})
}
}
}
})
}
})
</script>