26 Commits
Author SHA1 Message Date
蓝白 2423ffd7ae 更新 4.3版本 2026-03-21 15:20:45 +08:00
蓝白 6cf3c47d09 更新 4.3版本 2026-03-21 15:12:07 +08:00
蓝白 6e2a0e2cb3 优化页面 2026-03-21 15:11:25 +08:00
蓝白 6d83e16b1c 修复序列化问题 2026-03-21 15:10:25 +08:00
蓝白 25b991c13a 更新脚本 2026-03-10 13:58:04 +08:00
蓝白 32b1c1045b 更新版本 4.2 2026-03-07 15:39:14 +08:00
蓝白 458266297c 表下拉框添加悬浮提示表名 2026-03-07 15:38:25 +08:00
蓝白 4139d5bd7b 修复post请求异常 2026-03-07 15:33:55 +08:00
蓝白 8b09976db3 更新版本 2026-02-04 14:03:01 +08:00
蓝白 8e61b43c3f 更新版本 2026-02-04 14:01:21 +08:00
蓝白 b2f2772a17 更新页面 2026-02-04 13:59:58 +08:00
蓝白 4314d3410f 更新描述 2026-02-04 13:57:12 +08:00
蓝白 a2a07a2549 修复调用失效问题 2026-02-04 13:56:34 +08:00
蓝白 25c6b49775 修复调用失效问题 2026-02-04 13:56:19 +08:00
蓝白 ee9e51947e 修复连接泄露问题 2026-02-03 21:37:16 +08:00
蓝白 49e05081f9 修复连接池泄露问题 2026-02-03 21:33:09 +08:00
蓝白 9420a17bcc 修复bug 2026-02-03 12:56:29 +08:00
蓝白 3b6a95c731 更新 2026-01-20 19:54:45 +08:00
蓝白 c59192c144 更新版本至4.0 2026-01-18 16:18:50 +08:00
蓝白 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
248 changed files with 5443 additions and 332 deletions

No files matched your search

