您当前的位置:易学堂 > 运维教程

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":"金牛座双鱼座处女座"}
} 
标签: Java