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;

}
主要处理流程
  1. 解析
    在接受前端穿来的参数后,需要递归MockData实体类逐层解析,生成Mock.js 语法规则的JSON。

  2. 构建
    启动JS引擎加载Mock.js,通过上面解析生成的JSON构建出Mock初始数据。

  3. 数据与类型判断
    楼主发现构建完的初始数据,很多情况下与我们预期的不一致,比如:定义与生成的数据类型不一致、程序直接报错、空值等等。在这里需要进行初始数据的校验,校验通过后再进行入库等操作。
    注意:mockData.paramValue是可以存放@name占位符|正则表达式|字符串,在步骤1与当前步骤需要对type和paramValue进行数据验证与转换
    这里使用责任链模式,对每一种数据类型进行链式处理。
    在这里插入图片描述
    请添加图片描述
    在这里插入图片描述
    数据类型转换:Object paramValueConvert(String type, String paramValue)
    在这里插入图片描述
    类型判断 :Boolean dataTypeJudge(String type, Object value)
    在这里插入图片描述

  4. 数据再加工
    这一步主要处理初始数据中数据类型是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;
    }
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