+1 -1
View File
@@ -45,12 +45,12 @@ buildNumber.properties
####################### svn ##############################
.svn
####################### eclipse ##############################
.project
.settings
bin/
logs/
db/
.classpath
+153
View File
@@ -0,0 +1,153 @@
# AGENTS.md - Coding Guidelines for crabc-api
## Project Overview
A Java Spring Boot API development platform with multi-module Maven architecture for dynamic SQL-based API generation.
## Build Commands
```bash
# Compile all modules
mvn clean compile
# Run tests (no tests currently exist)
mvn test
# Run a single test class (when tests are added)
mvn test -Dtest=ClassName
# Run a single test method
mvn test -Dtest=ClassName#methodName
# Package application
mvn clean package
# Package without tests
mvn clean package -DskipTests
# Run the application
cd crabc-api/crabc-admin && mvn spring-boot:run
```
## Project Structure
```
cn.crabc
├── crabc-api/ # API modules parent
│ ├── crabc-admin/ # Boot module (port 9377)
│ ├── crabc-core/ # Core business logic
│ ├── crabc-datasource/ # Dynamic data sources
│ └── crabc-spi/ # Plugin interfaces
└── crabc-spring-boot-starter/ # Maven starter (optional)
```
## Technology Stack
- **Java**: 17
- **Spring Boot**: 3.5.9
- **MyBatis**: 3.0.5 (with MyBatis-Plus 3.5.15)
- **Database**: MySQL 8.0+, Oracle, PostgreSQL, SQL Server, TiDB
- **Connection Pool**: Druid 1.2.27
- **Cache**: Caffeine
- **JSON**: Fastjson 2.0.49
- **Auth**: JWT 0.12.5
- **Utilities**: Lombok, Apache Commons
## Code Style Guidelines
### Naming Conventions
- **Packages**: `cn.crabc.core.{module}.{layer}` (e.g., `cn.crabc.core.app.controller`)
- **Classes**: PascalCase (e.g., `DataSourceController`, `BaseApiService`)
- **Interfaces**: PascalCase with `I` prefix (e.g., `IBaseDataService`, `IApiMapper`)
- **Methods**: camelCase (e.g., `getDataSourcePage`, `testConnection`)
- **Variables**: camelCase (e.g., `dataSourceId`, `pageNum`)
- **Constants**: UPPER_SNAKE_CASE (e.g., `CODE_TAG`, `MSG_TAG`)
### Code Formatting
- **Indentation**: 4 spaces (no tabs)
- **Braces**: Allman style (opening brace on new line for class/method)
- **Line endings**: LF
- **Encoding**: UTF-8
- **Max line length**: 120 characters
### Import Organization
1. `java.*` and `javax.*`
2. Third-party libraries (Spring, MyBatis, etc.)
3. Project internal imports
4. Static imports (if any)
### Documentation
- All classes must have class-level Javadoc with:
- Description in Chinese
- `@author yuqf` tag
- All public methods should have Javadoc with Chinese descriptions
- Use block comments for complex logic sections
### Dependency Injection
- Use `@Autowired` for field injection (existing pattern)
- Prefer constructor injection for new code when possible
### REST API Controllers
- Use `@RestController` annotation
- Base path pattern: `/api/box/{module}/{resource}`
- Return `Result` wrapper for consistent API responses
- HTTP methods:
- `GET` for queries with `@GetMapping`
- `POST` for create with `@PostMapping`
- `PUT` for update with `@PutMapping`
- `DELETE` for delete with `@DeleteMapping`
### Entities and DTOs
- Use Lombok `@Getter` and `@Setter` annotations
- Include `serialVersionUID` for serializable classes
- Use `Long` for primary keys
- Database column names: use underscores (e.g., `api_id`)
- Java field names: use camelCase (e.g., `apiId`)
### MyBatis Mappers
- Place XML files in `src/main/resources/mapper/`
- Namespace must match mapper interface fully qualified name
- Use `<if>` tags for dynamic SQL conditions
- Parameter references: `#{fieldName}`
- Use `resultType` for selects, `parameterType` for inserts/updates
### Error Handling
- Use `Result.error()` for business errors
- Return HTTP 200 with error code in response body
- Success code: `0`, Error code: `500` or custom
### Database Guidelines
- Support multiple database types (MySQL, Oracle, PostgreSQL, etc.)
- Use parameterized queries (no string concatenation)
- Use PageHelper for pagination
## Testing
- Framework: JUnit 4 (from dependencies)
- Spring Boot Test for integration tests
- Test classes should follow `*Test.java` or `Test*.java` naming
- Place tests in `src/test/java` matching package structure
## Configuration
- Application config: `application.yml`
- MyBatis config: `mybatis-config.xml`
- Port: 9377 (development)
- Banner mode: off (`banner-mode: off`)
## Additional Notes
- This is a Chinese project with Chinese comments and documentation
- Licensed under Apache License 2.0
- No automated linting tools (checkstyle/spotbugs) currently configured
- Focus on dynamic SQL execution and API generation capabilities
+1 -1
View File
@@ -1,5 +1,5 @@
FROM eclipse-temurin:21-jdk-alpine
LABEL version="3.5.2"
LABEL version="4.0.0"
COPY crabc-admin.jar /app.jar
ENV LANG="en_US.UTF-8"
+46 -41
View File
@@ -3,18 +3,19 @@
crabc由create + abc 组合而来,意在为项目系统构建的通用的接口底座。
# 介绍
**crabc** 是低代码接口开发平台,企业数据服务平台,深度整合SpringBoot和Mybatis实现动态数据源和动态SQL、函数和存储过程。
**crabc-api** 是接口快速开发平台,企业数据服务平台,sql2Api,深度整合SpringBoot和Mybatis实现动态数据源和动态SQL、函数和存储过程。
支持接入(mysql、oracle、postgresql、sqlserver、达梦、TiDB、es和hive)等SQL或/NoSQL数据源,
在线可视化编写SQL后即可快速生成接口对外提供服务,接口一键上下线。减少通用接口的SQL编写,让开发人员专注更复杂的业务逻辑实现。
支持Mybatis中if等标签语法、数据脱敏、数据转换、国密加密、协同开发等功能,可集成微服务网关支持接入第三方接口、权限认证、限流、缓存、告警监控和统一日志等一站式API数据服务,
支持Mybatis中if等标签语法、数据脱敏、数据转换、国密加密、协同开发、接口编排等功能,可集成微服务网关支持接入第三方接口、权限认证、限流、缓存、告警监控和统一日志等一站式API数据服务,
助力于企业数据资产价值升级,成为企业数字化转型的重要推动力。
![img.png](doc/home.png)
## 功能
## 社区版功能
- **工作台**:可视化编写SQL快速的对外发布成API接口,支持多SQL同时执行、Mybatis标签语法,实现动态SQL和动态标签
- **数据源管理**:动态加载数据库,支持多种SQL/NoSQL数据库。
- **应用列表**:AppKey认证、签名认证的应用创建管理以及对API调用在线授权。
- **接口列表**:查看开发中和已发布的API接口,可进行上下线管理,编辑升级等
- **接口文档**:生成在线接口文档,支持在线测试。
- **接口日志**:查看发布的接口被调用日志列表和请求详情。
## 接口开发流程
@@ -31,11 +32,11 @@ cn.crabc
├── crabc-spring-boot-starter // 第三方项目maven集成依赖包
├── db // SQL脚本
├──pom.xml // 依赖
~~~
~~~
## 版本说明
dev分支框架SpringBoot已升级至3.5,jdk需17及以上版本 \
2.x分支框架SpringBoot还是2.x
jdk8分支框架SpringBoot是2.x + jdk8
## 企业版
https://pingapi.cn
@@ -59,9 +60,6 @@ github:https://github.com/pingapi/crabc-api
已申请软件著作,开源版仅供个人学习使用 \
企业版功能更强大,商用请加群联系\
## 数据库脚本
加群获取最新数据库脚本
## 源码地址
github: https://github.com/pingapi/crabc-api \
gitee: https://gitee.com/linebyte/crabc
@@ -72,7 +70,7 @@ gitee: https://gitee.com/linebyte/crabc
<dependency>
<groupId>cn.crabc</groupId>
<artifactId>crabc-spring-boot-starter</artifactId>
<version>3.5.2</version>
<version>4.3.0</version>
</dependency>
```
在程序启动类中添加下面注解
@@ -83,8 +81,13 @@ gitee: https://gitee.com/linebyte/crabc
## Docker启动
先执行db/dml.sql脚本,创建库表和初始化数据,建议mysql 8.0+版本
X86/AMD架构
```
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 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
```
访问地址:http://127.0.0.1:9377
@@ -93,7 +96,7 @@ docker run -p 9377:9377 -e DB_URL=jdbc:mysql://localhost:3306/crabc -e DB_USER=r
_## 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
```
## 效果截图
@@ -101,16 +104,16 @@ java -jar crabc-admin.jar --spring.datasource.url=jdbc:mysql://127.0.0.1:3306/cr
![img.png](doc/sql.png)
#### 接口属性
![img.png](doc/detail.png)
#### 请求参数
#### 返回参数
![img.png](doc/param.png)
#### 在线预览
![img_2.png](doc/img_test.png)
#### 接口列表
![img.png](doc/api.png)
![img.png](doc/apis.png)
#### 日志管理
![img.png](doc/logs.png)
#### 数据库类型
![img.png](doc/db.png)
### 限流熔断
![img.png](doc/flow.png)
## 交流群
**进群前请先点【Star】谢谢**
@@ -136,31 +139,33 @@ QQ群(请先Star):748993036
| 鹭栖技术信息公司 |
| 循脉医疗科技有限公司 |
| 云上贵州大数据有限公司 |
| 巨元瀚洋有限公司 |
## 企业版和开源版对比
| 功能 | 社区版 | 企业版 |
|--------|------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
| 架构设计 | 单体 | 微服务 |
| 接口创建模式 | 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控制 | 不支持 | 支持 |
| 监控报表 | 支持 | 支持 |
| 集群模式 | 不支持 | 支持 |
| 功能 | 社区版 | 企业版 |
|-------|--------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
| 架构设计 | 单体 | 微服务 |
| 开发模式 | 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/hbase <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>21</maven.compiler.source>
<maven.compiler.target>21</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>
@@ -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 # 开启多数据源分页识别
@@ -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>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
@@ -97,7 +97,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;
}
@@ -1,5 +1,6 @@
package cn.crabc.core.app.config;
import cn.crabc.core.app.entity.dto.ApiInfoDTO;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
@@ -28,9 +29,9 @@ public class CacheConfig {
* @return
*/
@Bean("apiCache")
public Cache<String, Object> apiCaffeine() {
public Cache<String, ApiInfoDTO> apiCaffeine() {
return Caffeine.newBuilder()
.expireAfterAccess(10, TimeUnit.HOURS)
.expireAfterAccess(1, TimeUnit.MINUTES)
.build();
}
@@ -56,7 +56,10 @@ public class DataSourceConfig {
dataSource.setTestWhileIdle(true);
dataSource.setTestOnBorrow(false);
dataSource.setTestOnReturn(false);
dataSource.setTestOnReturn(false);
// 连接泄漏检测配置
dataSource.setRemoveAbandoned(true);
dataSource.setRemoveAbandonedTimeout(300);
dataSource.setLogAbandoned(true);
JdbcDataSourceRouter dynamic = new JdbcDataSourceRouter();
dynamic.setTargetDataSources(new HashMap<>());
// 设置默认数据源
@@ -0,0 +1,91 @@
package cn.crabc.core.app.config;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* Jackson 配置类
* 解决时间字段带 T 和科学计数法问题
*
* @author yuqf
*/
@Configuration
public class JacksonConfig {
/**
* 日期时间格式
*/
private static final String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
/**
* 日期格式
*/
private static final String DATE_PATTERN = "yyyy-MM-dd";
/**
* 时间格式
*/
private static final String TIME_PATTERN = "HH:mm:ss";
/**
* 配置 Jackson ObjectMapper
* 针对 Spring Boot 3 优化
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> {
// 注册 JavaTimeModule 处理 Java 8 日期时间类型
JavaTimeModule javaTimeModule = new JavaTimeModule();
// LocalDateTime 序列化格式
javaTimeModule.addSerializer(LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATETIME_PATTERN)));
// LocalDate 序列化格式
javaTimeModule.addSerializer(LocalDate.class,
new LocalDateSerializer(DateTimeFormatter.ofPattern(DATE_PATTERN)));
// LocalTime 序列化格式
javaTimeModule.addSerializer(LocalTime.class,
new LocalTimeSerializer(DateTimeFormatter.ofPattern(TIME_PATTERN)));
builder.modules(javaTimeModule);
// BigDecimal 使用自定义序列化器,避免科学计数法(包括数值过小的情况)
builder.serializerByType(BigDecimal.class, new JsonSerializer<BigDecimal>() {
@Override
public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 使用 toPlainString() 确保不会输出科学计数法
gen.writeString(value.stripTrailingZeros().toPlainString());
}
});
// BigInteger、Long 使用 ToStringSerializer
builder.serializerByType(BigInteger.class, ToStringSerializer.instance);
builder.serializerByType(Long.class, ToStringSerializer.instance);
builder.serializerByType(long.class, ToStringSerializer.instance);
// 其他配置
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
builder.featuresToEnable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
};
}
}
@@ -15,8 +15,6 @@ import org.springframework.web.bind.annotation.*;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
@@ -41,7 +39,7 @@ public class ApiInfoController {
* @return
*/
@GetMapping("/page")
public Result page(String keyword, String devType, Integer pageNum, Integer pageSize) {
public Result page(@RequestParam(required = false) String keyword, String devType, Integer pageNum, Integer pageSize) {
PageInfo<BaseApiInfo> page = apiInfoService.getApiPage(keyword, devType, pageNum, pageSize);
return Result.success(page);
}
@@ -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是否正常!");
@@ -94,6 +90,9 @@ public class ApiTestController {
// 处理body参数
if (params.getBodyData() != null && params.getBodyData().startsWith("{")) {
paramsMap = objectMapper.readValue(params.getBodyData(), HashMap.class);
}else if (params.getBodyData() != null && params.getBodyData().startsWith("[")) {
List list = objectMapper.readValue(params.getBodyData(), List.class);
paramsMap.put("list", list);
}
// 处理query参数
@@ -110,7 +109,9 @@ public class ApiTestController {
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"));
if (entry.containsKey("name")) {
paramsMap.put(entry.get("name").toString(), entry.get("value"));
}
} else {
throw new IllegalArgumentException("参数校验失败");
}
@@ -35,9 +35,6 @@ public class CommonController {
public Result detail(Long apiId) {
Integer applyCount = 1;
BaseApiInfoVO apiDetail = apiInfoService.getApiDetail(apiId);
if ("0".equals(apiDetail.getShowSqlScript())){
apiDetail.setSqlScript("***");
}
apiDetail.setApplyed(applyCount);
return Result.success(apiDetail);
}
@@ -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);
}
}
/**
* 修改密码
@@ -80,11 +80,6 @@ public class BaseApiInfo extends BaseEntity {
*/
private String sqlScript;
/**
* 是否显示脚本1/0
*/
private String showSqlScript;
/**
* 分页设置,不分页:0、只分页:page、分页并统计:pageCount
*/
@@ -104,6 +99,8 @@ public class BaseApiInfo extends BaseEntity {
* schema
*/
private String schemaName;
// 表名
private String tableName;
/**
* 数据源类型
@@ -120,4 +117,7 @@ public class BaseApiInfo extends BaseEntity {
*/
private Long parentId;
/** 分组名称 */
private String groupName;
}
@@ -40,7 +40,7 @@ public class BaseApiParam {
/**
* 是否必填
*/
private Boolean required;
private String required;
/**
* 操作
@@ -40,6 +40,8 @@ public class BaseApiSql extends BaseEntity {
*/
private String schemaName;
private String tableName;
/**
* 数据源类型
*/
@@ -3,6 +3,8 @@ package cn.crabc.core.app.entity.param;
import lombok.Getter;
import lombok.Setter;
import java.util.Map;
@Setter
@Getter
public class ApiTestParam {
@@ -25,4 +27,5 @@ public class ApiTestParam {
private String bodyData;
private Map<String,Object> queryParam;
}
@@ -71,8 +71,6 @@ public class BaseApiInfoVO extends BaseEntity {
private Integer pageSetup;
private String sqlScript;
private String showSqlScript;
/**
* 发布时间
*/
@@ -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是否正确传参");
}
}
@@ -23,7 +23,7 @@ public class ApiFilter implements Filter {
ContentCachingResponseWrapper responseWrapper = null;
try {
// 包装类
if (request.getContentType() != null && request.getContentType().contains(MediaType.APPLICATION_JSON_VALUE)) {
if (request.getContentType() == null || request.getContentType().contains(MediaType.APPLICATION_JSON_VALUE)) {
BaseRequestWrapper requestWrapper = new BaseRequestWrapper(request);
responseWrapper = new ContentCachingResponseWrapper(response);
filterChain.doFilter(requestWrapper, responseWrapper);
@@ -40,6 +40,7 @@ public class ApiFilter implements Filter {
@Override
public void destroy() {
ApiThreadLocal.remove();
Filter.super.destroy();
}
@@ -3,13 +3,13 @@ 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.IBaseApiInfoService;
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 cn.crabc.core.datasource.exception.CustomException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Cache;
import jakarta.servlet.http.HttpServletRequest;
@@ -22,7 +22,6 @@ 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.servlet.ModelAndView;
import org.springframework.web.util.ContentCachingResponseWrapper;
import java.io.IOException;
@@ -42,25 +41,30 @@ public class AuthInterceptor implements HandlerInterceptor {
private static final String API_PRE = "/api/web/";
@Autowired
private IBaseApiLogService iBaseApiLogService;
@Autowired
private IBaseApiInfoService iBaseApiInfoService;
@Value("${crabc.auth.expiresTime:10}")
private Integer expiresTime;
@Autowired
private ObjectMapper objectMapper;
@Autowired
@Qualifier("apiCache")
private Cache<String, Object> apiCache;
private Cache<String, ApiInfoDTO> apiCache;
private ApiInfoDTO getApiData(String key) {
String[] split = key.split("_");
return iBaseApiInfoService.getApiInfoCache(split[0], split[1]);
}
@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) {
ApiInfoDTO apiInfo = apiCache.get(method + "_" + path.replace(API_PRE, ""), this::getApiData);
if (apiInfo == 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;
@@ -132,6 +136,7 @@ public class AuthInterceptor implements HandlerInterceptor {
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());
@@ -74,6 +74,13 @@ public interface BaseApiInfoMapper {
*/
List<ApiInfoDTO> selectApiDetail(@Param("apiId") Long apiId);
/**
* 根据apiId查询信息
* @param
* @return
*/
ApiInfoDTO getApiDetail(@Param("method") String method, @Param("apiPath") String apiPath);
/**
* 插入API
*
@@ -18,7 +18,13 @@ import java.util.List;
*/
public interface IBaseApiInfoService {
void initApi();
/**
* 根据接口url获取API关联数据
*
* @param
* @return
*/
ApiInfoDTO getApiInfoCache(String method, String apiPath);
/**
* 获取API关联数据
@@ -49,11 +49,23 @@ public class BaseApiInfoServiceImpl implements IBaseApiInfoService {
private BaseApiParamMapper apiParamMapper;
@Autowired
@Qualifier("apiCache")
Cache<String, Object> apiInfoCache;
Cache<String, ApiInfoDTO> apiInfoCache;
@Override
public void initApi() {
updateCache(null);
public ApiInfoDTO getApiInfoCache(String method, String apiPath) {
ApiInfoDTO apiInfo = apiInfoMapper.getApiDetail(method, apiPath);
if (apiInfo == null) {
return null;
}
// 应用
List<BaseApp> appApis = baseAppMapper.selectApiApp(apiInfo.getApiId());
// 请求参数
List<BaseApiParam> baseApiParams = apiParamMapper.selectReqParams(apiInfo.getApiId());
apiInfo.setAppList(appApis == null ? new ArrayList<>() : appApis);
apiInfo.setRequestParams(baseApiParams == null ? new ArrayList<>() : baseApiParams);
return apiInfo;
}
public void updateCache(Long apiId) {
@@ -150,6 +162,7 @@ public class BaseApiInfoServiceImpl implements IBaseApiInfoService {
api.setApiType("SQL");
api.setDatasourceId(sql.getDatasourceId());
api.setSchemaName(sql.getSchemaName());
api.setTableName(sql.getTableName());
api.setDatasourceType(sql.getDatasourceType());
api.setSqlScript(sql.getSqlScript());
api.setCreateTime(date);
@@ -183,6 +196,7 @@ public class BaseApiInfoServiceImpl implements IBaseApiInfoService {
api.setEnabled(0);
api.setDatasourceId(sql.getDatasourceId());
api.setSchemaName(sql.getSchemaName());
api.setTableName(sql.getTableName());
api.setDatasourceType(sql.getDatasourceType());
api.setSqlScript(sql.getSqlScript());
api.setUpdateTime(updateTime);
@@ -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;
@@ -61,7 +61,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 +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);
}
@@ -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());
}
@@ -23,12 +23,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
i.update_by,
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},'%')
@@ -90,7 +89,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
remarks,
version,
page_setup,
show_sql_script,
datasource_id,
datasource_type,
schema_name,
@@ -106,6 +104,33 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</if>
</select>
<select id="getApiDetail" resultType="cn.crabc.core.app.entity.dto.ApiInfoDTO">
SELECT
api_id,
api_name,
api_path,
api_type,
api_method,
auth_type,
api_status,
enabled,
group_id,
tenant_id,
remarks,
version,
page_setup,
datasource_id,
datasource_type,
schema_name,
sql_script,
sql_type,
result_type
FROM
base_api_info
WHERE
parent_id = 0 AND api_status ='release' and api_path =#{apiPath} and api_method =#{method}
</select>
<select id="selectApiById" resultType="cn.crabc.core.app.entity.BaseApiInfo">
SELECT
api_id,
@@ -127,8 +152,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
datasource_id,
datasource_type,
schema_name,
table_name,
sql_script,
show_sql_script,
page_setup,
sql_type,
result_type
@@ -153,7 +178,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
b.page_setup,
b.create_time,
b.sql_script,
b.show_sql_script,
b.update_time,
b.release_time,
b.version
@@ -186,14 +210,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="sqlType != null and sqlType != ''">sql_type,</if>
<if test="resultType != null and resultType != ''">result_type,</if>
<if test="sqlScript != null and sqlScript !=''">sql_script,</if>
<if test="showSqlScript != null and showSqlScript !=''">show_sql_script,</if>
<if test="datasourceId != null and datasourceId != ''">datasource_id,</if>
<if test="datasourceType != null and datasourceType != ''">datasource_type,</if>
<if test="schemaName != null and schemaName != ''">schema_name,</if>
<if test="tableName != null and tableName != ''">table_name,</if>
<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>
@@ -209,14 +234,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="sqlType != null and sqlType != ''">#{sqlType},</if>
<if test="resultType != null and resultType != ''">#{resultType},</if>
<if test="sqlScript != null and sqlScript !=''">#{sqlScript},</if>
<if test="showSqlScript != null and showSqlScript !=''">#{showSqlScript},</if>
<if test="datasourceId != null and datasourceId != ''">#{datasourceId},</if>
<if test="datasourceType != null and datasourceType != ''">#{datasourceType},</if>
<if test="schemaName != null and schemaName != ''">#{schemaName},</if>
<if test="tableName != null and tableName != ''">#{tableName},</if>
<if test="pageSetup != null">#{pageSetup},</if>
#{enabled},
#{createBy},
#{createTime}
#{createTime},
#{updateTime}
)
</insert>
@@ -238,10 +264,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="sqlType != null and sqlType != ''">sql_type = #{sqlType},</if>
<if test="resultType != null and resultType != ''">result_type = #{resultType},</if>
<if test="sqlScript != null and sqlScript !=''">sql_script = #{sqlScript},</if>
<if test="showSqlScript != null and showSqlScript !=''">show_sql_script =#{showSqlScript},</if>
<if test="datasourceId != null and datasourceId != ''">datasource_id =#{datasourceId},</if>
<if test="datasourceType != null and datasourceType != ''">datasource_type =#{datasourceType},</if>
<if test="schemaName != null and schemaName != ''">schema_name =#{schemaName},</if>
<if test="tableName != null and tableName != ''">table_name =#{tableName},</if>
<if test="pageSetup != null">page_setup =#{pageSetup},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
update_time = #{updateTime}
@@ -262,7 +288,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">
@@ -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,
Loaded 100 of 248 files, more files were not shown because too many files have changed in this diff. Show more