前言
平时开发项目时,总会写很多crud代码,开发过程基本一个套路,定义controller、service、dao、mapper、dto,感觉一直在repeat yourself
也接触过很多快速开发框架,定义一个sql就可以生成接口,或者定义一个框架脚本自动生成接口,但感觉这些框架没有说太成熟广泛使用的,出了问题也很难解决
本文重点研究一下如何只通过定义sql就自动生成接口,但是只是简单实现,为提供思路,毕竟真的实现高可用性工作量很大
思路
再实现之前,首先屡清一下思路,使用springboot+swagger2, 大概分为以下5个步骤
- 数据源信息的配置及测试连接
url,用户名,密码等信息 - 自定义接口信息的配置
路径,请求方式,参数,使用数据源, sql脚本等信息 - 注册spring接口
需按自定义的接口信息动态生成一个spring访问路径 - 执行sql并返回
接口请求时,执行自定义接口设置的sql脚本,并将结果返回json - 注册swgger2接口(这一步也可以不要)
把自定义的接口发布到swagger2文档中
实现
思路研究好,开始实现
数据源
作为一个低代码平台,我们希望数据源(即数据库)是可配的,并且不同的接口可以访问不同的数据源
在维护一个数据源表,主要字段如下
public class Source {
/**
* 数据源key
*/
private String key;
/**
* 数据源名称
*/
private String name;
/**
* 类型
*/
private DbTypeEnum type;
/**
* jdbc URL
*/
private String url;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
}
其中DbType我做的简单一点,只支持mysql和orcale
public enum DbTypeEnum {
MYSQL(0, "MYSQL"),
ORACLE(1, "ORACLE"),
}
而URL使用的是jdbc url这样通用性比较强且简单,客户端填写如:
jdbc:mysql://192.0.0.1:3306/test?characterEncoding=UTF8
代码就是简单的crud+测试连接
测试连接由于需要两种数据库的驱动,引入maven依赖
com.oracle
ojdbc6
mysql
mysql-connector-java
测试连接的代码如下
try {
Connection conn = DriverManager.getConnection(url, username, password);
} catch(Exception e) {
// 连接出错
} finally {
connection.close()
}
jdk的DriverManager会自动去找适合的驱动并连接(使用spi)
接口
接下来就是数据接口的管理,支持增删查改和发布
public class Api {
@TableId("ID")
private Long id;
@ApiModelProperty(value = "接口名称")
private String name;
@ApiModelProperty(value = "路径")
private String path;
@ApiModelProperty(value = "数据源key")
private String sourceKey;
@ApiModelProperty(value = "操作类型")
private OpTypeEnum method;
@ApiModelProperty(value = "sql脚本")
private String sql;
}
其中sourceKey
为数据源的key, path
即为接口发布的路径, method
即“GET/POST/PUT/DELETE”, sql
即执行的sq脚本
注册spring接口
比如我们通过客户端新增了一个接口,路径为/user,怎么能让该路径真实可访问,不可能用户没新增一个接口我们就写个@RequestMapping("/user")吧,那样太笨拙了
可以想一下spring是如何注册接口,平时开发springboot,写一个@RequestMapping("/xxx"),springboot启动时会扫描该注解,并获取路径进行注册,此时通过/xxx就可以访问,那么我们只需要找到这个注册器,创建自定义接口时手动注册即可
经查找,spring的web路径注册器就是RequestMappingHandlerMapping
,并且也是在spring容器中,它的主要方法
void registerMapping(RequestMappingInfo mapping,
Object handler, Method method)
// mapping 即路径信息,包含请求的Method等
// handler 即注册该路径发起请求时处理的对象
// method 即执行该对象的具体方法
因此我们向spring注册路径信息时,需要告知spring该请求出现时执行的对象和方法
此时我们写一个动态注册器,把Api注册到RequestMappingHandlerMapping
,实现如下
@Component
public class RequestDynamicRegistry {
/**
* spring 注册器
*/
@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
/**
* 请求到来的处理者
*/
@Autowired
private RequestHandler requestHandler;
/**
* 请求到来的处理者方法
*/
private final Method method = RequestHandler.class.getDeclaredMethod("invoke", HttpServletRequest.class, HttpServletResponse.class, Map.class, Map.class, Map.class);
/**
* 已缓存的映射信息
*/
private final Map apiCaches = new ConcurrentHashMap<>();
public RequestDynamicRegistry() throws NoSuchMethodException {
}
/**
* 转换为spring所需路径信息
* @param api
* @return
*/
private RequestMappingInfo toRequestMappingInfo(Api api) {
return RequestMappingInfo.paths(api.getPath())
.methods(RequestMethod.valueOf(api.getOpType().name().toUpperCase()))
.build();
}
/**
* 把api注册到spring
* @param api
* @return
*/
public boolean register(Api api) {
// 准备参数 RequestMappingInfo
RequestMappingInfo requestMappingInfo = toRequestMappingInfo(api);
if (requestMappingHandlerMapping.getHandlerMethods().containsKey(requestMappingInfo)) {
throw new BusinessException("接口冲突,无法注册");
}
// 注册到spring web
requestMappingHandlerMapping.registerMapping(requestMappingInfo, requestHandler, method);
// 添加缓存
apiCaches.put(api.getKey(), api);
return true;
}
/**
* 取消api在spring的注册
* @param api
* @return
*/
public boolean unregister(Api api) {
// 准备参数 RequestMappingInfo
RequestMappingInfo requestMappingInfo = toRequestMappingInfo(api);
// 注册到spring web
requestMappingHandlerMapping.unregisterMapping(requestMappingInfo);
// 移除缓存
apiCaches.remove(api.getKey());
return true;
}
/**
* 获取所有缓存的api信息
* @return
*/
public List apiCaches() {
return this.apiCaches.values().stream().collect(Collectors.toList());
}
/**
* 根据http请求获取缓存的api信息,以便请求出现时按api设置执行方法
* @param request
* @return
*/
public Api getApiFromReqeust(HttpServletRequest request) {
String mappingKey = Objects.toString(request.getMethod(), "GET").toUpperCase() + ":" + request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
return apiCaches.get(mappingKey);
}
}
以上就实现了一个动态按Api信息注册到spring请求匹配的方法,并把所有的Api请求发起的处理者指向了RequestHandler对象的invoke
方法,这也是我们自定义的处理器,定义如下
@Component
@Slf4j
public class RequestHandler {
/**
** 动态api注册器
**/
@Autowired
private RequestDynamicRegistry requestDynamicRegistry;
/**
* 自定义接口实际执行入口
* 参数都是spring自动塞进来的请求信息
*/
@ResponseBody
public CommonResult
此时我们定义一个Api对象(GET 请求),并使用动态注册器RequestDynamicRegistry
注册后,浏览器访问改路径,即可返回"ok"
执行sql并返回
接口搭建起来了,下面就是具体执行了,上面RequestHandler已经获取到Api信息了,再获取sql执行即可
@Component
@Slf4j
public class RequestHandler {
@Autowired
private RequestDynamicRegistry requestDynamicRegistry;
@Autowired
private SourceService sourceService;
/**
* 自定义接口实际执行入口
*/
@ResponseBody
public CommonResult
到此
一个配置sql后自动生成接口的低代码平台就搭建完了,只是个超简版,省略了很多功能,如参数处理、分页处理、使用数据库连接池等,这些功能一点点加就可以了
接口文档
自动生成接口实现了,但是如果没有接口文档还是很难用,所以结合Swagger2再实现一下自动接口文档
这里代码比较多,也不太熟悉,就不介绍了,主要参照了magic-api的实现,可以自行参考magic-api-plugin-swagger,主要是通过自定义SwaggerResourcesProvider
来把所有Api对象信息注册给swagger中
最后结果如下