以前经常和前端对接接口,一般我们都是用 swagger + knife4j 来生成当前的可用接口文档,然后前端根据文档描述的数据结构来调整页面信息。这种方式看起来还是挺不错的,但是也只是完成了数据结构的对接,接口调用仍然可能存在问题,并且前端也会需要很多的数据来展示,这就需要前端在实际接入时作很多的调整。
所以我一直都想做一个能让对接更方便的工具,更像swagger一样自动更新接口信息,也能让前端直接访问生成虚拟数据。这样前端就可以像真实对接一样直接调用,具体使用虚拟数据还是真逻辑就交由后端控制,可以加快前后端对接速度,前后端分离更明显。
这里我已经写好了这个组件 mockapi: mock data with spring boot ,可以直接使用,这里就介绍一下我的开发过程,各位大佬请随意提出建议或意见。
设计
首先,最重要的就是动态接口,也就是需要实现以下两点:
- 根据已有的接口,生成对应的虚拟数据接口,用给前端调用
- 能手动控制生成的虚拟数据接口,方便切换真假接口
也就是说,前端在调用某一个接口时,返回的数据是虚拟数据还是真数据都必须是无感的。这点其实很好处理,使用SpringBoot的RequestMappingHandlerMapping就可以动态地管理服务接口,我们就可以从中提取已有地所有接口信息,然后重新生成一份我们需要的虚拟数据接口替换进去。
虚拟数据生成
接口注册倒是很方便,但是数据生成就需要考虑数据结构和数据内容了。
这里我从requestMappingHandlerMapping.getHandlerMethods()中获取所有注册的接口信息,然后拿其中的HandlerMethod就可以拿到接口方法对象,那么也就有了接口返回值的数据结构。利用MockData直接对方法对象的返回值类型生成一个虚拟数据就完成了虚拟数据的生成。
此时将上面的生成逻辑放在一个特定的类方法中,再将这个方法替换到RequestMappingHandlerMapping中来代替原有的接口,这样前端访问这个接口时就会拿到生成的虚拟数据了。
并且由于我用的是RequestMappingHandlerMapping,这表示在swagger中也能看到这个接口信息,这样接口文档仍旧可以使用swagger。
控制开关
因为当某个接口开发完毕后,我希望前端能调用实际的业务逻辑而不是虚拟接口,所以要对接口作标注,那么注解自然就是最方便的方式。
这里我定义了一个@MockApi,只要有这个注解的类或是方法,就会使用虚拟接口。当接口开发好之后,只需要去掉注解,就不会调用虚拟接口了。
数据规范
因为MockData只会生成随机数据,所以为了能让前端的数据看起来正常,我就需要更改MockData的配置。我选择适配数据池,直接通过配置文件的方式来生成规范数据。具体代码可以看这里:YamlDataPool.java(数据池要使用mockapi-mock依赖)
这样我就可以通过这样的配置来控制生成的数据内容:
mockapi:
enabled: true # 开启或关闭MockApi功能,默认true
path-strategy: ignored # 地址重复策略,允许的值有:ignored - 忽略;replace - 使用新的替换(默认值)
data:
enabled: true # 开启或关闭数据池注入,默认true
pool:
- types: int # 数据类型类全名,多个使用英文逗号隔开。基础类型与java.lang包下可填写简称,没有则默认String
names: code # 数据绑定属性名正则,多个使用英文逗号隔开
values: "200" # 数据池,多个使用英文逗号隔开
- types: int
names: age
values: "18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 ,33, 34 ,35"
- names: name, nickname
values: "小明, 小红, 小刚, 小牛, 小羊, 小猪, 小狗, 小猫"
- belongs: idea.verlif.mockapi.global.domain.Favorite # 生效的类,表示此数据池仅在idea.verlif.mockapi.global.domain.Favorite类下生效,多个使用英文逗号隔开
names: name
values: "苹果, 西瓜, 梨子"
- belongs: idea.verlif.mockapi.global.domain.Favorite
enabled: false # 是否开启此数据池,默认true
types: String
names: type
values: "水果"
总结
这里面最麻烦的就是泛型处理了,不能直接用Method.getResultType来拿类型,而是要通过一个复杂的过程把泛型还原:
MethodGrc methodGrc;
try {
methodGrc = MethodUtil.getMethodGrc(pack.getOldMethod());
} catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) {
throw new RuntimeException(e);
}
ClassGrc result = methodGrc.getResult();
if (result.getTarget() == void.class) {
return null;
}
MockDataConfig config = mockDataConfigCollector.getMockDataConfig(item.getData());
if (config != null) {
return mockDataCreator.mock(result, config);
} else {
return mockDataCreator.mock(result);
}