项目背景
在项目开发中,通常是由后端写好接口,前端调用的方式进行开发,而后端开发中,常常需要写Controller
,Service
,Dao
,等一系列方法,而这些方法中,很多都是写一行代码调用,最后返回JSON
给前端,而在我们系统开发中,写的比较多的一般都是在SQL(这里不讨论ORM
框架)、和前端,其它的都是一写很啰嗦很繁琐的东西,于是我就想着能不能简化这些开发,只写SQL
就可以了,经过一番思考,最终还是想到了解决方案
预期想法
采用类似Mybatis
的XML
方式定义SQL
语句和请求路径
以及其他信息、解析XML、注册HTTP接口、执行的简单方法来实现。
为什么使用XML而不用其他格式
我们先看一下XML
的写法
username最小长度为6
select
from sys_user
where 1=1
and username like concat('%',#{username},'%')
and role_id in
#{roleId}
order by create_date desc
首先格式上选择了与Mybatis基本一致的写法,好理解,额外的我们发现有request-mapping
、request-method
、page
、validate
这些属性,从字面意思上也很好理解,分别是请求路径、请求方法、开启分页以及验证。这是最基本的一个查询,如果改用JSON
或者YML
或者自定义格式,你会发现if
和foreach
都是一个头疼的问题。设计出来的学习成本肯定会比XML
高,因为XML
的方式跟Mybatis
的方式很像,学习成本很低
代码实现
项目启动之后解析XML文件
// 代码节选自org.ssssssss.magicapi.utils.XmlFileLoader
// 提取所有符合表达式的XML文件
Resource[] resources = resourceResolver.getResources(pattern);
for (Resource resource : resources) {
File file = resource.getFile();
// 获取上次修改时间
Long lastModified = fileMap.get(resource.getDescription());
// 修改缓存
fileMap.put(resource.getDescription(), file.lastModified());
// 判断是否更新
if (lastModified == null || lastModified < file.lastModified()) {
XMLStatement xmlStatement = S8XMLFileParser.parse(file);
// 注册HTTP接口
xmlStatement.getStatements().forEach(configuration::addStatement);
}
}
通过RequestMappingHandlerMapping的registerMapping方法注册接口
/**
* 代码节选自org.ssssssss.magicapi.session.Configuration
* 注册Statement成接口,当已存在时,刷新其配置
*/
public void addStatement(Statement statement) {
RequestMappingInfo requestMappingInfo = getRequestMappingInfo(statement);
if (StringUtils.isNotBlank(statement.getId())) {
// 设置ID与statement的映射
statementIdMap.put(statement.getId(), statement);
}
if (requestMappingInfo == null) {
return;
}
// 如果已经注册过,则先取消注册(主要是热更新)
if (statementMappingMap.containsKey(statement.getRequestMapping())) {
logger.debug("刷新接口:{}", statement.getRequestMapping());
// 取消注册
requestMappingHandlerMapping.unregisterMapping(requestMappingInfo);
}else{
logger.debug("注册接口:{}", statement.getRequestMapping());
}
// 添加至缓存
statementMappingMap.put(statement.getRequestMapping(), statement);
// 注册接口
requestMappingHandlerMapping.registerMapping(requestMappingInfo,requestHandler,statement.isRequestBody() ? requestWithRequestBodyHandleMethod : requestHandleMethod);
}
至此已经可以把XML中写的配置给映射出HTTP接口
请求处理
/**
* 代码节选自org.ssssssss.magicapi.executor.RequestExecutor
* http请求入口
* @param request
* @return
*/
@ResponseBody
public Object invoke(HttpServletRequest request) {
return invoke(request, null);
}
/**
* http请求入口(带RequestBody)
*/
@ResponseBody
public Object invoke(HttpServletRequest request, @RequestBody(required = false) Object requestBody) {
try {
// 创建RequestContex对象,供后续使用
RequestContext requestContext = new RequestContext(request, expressionEngine);
if (!requestContext.containsKey("body")) {
requestContext.setRequestBody(requestBody);
}
Statement statement = configuration.getStatement(requestContext.getRequestMapping());
requestContext.setStatement(statement);
// 执行前置拦截器
for (RequestInterceptor requestInterceptor : requestInterceptors) {
Object value = requestInterceptor.preHandle(requestContext);
if (value != null) {
return value;
}
}
// 执行校验
Object value = validate(statement, requestContext);
if (value != null) {
return value;
}
// 执行语句
value = new JsonBean<>(statementExecutor.execute(statement, requestContext));
// 执行后置拦截器
for (RequestInterceptor requestInterceptor : requestInterceptors) {
Object target = requestInterceptor.postHandle(requestContext, value);
if (target != null) {
return target;
}
}
return value;
} catch (Exception e) {
if (configuration.isThrowException()) {
throw new MagicAPIException("magic-api执行出错", e);
}
logger.error("系统出现错误", e);
return new JsonBean<>(-1, e.getMessage());
}
}
至此,整个流程基本结束。
最后有额外的实现了spring-boot-starter
,进一步简化配置与开发
使用方式
maven引入
org.ssssssss
magic-api-spring-boot-starter
0.1.1
修改application.properties
server.port=9999
#配置magic-api的xml所在位置
magic-api.xml-locations: classpath*:magic-api/*.xml
#以下配置需跟实际情况修改
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=root
spring.datasource.password=123456789
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
创建XML
在src/main/resources/magic-api/
下建立user.xml
文件
select username,password from sys_user
测试
访问http://localhost:9999/user/list
结果如下:
{
"code": 1,
"message": "success",
"data": {
"total": 2,
"list": [{
"password": "123456",
"username": "admin"
}, {
"password": "1234567",
"username": "1234567"
}]
},
"timestamp": 1588586539249
}
结语
目前还有几处需要优化和改进,也欢迎提出意见和建议(可以在评论区留言,也可以在gitee/github上提issues,也可以加入交流群讨论)
- 支持存储过程(待改进)
- 增加可视化操作界面(待改进)
- 单表自动映射CRUD接口(待改进)
- 文档地址:http://ssssssss.org
- Gitee:https://gitee.com/ssssssss-team/magic-api
- Github:https://github.com/ssssssss-team/magic-api