69 Commits
Author SHA1 Message Date
蓝白 299bec29e0 更新版本至4.0 2026-01-18 15:58:26 +08:00
蓝白 86bb9c2f2a 更新版本至4.0 2026-01-18 15:56:05 +08:00
蓝白 121b6653ab 更新版本至4.0 2026-01-18 15:45:01 +08:00
蓝白 9fc3990411 更新描述 2026-01-13 23:03:46 +08:00
蓝白 7a53390946 更新描述 2026-01-04 16:47:29 +08:00
蓝白 2e8a930b16 修复ck链接问题 2025-12-30 14:32:01 +08:00
蓝白 444c288079 更新描述 2025-12-26 13:04:25 +08:00
蓝白 669c8261f5 修复数据库类型图标匹配问题 2025-12-25 22:21:57 +08:00
蓝白 2b752f56e1 更新驱动 2025-12-25 11:20:36 +08:00
蓝白 9b8858999c 优化查询返回字段排序问题 2025-12-25 11:15:11 +08:00
蓝白 548919485c 更新页面 2025-12-24 15:43:18 +08:00
蓝白 464ee5c738 更新驱动 2025-12-24 15:39:43 +08:00
蓝白 623599998f 更新描述 2025-10-27 23:52:50 +08:00
蓝白 37ba418f87 更新描述 2025-10-18 10:51:49 +08:00
蓝白 affcfb9234 更新描述 2025-10-13 16:01:52 +08:00
蓝白 add6362fb8 更新jdk环境 2025-10-01 21:09:57 +08:00
蓝白 ea85ff64e1 更新版本3.5.2 2025-10-01 11:04:35 +08:00
蓝白 f5ff5e4f54 更新版本3.5.2 2025-10-01 11:02:05 +08:00
蓝白 2183f2d2f6 更新版本3.5.2 2025-10-01 10:55:47 +08:00
蓝白 4e477d86c3 更新版本3.5.2 2025-10-01 10:40:07 +08:00
蓝白 284eb0394d 优化页面参数设置和样式图标 2025-10-01 10:39:06 +08:00
蓝白 8e56270cf5 1、接口日志新增响应结果
2、优化接口加载
3、优化异常处理
4、优化日志查询
2025-10-01 10:04:28 +08:00
蓝白 6b0c4bea49 版本更新至3.5.1 2025-09-30 20:05:18 +08:00
蓝白 9edd1c3e00 优化请求参数和优化测试bug 2025-09-30 20:04:53 +08:00
蓝白 3f0cf9af78 优化接口测试和token有效期 2025-09-30 19:51:07 +08:00
蓝白 ad8636ced0 更新描述 2025-09-25 10:47:18 +08:00
蓝白 03486448f1 升级达梦数据库驱动 2025-09-12 22:48:05 +08:00
蓝白 5925263443 更新描述 2025-07-16 12:30:00 +08:00
蓝白 dc7cc0bd79 跳转字段类型 2025-07-16 12:29:20 +08:00
蓝白 a805e90911 版本更新至3.5.0 2025-06-04 10:47:48 +08:00
蓝白 9225973cf8 spring boot更新至3.5.0 2025-06-04 10:47:31 +08:00
蓝白 3243a4b4e3 优化页面 2025-06-04 10:46:53 +08:00
蓝白 fd45c67416 更换本地线程对象 2025-06-04 10:34:40 +08:00
蓝白 75025818f5 appkey认证替换appcode 2025-06-04 10:34:17 +08:00
蓝白 0e954b2d1d 修复code认证问题 2025-05-21 18:55:51 +08:00
蓝白 07c840a55b 优化数据库加载 2025-04-21 09:21:57 +08:00
蓝白 55b7ffcb70 更新版本 2025-04-16 22:23:11 +08:00
蓝白 4594373799 签名认证修改为国密SM3加密 2025-04-16 22:11:32 +08:00
蓝白 df38332a7f 更新crabc-spring-boot-starter 2025-04-16 21:46:46 +08:00
蓝白 ab4aa2f493 更新描述 2025-04-14 00:54:53 +08:00
蓝白 863ca9120c 更新描述 2025-04-14 00:50:13 +08:00
蓝白 ebeca48061 更新版本3.3.1 2025-04-08 12:46:29 +08:00
蓝白 7c21c9ceb4 pom文件添加mybatis-puls依赖 2025-04-08 12:43:44 +08:00
蓝白 49393ec3aa 优化页面,请求参数支持手动添加 2025-04-08 12:42:46 +08:00
蓝白 2cf2f7be5c 更新描述 2025-02-18 23:30:49 +08:00
蓝白 02b79a1060 更新描述 2025-02-18 23:28:14 +08:00
蓝白 4bfe209ec0 修复打包问题 2025-02-18 00:55:15 +08:00
蓝白 a222ecd50d 更新描述 2025-02-07 19:48:41 +08:00
蓝白 bdc48ac634 修复-parameters问题 2025-01-07 22:58:01 +08:00
蓝白 3947e6eca3 更新描述 2024-12-30 12:24:16 +08:00
蓝白 b4dccc642c 更新3.3.0版本 2024-12-30 12:19:16 +08:00
蓝白 7747c1b4bc 升级jar包 2024-12-30 12:15:26 +08:00
蓝白 46a0398a98 优化整体代码逻辑 2024-12-30 12:15:07 +08:00
蓝白 c129fa6191 更新描述 2024-12-04 11:27:04 +08:00
蓝白 aebe1b8cbb 更新dockerfile 2024-12-03 13:34:44 +08:00
蓝白 91e8dcb740 更新描述 2024-11-26 18:59:10 +08:00
蓝白 c26a528e8c 更新描述 2024-11-08 14:11:43 +08:00
蓝白 96fcf8d447 3.2.2版本 2024-11-08 13:59:47 +08:00
蓝白 0773c9275c 优化页面 2024-11-08 13:53:06 +08:00
蓝白 907765831a 测试功能query参数类型支持传集合 2024-11-08 12:18:11 +08:00
蓝白 d99350bdfb query参数类型支持传集合 2024-11-08 12:17:21 +08:00
蓝白 79bfc632a8 新增发布接口删除功能 2024-11-08 12:16:47 +08:00
蓝白 18f204936d 更新描述 2024-10-03 23:45:43 +08:00
蓝白 874e2fd109 更新版本 2024-10-03 23:44:32 +08:00
蓝白 0448572932 新增数据源 2024-10-03 23:41:40 +08:00
蓝白 e4185aea04 优化代码 2024-10-03 23:39:11 +08:00
蓝白 d8b5d0d345 引入lombok替换get/set 2024-09-28 00:52:06 +08:00
蓝白 f8cce152bc 更新页面 2024-09-03 23:27:48 +08:00
蓝白 50debdd64b 更新描述 2024-09-02 22:49:26 +08:00
286 changed files with 7562 additions and 4390 deletions

No files matched your search

