Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea26c0fcd7 | ||
|
|
9d5f0c05cb | ||
|
|
9420a17bcc | ||
|
|
3b6a95c731 | ||
|
|
c59192c144 | ||
|
|
299bec29e0 | ||
|
|
86bb9c2f2a | ||
|
|
121b6653ab | ||
|
|
9fc3990411 | ||
|
|
7a53390946 | ||
|
|
2e8a930b16 | ||
|
|
444c288079 | ||
|
|
669c8261f5 | ||
|
|
2b752f56e1 | ||
|
|
9b8858999c | ||
|
|
548919485c | ||
|
|
464ee5c738 | ||
|
|
623599998f | ||
|
|
37ba418f87 | ||
|
|
affcfb9234 | ||
|
|
add6362fb8 | ||
|
|
ea85ff64e1 | ||
|
|
f5ff5e4f54 | ||
|
|
2183f2d2f6 | ||
|
|
4e477d86c3 | ||
|
|
284eb0394d | ||
|
|
8e56270cf5 | ||
|
|
6b0c4bea49 | ||
|
|
9edd1c3e00 | ||
|
|
3f0cf9af78 | ||
|
|
ad8636ced0 | ||
|
|
03486448f1 | ||
|
|
5925263443 | ||
|
|
dc7cc0bd79 |
No files matched your search
+1
-1
@@ -45,12 +45,12 @@ buildNumber.properties
|
||||
####################### svn ##############################
|
||||
.svn
|
||||
|
||||
|
||||
####################### eclipse ##############################
|
||||
.project
|
||||
.settings
|
||||
bin/
|
||||
logs/
|
||||
db/
|
||||
.classpath
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
FROM openjdk:17-jdk-alpine
|
||||
LABEL version="2.3.2"
|
||||
FROM eclipse-temurin:21-jdk-alpine
|
||||
LABEL version="4.0.0"
|
||||
|
||||
COPY crabc-admin.jar /app.jar
|
||||
ENV LANG="en_US.UTF-8"
|
||||
|
||||
@@ -3,19 +3,23 @@
|
||||
crabc由create + abc 组合而来,意在为项目系统构建的通用的接口底座。
|
||||
|
||||
# 介绍
|
||||
**crabc** 是低代码接口开发平台,企业数据服务平台,深度整合SpringBoot和Mybatis实现动态数据源和动态SQL。
|
||||
支持接入(mysql、oracle、postgresql、sqlserver、达梦、TiDB、es和mongodb)等SQL或/NoSQL数据源,
|
||||
**crabc** 是接口快速开发平台,企业数据服务平台,sql2Api,深度整合SpringBoot和Mybatis实现动态数据源和动态SQL、函数和存储过程。
|
||||
支持接入(mysql、oracle、postgresql、sqlserver、达梦、TiDB、es和hive)等SQL或/NoSQL数据源,
|
||||
在线可视化编写SQL后即可快速生成接口对外提供服务,接口一键上下线。减少通用接口的SQL编写,让开发人员专注更复杂的业务逻辑实现。
|
||||
支持Mybatis中if等标签语法、数据脱敏、数据转换等功能,集成微服务网关支持接入第三方接口、权限认证、限流、缓存、告警监控和统一日志等一站式API数据服务。
|
||||
支持Mybatis中if等标签语法、数据脱敏、数据转换、国密加密、协同开发等功能,可集成微服务网关支持接入第三方接口、权限认证、限流、缓存、告警监控和统一日志等一站式API数据服务,
|
||||
助力于企业数据资产价值升级,成为企业数字化转型的重要推动力。
|
||||
|
||||

|
||||
## 功能
|
||||
- **工作台**:可视化编写SQL快速的对外发布成API接口,支持多SQL同时执行、Mybatis标签语法,实现动态SQL和动态标签
|
||||
- **数据源管理**:动态加载数据库,支持多种SQL/NoSQL数据库。
|
||||
- **应用列表**:Code认证、签名认证的应用创建管理以及对API调用在线授权。
|
||||
- **应用列表**:AppKey认证、签名认证的应用创建管理以及对API调用在线授权。
|
||||
- **接口列表**:查看开发中和已发布的API接口,可进行上下线管理,编辑升级等
|
||||
- **接口文档**:生成在线接口文档,支持在线测试。
|
||||
- **接口日志**:查看发布的接口被调用日志列表和请求详情。
|
||||
|
||||
## 接口开发流程
|
||||

