开发http接口只需要写SQL就可以了?magic-api:是的

项目背景

在项目开发中,通常是由后端写好接口,前端调用的方式进行开发,而后端开发中,常常需要写Controller,Service,Dao,等一系列方法,而这些方法中,很多都是写一行代码调用,最后返回JSON给前端,而在我们系统开发中,写的比较多的一般都是在SQL(这里不讨论ORM框架)、和前端,其它的都是一写很啰嗦很繁琐的东西,于是我就想着能不能简化这些开发,只写SQL就可以了,经过一番思考,最终还是想到了解决方案

预期想法

采用类似MybatisXML方式定义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-mappingrequest-methodpagevalidate这些属性,从字面意思上也很好理解,分别是请求路径、请求方法、开启分页以及验证。这是最基本的一个查询,如果改用JSON或者YML或者自定义格式,你会发现ifforeach都是一个头疼的问题。设计出来的学习成本肯定会比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,也可以加入交流群讨论)

你可能感兴趣的:(java,http,sql,springboot)