-1
View File
@@ -45,7 +45,6 @@ buildNumber.properties
####################### svn ##############################
.svn
####################### eclipse ##############################
.project
.settings
+6 -7
View File
@@ -1,11 +1,10 @@
FROM openjdk:8-jdk-alpine
MAINTAINER crabc <creabc@qq.com>
FROM eclipse-temurin:21-jdk-alpine
LABEL version="4.0.0"
ADD crabc-admin.jar /app.jar
COPY crabc-admin.jar /app.jar
ENV LANG="en_US.UTF-8"
EXPOSE 9377
ENV db_url ${db_url}
ENV db_user ${db_user}
ENV db_pwd ${db_pwd}
CMD java -jar /app.jar --spring.datasource.url=${db_url} --spring.datasource.username=${db_user} --spring.datasource.password=${db_pwd}
ENV DB_URL=${db_url} DB_USER=${db_user} DB_PWD=${db_pwd}
CMD ["java", "-jar", "/app.jar", "--spring.datasource.url=${DB_URL}", "--spring.datasource.username=${DB_USER}", "--spring.datasource.password=${DB_PWD}"]
+89 -30
View File
@@ -1,19 +1,25 @@
## 介绍
**Crabc** 是低代码接口开发平台,企业级API管理系统,深度整合SpringBoot和Mybatis实现动态数据源和动态SQL。
支持接入(mysql、oracle、postgresql、sqlserver、达梦、TiDB、es)等SQL或/NoSQL数据源,
在编辑框内编写好SQL后即可快速生成Rest接口对外提供服务。支持Mybatis中if等标签语法、数据脱敏、
以及复杂的多SQL执行并支持事务, 减少通用接口的SQL编写,让开发人员专注更复杂的业务逻辑实现。可通过插件的
方式扩展支持其他的数据源。
# 由来
crabc由create + abc 组合而来,意在为项目系统构建的通用的接口底座。
# 介绍
**crabc** 是接口快速开发平台,企业数据服务平台,sql2Api,深度整合SpringBoot和Mybatis实现动态数据源和动态SQL、函数和存储过程。
支持接入(mysql、oracle、postgresql、sqlserver、达梦、TiDB、es和hive)等SQL或/NoSQL数据源,
在线可视化编写SQL后即可快速生成接口对外提供服务,接口一键上下线。减少通用接口的SQL编写,让开发人员专注更复杂的业务逻辑实现。
支持Mybatis中if等标签语法、数据脱敏、数据转换、国密加密、协同开发等功能,可集成微服务网关支持接入第三方接口、权限认证、限流、缓存、告警监控和统一日志等一站式API数据服务,
助力于企业数据资产价值升级,成为企业数字化转型的重要推动力。
![img.png](doc/home.png)
## 功能
- **工作台**:可视化编写SQL快速的对外发布成API接口,支持多SQL同时执行、Mybatis标签语法,实现动态SQL和动态标签
- **数据源管理**:动态加载数据库,支持多种SQL/NoSQL数据库。
- **应用列表**:Code认证、签名认证的应用创建管理以及对API调用在线授权。
- **应用列表**:AppKey认证、签名认证的应用创建管理以及对API调用在线授权。
- **接口列表**:查看开发中和已发布的API接口,可进行上下线管理,编辑升级等
- **接口文档**:生成在线接口文档,支持在线测试。
- **接口日志**:查看发布的接口被调用日志列表和请求详情。
- **流控规则**:限流、缓存等。
## 接口开发流程
![img.png](doc/dev_flow.png)
## 模块
~~~
cn.crabc
@@ -27,10 +33,19 @@ cn.crabc
├── db // SQL脚本
├──pom.xml // 依赖
~~~
## 数据库脚本
数据库脚本文件可加QQ群文件中获取
## 版本说明
dev分支框架SpringBoot已升级至3.x,jdk需17及以上版本 \
dev分支框架SpringBoot已升级至3.5,jdk需17及以上版本 \
2.x分支框架SpringBoot还是2.x
## 企业版
https://pingapi.cn
## 企业版架构图
## 运行启动
```
1、先执行db/dml.sql脚本,创建库表和初始化数据
@@ -48,9 +63,6 @@ github:https://github.com/pingapi/crabc-api
已申请软件著作,开源版仅供个人学习使用 \
企业版功能更强大,商用请加群联系\
## 企业版和开源版对比
功能对比清单:https://www.crabc.cn/business/
## 源码地址
github: https://github.com/pingapi/crabc-api \
gitee: https://gitee.com/linebyte/crabc
@@ -61,7 +73,7 @@ gitee: https://gitee.com/linebyte/crabc
<dependency>
<groupId>cn.crabc</groupId>
<artifactId>crabc-spring-boot-starter</artifactId>
<version>3.1.0</version>
<version>4.0.0</version>
</dependency>
```
在程序启动类中添加下面注解
@@ -72,13 +84,24 @@ gitee: https://gitee.com/linebyte/crabc
## Docker启动
先执行db/dml.sql脚本,创建库表和初始化数据,建议mysql 8.0+版本
X86/AMD架构
```
docker pull crabc/crabc-admin:latest
docker run -p 9377:9377 --env db_url=jdbc连接(如:jdbc:mysql://localhost:3306/crabc) --env db_user=数据库用户 --env db_pwd=数据库密码 -d --name crabc-admin crabc/crabc-admin:latest
docker run -p 9377:9377 -e DB_URL=jdbc:mysql://localhost:3306/crabc -e DB_USER=root -e DB_PWD=root -d --name crabc-api crabc/crabc-api:latest
```
ARM架构
```
docker run -p 9377:9377 -e DB_URL=jdbc:mysql://localhost:3306/crabc -e DB_USER=root -e DB_PWD=root -d --name crabc-admin crabc/crabc-admin:latest
```
访问地址:http://127.0.0.1:9377
账号密码:admin/admin123
_## Windows桌面运行
先执行db/dml.sql脚本,创建库表和初始化数据,建议mysql 8.0+版本
```
java -jar crabc-api.jar --spring.datasource.url=jdbc:mysql://127.0.0.1:3306/crabc --spring.datasource.username=root --spring.datasource.password=root
```
## 效果截图
### 接口开发
![img.png](doc/sql.png)
@@ -88,27 +111,63 @@ docker run -p 9377:9377 --env db_url=jdbc连接(如:jdbc:mysql://localhost:330
![img.png](doc/param.png)
#### 在线预览
![img_2.png](doc/img_test.png)
#### 接口列表
![img.png](doc/api.png)
#### 接口列表
![img.png](doc/api.png)
#### 数据库类型
![img.png](doc/db.png)
### 限流熔断
![img.png](doc/flow.png)
## 交流群
**进群前请先点【Star】谢谢**
QQ群(请先Star):748993036
商用咨询加微信 (请备注crabc)
![img.png](doc/img.png)
## 产品使用登记
以下为部分接入crabc-api的用户 ,<a href="https://gitee.com/linebyte/crabc/issues/IA98CA" target="_blank"> 点击进行登记</a>
| 公司机构 |
|:-------------------|
| 联通集团西部分公司 |
| 青岛巴士科技有限公司 |
| 阳光智维科技股份有限公司 |
| 一步数据科技有限公司 |
| 成都像素云图科技有限公司 |
| 公司机构 |
|:-------------|
| 西部通信研究院 |
| 青岛巴士科技有限公司 |
| 阳光智维科技股份有限公司 |
| 一步数据科技有限公司 |
| 像素云图科技有限公司 |
| 前景科技有限公司 |
| 方寸图信息有限公司 |
| 鹭栖技术信息公司 |
| 循脉医疗科技有限公司 |
| 云上贵州大数据有限公司 |
| 巨元瀚洋有限公司 |
## 交流群
**进群前请先点 Star**
加微信进群 (备注crabc)
![img.png](doc/img.png)
## 企业版和开源版对比
| 功能 | 社区版 | 企业版 |
|--------|------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
| 架构设计 | 单体 | 微服务 |
| 接口创建模式 | SQL脚本 | SQL脚本、图表、接口转发 |
| 动态标签 | 支持 | 支持 |
| 动态路由 | 不支持 | 支持 |
| 数据库 | SQL:mysql/oracle/postgresql/sqlserver <br>TiDB/OpenGauss/Doris | SQL:mysql/oracle/postgresql/sqlserver/tidb<br>/opengauss/doris/oceanbase/dm/gbase <br> NoSQL:es/mongodb <br> TSDB: TDengine |
| 认证方式 | 无/Code认证/签名认证 | 无/Code认证/签名认证 |
| SQL类型 | 查询SQL | 查询/插入/更新/删除等SQL |
| 结果类型 | json | json/Excel |
| 接口日志 | 支持 | 支持 |
| 数据模型 | 不支持 | 支持 |
| 协同开发 | 不支持 | 支持 |
| 结果转换 | 不支持 | 支持 |
| 接口市场 | 不支持 | 支持 |
| 申请审批 | 不支持 | 支持 |
| 接口授权 | 不支持 | 支持 |
| 系统权限 | 不支持 | 支持 |
| 用户管理 | 不支持 | 支持 |
| 数据脱敏 | 不支持 | 支持 |
| 版本管理 | 不支持 | 支持
| 接口缓存 | 不支持 | 支持 |
| 限流熔断 | 不支持 | 支持 |
| IP控制 | 不支持 | 支持 |
| 监控报表 | 支持 | 支持 |
| 集群模式 | 不支持 | 支持 |
@@ -5,14 +5,14 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.crabc</groupId>
<artifactId>crabc-boot</artifactId>
<artifactId>crabc-api</artifactId>
<version>${revision}</version>
</parent>
<artifactId>crabc-admin</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
@@ -37,6 +37,16 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -13,7 +13,7 @@ spring:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://127.0.0.1:3306/crabc?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: root
password:
# mybatis配置
mybatis:
# 搜索指定包别名
@@ -25,14 +25,4 @@ mybatis:
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
pagehelper:
autoRuntimeDialect: true # 开启多数据源分页识别
# es健康检查
management:
health:
elasticsearch:
enabled: false
# 签名认证过期时间
crabc:
auth:
expiresTime: 10
autoRuntimeDialect: true # 开启多数据源分页识别
@@ -3,7 +3,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>crabc-boot</artifactId>
<artifactId>crabc-api</artifactId>
<groupId>cn.crabc</groupId>
<version>${revision}</version>
</parent>
@@ -12,8 +12,8 @@
<artifactId>crabc-core</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
@@ -59,5 +59,17 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>${jdk18on.version}</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,157 @@
package cn.crabc.core.app.api;
import cn.crabc.core.app.entity.BaseApiParam;
import cn.crabc.core.app.entity.dto.ApiInfoDTO;
import cn.crabc.core.app.enums.ResultTypeEnum;
import cn.crabc.core.app.service.core.IBaseDataService;
import cn.crabc.core.app.util.ApiThreadLocal;
import cn.crabc.core.app.util.Result;
import cn.crabc.core.datasource.constant.BaseConstant;
import cn.crabc.core.datasource.enums.ErrorStatusEnum;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* API 接收处理层
*
* @author yuqf
*/
@RestController
@RequestMapping("/api/web")
public class ApiServiceController {
@Autowired
private IBaseDataService baseDataService;
/**
* API GET请求方法
*
* @return
*/
@RequestMapping(value = "/**", method = {RequestMethod.GET, RequestMethod.DELETE})
public Result getService(@RequestParam(required = false) Map<String, Object> paramMap, HttpServletResponse response) {
ApiInfoDTO api = ApiThreadLocal.get();
if (api == null) {
response.setStatus(400);
return Result.error(ErrorStatusEnum.API_INVALID.getCode(), ErrorStatusEnum.API_INVALID.getMassage());
}
// 参数校验
if (!validateParams(api, paramMap)) {
response.setStatus(400);
return Result.error(ErrorStatusEnum.PARAM_NOT_FOUNT.getCode(), ErrorStatusEnum.PARAM_NOT_FOUNT.getMassage());
}
return executeApi(api, paramMap);
}
/**
* API post请求
*
* @param paramMap
* @param body
* @return
*/
@RequestMapping(value = "/**", method = {RequestMethod.POST, RequestMethod.PUT})
public Result postService(@RequestParam(required = false) Map<String, Object> paramMap, @RequestBody(required = false) Object body, HttpServletResponse response) {
ApiInfoDTO api = ApiThreadLocal.get();
if (api == null) {
response.setStatus(400);
return Result.error(ErrorStatusEnum.API_INVALID.getCode(), ErrorStatusEnum.API_INVALID.getMassage());
}
// 合并请求参数
Map<String, Object> mergedParams = mergeParams(paramMap, body);
// 参数校验
if (!checkParams(api.getRequestParams(), mergedParams)) {
response.setStatus(400);
return Result.error(ErrorStatusEnum.PARAM_NOT_FOUNT.getCode(), ErrorStatusEnum.PARAM_NOT_FOUNT.getMassage());
}
mergedParams.put(BaseConstant.PAGE_SETUP, api.getPageSetup());
return executeApi(api, mergedParams);
}
/**
* 校验参数
*
* @param apiParams
* @param paramMap
* @return
*/
public boolean checkParams(List<BaseApiParam> apiParams, Map<String, Object> paramMap) {
if (apiParams == null || paramMap == null) {
return true;
}
for (BaseApiParam param : apiParams) {
String paramName = param.getParamName();
String paramType = param.getParamType();
Object value = paramMap.get(paramName);
// 必填参数校验
if ("Y".equals(param.getRequired()) && !paramMap.containsKey(paramName)) {
return false;
}
// Array类型参数处理
if ("Array".equalsIgnoreCase(paramType)) {
if (value == null || "".equals(value)) {
return false;
}
String[] values = value.toString().split(",");
paramMap.put(paramName, Arrays.asList(values));
}
}
return true;
}
/**
* 验证请求参数
*/
private boolean validateParams(ApiInfoDTO api, Map<String, Object> paramMap) {
if (paramMap == null) {
return true;
}
paramMap.put(BaseConstant.PAGE_SETUP, api.getPageSetup() == null ? 0 : api.getPageSetup());
return checkParams(api.getRequestParams(), paramMap) &&
!(api.getPageSetup() == 1 && !paramMap.containsKey(BaseConstant.PAGE_NUM));
}
/**
* 合并请求参数
*/
private Map<String, Object> mergeParams(Map<String, Object> paramMap, Object body) {
Map<String, Object> mergedParams = new HashMap<>();
if (paramMap != null) {
mergedParams.putAll(paramMap);
}
if (body instanceof Map) {
mergedParams.putAll((Map<String, Object>) body);
}
return mergedParams;
}
/**
* 执行API调用
*/
private Result executeApi(ApiInfoDTO api, Map<String, Object> params) {
Object data = baseDataService.execute(api.getDatasourceId().toString(), api.getDatasourceType(),
api.getSchemaName(), api.getSqlScript(), params);
if (ResultTypeEnum.ONE.getName().equals(api.getResultType()) && data instanceof List) {
List<Object> list = (List<Object>) data;
return Result.success(list.isEmpty() ? null : list.get(0));
}
return Result.success(data);
}
}
@@ -0,0 +1,34 @@
package cn.crabc.core.app.config;
import cn.crabc.core.app.service.system.IBaseApiInfoService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* 加载数据
*/
@Component
public class LoadingData implements InitializingBean {
@Autowired
private IBaseApiInfoService iBaseApiInfoService;
/**
* 启动加载
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
iBaseApiInfoService.initApi();
}
/**
* 定时加载
*/
@Scheduled(cron = "${crabc.corn.api:0 0/5 * * * ?}") // 每5分钟全量加载一次
public void task() {
iBaseApiInfoService.initApi();
}
}
@@ -122,6 +122,16 @@ public class ApiInfoController {
return Result.success(apiInfoService.deleteApi(apiId, UserThreadLocal.getUserId()));
}
/**
* 销毁发布接口
* @param apiId
* @return
*/
@DeleteMapping("/destroy/{apiId}")
public Result destroy(@PathVariable Long apiId) {
apiInfoService.destroyApiInfo(apiId);
return Result.success();
}
/**
* SQL解析
*
@@ -132,63 +142,48 @@ public class ApiInfoController {
public Result sqlParse(@RequestBody SqlParseVO sqlParse) {
SqlParseVO sqlParseVO = new SqlParseVO();
String sql = sqlParse.getSqlScript();
// 去除末尾分号
if (sql.endsWith(";")) {
sql = sql.substring(0, sql.lastIndexOf(";"));
}
// 对</foreach>标签取值特殊处理
Set<String> fields = new HashSet<>();
// 收集所有请求参数
Set<String> paramNames = new HashSet<>();
// 解析foreach标签中的collection参数
if (sql.contains("</foreach>")) {
String regex = "collection='(.*?)'";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(sql);
while (matcher.find()) {
String field = matcher.group(1);
fields.add(field);
}
paramNames.addAll(SQLUtil.extractForeachParams(sql));
}
// 提取if标签里面的请求参数
// 解析if/when标签中的test条件参数
if (sql.contains("<if ") || sql.contains("<when ")) {
String regex = " test=['\"](\\w+)\\s*(?:!=|>=|<=|==|<|>|&lt;|&lt;=|&gt;|&gt;=)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(sql);
while (matcher.find()) {
String field = matcher.group(1);
fields.add(field);
}
}
String forRegex = "<foreach[\\s\\S]*?</foreach>";
sql = sql.replaceAll(forRegex,"()");
// 条件字段
Set<String> paramNames = SQLUtil.parseParams(sql);
if (!fields.isEmpty()) {
paramNames.addAll(fields);
paramNames.addAll(SQLUtil.extractIfParams(sql));
}
// 移除foreach标签内容
sql = sql.replaceAll("<foreach[\\s\\S]*?</foreach>", "()");
// 解析SQL中的参数
paramNames.addAll(SQLUtil.parseParams(sql));
sqlParseVO.setReqColumns(paramNames);
// 返回字段
// 过滤SQL并补全条件
sql = SQLUtil.sqlFilter(sql);
// 判断去除标签后的 sql是否正确,自动补全条件
if (sql.trim().toLowerCase().endsWith("from")) {
sql = sql +" test ";
}else if (sql.trim().toLowerCase().endsWith("where")) {
sql = sql +" 1=1 ";
}
sql = SQLUtil.completeSql(sql);
// 解析返回字段
Set<ColumnParseVo> resColumns = new HashSet<>();
Set<String> resNames = SQLUtil.analyzeSQL(sql.trim(), sqlParse.getDatasourceType());
if (resNames.isEmpty()){
sql = SQLUtil.checkTable(sql, " test ");
resNames = SQLUtil.analyzeSQL(sql.trim(), sqlParse.getDatasourceType());
}
Set<String> resNames = SQLUtil.parseResultColumns(sql, sqlParse.getDatasourceType());
// 构建返回字段信息
for (String name : resNames) {
if (name.startsWith("*")){
continue;
if (!name.startsWith("*")) {
resColumns.add(SQLUtil.buildColumnInfo(name));
}
ColumnParseVo resColumn = new ColumnParseVo();
resColumn.setColName(name);
resColumn.setColType("String");
resColumn.setItemIndex(0);
resColumns.add(resColumn);
}
sqlParseVO.setResColumns(resColumns);
return Result.success(sqlParseVO);
}
@@ -0,0 +1,152 @@
package cn.crabc.core.app.controller;
import cn.crabc.core.app.entity.param.ApiTestParam;
import cn.crabc.core.app.entity.vo.PreviewVO;
import cn.crabc.core.app.enums.ResultTypeEnum;
import cn.crabc.core.app.service.core.IBaseDataService;
import cn.crabc.core.app.util.Result;
import cn.crabc.core.datasource.enums.ErrorStatusEnum;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* API运行和测试
*
* @author yuqf
*/
@RestController
@RequestMapping("/api/box/sys/test")
public class ApiTestController {
@Autowired
private IBaseDataService baseDataService;
@Autowired
private ObjectMapper objectMapper;
/**
* 运行预览
*
* @param api
* @return
*/
@PostMapping("/running")
public Result runApiSql(@RequestBody ApiTestParam api) {
if (api.getDatasourceId() == null) {
return Result.error(ErrorStatusEnum.PARAM_NOT_FOUNT.getCode(), ErrorStatusEnum.PARAM_NOT_FOUNT.getMassage());
}
PreviewVO previewVO = baseDataService.sqlPreview(api.getDatasourceId(),api.getDatasourceType(), api.getSchemaName(), api.getSqlScript());
return Result.success(previewVO);
}
/**
* 在线测试API
*
* @param params
* @return
*/
@PostMapping("/verify/{apiId}")
public Result testApiSql(@PathVariable Long apiId, @RequestBody ApiTestParam params) throws Exception {
// 参数校验
if (params.getDatasourceId() == null) {
return Result.error(ErrorStatusEnum.PARAM_NOT_FOUNT.getCode(), ErrorStatusEnum.PARAM_NOT_FOUNT.getMassage());
}
// 设置默认数据源类型
params.setDatasourceType(params.getDatasourceType() == null ? "mysql" : params.getDatasourceType());
try {
// 构建参数Map
Map<String, Object> paramsMap = buildParamsMap(params);
// 执行SQL
Object data = baseDataService.execute(
params.getDatasourceId(),
params.getDatasourceType(),
params.getSchemaName(),
params.getSqlScript(),
paramsMap
);
// 处理返回结果
return Result.success(formatResultData(data, params.getResultType()));
} catch (Exception e) {
return Result.error("测试异常,请检查参数或者SQL是否正常!");
}
}
/**
* 构建参数Map
*/
private Map<String, Object> buildParamsMap(ApiTestParam params) throws Exception {
Map<String, Object> paramsMap = new HashMap<>();
// 处理body参数
if (params.getBodyData() != null && params.getBodyData().startsWith("{")) {
paramsMap = objectMapper.readValue(params.getBodyData(), HashMap.class);
}
// 处理query参数
Object requestParams = params.getRequestParams();
if (requestParams instanceof Map) {
Map<String, Object> queryParam = (Map<String, Object>) requestParams;
if (validateParams(queryParam)) {
paramsMap.putAll(queryParam);
} else {
throw new IllegalArgumentException("参数校验失败");
}
paramsMap.put("pageSetup", params.getPageSetup());
} else if (requestParams instanceof List) {
List<Map<String,Object>> paramsList = (List<Map<String, Object>>) requestParams;
for (Map<String, Object> entry : paramsList) {
if (validateParams(entry)) {
paramsMap.put(entry.get("name").toString(), entry.get("value"));
} else {
throw new IllegalArgumentException("参数校验失败");
}
}
if (paramsList.isEmpty()) {
paramsMap.put("pageSetup", params.getPageSetup());
}
}
return paramsMap;
}
/**
* 格式化返回数据
*/
private String formatResultData(Object data, String resultType) throws Exception {
if (ResultTypeEnum.ONE.getName().equals(resultType) && data instanceof List) {
List<Object> list = (List<Object>) data;
return objectMapper.writeValueAsString(Result.success(list.isEmpty() ? null : list.get(0)));
}
return objectMapper.writeValueAsString(Result.success(data));
}
/**
* 校验并处理参数
*/
private boolean validateParams(Map<String, Object> params) {
if (params == null || params.isEmpty()) {
return true;
}
String paramType = String.valueOf(params.get("paramType"));
Object paramValue = params.get("value");
if ("Array".equalsIgnoreCase(paramType)) {
if (paramValue == null || "".equals(paramValue)) {
return false;
}
String[] values = paramValue.toString().split(",");
params.put("value", Arrays.asList(values));
}
return true;
}
}
@@ -4,6 +4,8 @@ import cn.crabc.core.app.entity.BaseUser;
import cn.crabc.core.app.entity.param.UserParam;
import cn.crabc.core.app.service.system.IBaseUserService;
import cn.crabc.core.app.util.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Cache;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.BeanUtils;
@@ -13,6 +15,7 @@ import org.springframework.web.bind.annotation.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -26,7 +29,8 @@ public class SysUserController {
@Autowired
@Qualifier("dataCache")
Cache<String, Object> caffeineCache;
@Autowired
private ObjectMapper objectMapper;
/**
* 登录
*
@@ -75,6 +79,21 @@ public class SysUserController {
BaseUser userInfo = iBaseUserService.getUserById(Long.parseLong(UserThreadLocal.getUserId()));
return Result.success(userInfo);
}
/**
* 用户菜单
* @return
*/
@GetMapping("/menus")
public Result userMenus(){
// 固定菜单
String menus ="[{\"name\":\"API Hub\",\"checked\":null,\"id\":2,\"parentId\":1,\"open\":null,\"path\":null,\"url\":\"/hub\",\"sort\":2,\"menuType\":1,\"code\":\"Home\",\"hidden\":false,\"permission\":\"/api/box/sys/user/info;/api/box/sys/user/menus;/api/box/sys/message/**;/api/box/sys/user/update/pwd\",\"subMenus\":[{\"name\":\"接口详情\",\"checked\":null,\"id\":7,\"parentId\":2,\"open\":null,\"path\":null,\"url\":\"/doc\",\"sort\":6,\"menuType\":1,\"code\":\"DataCatelogApiDetails\",\"hidden\":true,\"permission\":\"doc\",\"subMenus\":[]}]},{\"name\":\"工作台\",\"checked\":null,\"id\":3,\"parentId\":1,\"open\":null,\"path\":null,\"url\":\"/working\",\"sort\":3,\"menuType\":1,\"code\":\"DataService\",\"hidden\":false,\"permission\":\"/api/box/sys/common/**;/api/box/sys/api/info/**;/api/box/sys/metadata/**;/api/box/sys/test/**;/api/box/sys/group/**;/api/box/sys/user/check/name\",\"subMenus\":[]},{\"name\":\"服务台\",\"checked\":null,\"id\":4,\"parentId\":1,\"open\":null,\"path\":null,\"url\":\"/manage\",\"sort\":4,\"menuType\":1,\"code\":\"Manage\",\"hidden\":false,\"permission\":\"manage\",\"subMenus\":[{\"name\":\"应用管理\",\"checked\":null,\"id\":8,\"parentId\":4,\"open\":null,\"path\":null,\"url\":\"/manage/keys\",\"sort\":1,\"menuType\":1,\"code\":\"Application\",\"hidden\":true,\"permission\":\"/api/box/sys/app/**\",\"subMenus\":[]},{\"name\":\"接口列表\",\"checked\":null,\"id\":9,\"parentId\":4,\"open\":null,\"path\":null,\"url\":\"/manage/apis\",\"sort\":1,\"menuType\":1,\"code\":\"MyApi\",\"hidden\":true,\"permission\":\"/api/box/sys/api/info/**\",\"subMenus\":[]},{\"name\":\"日志管理\",\"checked\":null,\"id\":12,\"parentId\":4,\"open\":null,\"path\":null,\"url\":\"/manage/logs\",\"sort\":1,\"menuType\":1,\"code\":\"UseLog\",\"hidden\":true,\"permission\":\"/api/box/sys/api/log/**\",\"subMenus\":[{\"name\":\"日志列表\",\"checked\":null,\"id\":13,\"parentId\":12,\"open\":null,\"path\":null,\"url\":\"/manage/logs/list\",\"sort\":1,\"menuType\":1,\"code\":\"UseLogList\",\"hidden\":true,\"permission\":\"/api/box/sys/api/log/**\",\"subMenus\":[]},{\"name\":\"日志详情\",\"checked\":null,\"id\":14,\"parentId\":12,\"open\":null,\"path\":null,\"url\":\"/manage/logs/detail\",\"sort\":1,\"menuType\":1,\"code\":\"DetailUesLog\",\"hidden\":true,\"permission\":null,\"subMenus\":[]}]},{\"name\":\"分类管理\",\"checked\":null,\"id\":30,\"parentId\":4,\"open\":null,\"path\":null,\"url\":\"/manage/classification\",\"sort\":1,\"menuType\":1,\"code\":\"Classification\",\"hidden\":true,\"permission\":\"manage:classification\",\"subMenus\":[]},{\"name\":\"数据源\",\"checked\":null,\"id\":31,\"parentId\":4,\"open\":null,\"path\":null,\"url\":\"/manage/datasource\",\"sort\":1,\"menuType\":1,\"code\":\"DataSource\",\"hidden\":true,\"permission\":\"/api/box/sys/datasource/**;/api/box/sys/user/pubKey\",\"subMenus\":[]},{\"name\":\"密码重置\",\"checked\":null,\"id\":32,\"parentId\":4,\"open\":null,\"path\":null,\"url\":\"/manage/edit-pwd\",\"sort\":1,\"menuType\":1,\"code\":\"ChangePassword\",\"hidden\":true,\"permission\":\"/api/box/sys/user/update/pwd\",\"subMenus\":[]}]}]";
try {
List<Map<String, Object>> list = objectMapper.readValue(menus, List.class);
return Result.success(list);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
/**
* 修改密码
@@ -0,0 +1,126 @@
package cn.crabc.core.app.entity;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* API基本信息
*
* @author yuqf
*/
@Setter
@Getter
public class BaseApiInfo extends BaseEntity {
/**
* api业务唯一ID
*/
private Long apiId;
/**
* 接口名称
*/
private String apiName;
/**
* 接口路径
*/
private String apiPath;
/**
* 请求方式 get、post、put、delete、aptch
*/
private String apiMethod;
/**
* API类型:sql、table
*/
private String apiType;
/**
* 授权类型:none、code、secret
*/
private String authType;
/**
* 接口状态:编辑edit、审批audit、发布release、销毁destroy
*/
private String apiStatus;
/**
* 开放启用 1/0
*/
private Integer enabled;
/**
* 分组ID
*/
private Integer groupId;
/**
* 租户ID
*/
private String tenantId;
/**
* API描述
*/
private String remarks;
/**
* 版本
*/
private String version;
/**
* sql类型,select、insert、update、delete
*/
private String sqlType;
/**
* 返回结果类型:one、array、excel
*
*/
private String resultType;
/**
* sql语句脚本
*/
private String sqlScript;
/**
* 是否显示脚本1/0
*/
private String showSqlScript;
/**
* 分页设置,不分页:0、只分页:page、分页并统计:pageCount
*/
private Integer pageSetup;
/**
* 数据源Id
*/
private Integer datasourceId;
/**
* 数据源名称
*/
private String datasourceName;
/**
* schema
*/
private String schemaName;
/**
* 数据源类型
*/
private String datasourceType;
/**
* 发布时间
*/
private Date releaseTime;
/**
* 父级关联ID
*/
private Long parentId;
/** 分组名称 */
private String groupName;
}
@@ -0,0 +1,50 @@
package cn.crabc.core.app.entity;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.Date;
/**
* api访问日志
*
* @author yuqf
*/
@Setter
@Getter
public class BaseApiLog implements Serializable {
private Long logId;
/**
* api业务唯一ID
*/
private Long apiId;
/**
* 接口名称
*/
private String apiName;
/**
* 接口路径
*/
private String apiPath;
/**
* 请求方式 get、post、put、delete、aptch
*/
private String apiMethod;
/**
* 授权类型:none、code、secret
*/
private String authType;
private String appName;
private String requestIp;
private String queryParam;
private String requestBody;
private String responseBody;
private Integer bodySize;
private String requestStatus;
private Date requestTime;
private Date responseTime;
private Long costTime;
private Integer responseCode;
}
@@ -0,0 +1,85 @@
package cn.crabc.core.app.entity;
import lombok.Getter;
import lombok.Setter;
/**
* API参数
*
* @author yuqf
*/
@Setter
@Getter
public class BaseApiParam {
private static final long serialVersionUID = 1L;
/**
* 自增主键ID
*/
private Long paramId;
/** api主键 */
private Long apiId;
/**
* 参数名
*/
private String paramName;
/**
* 映射字段名
*/
private String columnName;
/**
* 参数模型,request,response
*/
private String paramModel;
/**
* 是否必填
*/
private String required;
/**
* 操作
*/
private String operation;
/**
* 参数类型
*/
private String paramType;
/**
* 参数默认值
*/
private String defaultValue;
/**
* 参数示例值
*/
private String example;
/**
* 参数备注
*/
private String paramDesc;
/**
* 数据源Id
*/
private Integer datasourceId;
/**
* schema
*/
private String schemaName;
/**
* 表名
*/
private String tableName;
}
@@ -0,0 +1,48 @@
package cn.crabc.core.app.entity;
import lombok.Getter;
import lombok.Setter;
/**
* API SQL配置
*
* @author yuqf
*/
@Setter
@Getter
public class BaseApiSql extends BaseEntity {
/**
* 自增主键ID
*/
private Long sqlId;
/** api主键 */
private Long apiId;
/**
* sql语句脚本
*/
private String sqlScript;
/**
* 分页设置,不分页:0、只分页:page、分页并统计:pageCount
*/
private Integer pageSetup;
/**
* 数据源Id
*/
private Integer datasourceId;
/**
* schema
*/
private String schemaName;
/**
* 数据源类型
*/
private String datasourceType;
}
@@ -0,0 +1,56 @@
package cn.crabc.core.app.entity;
import lombok.Getter;
import lombok.Setter;
/**
* 应用秘密
*
* @author yuqf
*/
@Setter
@Getter
public class BaseApp extends BaseEntity {
/**
* 应用ID
*/
private Long appId;
/**
* 应用名
*/
private String appName;
/**
* 应用描述
*/
private String appDesc;
/**
* 认证code
*/
private String appCode;
/**
* 密钥ID
*/
private String appKey;
/**
* 密钥
*/
private String appSecret;
/**
* 策略类型:白名单:white、黑名单black
*/
private String strategyType;
/**
* ip地址,分号分割
*/
private String ips;
/**
* 状态:1启用,0,禁用
*/
private Integer enabled;
/**
* 关联的apiId
*/
private Long apiId;
}
@@ -0,0 +1,28 @@
package cn.crabc.core.app.entity;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
import java.util.List;
/**
* 应用和API关联关系
*
* @author yuqf
*/
@Setter
@Getter
public class BaseAppApi {
private Long appId;
private Long apiId;
private String createBy;
private Date createTime;
private List<Long> apiIds;
}
@@ -0,0 +1,39 @@
package cn.crabc.core.app.entity;
import cn.crabc.core.spi.bean.BaseDataSource;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
*
* 数据源配置
*
* @author yuqf
*/
@Setter
@Getter
@JsonIgnoreProperties(value = {"secretKey"})
public class BaseDatasource extends BaseDataSource {
/**
* 租户
*/
private Integer tenantId;
private String createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新者
*/
private String updateBy;
/**
* 更新时间
*/
private Date updateTime;
}
@@ -0,0 +1,39 @@
package cn.crabc.core.app.entity;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 实体类基类
*
* @author yuqf
*/
@Setter
@Getter
public class BaseEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 创建者
*/
private String createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新者
*/
private String updateBy;
/**
* 更新时间
*/
private Date updateTime;
}
@@ -0,0 +1,22 @@
package cn.crabc.core.app.entity;
import lombok.Getter;
import lombok.Setter;
/**
* api分组
*
* @author yuqf
*/
@Setter
@Getter
public class BaseGroup extends BaseEntity{
private Integer groupId;
private String groupName;
private String groupDesc;
private Integer parentId;
}
@@ -0,0 +1,49 @@
package cn.crabc.core.app.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Getter;
import lombok.Setter;
/**
* 用户信息
*
* @author yuqf
*/
@Setter
@Getter
@JsonIgnoreProperties(value = {"password"})
public class BaseUser extends BaseEntity {
/** 用户ID */
private Long userId;
/** 角色 */
private String role;
/** 用户账号 */
private String username;
/** 用户昵称 */
private String nickname;
/** 用户邮箱 */
private String email;
/** 手机号码 */
private String phone;
/** 用户性别 */
private String sex;
/** 用户头像 */
private String picture;
/** 密码 */
private String password;
/** 帐号状态(0正常 1停用) */
private String status;
private String remark;
}
@@ -0,0 +1,41 @@
package cn.crabc.core.app.entity.dto;
import cn.crabc.core.app.entity.BaseApiInfo;
import cn.crabc.core.app.entity.BaseApiParam;
import cn.crabc.core.app.entity.BaseApp;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* API相关的详情信息
*
* @author yuqf
*/
@Setter
@Getter
public class ApiInfoDTO extends BaseApiInfo {
/**
* 请求时间
*/
private Date requestDate;
private Long requestTime;
private String userId;
/**
* 有权限的应用
*/
private List<BaseApp> appList = new ArrayList<>();
/**
* API参数
*/
private List<BaseApiParam> requestParams;
}
@@ -0,0 +1,30 @@
package cn.crabc.core.app.entity.dto;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
* 字段信息
*
* @author yuqf
*/
@Setter
@Getter
public class ColumnDTO implements Serializable {
/** 名称 */
private String columnName;
/** 类型 */
private String columnType;
/** 别名 */
private String alias;
/** 操作符 */
private String operator;
private String nullable;
private String value;
private String remarks;
}
@@ -0,0 +1,43 @@
package cn.crabc.core.app.entity.param;
import cn.crabc.core.app.entity.BaseApiInfo;
import cn.crabc.core.app.entity.BaseApiParam;
import cn.crabc.core.app.entity.BaseApiSql;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* API请求参数
*
* @author yuqf
*/
@Setter
@Getter
public class ApiInfoParam {
/**
* 基本信息
*/
private BaseApiInfo baseInfo;
/**
* sql
*/
private BaseApiSql sqlInfo;
/**
* 请求参数
*/
private List<BaseApiParam> requestParam;
/**
* 返回参数
*/
private List<BaseApiParam> responseParam;
/**
* 查询引擎
*/
private String queryEngine;
}
@@ -0,0 +1,25 @@
package cn.crabc.core.app.entity.param;
import lombok.Getter;
import lombok.Setter;
/**
* API日志查询请求参数
*
* @author yuqf
*/
@Setter
@Getter
public class ApiLogParam {
private String result;
private String keyword;
private String startTime;
private String endTime;
private Integer pageNum;
private Integer pageSize;
}
@@ -0,0 +1,28 @@
package cn.crabc.core.app.entity.param;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class ApiTestParam {
private String sqlScript;
private String datasourceId;
private String datasourceType;
private String schemaName;
private String sqlParams;
private Integer pageSetup;
private String resultType;
private Object requestParams;
private String bodyData;
}
@@ -0,0 +1,38 @@
package cn.crabc.core.app.entity.param;
import lombok.Getter;
import lombok.Setter;
/**
* 用户请求对象
*
* @author yuqf
*/
@Setter
@Getter
public class UserParam {
private Long userId;
private String username;
/** 用户昵称 */
private String nickname;
/** 用户邮箱 */
private String email;
/** 手机号码 */
private String phone;
/** 用户性别 */
private String sex;
/** 用户头像 */
private String picture;
/** 密码 */
private String password;
private String newPassword;
}
@@ -0,0 +1,19 @@
package cn.crabc.core.app.entity.vo;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class ApiComboBoxVO {
private Long apiId;
private String apiName;
private String apiType;
private String apiStatus;
private Integer groupId;
}
@@ -0,0 +1,42 @@
package cn.crabc.core.app.entity.vo;
import cn.crabc.core.app.entity.BaseApiInfo;
import cn.crabc.core.app.entity.BaseApiParam;
import cn.crabc.core.app.entity.BaseApiSql;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
/**
* API详情
*/
@Setter
@Getter
public class ApiInfoVO {
/**
* 基本信息
*/
private BaseApiInfo baseInfo;
/**
* sql
*/
private BaseApiSql sqlInfo;
/**
* 查询引擎
*/
private String queryEngine;
/**
* 请求参数
*/
private List<BaseApiParam> requestParam = new ArrayList<>();
/**
* 返回参数
*/
private List<BaseApiParam> responseParam = new ArrayList<>();
}
@@ -0,0 +1,20 @@
package cn.crabc.core.app.entity.vo;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* api详情参数
*/
@Setter
@Getter
public class ApiParamsVO {
// 请求参数
List<RequestParamsVO> reqParams;
//响应参数
List<RequestParamsVO> resParams;
}
@@ -0,0 +1,83 @@
package cn.crabc.core.app.entity.vo;
import cn.crabc.core.app.entity.BaseEntity;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* API基本信息
*
* @author yuqf
*/
@Setter
@Getter
public class BaseApiInfoVO extends BaseEntity {
/**
* api业务唯一ID
*/
private Long apiId;
/**
* 接口名称
*/
private String apiName;
/**
* 接口路径
*/
private String apiPath;
/**
* 请求方式 get、post、put、delete、aptch
*/
private String apiMethod;
/**
* API类型:sql、table
*/
private String apiType;
/**
* 授权类型:none、code、secret
*/
private String authType;
/**
* 权限级别:public、default、private
*/
private String apiLevel;
/**
* 开放启用 1/0
*/
private Integer enabled;
/**
* API描述
*/
private String remarks;
/**
* 版本
*/
private String version;
/**
* sql类型,select、insert、update、delete
*/
private String sqlType;
/**
* 分页设置,不分页:0、只分页:page、分页并统计:pageCount
*/
private Integer pageSetup;
private String sqlScript;
private String showSqlScript;
/**
* 发布时间
*/
private Date releaseTime;
private Integer applyed;
}
@@ -0,0 +1,16 @@
package cn.crabc.core.app.entity.vo;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class ColumnParseVo {
private String colName;
private String colType;
private Integer itemIndex;
}
@@ -0,0 +1,24 @@
package cn.crabc.core.app.entity.vo;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 分组下的API
*/
@Setter
@Getter
public class GroupApiVO implements Serializable {
private Integer groupId;
private String groupName;
private Integer apiId;
private String apiName;
private Integer parentId;
private List<GroupApiVO> child = new ArrayList<>();
}
@@ -0,0 +1,16 @@
package cn.crabc.core.app.entity.vo;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Setter
@Getter
public class GroupVO {
private Integer groupId;
private String groupName;
private Integer parentId;
private List<GroupVO> children;
}
@@ -1,5 +1,8 @@
package cn.crabc.core.app.entity.vo;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
@@ -10,9 +13,10 @@ import java.util.Set;
*
* @author yuqf
*/
@Setter
@Getter
public class PreviewVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 标题字段名
*/
@@ -23,19 +27,4 @@ public class PreviewVO implements Serializable {
*/
List<Map<String, Object>> data;
public Set<String> getMetadata() {
return metadata;
}
public void setMetadata(Set<String> metadata) {
this.metadata = metadata;
}
public List<Map<String, Object>> getData() {
return data;
}
public void setData(List<Map<String, Object>> data) {
this.data = data;
}
}
@@ -0,0 +1,52 @@
package cn.crabc.core.app.entity.vo;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Setter
@Getter
public class RequestParamsVO {
/**
* 参数名
*/
private String paramName;
/**
* 参数模型,request,response
*/
private String paramModel;
/**
* 是否必填
*/
private String required;
/**
* 操作
*/
private String operation;
/**
* 参数类型
*/
private String paramType;
/**
* 参数默认值
*/
private String defaultValue;
/**
* 参数示例值
*/
private String example;
/**
* 参数备注
*/
private String paramDesc;
private List<RequestParamsVO> children;
}
@@ -0,0 +1,21 @@
package cn.crabc.core.app.entity.vo;
import lombok.Getter;
import lombok.Setter;
import java.util.Set;
@Setter
@Getter
public class SqlParseVO {
private Set<String> reqColumns;
private Set<ColumnParseVo> resColumns;
private String sqlScript;
private String datasourceType;
}
@@ -79,6 +79,7 @@ public class ExceptionHandler {
@org.springframework.web.bind.annotation.ExceptionHandler(value = HttpMessageNotReadableException.class)
@ResponseBody
public Result exceptionHandler(HttpServletRequest req, HttpMessageNotReadableException e) {
e.printStackTrace();
return Result.error(50002, "Body参数异常,请检查Body/json是否正确传参");
}
}
@@ -0,0 +1,47 @@
package cn.crabc.core.app.filter;
import cn.crabc.core.app.util.ApiThreadLocal;
import cn.crabc.core.app.util.UserThreadLocal;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.web.util.ContentCachingResponseWrapper;
import java.io.IOException;
public class ApiFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
ContentCachingResponseWrapper responseWrapper = null;
try {
// 包装类
if (request.getContentType() == null || request.getContentType().contains(MediaType.APPLICATION_JSON_VALUE)) {
BaseRequestWrapper requestWrapper = new BaseRequestWrapper(request);
responseWrapper = new ContentCachingResponseWrapper(response);
filterChain.doFilter(requestWrapper, responseWrapper);
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}finally {
ApiThreadLocal.remove();
if (responseWrapper != null) {
responseWrapper.copyBodyToResponse();
}
}
}
@Override
public void destroy() {
ApiThreadLocal.remove();
Filter.super.destroy();
}
}
@@ -0,0 +1,240 @@
package cn.crabc.core.app.filter;
import cn.crabc.core.app.entity.BaseApiLog;
import cn.crabc.core.app.entity.BaseApp;
import cn.crabc.core.app.entity.dto.ApiInfoDTO;
import cn.crabc.core.app.service.system.IBaseApiLogService;
import cn.crabc.core.app.util.ApiThreadLocal;
import cn.crabc.core.app.util.RequestUtils;
import cn.crabc.core.app.util.Result;
import cn.crabc.core.app.util.SM3Util;
import cn.crabc.core.datasource.enums.ErrorStatusEnum;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Cache;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.lang.Nullable;
import org.springframework.util.StreamUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.util.ContentCachingResponseWrapper;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* API开放接口鉴权过滤 拦截器
*
* @author yuqf
*/
public class AuthInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(AuthInterceptor.class);
// API开放接口前缀
private static final String API_PRE = "/api/web/";
@Autowired
private IBaseApiLogService iBaseApiLogService;
@Value("${crabc.auth.expiresTime:10}")
private Integer expiresTime;
@Autowired
private ObjectMapper objectMapper;
@Autowired
@Qualifier("apiCache")
private Cache<String, Object> apiCache;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String path = request.getRequestURI();
String method = request.getMethod();
Object apiData = apiCache.getIfPresent(method + "_" + path.replace(API_PRE, ""));
if (apiData == null) {
setErrorResponse(request, response,ErrorStatusEnum.API_INVALID.getCode(),ErrorStatusEnum.API_INVALID.getMassage());
return false;
}
ApiInfoDTO apiInfo = (ApiInfoDTO) apiData;
if (apiInfo.getEnabled() == 0) {
setErrorResponse(request, response,ErrorStatusEnum.API_OFFLINE.getCode(),ErrorStatusEnum.API_OFFLINE.getMassage());
return false;
}
// 存入当前时间,当作是日志的请求时间
apiInfo.setRequestDate(new Date());
apiInfo.setRequestTime(System.currentTimeMillis());
// 放入上下文
ApiThreadLocal.set(apiInfo);
try {
// 应用列表
List<BaseApp> appList = apiInfo.getAppList();
return switch (apiInfo.getAuthType().toUpperCase()) {
case "APP_CODE" -> checkAppCode(request,response, appList);
case "APP_KEY" -> checkAppKey(request,response, appList);
case "APP_SECRET" -> checkSM3(request,response, appList);
default -> true;
};
}catch (Exception e) {
setErrorResponse(request,response,ErrorStatusEnum.API_UN_AUTH.getCode(),ErrorStatusEnum.API_UN_AUTH.getMassage());
return false;
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
addLog(request, response, "");
// 清除上下文
ApiThreadLocal.remove();
}
/**
* 异常返回
* @param response
* @param status
* @param message
* @throws Exception
*/
private void setErrorResponse(HttpServletRequest request, HttpServletResponse response, int status, String message)
throws Exception {
// 指定错误码
response.setStatus(400);
response.setContentType("application/json;charset=UTF-8");
Result result = Result.error(status, message);
String json = objectMapper.writeValueAsString(result);
// 日记记录
addLog(request, response, json);
response.getWriter().write(json);
}
/**
* 记录访问日志
*
* @param request
* @param response
*/
private void addLog(HttpServletRequest request, HttpServletResponse response, String msg) throws Exception {
ContentCachingResponseWrapper responseWrapper = null;
BaseApiLog apiLog = new BaseApiLog();
long endTime = System.currentTimeMillis();
ApiInfoDTO apiInfo = ApiThreadLocal.get();
if (apiInfo != null) {
apiLog.setApiId(apiInfo.getApiId());
apiLog.setApiName(apiInfo.getApiName());
apiLog.setApiMethod(apiInfo.getApiMethod());
apiLog.setAuthType(apiInfo.getAuthType());
apiLog.setRequestTime(apiInfo.getRequestDate());
apiLog.setCostTime(endTime - apiInfo.getRequestTime());
}
int status = response.getStatus();
apiLog.setAppName(RequestUtils.getAppKey(request));
apiLog.setApiPath(request.getRequestURI());
apiLog.setRequestIp(RequestUtils.getIp(request));
apiLog.setResponseTime(new Date());
apiLog.setQueryParam(request.getQueryString());
apiLog.setResponseCode(status);
apiLog.setRequestStatus(status == 200 ? "success" : "fail");
try {
if (request instanceof BaseRequestWrapper) {
String requestBody = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
apiLog.setRequestBody(requestBody);
}
if (response instanceof ContentCachingResponseWrapper) {
responseWrapper = (ContentCachingResponseWrapper) response;
byte[] content = responseWrapper.getContentAsByteArray();
String responseBody = new String(content, StandardCharsets.UTF_8);
apiLog.setResponseBody(responseBody);
}
if (status == 400) {
apiLog.setResponseBody(msg);
}
iBaseApiLogService.addLog(apiLog);
} catch (Exception e) {
log.error("响应结果转换异常", e);
} finally {
if (responseWrapper != null) {
responseWrapper.copyBodyToResponse();
}
}
}
/**
* 验证接口访问权限APP_CODE
*
* @param request
* @param appList
* @return
*/
private boolean checkAppCode(HttpServletRequest request,HttpServletResponse response, List<BaseApp> appList) throws Exception {
String appCode = RequestUtils.getAppCode(request);
if (appCode == null || appCode.isEmpty()) {
setErrorResponse(request,response,ErrorStatusEnum.API_UN_AUTH.getCode(),ErrorStatusEnum.API_UN_AUTH.getMassage());
return false;
}
boolean check = appList.stream().anyMatch(app -> app.getAppCode().equals(appCode));
if (!check) {
setErrorResponse(request,response,ErrorStatusEnum.API_UN_AUTH.getCode(),ErrorStatusEnum.API_UN_AUTH.getMassage());
}
return check;
}
/**
* 验证接口访问权限APP_KEY
*
* @param request
* @param appList
* @return
*/
private boolean checkAppKey(HttpServletRequest request, HttpServletResponse response, List<BaseApp> appList) throws Exception {
String appKey = RequestUtils.getAppKey(request);
if (appKey == null || appKey.isEmpty()) {
setErrorResponse(request,response,ErrorStatusEnum.SHA_APPKEY_NOT_FOUNT.getCode(),ErrorStatusEnum.SHA_APPKEY_NOT_FOUNT.getMassage());
return false;
}
boolean check = appList.stream().anyMatch(app -> app.getAppKey().equals(appKey));
if (!check) {
setErrorResponse(request,response,ErrorStatusEnum.APP_UN_AUTH.getCode(),ErrorStatusEnum.APP_UN_AUTH.getMassage());
}
return check;
}
/**
* 国密签名认证
*
* @param request
* @param appList
* @return
* @throws Exception
*/
public boolean checkSM3(HttpServletRequest request, HttpServletResponse response, List<BaseApp> appList) throws Exception {
// 认证参数
String sign = Optional.ofNullable(request.getHeader("sign")).orElse(request.getParameter("sign"));
String timeStamp = Optional.ofNullable(request.getHeader("timestamp")).orElse(request.getParameter("timestamp"));
String appKey = Optional.ofNullable(request.getHeader("appkey")).orElse(request.getParameter("appkey"));
if (appKey == null || sign == null || timeStamp == null) {
setErrorResponse(request,response,ErrorStatusEnum.SHA_PARAM_NOT_FOUNT.getCode(), ErrorStatusEnum.PARAM_NOT_FOUNT.getMassage());
return false;
}
// 校验时间戳,超过10分钟失效
long authTime = Long.parseLong(timeStamp);
long nowTime = System.currentTimeMillis() - authTime;
if (nowTime > expiresTime * 60 * 1000) {
setErrorResponse(request,response,ErrorStatusEnum.SHA_TIMESTAMP_EXPIRE.getCode(), ErrorStatusEnum.SHA_TIMESTAMP_EXPIRE.getMassage());
return false;
}
String appSecret = appList.stream()
.filter(app -> app.getAppKey().equals(appKey))
.map(BaseApp::getAppSecret)
.findFirst()
.orElse("");
boolean verify = SM3Util.verify(appSecret + timeStamp, sign);
if (!verify) {
setErrorResponse(request,response,ErrorStatusEnum.API_UN_AUTH.getCode(),ErrorStatusEnum.API_UN_AUTH.getMassage());
}
return verify;
}
}
@@ -18,30 +18,25 @@ import org.springframework.web.servlet.HandlerInterceptor;
*/
public class JwtInterceptor implements HandlerInterceptor {
@Value("${crabc.token.expireTime:36000}")
private long expireTime;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
if (request.getMethod().toUpperCase().equals("OPTIONS")){
return true; // 通过OPTION请求
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 放行OPTIONS请求
if ("OPTIONS".equals(request.getMethod().toUpperCase())) {
return true;
}
// 获取并校验token
String token = JwtUtil.getToken(request);
if (token == null) {
throw new CustomException(ErrorStatusEnum.JWT_LOGIN_EXPIRE.getCode(), ErrorStatusEnum.JWT_UN_AUTH.getMassage());
}
// 解析token
Claims claims = JwtUtil.parseToken(token);
if (claims == null) {
throw new CustomException(ErrorStatusEnum.JWT_LOGIN_EXPIRE.getCode(), ErrorStatusEnum.JWT_UN_AUTH.getMassage());
}
long nowTime = System.currentTimeMillis();
Long expire = Long.parseLong(claims.get("expireTime").toString());
long time = nowTime - expire;
if (time/1000 > expireTime) {
throw new CustomException(ErrorStatusEnum.JWT_LOGIN_EXPIRE.getCode(), ErrorStatusEnum.JWT_LOGIN_EXPIRE.getMassage());
}else{
}
// 设置用户信息到线程上下文
UserThreadLocal.set(claims);
return true;
}
@@ -23,6 +23,7 @@ public class BaseDataServiceImpl implements IBaseDataService {
@Autowired
private DataSourceManager dataSourceManager;
@Override
public String testConnection(BaseDataSource dataSource) {
return dataSourceManager.test(dataSource);
@@ -40,21 +41,17 @@ public class BaseDataServiceImpl implements IBaseDataService {
Object result = this.execute(datasourceId, datasourceType, schema, sql, params);
PreviewVO preview = new PreviewVO();
if (result instanceof List) {
List<Map<String, Object>> list = (List<Map<String, Object>>)result;
List<Map<String, Object>> list = (List<Map<String, Object>>) result;
if (!list.isEmpty()) {
preview.setData(list);
Set<String> fieldName = list.get(0).keySet();
preview.setMetadata(fieldName);
preview.setMetadata(list.get(0).keySet());
}
}else{
Set<String> fieldName = new HashSet<>();
List<Map<String, Object>> list = new ArrayList<>();
} else {
Map<String, Object> data = new HashMap<>();
data.put("执行结果", "执行成功,影响条数:"+result);
list.add(data);
data.put("执行结果", String.format("执行成功,影响条数:%s", result));
List<Map<String, Object>> list = Collections.singletonList(data);
preview.setData(list);
fieldName.add("执行结果");
preview.setMetadata(fieldName);
preview.setMetadata(Collections.singleton("执行结果"));
}
return preview;
}
@@ -62,25 +59,28 @@ public class BaseDataServiceImpl implements IBaseDataService {
@Override
public Object execute(String datasourceId, String datasourceType, String schema, String sql, Map<String, Object> params) {
StatementMapper statementMapper = dataSourceManager.getStatementMapper(datasourceId);
// 数据源类型
if (datasourceType != null && !params.containsKey(BaseConstant.DATA_SOURCE_TYPE)) {
params.put(BaseConstant.DATA_SOURCE_TYPE, datasourceType);
}
String sqlType = SQLUtil.getOperateType(sql);
if ("insert".equalsIgnoreCase(sqlType)) {
return statementMapper.insert(datasourceId, schema, sql, params);
}else if("update".equalsIgnoreCase(sqlType)){
return statementMapper.update(datasourceId, schema, sql, params);
}else if("delete".equalsIgnoreCase(sqlType)){
return statementMapper.delete(datasourceId, schema, sql, params);
} else {
Object pageNum = params.get(BaseConstant.PAGE_NUM);
Object pageSize = params.get(BaseConstant.PAGE_SIZE);
if (pageNum != null && pageSize != null) {
return statementMapper.selectPage(datasourceId, schema, sql, params, Integer.parseInt(pageNum.toString()), Integer.parseInt(pageSize.toString()));
} else {
String sqlType = SQLUtil.getOperateType(sql).toLowerCase();
switch (sqlType) {
case "insert":
return statementMapper.insert(datasourceId, schema, sql, params);
case "update":
return statementMapper.update(datasourceId, schema, sql, params);
case "delete":
return statementMapper.delete(datasourceId, schema, sql, params);
default:
Object pageNum = params.get(BaseConstant.PAGE_NUM);
Object pageSize = params.get(BaseConstant.PAGE_SIZE);
if (pageNum != null && pageSize != null) {
return statementMapper.selectPage(datasourceId, schema, sql, params,
Integer.parseInt(pageNum.toString()),
Integer.parseInt(pageSize.toString()));
}
return statementMapper.selectList(datasourceId, schema, sql, params);
}
}
}
}
@@ -153,4 +153,10 @@ public interface IBaseApiInfoService {
*/
Integer addChooseApi(BaseAppApi appApi);
/**
* 销毁接口
* @param apiId
* @return
*/
Integer destroyApiInfo(Long apiId);
}
@@ -50,12 +50,7 @@ public class BaseApiInfoServiceImpl implements IBaseApiInfoService {
@Autowired
@Qualifier("apiCache")
Cache<String, Object> apiInfoCache;
@Scheduled(cron = "${crabc.corn.api:0 0/1 * * * ?}")
public void task() {
initApi();
}
@Override
public void initApi() {
updateCache(null);
@@ -71,7 +66,7 @@ public class BaseApiInfoServiceImpl implements IBaseApiInfoService {
@Override
public List<ApiInfoDTO> getApiCache(Long apiId) {
List<ApiInfoDTO> apiInfos = apiInfoMapper.selectApiDetail(apiId);
if (apiInfos.size() == 0) {
if (apiInfos.isEmpty()) {
return apiInfos;
}
// 应用
@@ -286,6 +281,21 @@ public class BaseApiInfoServiceImpl implements IBaseApiInfoService {
return baseAppApiMapper.insert(list);
}
@Override
public Integer destroyApiInfo(Long apiId) {
String userId = UserThreadLocal.getUserId();
BaseApiInfo baseApiInfo = new BaseApiInfo();
baseApiInfo.setUpdateTime(new Date());
baseApiInfo.setUpdateBy(userId);
baseApiInfo.setApiStatus(ApiStateEnum.DESTROY.getName());
baseApiInfo.setApiId(apiId);
apiInfoMapper.updateApiState(baseApiInfo);
// 更新缓存
this.updateCache(apiId);
return 1;
}
/**
* api信息历史发布版本存档
*
@@ -0,0 +1,88 @@
package cn.crabc.core.app.service.system.impl;
import cn.crabc.core.app.entity.vo.ApiParamsVO;
import cn.crabc.core.app.entity.vo.RequestParamsVO;
import cn.crabc.core.app.mapper.BaseApiParamMapper;
import cn.crabc.core.app.service.system.IBaseApiParamService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* API参数 服务实现
*
* @author yuqf
*/
@Service
public class BaseApiParamServiceImpl implements IBaseApiParamService {
@Autowired
private BaseApiParamMapper baseApiParamMapper;
@Value("${crabc.result.code:code}")
private String resultCode;
@Value("${crabc.result.msg:msg}")
private String resultMsg;
@Value("${crabc.result.data:data}")
private String resultData;
public static final String REQUEST ="request";
/**
* 响应参数
*/
public static final String RESPONSE ="response";
@Override
public ApiParamsVO getApiDetailsParams(Long apiId) {
ApiParamsVO apiParams = new ApiParamsVO();
List<RequestParamsVO> requestParams = baseApiParamMapper.selectApiParams(apiId);
Map<String, List<RequestParamsVO>> paramMap = requestParams.stream().collect(Collectors.groupingBy(RequestParamsVO::getParamModel));
apiParams.setReqParams(paramMap.get(REQUEST));
List<RequestParamsVO> responseParam = paramMap.get(RESPONSE);
apiParams.setResParams(this.setResponseParams(responseParam));
return apiParams;
}
/**
* 处理返回参数
* @param responseParams
* @return
*/
public List<RequestParamsVO> setResponseParams(List<RequestParamsVO> responseParams) {
List<RequestParamsVO> list = new ArrayList<>();
if (responseParams == null || responseParams.isEmpty()) {
return list;
}
RequestParamsVO responseCode = new RequestParamsVO();
responseCode.setParamName(resultCode);
responseCode.setParamDesc("状态码:0 成功");
responseCode.setParamType("Int");
responseCode.setExample("0");
responseCode.setRequired("Y");
list.add(responseCode);
RequestParamsVO responseMsg = new RequestParamsVO();
responseMsg.setParamName(resultMsg);
responseMsg.setParamDesc("错误信息");
responseMsg.setParamType("String");
responseMsg.setExample("");
responseMsg.setChildren(null);
responseCode.setRequired("Y");
list.add(responseMsg);
RequestParamsVO responseData = new RequestParamsVO();
responseData.setParamName(resultData);
responseData.setParamDesc("数据");
responseData.setParamType("Array");
responseData.setExample("");
responseCode.setRequired("Y");
responseData.setChildren(responseParams);
list.add(responseData);
return list;
}
}
@@ -113,6 +113,7 @@ public class BaseDataSourceServiceImpl implements IBaseDataSourceService {
dataSource.setClassify("jdbc");
dataSource.setCreateBy(UserThreadLocal.getUserId());
dataSource.setCreateTime(new Date());
dataSource.setUpdateTime(new Date());
// 判断是否是自定义
if ("custom".equals(dataSource.getDatasourceType())){
String dataSourceType = SQLUtil.getDataSourceType(dataSource.getJdbcUrl());
@@ -176,7 +177,7 @@ public class BaseDataSourceServiceImpl implements IBaseDataSourceService {
byte[] decode = Base64.getDecoder().decode(password);
pwd = new String(decode);
}else if (dataSource.getDatasourceId() != null){
BaseDatasource baseDatasource = dataSourceMapper.selectOne(Integer.parseInt(dataSource.getDatasourceId()));
BaseDatasource baseDatasource = dataSourceMapper.selectOne(dataSource.getDatasourceId());
byte[] decode = Base64.getDecoder().decode(baseDatasource.getPassword());
pwd = new String(decode);
}
@@ -1,6 +1,9 @@
package cn.crabc.core.app.util;
import cn.crabc.core.app.entity.dto.ApiInfoDTO;
import cn.crabc.core.app.filter.AuthInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 上下文 当前用户API信息
@@ -8,7 +11,7 @@ import cn.crabc.core.app.entity.dto.ApiInfoDTO;
* @author yuqf
*/
public class ApiThreadLocal {
private static final Logger log = LoggerFactory.getLogger(ApiThreadLocal.class);
private static ThreadLocal<ApiInfoDTO> apiInfo =new ThreadLocal<>();
/**
@@ -16,6 +19,7 @@ public class ApiThreadLocal {
* @param api
*/
public static void set(ApiInfoDTO api){
log.info("--> 设置上下文API信息,{}",api.getApiPath());
apiInfo.set(api);
}
@@ -37,6 +41,7 @@ public class ApiThreadLocal {
* 清除
*/
public static void remove(){
log.info("<-- 清除上下文API信息");
apiInfo.remove();
}
}
@@ -5,6 +5,7 @@ import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@@ -17,8 +18,9 @@ import java.util.UUID;
public class JwtUtil {
private static String header = "Authorization";
// 令牌秘钥
private static String secret = "crabcjfakdjfaldjflkadjlafjaldkjlaflkalk";
private static String secret = "KLSDJIWKD27JKZDFJALKJKD82KM90DL1NWMD";
public static final String TOKEN_PREFIX = "bearer ";
public static long expirationTime = 1000L * 60 * 60 * 8; // 8小时过期
/**
* 创建令牌
@@ -27,7 +29,6 @@ public class JwtUtil {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
claims.put("userName", userName);
claims.put("expireTime", System.currentTimeMillis());
return createToken(claims);
}
@@ -36,6 +37,8 @@ public class JwtUtil {
*/
public static String createToken(Map<String, Object> claims) {
String uuid = UUID.randomUUID().toString();
Date now = new Date();
Date expiration = new Date(now.getTime() + expirationTime);
try {
return Jwts.builder()
.header()
@@ -43,7 +46,7 @@ public class JwtUtil {
.add("alg", "HS256")
.and()
.claims(claims)
// 令牌ID
.expiration(expiration)
.id(uuid)
.signWith(Keys.hmacShaKeyFor(secret.getBytes()))
.compact();
@@ -1,13 +1,11 @@
package cn.crabc.core.app.util;
import org.springframework.util.Base64Utils;
import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* RSA加/解密工具类
@@ -24,13 +22,13 @@ public class RSAUtils {
* @throws Exception
*/
public static String decryptByPubKey(String pubKey, String text) throws Exception {
byte[] bytes = Base64Utils.decodeFromString(pubKey);
byte[] bytes = Base64.getDecoder().decode(pubKey);
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(bytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] result = Base64Utils.decodeFromString(text);
byte[] result = Base64.getDecoder().decode(text);
return new String(result);
}
@@ -43,14 +41,14 @@ public class RSAUtils {
* @throws Exception
*/
public static String encryptByPriKey(String priKey, String text) throws Exception {
byte[] bytes = Base64Utils.decodeFromString(priKey);
byte[] bytes = Base64.getDecoder().decode(priKey);
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(bytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] result = cipher.doFinal(text.getBytes());
return Base64Utils.encodeToString(result);
return Base64.getEncoder().encodeToString(result);
}
/**
@@ -65,13 +63,13 @@ public class RSAUtils {
if (text == null || priKey == null) {
return null;
}
byte[] bytes = Base64Utils.decodeFromString(priKey);
byte[] bytes = Base64.getDecoder().decode(priKey);
PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(bytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] result = cipher.doFinal(Base64Utils.decodeFromString(text));
byte[] result = cipher.doFinal(Base64.getDecoder().decode(text));
return new String(result);
}
@@ -86,7 +84,7 @@ public class RSAUtils {
if (text == null || pubKey == null) {
return null;
}
byte[] bytes = Base64Utils.decodeFromString(pubKey);
byte[] bytes = Base64.getDecoder().decode(pubKey);
X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(bytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
@@ -94,7 +92,7 @@ public class RSAUtils {
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] result = cipher.doFinal(text.getBytes());
return Base64Utils.encodeToString(result);
return Base64.getEncoder().encodeToString(result);
}
/**
@@ -109,8 +107,8 @@ public class RSAUtils {
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
String pubKey = Base64Utils.encodeToString(rsaPublicKey.getEncoded());
String priKey = Base64Utils.encodeToString(rsaPrivateKey.getEncoded());
String pubKey = Base64.getEncoder().encodeToString(rsaPublicKey.getEncoded());
String priKey = Base64.getEncoder().encodeToString(rsaPrivateKey.getEncoded());
RSAKeyPair rsaKeyPair = new RSAKeyPair();
rsaKeyPair.setPublicKey(pubKey);
rsaKeyPair.setPrivateKey(priKey);
@@ -81,7 +81,27 @@ public class RequestUtils {
public static String getAppCode(HttpServletRequest req) {
String appCode = req.getHeader("appCode");
if (appCode == null) {
String[] codeNames = new String[]{"appCode","AppCode", "appcode", "app_code"};
String[] codeNames = new String[]{"appCode","AppCode", "app_code"};
for(String codeName : codeNames) {
appCode = req.getParameter(codeName);
if (appCode != null) {
break;
}
}
}
return appCode;
}
/**
* 获取请求头和URL参数中的认证AppKey
*
* @param req
* @return
*/
public static String getAppKey(HttpServletRequest req) {
String appCode = req.getHeader("appKey");
if (appCode == null) {
String[] codeNames = new String[]{"appKey","AppKey", "app_key"};
for(String codeName : codeNames) {
appCode = req.getParameter(codeName);
if (appCode != null) {
@@ -123,6 +143,9 @@ public class RequestUtils {
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if ("0:0:0:0:0:0:0:1".equals(ip) || "localhost".equals(ip)) {
ip ="127.0.0.1";
}
return ip == null ? "" : ip.trim();
}
@@ -0,0 +1,52 @@
package cn.crabc.core.app.util;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.HexFormat;
/**
* 国密SM3加密工具类
*
* @author yuqf
*/
public class SM3Util {
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* 字符串的SM3算法哈希值
* @param data 输入字符串
* @return 十六进制格式的哈希值
*/
public static String hash(String data) {
return hash(data.getBytes());
}
/**
* 节数组的SM3算法哈希值
* @param bytes 输入字节数组
* @return 十六进制格式的哈希值
*/
public static String hash(byte[] bytes) {
SM3Digest digest = new SM3Digest();
digest.update(bytes, 0, bytes.length);
byte[] result = new byte[digest.getDigestSize()];
digest.doFinal(result, 0);
return HexFormat.of().formatHex(result);
}
/**
* 验证SM3签名哈希值
* @param data 待签名字符串
* @param sign 预期哈希值
* @return 是否验证通过
*/
public static boolean verify(String data, String sign) {
String hash = hash(data.getBytes(StandardCharsets.UTF_8));
return hash.equalsIgnoreCase(sign);
}
}
@@ -2,6 +2,7 @@ package cn.crabc.core.app.util;
import cn.crabc.core.app.entity.BaseApiParam;
import cn.crabc.core.app.entity.vo.ColumnParseVo;
import cn.crabc.core.datasource.exception.CustomException;
import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
@@ -14,7 +15,7 @@ import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLSelectItem;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.sql.dialect.clickhouse.visitor.ClickSchemaStatVisitor;
import com.alibaba.druid.sql.dialect.clickhouse.visitor.CKStatVisitor;
import com.alibaba.druid.sql.dialect.db2.visitor.DB2SchemaStatVisitor;
import com.alibaba.druid.sql.dialect.hive.visitor.HiveSchemaStatVisitor;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor;
@@ -100,19 +101,19 @@ public class SQLUtil {
public static Set<String> analyzeSQL(String sql, String dbType) {
Set<String> aliasList = new HashSet<>();
try {
if ("doris".equalsIgnoreCase(dbType) || "starrocks".equalsIgnoreCase(dbType)) {
dbType = "mysql";
}
List<SQLStatement> sqlStatementList = SQLUtils.parseStatements(sql, DbType.valueOf(dbType), false);
if (sqlStatementList == null || sqlStatementList.isEmpty()) {
//throw new IllegalArgumentException("不是有效语句");
return aliasList;
} else if (sqlStatementList.size() > 1) {
throw new CustomException(55000, "不支持多条SQL语句!请删除多余的SQL");
}
if (!(sqlStatementList.get(0) instanceof SQLSelectStatement)) {
if (!(sqlStatementList.get(sqlStatementList.size() -1) instanceof SQLSelectStatement)) {
return aliasList;
}
SQLSelectStatement selectStatement = (SQLSelectStatement) sqlStatementList.get(0);
SQLSelectStatement selectStatement = (SQLSelectStatement) sqlStatementList.get(sqlStatementList.size() -1);
SQLSelect sqlSelect = selectStatement.getSelect();
@@ -195,7 +196,7 @@ public class SQLUtil {
visitor = new DB2SchemaStatVisitor();
break;
case "clickhouse":
visitor = new ClickSchemaStatVisitor();
visitor = new CKStatVisitor();
break;
case "hive":
visitor = new HiveSchemaStatVisitor();
@@ -329,10 +330,10 @@ public class SQLUtil {
if (reqParams instanceof Map) {
Map<String, Object> paramMap = (Map<String, Object>) reqParams;
for (BaseApiParam param : paramsConfig) {
if (param.getRequired() && !paramMap.containsKey(param.getParamName())) {
if ("Y".equals(param.getRequired()) && !paramMap.containsKey(param.getParamName())) {
// 必传参数没有传,则报错提示
throw new CustomException(52005, "请求参数缺少!");
} else if (!param.getRequired() && !paramMap.containsKey(param.getParamName())) {
} else if ("N".equals(param.getRequired()) && !paramMap.containsKey(param.getParamName())) {
// 非比传参数,去掉SQL中相应的条件
sql = regexSql(sql, param.getParamName());
}
@@ -416,4 +417,50 @@ public class SQLUtil {
return sb.toString();
}
public static Set<String> extractForeachParams(String sql) {
Set<String> params = new HashSet<>();
Pattern pattern = Pattern.compile("collection='(.*?)'");
Matcher matcher = pattern.matcher(sql);
while (matcher.find()) {
params.add(matcher.group(1));
}
return params;
}
public static Set<String> extractIfParams(String sql) {
Set<String> params = new HashSet<>();
Pattern pattern = Pattern.compile(" test=['\"](\\w+)\\s*(?:!=|>=|<=|==|<|>|&lt;|&lt;=|&gt;|&gt;=)");
Matcher matcher = pattern.matcher(sql);
while (matcher.find()) {
params.add(matcher.group(1));
}
return params;
}
public static String completeSql(String sql) {
sql = sql.trim().toLowerCase();
if (sql.endsWith("from")) {
return sql + " test";
} else if (sql.endsWith("where")) {
return sql + " 1=1";
}
return sql;
}
public static Set<String> parseResultColumns(String sql, String datasourceType) {
Set<String> resNames = SQLUtil.analyzeSQL(sql.trim(), datasourceType);
if (resNames.isEmpty()) {
sql = SQLUtil.checkTable(sql, " test ");
resNames = SQLUtil.analyzeSQL(sql.trim(), datasourceType);
}
return resNames;
}
public static ColumnParseVo buildColumnInfo(String name) {
ColumnParseVo column = new ColumnParseVo();
column.setColName(name);
column.setColType("String");
column.setItemIndex(0);
return column;
}
}
@@ -24,11 +24,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
i.update_time,
i.release_time,
i.show_sql_script,
d.datasource_name
g.group_name
FROM
base_api_info i
left join base_datasource d
on i.datasource_id = d.datasource_id
left join base_group g
on i.group_id = g.group_id
<where>
<if test="apiName !=null and apiName !=''">
(i.api_name like concat('%',#{apiName},'%')
@@ -49,7 +49,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
api_status,
group_id
FROM
base_api_info where api_status !='history'
base_api_info where api_status in ('edit','release')
<if test="groupId !=null">
and group_id = #{groupId}
</if>
@@ -69,8 +69,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
base_app_api aa on
ai.api_id = aa.api_id
WHERE
ai.enabled = 1
AND ai.api_status = 'release'
ai.api_status = 'release'
<if test="appId != null">
and aa.app_id = #{appId}
</if>
@@ -194,7 +193,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="pageSetup != null">page_setup,</if>
enabled,
create_by,
create_time
create_time,
update_time
)values(
<if test="apiName != null and apiName != ''">#{apiName},</if>
<if test="apiPath != null and apiPath != ''">#{apiPath},</if>
@@ -217,7 +217,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="pageSetup != null">#{pageSetup},</if>
#{enabled},
#{createBy},
#{createTime}
#{createTime},
#{updateTime}
)
</insert>
@@ -263,7 +264,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</update>
<delete id="deleteApiInfo">
DELETE FROM base_api_info where api_status ='edit' and api_id =#{apiId} and create_by =#{userId}
DELETE FROM base_api_info where api_id =#{apiId} and create_by =#{userId}
</delete>
<select id="countApi" resultType="java.lang.Integer">
@@ -33,8 +33,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and request_status =#{result}
</if>
<if test="startTime !='' and endTime !=''">
and request_time &gt;=#{startTime}
and request_time &lt;=#{endTime}
and DATE_FORMAT(request_time,'%Y-%m-%d') &gt;=#{startTime}
and DATE_FORMAT(request_time,'%Y-%m-%d') &lt;=#{endTime}
</if>
</where>
order by log_id desc
@@ -22,6 +22,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectList" resultType="cn.crabc.core.app.entity.BaseApiParam">
SELECT
param_id,
api_id,
param_name,
column_name,
@@ -68,7 +68,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
UPDATE base_app
<set>
<if test="appName != null and appName != ''">app_name =#{appName},</if>
<if test="appDesc != null and appDesc != ''">app_desc =#{appDesc},</if>
<if test="appCode != null and appCode != ''">app_code =#{appCode},</if>
<if test="appKey != null and appKey != ''">app_key =#{appKey},</if>
<if test="appSecret != null and appSecret != ''">app_secret =#{appSecret},</if>
@@ -76,7 +75,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="ips != null and ips != ''">ips =#{ips},</if>
<if test="enabled != null">enabled =#{enabled},</if>
<if test="updateBy != null and updateBy != ''">update_by=#{updateBy},</if>
update_time =#{updateTime}
update_time =#{updateTime},app_desc =#{appDesc}
</set>
WHERE app_id = #{appId}
</update>
@@ -16,7 +16,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
password,
remarks,
extend,
secret_key
secret_key,
min_idle,
max_active,
connect_timeout,
idle_timeout,
max_lifetime,
keepalive_time
FROM
base_datasource ORDER BY datasource_id DESC
</select>
@@ -33,6 +39,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
'******' as password,
remarks,
extend,
min_idle,
max_active,
connect_timeout,
idle_timeout,
max_lifetime,
keepalive_time,
update_time
FROM
base_datasource
@@ -56,6 +68,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
username,
password,
extend,
min_idle,
max_active,
connect_timeout,
idle_timeout,
max_lifetime,
keepalive_time,
secret_key
FROM
base_datasource WHERE datasource_id=#{datasourceId}
@@ -79,7 +97,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="remarks != null and remarks != ''">remarks,</if>
<if test="secretKey != null and secretKey != ''">secret_key,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
create_time
<if test="minIdle != null">min_idle,</if>
<if test="maxActive != null">max_active,</if>
<if test="connectTimeout != null">connect_timeout,</if>
<if test="idleTimeout != null">idle_timeout,</if>
<if test="maxLifetime != null">max_lifetime,</if>
<if test="keepaliveTime != null">keepalive_time,</if>
create_time,
update_time
)values(
<if test="datasourceId != null and datasourceId != ''">#{datasourceId},</if>
<if test="datasourceName != null and datasourceName != ''">#{datasourceName},</if>
@@ -93,7 +118,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="remarks != null and remarks != ''">#{remarks},</if>
<if test="secretKey != null and secretKey != ''">#{secretKey},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
#{createTime}
<if test="minIdle != null">#{minIdle},</if>
<if test="maxActive != null">#{maxActive},</if>
<if test="connectTimeout != null">#{connectTimeout},</if>
<if test="idleTimeout != null">#{idleTimeout},</if>
<if test="maxLifetime != null">#{maxLifetime},</if>
<if test="keepaliveTime != null">#{keepaliveTime},</if>
#{createTime},
#{updateTime}
)
</insert>
@@ -108,10 +140,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="username != null and username != ''">username = #{username},</if>
<if test="password != null and password != ''">password = #{password},</if>
<if test="classify != null and classify != ''">classify = #{classify},</if>
<if test="remarks != null and remarks != ''">remarks = #{remarks},</if>
<if test="secretKey != null and secretKey != ''">secret_key = #{secretKey},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
update_time =#{updateTime}
<if test="minIdle != null">min_idle=#{minIdle},</if>
<if test="maxActive != null">max_active=#{maxActive},</if>
<if test="connectTimeout != null">connect_timeout=#{connectTimeout},</if>
<if test="idleTimeout != null">idle_timeout=#{idleTimeout},</if>
<if test="maxLifetime != null">max_lifetime=#{maxLifetime},</if>
<if test="keepaliveTime != null">keepalive_time=#{keepaliveTime},</if>
update_time =#{updateTime},remarks = #{remarks}
</set>
WHERE datasource_id = #{datasourceId}
</update>
Loaded 100 of 286 files, more files were not shown because too many files have changed in this diff. Show more