|
||||
## 模块
|
||||
~~~
|
||||
cn.crabc
|
||||
@@ -30,10 +34,18 @@ cn.crabc
|
||||
├──pom.xml // 依赖
|
||||
~~~
|
||||
|
||||
## 数据库脚本
|
||||
数据库脚本文件可加QQ群文件中获取
|
||||
|
||||
## 版本说明
|
||||
dev分支框架SpringBoot已升级至3.5,jdk需17及以上版本 \
|
||||
2.x分支框架SpringBoot还是2.x
|
||||
|
||||
## 企业版
|
||||
https://pingapi.cn
|
||||
|
||||
## 企业版架构图
|
||||
|
||||
## 运行启动
|
||||
```
|
||||
1、先执行db/dml.sql脚本,创建库表和初始化数据
|
||||
@@ -61,7 +73,7 @@ gitee: https://gitee.com/linebyte/crabc
|
||||
<dependency>
|
||||
<groupId>cn.crabc</groupId>
|
||||
<artifactId>crabc-spring-boot-starter</artifactId>
|
||||
<version>3.5.0</version>
|
||||
<version>4.0.0</version>
|
||||
</dependency>
|
||||
```
|
||||
在程序启动类中添加下面注解
|
||||
@@ -72,60 +84,48 @@ 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 -e DB_URL='jdbc:mysql://localhost:3306/crabc' -e DB_USER=root -e DB_PWD=root -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桌面运行
|
||||
_## Windows桌面运行
|
||||
先执行db/dml.sql脚本,创建库表和初始化数据,建议mysql 8.0+版本
|
||||
```
|
||||
java -jar crabc-admin.jar --spring.datasource.url=jdbc:mysql://127.0.0.1:3306/crabc --spring.datasource.username=root --spring.datasource.password=root
|
||||
java -jar crabc-api.jar --spring.datasource.url=jdbc:mysql://127.0.0.1:3306/crabc --spring.datasource.username=root --spring.datasource.password=root
|
||||
```
|
||||
|
||||
## 企业版和开源版对比
|
||||
| 功能 | 社区版 | 企业版 |
|
||||
|--------|------------------------------| --------------------------|
|
||||
| 架构设计 | 单体 | 微服务 |
|
||||
| 接口创建模式 | 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 |
|
||||
| 接口日志 | 支持 | 支持 |
|
||||
| 数据转换 | 不支持 | 支持 |
|
||||
| 嵌套结构 | 不支持 | 支持 |
|
||||
| 接口市场 | 不支持 | 支持 |
|
||||
| 申请审批 | 不支持 | 支持 |
|
||||
| 接口授权 | 不支持 | 支持 |
|
||||
| 系统权限 | 不支持 | 支持 |
|
||||
| 用户管理 | 不支持 | 支持 |
|
||||
| 数据脱敏 | 不支持 | 支持 |
|
||||
| 版本管理 | 不支持 | 支持
|
||||
| 接口缓存 | 不支持 | 支持 |
|
||||
| 限流熔断 | 不支持 | 限流和熔断 |
|
||||
| IP黑白名单 | 不支持 | 支持 |
|
||||
| 监控报表 | 支持 | 支持 |
|
||||
| 集群模式 | 不支持 | 支持 |
|
||||
## 效果截图
|
||||
### 接口开发
|
||||

|
||||
#### 接口属性
|
||||

|
||||
#### 请求参数
|
||||
#### 返回参数
|
||||

|
||||
#### 在线预览
|
||||

|
||||
#### 接口列表
|
||||

|
||||
#### 接口列表
|
||||

|
||||
#### 日志管理
|
||||

|
||||
#### 数据库类型
|
||||

|
||||
### 限流熔断
|
||||

|
||||
|
||||
## 交流群
|
||||
**进群前请先点【Star】谢谢**
|
||||
|
||||
QQ群(请先Star):748993036
|
||||
|
||||
商用咨询加微信 (请备注crabc)
|
||||
|
||||

