Java后端实现Mock模拟数据
Java实现Mock数据需求我们在开发的时候经常遇到第三方接口还没完成的情况,或者需要向数据库插入各种测试数据。此时,如果数量级少还可以写几行代码满足,但数量级大或数据结构复杂就很麻烦了。对比过mockito、javafacker等工具,总得来说不太适用,需要硬编码。有没有只需要简单配置就能直接生成数据的,发现前端的Mock.js非常合适,突然有一种想法让Java去跑js代码,不就可以了么。实现前
Java实现Mock数据
需求
我们在开发的时候经常遇到第三方接口还没完成的情况,或者需要向数据库插入各种测试数据。此时,如果数量级少还可以写几行代码满足,但数量级大或数据结构复杂就很麻烦了。对比过mockito、javafacker等工具,总得来说不太适用,需要硬编码。有没有只需要简单配置就能直接生成数据的,发现前端的Mock.js非常合适,突然有一种想法让Java去跑js代码,不就可以了么。
实现
前端根据mock.js的规则语法配置数据结构与生成规则,后端调用mock.js,通过配置规则生成相应的数据。这种方案优点在于更加灵活,后端不会写太多死代码。其中,mock规则可以放在一个配置文件、缓存、nacos等等,下面案例主要以给前端返回模拟数据为主题,如果想要做推送数据、插库等可以用定时任务去调mock方法。
Mock.js官方文档: https://github.com/nuysoft/Mock/wiki
数据分类
-
固定值 :始终返回前端配置时的固定值
-
随机值:根据规则生成的随机值
Mock语法规则
数据模板中的每个属性由 3 部分构成:属性名、生成规则、属性值:
// 属性名 name
// 生成规则 rule
// 属性值 value
'name|rule': value
注意:
属性名 和 生成规则 之间用竖线 | 分隔。
生成规则 是可选的。
生成规则 有 7 种格式:
'name|min-max': value
'name|count': value
'name|min-max.dmin-dmax': value
'name|min-max.dcount': value
'name|count.dmin-dmax': value
'name|count.dcount': value
'name|+step': value
生成规则 的 含义 需要依赖 属性值的类型 才能确定。
属性值 中可以含有 @占位符。
属性值 还指定了最终值的初始值和类型
测试例子
这里我们以官方的语法规则做测试
<!-- hutool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${cn.hutool.version}</version>
</dependency>
// Java 测试Demo
public static void main(String[] args) throws ScriptException {
// 获取JS引擎
JavaScriptEngine scriptEngine = ScriptUtil.getJavaScriptEngine();
// 引用Mock.js文件
String url = "mock.js文件路径"
String mockJs = FileUtil.readString(url, CharsetUtil.UTF_8);
scriptEngine.eval(mockJs);
/*
此处为生成相应数据代码
*/
}
-
属性值是字符串 String
1.'name|min-max': string 通过重复 string 生成一个字符串,重复次数大于等于 min,小于等于 max。 2.'name|count': string 通过重复 string 生成一个字符串,重复次数等于 count。
System.out.println(scriptEngine.eval("JSON.stringify( Mock.mock({'name|1-3':'张三'}) )")); // {"name":"张三张三张三"} System.out.println(scriptEngine.eval("JSON.stringify( Mock.mock({'name|1':'张三'}) )")); // {"name":"张三"}
-
属性值是数字 Number
1.'name|+1': number 属性值自动加 1,初始值为 number。 2.'name|min-max': number 生成一个大于等于 min、小于等于 max 的整数,属性值 number 只是用来确定类型。 3.'name|min-max.dmin-dmax': number 生成一个浮点数,整数部分大于等于 min、小于等于 max,小数部分保留 dmin 到 dmax 位。
System.out.println(scriptEngine.eval("JSON.stringify( Mock.mock({'name|+1':1}) )")); // {"num":1} System.out.println(scriptEngine.eval("JSON.stringify( Mock.mock({'name|10-30':1}) )")); // {"num":17} System.out.println(scriptEngine.eval("JSON.stringify( Mock.mock({'name|10-29.1-2':1.2}) )")); // {"num":24.25}
-
属性值是布尔型 Boolean
1.'name|1': boolean 随机生成一个布尔值,值为 true 的概率是 1/2,值为 false 的概率同样是 1/2。 2.'name|min-max': value 随机生成一个布尔值,值为 value 的概率是 min / (min + max),值为 !value 的概率是 max / (min + max)。
System.out.println(scriptEngine.eval("JSON.stringify( Mock.mock({'name|1':true}) )")); // {"name":true} System.out.println(scriptEngine.eval("JSON.stringify( Mock.mock({'name|1-10':true}) )")); // {"name":false}
-
属性值是对象 Object
1.'name|count': object
从属性值 object 中随机选取 count 个属性。
2.'name|min-max': object
从属性值 object 中随机选取 min 到 max 个属性。
System.out.println(scriptEngine.eval("JSON.stringify( Mock.mock({'name|1':{a:1,b:2,c:3,d:4}}) )"));
// {"name":{"c":3}}
System.out.println(scriptEngine.eval("JSON.stringify( Mock.mock({'name|1-2':{a:1,b:2,c:3,d:4}}) )"));
// {"name":{"b":2,"d":4}}
-
属性值是数组 Array
1.'name|1': array 从属性值 array 中随机选取 1 个元素,作为最终值。 2.'name|+1': array 从属性值 array 中顺序选取 1 个元素,作为最终值。 3.'name|min-max': array 通过重复属性值 array 生成一个新数组,重复次数大于等于 min,小于等于 max。 4.'name|count': array 通过重复属性值 array 生成一个新数组,重复次数为 count。
System.out.println(scriptEngine.eval("JSON.stringify( Mock.mock({'name|1':[1,2,3,4,5,6]}) )")); // {"name":3} System.out.println(scriptEngine.eval("JSON.stringify( Mock.mock({'name|+1':[1,2,3,4,5,6]}) )")); // {"name":1} System.out.println(scriptEngine.eval("JSON.stringify( Mock.mock({'name|1-2':[1,2,3,4,5,6]}) )")); // {"name":[1,2,3,4,5,6]} System.out.println(scriptEngine.eval("JSON.stringify( Mock.mock({'name|2':[1,2,3,4,5,6]}) )")); // {"name":[1,2,3,4,5,6,1,2,3,4,5,6]}
-
属性值是函数 Function
1.'name': function 执行函数 function,取其返回值作为最终的属性值,函数的上下文为属性 'name' 所在的对象。
System.out.println(scriptEngine.eval("JSON.stringify( Mock.mock({'name': function(){return 1+1;}}))")); // {"name":2}
-
属性值是正则表达式 RegExp
1.'name': regexp 根据正则表达式 regexp 反向生成可以匹配它的字符串。用于生成自定义格式的字符串。
System.out.println(scriptEngine.eval("JSON.stringify( Mock.mock({'name': /[a-z][A-Z][0-9]/ }) )")); // {"name":"tK7"}
-
数据占位符定义规范 DPD
占位符 只是在属性值字符串中占个位置,并不出现在最终的属性值中。 占位符 的格式为: @占位符 @占位符(参数 [, 参数]) 注意: 用 @ 来标识其后的字符串是 占位符。 占位符 引用的是 Mock.Random 中的方法。 通过 Mock.Random.extend() 来扩展自定义占位符。 占位符 也可以引用 数据模板 中的属性。 占位符 会优先引用 数据模板 中的属性。 占位符 支持 相对路径 和 绝对路径。 我的理解:生成地名、邮箱、姓名等有意义或专有名词的值直接使用 @占位符 来解决,对应的Mock.Random用法如下图所示:
System.out.println(scriptEngine.eval(
"JSON.stringify(Mock.mock({'person|3':[{'id|+1':10001,'name':'@name','age|20-40':1,'email':'@email'}]}))"));
/*
{
"person": [
{
"id": 10001,
"name": "Christopher Anderson",
"age": 38,
"email": "y.ymiwu@bhfpdwm.to"
},
{
"id": 10002,
"name": "Lisa Young",
"age": 29,
"email": "i.gwyk@kfkisonkgm.gw"
},
{
"id": 10003,
"name": "Margaret Williams",
"age": 29,
"email": "u.rnm@jaiveqtvbx.hk"
}
]
}
*/
扩展
Mock.Random方法不能满足实际需求时,也可以对其进行拓展,实现如下:
// 为维护方便,不在mock.js源文件中进行修改,新建一个.js文件(.txt也可以,因为程序中都以读取字符串处理的)
// mock-extend.js
Mock.Random.extend({
constellation: function(date) {
var constellations = ['白羊座', '金牛座', '双子座', '巨蟹座', '狮子座', '处女座', '天秤座', '天蝎座', '射手座', '摩羯座', '水瓶座', '双鱼座']
return this.pick(constellations)
}
})
public static void main(String[] args) throws ScriptException {
JavaScriptEngine scriptEngine = ScriptUtil.getJavaScriptEngine();
String mockUrl = "mock.js文件路径";
String extendUrl = "mock-extend.js文件路径";
String mockJs = FileUtil.readString(mockUrl, CharsetUtil.UTF_8);
String mockExtendJs = FileUtil.readString(extendUrl, CharsetUtil.UTF_8);
scriptEngine.eval(mockJs+mockExtendJs);
// 此处引用扩展的方法
System.out.println(scriptEngine.eval("JSON.stringify( Mock.mock({ 'constellation|3':'@CONSTELLATION'}))"));
// {"constellation":"金牛座双鱼座处女座"}
}
案例设计
原型图
本案例定义5种数据类型:Object 、Array 、String 、Number (所有数值类型)、Boolean。
Mock配置实体类
为了友好的进行Mock数据的配置、管理以及参数调用,我们分析出上文Mock.js中定义Mock数据的语法规则主要包括三部分:变量名称 、生成规则 、变量值,这里定义Model进行封装。
@Data
@Slf4j
@ApiModel("Mock配置")
public class MockData {
/**
* 参数名称
*/
@ApiModelProperty(value = "参数名称")
private String name;
/**
* 规则
*/
@ApiModelProperty(value = "规则")
private String rule;
/**
* 数据类型 string|number|boolean|array|object
*/
@ApiModelProperty(value = "数据类型 string|number|boolean|array|object")
private String type;
/**
* 参数值 @name占位符|正则表达式|字符串
*/
@ApiModelProperty(value = "参数值 @name占位符|正则表达式|字符串")
private String paramValue;
/**
* 子节点数据类型为object
*/
@ApiModelProperty(value = "子节点数据类型为object")
private List<MockData> objectItems;
/**
* 子节点数据类型为array
*/
@ApiModelProperty(value = "子节点数据类型为array")
private MockData arrayItems;
}
主要处理流程
-
解析
在接受前端穿来的参数后,需要递归MockData实体类逐层解析,生成Mock.js 语法规则的JSON。 -
构建
启动JS引擎加载Mock.js,通过上面解析生成的JSON构建出Mock初始数据。 -
数据与类型判断
楼主发现构建完的初始数据,很多情况下与我们预期的不一致,比如:定义与生成的数据类型不一致、程序直接报错、空值等等。在这里需要进行初始数据的校验,校验通过后再进行入库等操作。
注意:mockData.paramValue是可以存放@name占位符|正则表达式|字符串,在步骤1与当前步骤需要对type和paramValue进行数据验证与转换
这里使用责任链模式,对每一种数据类型进行链式处理。
数据类型转换:Object paramValueConvert(String type, String paramValue)
类型判断 :Boolean dataTypeJudge(String type, Object value)
-
数据再加工
这一步主要处理初始数据中数据类型是Array、Object的,在某些情况下后多包一层 “{ }”,进而导致数据不正确。
如下图例子中生成的初始数据,root中多嵌套了一层object。
部分代码
楼主就不再详细规则代码了,有兴趣的朋友可以看一看,有需要可以扒一扒。
责任链相关
@Slf4j
@Data
public abstract class AbstractHandler {
private AbstractHandler next;
public AbstractHandler getNext() {
return this.next;
}
/**
* 判断Mock定义的数据类型与生成的数据值是否一致
*
* @param type Mock数据类型
* @param value 数据
* @return boolean
* @throws Exception 异常
*/
public abstract Boolean dataTypeJudge(String type, Object value);
/**
* 参数值转换成相应数据类型
*
* @param type Mock数据类型
* @param paramValue 参数值
* @return Object
* @throws Exception 异常
*/
public abstract Object paramValueConvert(String type, String paramValue) throws Exception;
}
public class BooleanHandler extends AbstractHandler {
@Override
public Boolean dataTypeJudge(String type, Object value) {
if (type.equals(BOOLEAN) && value instanceof Boolean) {
return true;
}
if (getNext() != null) {
return getNext().dataTypeJudge(type, value);
} else {
return false;
}
}
@Override
public Object paramValueConvert(String type, String paramValue) throws Exception {
Boolean value = null;
if (type.equals(BOOLEAN)) {
try {
if (TRUE.equalsIgnoreCase(paramValue) || FALSE.equalsIgnoreCase(paramValue)) {
value = Boolean.parseBoolean(paramValue);
}
} catch (NumberFormatException e) {
// 类型转换报错,执行下一个handler
if (getNext() != null) {
return getNext().paramValueConvert(type, paramValue);
}
}
} else {
if (getNext() != null) {
return getNext().paramValueConvert(type, paramValue);
}
}
return value;
}
}
public class DoubleHandler extends AbstractHandler {
@Override
public Boolean dataTypeJudge(String type, Object value) {
boolean flag = value instanceof BigDecimal || value instanceof Double;
if (type.equals(NUMBER) && flag) {
return true;
}
if (getNext() != null) {
return getNext().dataTypeJudge(type, value);
} else {
return false;
}
}
@Override
public Object paramValueConvert(String type, String paramValue) throws Exception {
Double value = null;
if (type.equals(NUMBER)) {
try {
value = Double.parseDouble(paramValue);
} catch (NumberFormatException e) {
// 类型转换报错,执行下一个handler
if (getNext() != null) {
return getNext().paramValueConvert(type, paramValue);
}
}
} else {
if (getNext() != null) {
return getNext().paramValueConvert(type, paramValue);
}
}
return value;
}
}
public class IntegerHandler extends AbstractHandler {
@Override
public Boolean dataTypeJudge(String type, Object value) {
boolean flag = value instanceof BigDecimal || value instanceof Integer;
if (type.equals(NUMBER) && flag) {
return true;
}
if (getNext() != null) {
return getNext().dataTypeJudge(type, value);
} else {
return false;
}
}
@Override
public Object paramValueConvert(String type, String paramValue) throws Exception {
Integer value = null;
if (type.equals(NUMBER)) {
try {
value = Integer.parseInt(paramValue);
} catch (NumberFormatException e) {
// 类型转换报错,执行下一个handler
if (getNext() != null) {
return getNext().paramValueConvert(type, paramValue);
}
}
} else {
if (getNext() != null) {
return getNext().paramValueConvert(type, paramValue);
}
}
return value;
}
}
public class LongHandler extends AbstractHandler {
@Override
public Boolean dataTypeJudge(String type, Object value) {
boolean flag = value instanceof BigDecimal || value instanceof Long;
if (type.equals(NUMBER) && flag) {
return true;
}
if (getNext() != null) {
return getNext().dataTypeJudge(type, value);
} else {
return false;
}
}
@Override
public Object paramValueConvert(String type, String paramValue) throws Exception {
Long value = null;
if (type.equals(NUMBER)) {
try {
value = Long.parseLong(paramValue);
} catch (NumberFormatException e) {
// 类型转换报错,执行下一个handler
if (getNext() != null) {
return getNext().paramValueConvert(type, paramValue);
}
}
} else {
if (getNext() != null) {
return getNext().paramValueConvert(type, paramValue);
}
}
return value;
}
}
public class ObjectHandler extends AbstractHandler {
@Override
public Boolean dataTypeJudge(String type, Object value) {
if (type.equals(OBJECT) && value instanceof Map) {
return true;
}
if (getNext() != null) {
return getNext().dataTypeJudge(type, value);
} else {
return false;
}
}
@Override
public Object paramValueConvert(String type, String paramValue) throws Exception {
// object类型参数值前端不能设置,一般为空,这里直接返回
if (type.equals(OBJECT)) {
return paramValue;
}
if (getNext() != null) {
return getNext().paramValueConvert(type, paramValue);
}
return null;
}
}
public class StringHandler extends AbstractHandler {
@Override
public Boolean dataTypeJudge(String type, Object value) {
if (type.equals(STRING) && value instanceof String) {
return true;
}
if (getNext() != null) {
return getNext().dataTypeJudge(type, value);
} else {
return false;
}
}
@Override
public Object paramValueConvert(String type, String paramValue) throws Exception {
if (type.equals(STRING)) {
// string类型不做处理,直接返回
return paramValue;
} else {
if (getNext() != null) {
return getNext().paramValueConvert(type, paramValue);
}
}
return null;
}
}
public class ArrayHandler extends AbstractHandler {
@Override
public Boolean dataTypeJudge(String type, Object value) {
if (type.equals(ARRAY) && value instanceof List) {
return true;
}
if (getNext() != null) {
return getNext().dataTypeJudge(type, value);
} else {
return false;
}
}
@Override
public Object paramValueConvert(String type, String paramValue) throws Exception {
// object类型参数值前端不能设置,一般为空,这里直接返回
if(type.equals(ARRAY)){
return paramValue;
}
if (getNext() != null) {
return getNext().paramValueConvert(type, paramValue);
}
return null;
}
}
Mock处理相关
private Object runMock(MockData mockData) {
Object root;
// 1.生成Mock数据
LinkedHashMap<String, Object> data = generateMock(mockData);
root = data.get(ROOT);
try {
// 2.将生成的数据重新整理(mock.js在生成array类型数据时,会多包一层{},需要提出来),根节点名称固定为root
return arrangeData(root);
} catch (RuntimeException e) {
throw new MockDataException("处理Mock数据结果错误");
}
}
/**
* 生成mock数据
*
* @param mockData MockData实体
* @return map
*/
@Override
public LinkedHashMap<String, Object> generateMock(MockData mockData) {
// 1.判断mock.js是否为空
if (StringUtils.isBlank(mockJsStr)) {
getMockJsFile();
}
// 节点类型关系Map
HashMap<String, String> nodeTypeMap = new HashMap<>(16);
// 1.获取mock结构信息 && 对每个节点类型统计
LinkedHashMap<String, Object> mockConfig = getMockExpression(mockData, nodeTypeMap, EMPTY);
// 2.运行MockJS
LinkedHashMap<String, Object> data = runMockJsScript(mockConfig);
// 3.生成的结果进行类型验证
validateDataType(data, nodeTypeMap);
return data;
}
/**
* 解析获取Mock语法表达式
* <p>
* 1.解析获取mock语法规则json
* 2.节点与数据类型映射,规则如下:
* {
* "root":"array",
* "root.people":"object",
* "root.people.name":"string",
* "root.people.age":"number"
* }
* 上下级之间用 "."拼接 ,同级中前端已做重复校验
*
* @param mockData MockData实体
* @return LinkedHashMap
*/
private LinkedHashMap<String, Object> getMockExpression(MockData mockData, HashMap<String, String> nodeTypeMap, String fatherPath) {
LinkedHashMap<String, Object> mockMap = new LinkedHashMap<>();
// "|" 拼接时不能有空格,否则执行结果异常
String name = mockData.getName() + (StringUtils.isNotBlank(mockData.getRule()) ? "|" + mockData.getRule() : EMPTY);
String dataType = mockData.getType();
String path = StringUtils.isNotBlank(fatherPath) ? fatherPath + SPOT + mockData.getName() : mockData.getName();
String paramValue = StringUtils.isNotBlank(mockData.getParamValue()) ? mockData.getParamValue() : EMPTY;
// 节点数据类型为object
if (dataType.equals(OBJECT)) {
LinkedHashMap<String, Object> objectMap = new LinkedHashMap<>();
List<MockData> objectItems = mockData.getObjectItems();
for (MockData item : objectItems) {
String key = item.getName() + (StringUtils.isNotBlank(item.getRule()) ? "|" + item.getRule() : EMPTY);
// 节点类型是数组,规则写1或+1,mock语法报错,这里是容错处理
boolean var1 = "1".equals(item.getRule().trim()) || "+1".equals(item.getRule().trim());
boolean var3 = item.getType().equals(ARRAY);
if (var1 && var3) {
key = item.getName();
}
LinkedHashMap<String, Object> map = getMockExpression(item, nodeTypeMap, path);
objectMap.put(key, map.get(key));
}
mockMap.put(name, objectMap);
}
// 节点数据类型为array,其子节点在配置时只能有一个,这里需要注意:
// 1.规则<1 结果:[]
// 2.规则=1 结果:{} ,正确的应该是[{}]才对,这种情况需要处理下。
// 3.规则>1 结果:[{}] ,重复相应次数
// 4.规则“+1”结果:{}, 正确的应该是[{}]才对,这种情况需要处理下。
if (dataType.equals(ARRAY)) {
// 节点类型是数组,规则写1或+1,mock语法报错,这里是容错处理
String var1 = "1";
String var2 = "+1";
if (var1.equals(mockData.getRule().trim()) || var2.equals(mockData.getRule().trim())) {
name = mockData.getName();
}
MockData arrayItems = mockData.getArrayItems();
List<Object> items = new ArrayList<>();
items.add(getMockExpression(arrayItems, nodeTypeMap, path));
mockMap.put(name, items);
}
// 节点数据类型为number
if (dataType.equals(NUMBER)) {
Object numberParam;
try {
numberParam = new MockChainOfResponsibility().getNumberChainOfMock().paramValueConvert(dataType, paramValue);
} catch (Exception e) {
throw new MockDataException("mock数据配置不正确");
}
// param为null说明参数值为非数字类型,按字符串处理
if (numberParam == null) {
// 参数值如果是传的 ""空字符串 ,而不是 "@integer" "/\d{5,10}/"等,给默认值1,不然会生成字符串类型数据
if (paramValue.length() == 0) {
mockMap.put(name, 1);
} else {
mockMap.put(name, paramValue);
}
} else {
mockMap.put(name, numberParam);
}
}
// 节点数据类型为boolean
if (dataType.equals(BOOLEAN)) {
Object booleanParam;
try {
booleanParam = new MockChainOfResponsibility().getBooleanChainOfMock().paramValueConvert(dataType, paramValue);
} catch (Exception e) {
throw new MockDataException("mock数据配置不正确");
}
// param为null说明参数值为非布尔类型,按字符串处理
if (booleanParam == null) {
mockMap.put(name, paramValue);
} else {
mockMap.put(name, booleanParam);
}
}
// 节点数据类型为string,不管参数值是什么样都按字符串处理
if (dataType.equals(STRING)) {
Object stringParam;
try {
stringParam = new MockChainOfResponsibility().getStringChainOfMock().paramValueConvert(dataType, paramValue);
} catch (Exception e) {
throw new MockDataException("mock数据配置不正确");
}
if (stringParam == null) {
mockMap.put(name, EMPTY);
} else {
mockMap.put(name, wrapRegular(paramValue));
}
}
// 添加节点数据类型映射关系
nodeTypeMap.put(path, dataType);
return mockMap;
}
/**
* 运行mock.js
*
* @return 模拟数据结果
*/
private LinkedHashMap runMockJsScript(LinkedHashMap<String, Object> mockMap) {
JavaScriptEngine scriptEngine = ScriptUtil.getJavaScriptEngine();
try {
scriptEngine.eval(mockJsStr);
String jsonString = JSON.toJSONString(mockMap);
String str = jsonString.replace("\"", "'").replace("\\\\", "\\").replace("'startRegDSM", "").replace("endRegDSM'", "");
Object eval = scriptEngine.eval("JSON.stringify( Mock.mock(" + str + " ))");
return JSON.parseObject(eval.toString(), LinkedHashMap.class, Feature.OrderedField);
} catch (ScriptException e) {
throw new MockDataException("javascript脚本错误");
}
}
/**
* 数据正确性验证
* 注:生成的数据只存在 map,list 两种结构
*
* @param data mock.js生成后的数据
* @param nodeTypeMap 节点类型映射
* @return Boolean
*/
private void validateDataType(LinkedHashMap<String, Object> data, HashMap<String, String> nodeTypeMap) {
Object rootValue = data.get(ROOT);
validateDataTypeRecursion(rootValue, nodeTypeMap, ROOT);
}
/**
* 递归验证生成的数据是否合规(类型验证)
*
* @param data js生成后结果
* @param nodeTypeMap 节点类型映射
* @param fatherName 父级节点拼接key
*/
private void validateDataTypeRecursion(Object data, HashMap<String, String> nodeTypeMap, String fatherName) {
// Map结构
if (data instanceof Map) {
LinkedHashMap<String, Object> map = JSON.parseObject(JSON.toJSONString(data), LinkedHashMap.class, Feature.OrderedField);
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
String name = fatherName + SPOT + key;
if (nodeTypeMap.containsKey(name)) {
typeJudgment(nodeTypeMap, value, name);
}
}
}
// List结构
if (data instanceof List) {
List<Object> list = (List<Object>) data;
if (list.isEmpty()) {
return;
}
// 数据类型为array,生成后的数据其子集一定是map
LinkedHashMap<String, Object> map = JSON.parseObject(JSON.toJSONString(list.get(0)), LinkedHashMap.class, Feature.OrderedField);
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
String name = fatherName + SPOT + key;
typeJudgment(nodeTypeMap, value, name);
}
}
}
/**
* validateDataTypeRecursion()方法的抽取
*/
private void typeJudgment(HashMap<String, String> nodeTypeMap, Object value, String name) {
Boolean flag;
if (nodeTypeMap.get(name) == null) {
throw new MockDataException("mock数据配置不正确:" + name);
}
// 职责链验证数据类型
flag = new MockChainOfResponsibility().getAllChainOfMock().dataTypeJudge(nodeTypeMap.get(name), value);
if (Boolean.TRUE.equals(flag)) {
validateDataTypeRecursion(value, nodeTypeMap, name);
} else {
throw new MockDataException("mock数据配置不正确:" + name);
}
}
/**
* 包装正则表达式参数值
*
* @param paramValue 正则表达式
* @return 处理后正则表达式
*/
private String wrapRegular(String paramValue) {
String sign = "/";
// 判断参数值是一个正则表达式,在其前后加上自定义标识,在执行mock.js时方便进行处理
boolean startsWith = paramValue.startsWith(sign);
boolean endsWith = paramValue.endsWith(sign);
// 以"/"开头 不以"/" 结尾说明这个正则表达式不正确
if (startsWith && !endsWith) {
throw new MockDataException("参数值:" + paramValue + ",不符合正则表达式规则");
}
if (startsWith) {
return "startRegDSM" + paramValue + "endRegDSM";
}
return paramValue;
}
/**
* 处理生成的模拟数据,节点类型为array的子级数据
*
* @param data mock生成的数据
* @return
*/
private Object arrangeData(Object data) {
// Map结构
if (data instanceof Map) {
LinkedHashMap<String, Object> map = JSON.parseObject(JSON.toJSONString(data), LinkedHashMap.class, Feature.OrderedField);
for (Map.Entry<String, Object> entry : map.entrySet()) {
Object value = entry.getValue();
if (value == null) {
continue;
}
// 向下递归
arrangeData(value);
}
data = map;
}
// List结构,集合内元素必定是Map
if (data instanceof List) {
List<Object> list = (List<Object>) data;
if (!list.isEmpty()) {
for (int i = 0; i < list.size(); i++) {
Object item = list.get(i);
LinkedHashMap<String, Object> map = JSON.parseObject(JSON.toJSONString(item), LinkedHashMap.class, Feature.OrderedField);
//此时的map只会存在一对K-V
for (Map.Entry<String, Object> entry : map.entrySet()) {
Object value = entry.getValue();
list.set(i, value);
// 向下递归
arrangeData(value);
}
}
}
}
return data;
}
更多推荐