Java后端实现Mock模拟数据详细方法教程
时间:2022-05-24 10:18:20
我们在开发的时候经常遇到第三方接口还没完成的情况,或者需要向数据库插入各种测试数据。此时,如果数量级少还可以写几行代码满足,但数量级大或数据结构复杂就很麻烦了。对比过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":"金牛座双鱼座处女座"}
}