|
||||
|
||||
## 产品使用登记
|
||||
以下为部分接入crabc-api的用户 ,<a href="https://gitee.com/linebyte/crabc/issues/IA98CA" target="_blank"> 点击进行登记</a>
|
||||
@@ -142,12 +142,32 @@ java -jar crabc-admin.jar --spring.datasource.url=jdbc:mysql://127.0.0.1:3306/cr
|
||||
| 鹭栖技术信息公司 |
|
||||
| 循脉医疗科技有限公司 |
|
||||
| 云上贵州大数据有限公司 |
|
||||
| 巨元瀚洋有限公司 |
|
||||
|
||||
## 交流群
|
||||
**进群前请先点【Star】谢谢**
|
||||
|
||||
QQ群:748993036
|
||||
|
||||
商用咨询加微信 (请备注crabc)
|
||||
|
||||

|
||||
## 企业版和开源版对比
|
||||
| 功能 | 社区版 | 企业版 |
|
||||
|--------|------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
|
||||
| 架构设计 | 单体 | 微服务 |
|
||||
| 接口创建模式 | 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,16 +5,12 @@
|
||||
<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>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.crabc</groupId>
|
||||
File renamed without changes.
+3
-13
@@ -3,7 +3,7 @@ server:
|
||||
port: 9377
|
||||
spring:
|
||||
application:
|
||||
name: crabc-admin
|
||||
name: crabc-api
|
||||
main:
|
||||
banner-mode: off
|
||||
jackson:
|
||||
@@ -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 # 开启多数据源分页识别
|
||||
File renamed without changes.
File renamed without changes.
@@ -3,19 +3,13 @@
|
||||
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>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>crabc-core</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
+10
-4
@@ -8,9 +8,11 @@ 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 org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -34,14 +36,16 @@ public class ApiServiceController {
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping(value = "/**", method = {RequestMethod.GET, RequestMethod.DELETE})
|
||||
public Result getService(@RequestParam(required = false) Map<String, Object> paramMap) {
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -56,9 +60,10 @@ public class ApiServiceController {
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping(value = "/**", method = {RequestMethod.POST, RequestMethod.PUT})
|
||||
public Result postService(@RequestParam(required = false) Map<String, Object> paramMap, @RequestBody(required = false) Object body) {
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -67,6 +72,7 @@ public class ApiServiceController {
|
||||
|
||||
// 参数校验
|
||||
if (!checkParams(api.getRequestParams(), mergedParams)) {
|
||||
response.setStatus(400);
|
||||
return Result.error(ErrorStatusEnum.PARAM_NOT_FOUNT.getCode(), ErrorStatusEnum.PARAM_NOT_FOUNT.getMassage());
|
||||
}
|
||||
|
||||
@@ -92,7 +98,7 @@ public class ApiServiceController {
|
||||
Object value = paramMap.get(paramName);
|
||||
|
||||
// 必填参数校验
|
||||
if (param.getRequired() && !paramMap.containsKey(paramName)) {
|
||||
if ("Y".equals(param.getRequired()) && !paramMap.containsKey(paramName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -140,7 +146,7 @@ public class ApiServiceController {
|
||||
* 执行API调用
|
||||
*/
|
||||
private Result executeApi(ApiInfoDTO api, Map<String, Object> params) {
|
||||
Object data = baseDataService.execute(api.getDatasourceId(), api.getDatasourceType(),
|
||||
Object data = baseDataService.execute(api.getDatasourceId().toString(), api.getDatasourceType(),
|
||||
api.getSchemaName(), api.getSqlScript(), params);
|
||||
|
||||
if (ResultTypeEnum.ONE.getName().equals(api.getResultType()) && data instanceof List) {
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+5
-5
@@ -74,11 +74,7 @@ public class ApiTestController {
|
||||
);
|
||||
|
||||
// 处理返回结果
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
resultMap.put("code", 0);
|
||||
resultMap.put("data", formatResultData(data, params.getResultType()));
|
||||
|
||||
return Result.success(resultMap);
|
||||
return Result.success(formatResultData(data, params.getResultType()));
|
||||
|
||||
} catch (Exception e) {
|
||||
return Result.error("测试异常,请检查参数或者SQL是否正常!");
|
||||
@@ -105,6 +101,7 @@ public class ApiTestController {
|
||||
} 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) {
|
||||
@@ -114,6 +111,9 @@ public class ApiTestController {
|
||||
throw new IllegalArgumentException("参数校验失败");
|
||||
}
|
||||
}
|
||||
if (paramsList.isEmpty()) {
|
||||
paramsMap.put("pageSetup", params.getPageSetup());
|
||||
}
|
||||
}
|
||||
return paramsMap;
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+20
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
+4
-1
@@ -93,7 +93,7 @@ public class BaseApiInfo extends BaseEntity {
|
||||
/**
|
||||
* 数据源Id
|
||||
*/
|
||||
private String datasourceId;
|
||||
private Integer datasourceId;
|
||||
|
||||
/**
|
||||
* 数据源名称
|
||||
@@ -120,4 +120,7 @@ public class BaseApiInfo extends BaseEntity {
|
||||
*/
|
||||
private Long parentId;
|
||||
|
||||
/** 分组名称 */
|
||||
private String groupName;
|
||||
|
||||
}
|
||||
File renamed without changes.
+2
-2
@@ -40,7 +40,7 @@ public class BaseApiParam {
|
||||
/**
|
||||
* 是否必填
|
||||
*/
|
||||
private Boolean required;
|
||||
private String required;
|
||||
|
||||
/**
|
||||
* 操作
|
||||
@@ -70,7 +70,7 @@ public class BaseApiParam {
|
||||
/**
|
||||
* 数据源Id
|
||||
*/
|
||||
private String datasourceId;
|
||||
private Integer datasourceId;
|
||||
|
||||
/**
|
||||
* schema
|
||||
+1
-1
@@ -33,7 +33,7 @@ public class BaseApiSql extends BaseEntity {
|
||||
/**
|
||||
* 数据源Id
|
||||
*/
|
||||
private String datasourceId;
|
||||
private Integer datasourceId;
|
||||
|
||||
/**
|
||||
* schema
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
-3
@@ -1,10 +1,8 @@
|
||||
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;
|
||||
|
||||
@@ -16,7 +14,6 @@ import java.util.Date;
|
||||
@Setter
|
||||
@Getter
|
||||
public class BaseEntity implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
@@ -17,6 +17,8 @@ public class ApiTestParam {
|
||||
|
||||
private String sqlParams;
|
||||
|
||||
private Integer pageSetup;
|
||||
|
||||
private String resultType;
|
||||
|
||||
private Object requestParams;
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-1
@@ -3,7 +3,7 @@ package cn.crabc.core.app.exception;
|
||||
import cn.crabc.core.app.util.Result;
|
||||
import cn.crabc.core.datasource.exception.CustomException;
|
||||
import cn.crabc.core.spi.PluginException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
@@ -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 javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.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,243 @@
|
||||
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 javax.servlet.http.HttpServletRequest;
|
||||
import javax.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();
|
||||
switch (apiInfo.getAuthType().toUpperCase()) {
|
||||
case "APP_CODE":
|
||||
return checkAppCode(request, response, appList);
|
||||
case "APP_KEY":
|
||||
return checkAppKey(request, response, appList);
|
||||
case "APP_SECRET":
|
||||
return checkSM3(request, response, appList);
|
||||
default:
|
||||
return 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;
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
package cn.crabc.core.app.filter;
|
||||
|
||||
import jakarta.servlet.ReadListener;
|
||||
import jakarta.servlet.ServletInputStream;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.servlet.ReadListener;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import org.apache.tomcat.util.http.fileupload.IOUtils;
|
||||
|
||||
|
||||
+2
-13
@@ -5,8 +5,8 @@ import cn.crabc.core.app.util.UserThreadLocal;
|
||||
import cn.crabc.core.datasource.enums.ErrorStatusEnum;
|
||||
import cn.crabc.core.datasource.exception.CustomException;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
@@ -18,9 +18,6 @@ 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) {
|
||||
// 放行OPTIONS请求
|
||||
@@ -39,14 +36,6 @@ public class JwtInterceptor implements HandlerInterceptor {
|
||||
if (claims == null) {
|
||||
throw new CustomException(ErrorStatusEnum.JWT_LOGIN_EXPIRE.getCode(), ErrorStatusEnum.JWT_UN_AUTH.getMassage());
|
||||
}
|
||||
|
||||
// 校验token是否过期
|
||||
long expireTime = Long.parseLong(claims.get("expireTime").toString());
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - expireTime) / 1000 > this.expireTime) {
|
||||
throw new CustomException(ErrorStatusEnum.JWT_LOGIN_EXPIRE.getCode(), ErrorStatusEnum.JWT_LOGIN_EXPIRE.getMassage());
|
||||
}
|
||||
|
||||
// 设置用户信息到线程上下文
|
||||
UserThreadLocal.set(claims);
|
||||
return true;
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-7
@@ -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;
|
||||
}
|
||||
// 应用
|
||||
+3
-3
@@ -62,7 +62,7 @@ public class BaseApiParamServiceImpl implements IBaseApiParamService {
|
||||
responseCode.setParamDesc("状态码:0 成功");
|
||||
responseCode.setParamType("Int");
|
||||
responseCode.setExample("0");
|
||||
responseCode.setRequired("1");
|
||||
responseCode.setRequired("Y");
|
||||
list.add(responseCode);
|
||||
|
||||
RequestParamsVO responseMsg = new RequestParamsVO();
|
||||
@@ -71,7 +71,7 @@ public class BaseApiParamServiceImpl implements IBaseApiParamService {
|
||||
responseMsg.setParamType("String");
|
||||
responseMsg.setExample("");
|
||||
responseMsg.setChildren(null);
|
||||
responseCode.setRequired("1");
|
||||
responseCode.setRequired("Y");
|
||||
list.add(responseMsg);
|
||||
|
||||
RequestParamsVO responseData = new RequestParamsVO();
|
||||
@@ -79,7 +79,7 @@ public class BaseApiParamServiceImpl implements IBaseApiParamService {
|
||||
responseData.setParamDesc("数据");
|
||||
responseData.setParamType("Array");
|
||||
responseData.setExample("");
|
||||
responseCode.setRequired("1");
|
||||
responseCode.setRequired("Y");
|
||||
responseData.setChildren(responseParams);
|
||||
list.add(responseData);
|
||||
return list;
|
||||
File renamed without changes.
+4
-4
@@ -11,15 +11,14 @@ import cn.crabc.core.datasource.driver.DataSourceManager;
|
||||
import cn.crabc.core.spi.bean.BaseDataSource;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.sql.DataSource;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
@@ -61,7 +60,7 @@ public class BaseDataSourceServiceImpl implements IBaseDataSourceService {
|
||||
public void init() {
|
||||
List<BaseDataSource> baseDataSources = this.getList();
|
||||
for (BaseDataSource dataSource : baseDataSources) {
|
||||
DataSource ds = DataSourceManager.DATA_SOURCE_POOL_JDBC.get(dataSource.getDatasourceId());
|
||||
DataSource ds = DataSourceManager.DATA_SOURCE_POOL_JDBC.get(dataSource.getDatasourceId().toString());
|
||||
try {
|
||||
String jdbcUrl = null;
|
||||
String username = null;
|
||||
@@ -113,6 +112,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 +176,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);
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+6
-1
@@ -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();
|
||||
}
|
||||
}
|
||||
+7
-4
@@ -3,8 +3,9 @@ package cn.crabc.core.app.util;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import javax.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();
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+4
-1
@@ -4,7 +4,7 @@ import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.util.HashMap;
|
||||
@@ -143,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();
|
||||
}
|
||||
|
||||
File renamed without changes.
+2
-2
@@ -1,10 +1,10 @@
|
||||
package cn.crabc.core.app.util;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
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加密工具类
|
||||
@@ -36,7 +36,7 @@ public class SM3Util {
|
||||
digest.update(bytes, 0, bytes.length);
|
||||
byte[] result = new byte[digest.getDigestSize()];
|
||||
digest.doFinal(result, 0);
|
||||
return HexFormat.of().formatHex(result);
|
||||
return new String(Hex.encodeHex(result));
|
||||
}
|
||||
|
||||
/**
|
||||
+2
-2
@@ -330,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());
|
||||
}
|
||||
File renamed without changes.
+9
-8
@@ -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},'%')
|
||||
@@ -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">
|
||||
+2
-2
@@ -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 >=#{startTime}
|
||||
and request_time <=#{endTime}
|
||||
and DATE_FORMAT(request_time,'%Y-%m-%d') >=#{startTime}
|
||||
and DATE_FORMAT(request_time,'%Y-%m-%d') <=#{endTime}
|
||||
</if>
|
||||
</where>
|
||||
order by log_id desc
|
||||
+1
@@ -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,
|
||||
File renamed without changes.
File renamed without changes.
+41
-3
@@ -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>
|
||||
|
||||
@@ -110,6 +142,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="classify != null and classify != ''">classify = #{classify},</if>
|
||||
<if test="secretKey != null and secretKey != ''">secret_key = #{secretKey},</if>
|
||||
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
|
||||
<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}
|
||||
Loaded 100 of 250 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user