init: test-backend 初始化,基于 ghb-base [AI: Claude Code]

This commit is contained in:
zwt13703 2026-07-05 17:30:23 +08:00
parent 062b2293ef
commit b3bd04aa59
1610 changed files with 263275 additions and 0 deletions

217
.claude/backend.md Normal file
View File

@ -0,0 +1,217 @@
# 02 — 后端规则
> **适用**:所有后端 AI 开发任务。
---
## 分层不变式
```
Controller → 参数校验 + 调 Service + 返回统一格式
禁止:写业务逻辑、直接调 Mapper、处理事务
Service → 业务逻辑 + 事务管理
禁止:处理 HTTP 请求/响应、直接操作 Request/Response
Mapper → 数据访问MyBatis-Plus BaseMapper
禁止:写业务逻辑、调用其他 Mapper
Entity → 数据映射(@TableName 对应表名)
禁止:写业务方法、放非持久化字段(放 DTO/VO
```
### 包结构
```
com.ghb.base/
├── system/ # 系统模块(不改)
│ ├── entity/ SysUser, SysRole, SysMenu...
│ ├── controller/
│ └── service/
└── business/ # 业务模块 ← 你的代码放这里
├── entity/ # 继承 JeecgBoot 的 BaseEntity
├── controller/ # 继承 JeecgController
├── service/ # impl/ 继承 ServiceImpl
├── mapper/ # 继承 BaseMapper
│ └── xml/ # MyBatis XML复杂查询才需要
└── vo/ # 请求/响应 DTO (*VO, *Request, *ImportRow)
```
---
## 命名规则
| 元素 | 规则 | 示例(正确 → 错误) |
|------|------|---------------------|
| **类名** | 大驼峰,名词 | `TaskController` `OrderService` `UserMapper` |
| **方法名** | 小驼峰,动词开头 | `getById()` `saveTask()` `listByStatus()` |
| **变量** | 小驼峰,有意义 | `taskList` `userId` → ❌ `list` `id` `data` |
| **包名** | 全小写,点分隔 | `com.xxx.server.controller` |
| **Entity** | 大驼峰,对应表名 | 表 `task_apply` → 类 `TaskApply` |
| **DTO** | 大驼峰,后缀 DTO/VO/Request | `TaskCreateRequest` `UserListVO` |
| **常量** | 全大写,下划线分隔 | `MAX_RETRY_COUNT` `DEFAULT_PAGE_SIZE` |
| **SQL 表名** | 小写,下划线分隔 | `task_apply` `user_profile` |
| **SQL 字段** | 小写,下划线分隔 | `create_time` `task_id` `del_flag` |
| **URL 路径** | 小写,短横线或斜杠 | `/app/task/list` → ❌ `/app/Task/GetList` |
| **配置文件** | kebab-case 或小写 | `application-prod.yml` |
### 长度控制
类名、方法名、表名不要过长。超过 3 个单词或 30 个字符时,用通用缩写。
**前缀也要缩**:模块前缀用一个单词,别堆多个词。
| 完整 | 缩写 | JeecgBoot 实际案例 |
|------|------|-------------------|
| Department | Dept | `SysDepartRolePermission``SysDeptRolePerm` |
| Permission | Perm | 同上 |
| Announcement | Notice | `SysAnnouncementSend``SysNoticeSend` |
| Enterprise | Ent | `WechatEnterprise``WxEnt` |
| Message | Msg | `SysMessageTemplate``SysMsgTemplate` |
```java
// ❌ JeecgBoot 原版 — 太长
SysDepartRolePermissionServiceImpl // 35 字符
SysAnnouncementSendServiceImpl // 30 字符
ThirdAppWechatEnterpriseServiceImpl // 35 字符
// ✅ 缩写后
SysDeptRolePermServiceImpl // 26 字符
SysNoticeSendServiceImpl // 25 字符
ThirdAppWxEntServiceImpl // 27 字符
```
**原则**:优先用全称,超长再缩写。缩写必须一眼能认出含义,不自造别人看不懂的缩写。
---
## 注释规则
| 位置 | 要求 | 示例 |
|------|------|------|
| **类/接口** | 必须有 Javadoc说明职责 | `/** 任务管理 Controller处理任务的增删改查 */` |
| **公共方法** | 必须有 Javadoc说明入参/返回/异常 | `/** @param taskId 任务ID @return 任务详情 @throws 无 */` |
| **复杂逻辑** | 行内注释说明"为什么这么做" | `// 先查缓存再查库,避免击穿` |
| **常量** | 必须有注释说明含义 | `/** 最大重试次数 */` |
| **TODO/FIXME** | 必须有负责人和日期 | `// TODO(yaoshuli 2026-06) 后续改为配置项` |
| **禁止** | ❌ 注释写"做了什么"(代码本身已说明) | `// 查询任务列表` ← 删掉 |
| **禁止** | ❌ 注释掉的旧代码 | 直接删Git 有历史 |
| **禁止** | ❌ 无意义注释 | `// 定义一个变量` `// 返回结果` |
| **禁止** | ❌ Controller 方法加 Javadoc 说"这是xxx接口" | 写业务含义,不写技术废话 |
### 注释示例
```java
/** 任务服务,负责任务的创建、分配、状态流转 */
@Service
public class TaskServiceImpl implements TaskService {
/** 单次批量操作上限 */
private static final int BATCH_LIMIT = 100;
/**
* 根据状态和截止时间查询待处理任务。
* 先查 Redis 缓存,未命中再查 MySQL。
*
* @param status 任务状态,不能为 null
* @param deadline 截止时间,只查此时间之前的任务
* @return 任务列表,可能为空
*/
@Override
public List<Task> listPending(String status, LocalDateTime deadline) {
// 缓存 key: pending:{status}
String cacheKey = "pending:" + status;
List<Task> cached = cacheService.get(cacheKey);
if (cached != null) return cached;
// 查库(这里用复合索引 idx_status_deadline
List<Task> tasks = taskMapper.selectByStatusBefore(status, deadline);
cacheService.set(cacheKey, tasks, 300);
return tasks;
}
}
```
## 新增功能标准步骤
```
1. 建表 SQL → [项目数据库脚本目录]
2. Entity → 表映射
3. Mapper → 继承 BaseMapper
4. Service 接口+实现 → 业务逻辑
5. Controller → 接口暴露
6. 编译验证 → 构建通过
```
---
## CRUD 模板(填空式)
```java
// ===== 分页列表 =====
@GetMapping("/list")
public Result<IPage<Xxx>> list(@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {
Page<Xxx> p = new Page<>(page, size);
// [填空] 构建查询条件
return Result.ok(service.page(p, wrapper));
}
// ===== 详情 =====
@GetMapping("/{id}")
public Result<Xxx> detail(@PathVariable Long id) {
Xxx entity = service.getById(id);
if (entity == null) return Result.error("[资源]不存在");
return Result.ok(entity);
}
// ===== 新增 =====
@PostMapping
public Result<?> create(@RequestBody @Valid XxxDto dto) {
// [填空] DTO → Entity 转换
service.save(entity);
return Result.ok();
}
// ===== 更新 =====
@PutMapping("/{id}")
public Result<?> update(@PathVariable Long id, @RequestBody @Valid XxxDto dto) {
Xxx entity = service.getById(id);
if (entity == null) return Result.error("[资源]不存在");
// [填空] 字段更新
service.updateById(entity);
return Result.ok();
}
```
---
## 禁止清单
| 类别 | ❌ 禁止 |
|------|--------|
| **分层** | Controller 里写 if/else 业务判断 |
| **分层** | Controller 直接调 Mapper/Repository |
| **分层** | Entity 里写业务方法 |
| **数据** | SQL/查询条件字符串拼接 |
| **数据** | 查不到不判空,直接返回可能 NPE |
| **数据** | `selectOne` / `getOne` 不做唯一性约束 |
| **输入** | 入参不校验(该用 `@Valid` 的地方手撸 if |
| **输出** | 返回裸对象、裸字符串、自造格式 |
| **值** | 代码里硬编码魔法数字、魔法字符串 |
| **事务** | 该加事务的地方漏加 |
---
## 检查表
每完成一个后端任务,逐项自检:
- [ ] 编译通过(`mvn compile` / `go build` / 对应构建命令)
- [ ] 所有返回值是统一格式,无裸返回
- [ ] 分页接口格式正确(`list/total/pages/current/size`
- [ ] Controller 方法体不超过 20 行(超过说明写了业务逻辑)
- [ ] 查不到/null 路径有明确的 `fail` 返回
- [ ] 改了接口 → 已 grep 同步所有前端调用方
- [ ] 新增/改字段 → 已更新数据库脚本
- [ ] 无禁止清单中的违规项

108
.claude/contract.md Normal file
View File

@ -0,0 +1,108 @@
# 01 — 接口契约
> **这是前后端共享的唯一真相来源。** 修改本文件 = 修改协议,必须同步更新所有端。
---
## 契约生命周期(任务拆分时决定)
契约由**任务拆分时先开工的那一端**负责起草。这是任务产出物之一,不是额外工作。
```
任务拆分阶段:
┌─ 任务A后端-用户管理CRUD ──→ 产出:接口契约(后端起草)
│ + 后端代码
└─ 任务B前端-用户管理页面 ──→ 拿到契约后对接
反过来:
┌─ 任务A前端-首页改版 ──→ 产出:接口契约(前端起草)
│ + 前端页面
└─ 任务B后端-首页数据接口 ──→ 按契约实现
```
**铁律**
- 拆分任务时明确标注:**本任务包含接口契约起草**
- 契约随任务一起交付,不单独走审批
- 后开工的一方拿到契约后,发现问题直接跟起草方沟通修改
- 联调时对照本文件,不一致的以本文件为准
### 分仓库时契约放哪
```
Monorepo推荐 分仓库:
项目根/ 后端仓库/ 前端仓库/
└── rules/ ├── rules/ ├── rules/
└── 01-接口契约.md │ └── 01-接口契约.md │ └── 01-接口契约.md
← git submodule 引用同一份 →
```
---
## 返回格式(不变式)
```json
// ✅ 唯一正确格式
{ "code": 200, "message": "success", "data": { ... } }
{ "code": 500, "message": "错误原因", "data": null }
// ❌ 永远不许出现
return data; // 裸对象
return "ok"; // 裸字符串
{ "success": true, "result": {...} } // 自造格式
```
---
## 分页格式(不变式)
```json
{ "list": [...], "total": 100, "pages": 10, "current": 1, "size": 10 }
```
---
## 接口命名约定
| 前缀 | 用途 | 示例 |
|------|------|------|
| `/admin/**` | 管理后台 | `/admin/user/list` |
| `/app/**` | 移动端/H5/小程序 | `/app/task/list` |
| `/auth/**` | 登录注册(共享) | `/auth/login` |
| `/open/**` | 对外开放接口 | `/open/callback` |
---
## 状态值映射表
**← 改这里 = 改协议,必须同步后端 + 所有前端。**
| 含义 | 后端值 | 前端显示 | 前端 CSS 类 |
|------|--------|----------|------------|
| [状态1] | [值] | [文字] | [class] |
| [状态2] | [值] | [文字] | [class] |
---
## 字段名桥接表
同一个概念在不同端字段名不同时,在此登记:
| 概念 | 后端字段 | 前端A字段 | 前端B字段 |
|------|----------|-----------|-----------|
| | | | |
---
## 契约同步铁律
```
改后端接口 → grep 所有前端 → 逐个同步 → 两端都通过才算完成
步骤:
1. 改之前grep -r "接口路径" 所有前端目录/
2. 改后端 → 编译通过
3. 逐个改前端 → 构建通过
4. 更新本文件的映射表(如有新增状态值/字段差异)
```

175
.claude/database.md Normal file
View File

@ -0,0 +1,175 @@
# 04 — 数据库表设计规则
> **适用**:所有涉及建表、改表、索引设计的 AI 开发任务。
---
## 命名规则
| 元素 | 规则 | 示例(正确 → 错误) |
| -------- | ------------- | -------------------------------------------------------- |
| **表名** | 小写,下划线分隔,单数名词 | `task_apply` → ❌ `TaskApply` `task_applies` |
| **字段名** | 小写,下划线分隔 | `create_time` `task_id` → ❌ `createdAt` `taskId` |
| **主键** | 统一用 `id` | `id` → ❌ `task_id` `pk_id` |
| **外键** | `关联表名_id` | `task_id` `user_id` → ❌ `tid` `uid` |
| **布尔字段** | `del_flag` 或明确业务含义的 `is_` 前缀 | `del_flag` `is_active` → ❌ `deleted` `status` |
| **时间字段** | `_time` 后缀 | `create_time` `update_time` → ❌ `created_at` `updateTime` |
| **金额字段** | `_amount` 后缀 | `reward_amount` → ❌ `reward` `price` |
| **索引名** | `idx_表名_字段` | `idx_task_status` → ❌ `index1` `task_status_idx` |
| **唯一索引** | `uk_表名_字段` | `uk_user_phone` → ❌ `uq_phone` |
| **关联表** | 两表名用下划线连接 | `task_apply` `user_role` → ❌ `apply_task` |
### 长度控制
表名超过 3 个单词或 30 个字符时缩写。前缀也要缩成一个单词。
| 完整 | 缩写 | JeecgBoot 实际案例 |
|------|------|-------------------|
| department | dept | `sys_depart_role_permission``sys_dept_role_perm` |
| permission | perm | 同上 |
| announcement | notice | `sys_announcement_send``sys_notice_send` |
| message | msg | `sys_message_template``sys_msg_template` |
```sql
-- ❌ JeecgBoot 原版 — 太长
sys_depart_role_permission -- 26 字符
sys_permission_data_rule -- 24 字符
-- ✅ 缩写后
sys_dept_role_perm -- 19 字符
sys_perm_data_rule -- 20 字符
```
---
## 字段类型规范
| 场景 | 类型 | 说明 |
|------|------|------|
| 主键 | `BIGINT` 自增 或 `VARCHAR(32)` | 推荐 BIGINT 自增分布式用雪花ID |
| 短文本 | `VARCHAR(N)` | 姓名(50)、标题(200)、URL(500) |
| 长文本 | `TEXT` / `LONGTEXT` | 文章、JSON、描述 |
| 金额 | `DECIMAL(12,2)` | **禁止**用 FLOAT/DOUBLE |
| 状态/类型 | `VARCHAR(20)``TINYINT` | 枚举值,必须有注释说明 |
| 布尔 | `TINYINT(1)` | 0=否 1=是,注释必须写清含义 |
| 时间 | `DATETIME` | **禁止**用 TIMESTAMP2038 问题) |
| 日期 | `DATE` | 生日、截止日期 |
| 数量 | `INT``BIGINT` | |
---
## 每张表必须有的字段
```sql
CREATE TABLE xxx (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
-- [业务字段]
create_by VARCHAR(32) NULL DEFAULT NULL COMMENT '创建人',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(32) NULL DEFAULT NULL COMMENT '更新人',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除0-正常 1-已删除',
INDEX idx_xxx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='表说明';
```
**强制要求**
- `create_by` — 创建人,建议保留;系统自动填充时可为空
- `create_time` — 创建时间,必须有
- `update_by` — 更新人,建议保留;系统自动填充时可为空
- `update_time` — 更新时间,必须有(`ON UPDATE CURRENT_TIMESTAMP` 自动更新)
- `del_flag` — 逻辑删除标记,除非明确不需要软删除
- 每张表必须有 `COMMENT`
- 引擎统一 `InnoDB`,字符集 `utf8mb4`
---
## 索引规则
| 规则 | 说明 |
|------|------|
| **主键** | 每张表必须有,推荐自增 BIGINT |
| **外键** | 必须加索引MySQL 自动加,但显式声明更清晰) |
| **查询条件** | WHERE 条件字段必须加索引 |
| **排序字段** | ORDER BY 字段考虑加索引 |
| **联合索引** | 遵循最左前缀原则,区分度高的在前 |
| **唯一约束** | 业务唯一字段必须加唯一索引 |
| **禁止** | ❌ 不加索引的 WHERE / JOIN / ORDER BY |
| **禁止** | ❌ 在大字段TEXT/BLOB上建索引 |
| **禁止** | ❌ 过多索引(单表建议 ≤ 5 个) |
### 索引命名
```sql
INDEX idx_表名_字段 -- 普通索引
UNIQUE uk_表名_字段 -- 唯一索引
INDEX idx_表名_字段1_字段2 -- 联合索引
```
---
## 字段约束规则
| 规则 | 示例 |
|------|------|
| 主键 | `PRIMARY KEY``NOT NULL AUTO_INCREMENT` |
| 非空 | 业务必填字段 `NOT NULL` |
| 默认值 | 有默认值的字段 `DEFAULT xxx`**禁止**依赖代码设默认值 |
| 唯一 | 业务唯一字段 `UNIQUE` |
| 外键 | 尽量用逻辑外键(代码维护),**不推荐**物理外键(`FOREIGN KEY` |
---
## 禁止清单
| ❌ 禁止 | 原因 |
|---------|------|
| 表名/字段名用大写或驼峰 | 跨平台兼容 |
| 金额用 FLOAT/DOUBLE | 精度丢失 |
| 时间用 TIMESTAMP | 2038 年溢出 |
| 用物理外键 | 分库分表/数据迁移困难 |
| 不加注释 | 无人知道字段含义 |
| 字符串代替布尔 | 空间浪费,索引效率低 |
| 大表无索引 | 性能灾难 |
| 字段用 NULL 代替默认值 | 查询需额外处理 `IS NULL` |
| 在代码里设默认值 | 数据一致性依赖应用层 |
---
## 修改表规则
```sql
-- ✅ 正确:显式命名约束
ALTER TABLE task ADD COLUMN priority TINYINT NOT NULL DEFAULT 0 COMMENT '优先级0-普通 1-紧急';
ALTER TABLE task ADD INDEX idx_task_priority (priority);
-- ❌ 禁止:不写 COMMENT
ALTER TABLE task ADD COLUMN priority TINYINT;
-- ❌ 禁止:不写默认值导致存量数据为 NULL
ALTER TABLE task ADD COLUMN priority TINYINT NOT NULL;
```
**修改表必须**
- 新字段有 `COMMENT`
- 非空字段有 `DEFAULT`
- 考虑对存量数据的影响
- 附带回滚 SQL
---
## 检查表
每涉及建表/改表,逐项自检:
- [ ] 表名、字段名全小写+下划线
- [ ] 有 `create_by` `create_time` `update_by` `update_time`
- [ ] 软删除字段 `del_flag`(如需要)
- [ ] 每张表有 `COMMENT`,每个字段有 `COMMENT`
- [ ] 金额用 `DECIMAL`,时间用 `DATETIME`
- [ ] 主键 + 外键 + WHERE 条件字段有索引
- [ ] 非空字段有 `NOT NULL DEFAULT`
- [ ] 无物理外键
- [ ] 索引命名 `idx_表名_字段` / `uk_表名_字段`
- [ ] 附带回滚 SQL
- [ ] 引擎 InnoDB字符集 utf8mb4

44
.claude/encoding.md Normal file
View File

@ -0,0 +1,44 @@
# 编码规范(字符编码)
> 本规则因一次事故而立:批量改包名时用了非 UTF-8 感知的脚本,把中文注释整成乱码(`鐗堟湰`=版本、`寰湇鍔`=微服务),且引入 BOM。以下为硬性约束。
## 不变式(违反即 Bug
1. **所有源码/配置/文本文件一律 UTF-8 编码**`.java` `.xml` `.yml` `.yaml` `.properties` `.sql` `.md` `.vue` `.ts` `.js` `.json` 等。
2. **禁止 BOM**UTF-8 文件不得带 BOM`EF BB BF`。BOM 会让 XML/YAML 解析、shell 脚本、diff 出错。
3. **禁止 GBK / GB2312 / GB18030 / Latin1 落盘**:任何环节(编辑器、脚本、终端重定向)都不得以非 UTF-8 写文件。
4. **禁止用非 UTF-8 感知的工具批量改写文本**:如必须批量替换,工具必须显式按 UTF-8 读、UTF-8 写Python 用 `io.open(p, encoding='utf-8')``sed`/`grep` 在 UTF-8 locale 下运行)。
5. **换行**:统一 LF`\n``.gitattributes` 固化,避免 CRLF 混入触发整文件 diff。
## 构建/工程配置(必须存在)
- **Maven**:父 pom 设
```xml
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
```
- **`.editorconfig`**(后端仓库和前端仓库根目录各一个 `.editorconfig` 文件):
```
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
```
- **前端**Vite/Node 默认 UTF-8源文件保持 UTF-8 无 BOM 即可。
- **Windows 终端**PowerShell 默认输出可能是 GBK**不要用 `>`/`>>` 重定向把含中文的内容写进源文件**(会按当前代码页编码)。要写文件用 UTF-8 感知的程序,不要靠 shell 重定向。
## 处理已损坏文件mojibake 还原)
经典双重编码UTF-8 被当 GBK 再存 UTF-8可逆转`坏文本.encode('gbk').decode('utf-8')`。
但若 mojibake 中已出现 `?`0x3F该字节已丢失、不可逆只能按上下文重写注释。
## 检查表(提交前)
- [ ] 新增/修改的文本文件是 UTF-8 无 BOM`file *.xml` 或编辑器状态栏确认)
- [ ] 没有出现 `鐗堟湰`/`寰湇`/`锛` 这类乱码片段
- [ ] 仓库根有 `.editorconfig`charset=utf-8
- [ ] 父 pom 有 `project.build.sourceEncoding=UTF-8`
- [ ] 批量文本替换用的是 UTF-8 感知工具,不是裸 `>` 重定向

199
.claude/frontend.md Normal file
View File

@ -0,0 +1,199 @@
# 03 — 前端规则
> **适用**:所有前端 AI 开发任务Web / H5 / 小程序)。
---
## 样式铁律
**禁止在组件文件中硬编码**:颜色值(`#xxx` / `rgb()`)、字号(`px` / `rpx` / `rem`)、间距(`margin` / `padding` 数值)、圆角(`border-radius` 数值)。
所有样式必须来自:
1. **Design Token 文件** → 变量(`$color-primary`
2. **全局 CSS 类** → 语义类(`.text-h1` `.card` `.gap-2`
3. **UI 框架内置类** → Tailwind / Ant Design 等
```vue
<!-- ❌ 禁止 -->
<style scoped>
.title { color: #333; font-size: 32rpx; margin: 20rpx; }
<!-- ✅ 正确 -->
<style scoped>
.title { color: $color-gray-800; @extend .text-h2; margin: $space-3; }
```
---
## 命名规则
| 元素 | 规则 | 示例(正确 → 错误) |
|------|------|---------------------|
| **组件文件** | 短横线分隔kebab-case | `task-card.vue` → ❌ `TaskCard.vue` |
| **页面文件** | 小写或短横线 | `task-detail.vue` `index.vue` |
| **组件 name** | 大驼峰PascalCase | `TaskCard` `UserAvatar` |
| **CSS 类名** | 短横线分隔 | `.task-card` `.btn-primary` → ❌ `.taskCard` |
| **JS 变量** | 小驼峰 | `taskList` `userName` → ❌ `task_list` |
| **JS 常量** | 全大写,下划线分隔 | `MAX_PAGE_SIZE` `API_BASE_URL` |
| **JS 函数** | 小驼峰,动词开头 | `fetchTasks()` `handleSubmit()` |
| **API 方法** | 小驼峰,动词+名词 | `taskApi.list()` `taskApi.create()` |
| **路由路径** | 小写,短横线 | `/task-detail` → ❌ `/taskDetail` |
| **文件目录** | 小写或短横线 | `task-hall/` `user-center/` |
| **静态资源** | 短横线分隔 | `icon-phone.svg` `bg-home.jpg` |
---
## 注释规则
| 位置 | 要求 | 示例 |
|------|------|------|
| **组件** | 顶部注释说明用途和 Props | `<!-- 任务卡片组件,用于列表展示 -->` |
| **复杂逻辑** | 行内注释说明"为什么" | `// 先查本地缓存,未命中再请求接口` |
| **计算属性** | 注释说明计算依据 | `// 根据任务状态和截止时间计算紧急程度` |
| **API 方法** | JSDoc 说明入参/返回 | `@param {Object} params @returns {Promise<{list,total}>}` |
| **TODO/FIXME** | 负责人+日期 | `// TODO(yaoshuli 2026-06) 待产品确认逻辑` |
| **禁止** | ❌ 注释写"做了什么" | `<!-- 循环渲染列表 -->` ← 删掉 |
| **禁止** | ❌ 注释掉的旧代码/旧模板 | 直接删Git 有历史 |
| **禁止** | ❌ 无意义分隔注释 | `// ========== 数据获取 ==========` |
```vue
<!--
任务详情页面
- 展示任务基本信息、报名列表、结算记录
- 支持接单、提交、结算等操作
-->
<template>
<app-layout>
<!-- 任务信息卡片 -->
<m-card>
...
</m-card>
<!-- 仅管理员可见 -->
<m-card v-if="isAdmin">
...
</m-card>
</app-layout>
</template>
<script setup>
/**
* 提交任务成果。
* 提交成功后自动刷新列表并发送通知。
*
* @param {string} taskId - 任务ID
* @param {Object} payload - 提交内容 { files, remark }
* @returns {Promise<void>}
*/
const handleSubmit = async (taskId, payload) => {
// 先本地校验,避免无效请求
if (!payload.files.length) return;
await taskApi.submit(taskId, payload);
// TODO(yaoshuli 2026-06) 成功后应跳转到提交记录页
router.push('/task-list');
};
</script>
```
---
## 组件使用铁律
| 场景 | ✅ 必须用 | ❌ 禁止 |
|------|----------|---------|
| 页面外壳 | 项目统一布局组件 | 自己写页头/侧栏/底栏/TabBar |
| 图标 | 项目统一图标组件 | 裸 `<img>` / emoji / Unicode |
| 按钮 | 项目统一按钮组件 | 自己写 button 样式 |
| 卡片 | 项目统一卡片组件 | 裸露 div+手写圆角阴影 |
---
## API 调用铁律
**所有 HTTP 请求走统一封装**,禁止组件内直调原生 API。
```javascript
// ✅ 正确:走项目统一封装
import { taskApi } from '@/services/task';
const res = await taskApi.list({ page: 1, size: 20 });
// ❌ 禁止
fetch('/api/task/list').then(...)
axios.get('/api/task/list')
uni.request({ url: '/api/task/list' })
```
---
## 新增页面模板(填空式)
```vue
<template>
<app-layout>
<!-- [填空] 页面内容 -->
<view v-if="loading">加载中...</view>
<view v-else-if="list.length === 0" class="empty-state">
<text>暂无数据</text>
</view>
<view v-else>
<!-- [填空] 数据展示 -->
</view>
</app-layout>
</template>
<script setup>
import { ref, onMounted } from 'vue';
// [填空] import API 方法
const list = ref([]);
const loading = ref(false);
const fetchData = async () => {
loading.value = true;
try {
const res = await [API方法]({ page: 1, size: 20 });
list.value = res.list;
} finally {
loading.value = false;
}
};
onMounted(() => fetchData());
</script>
<style scoped lang="scss">
// ⚠️ 不得超过 100 行,超过说明在造轮子
</style>
```
---
## 禁止清单
| 类别 | ❌ 禁止 |
|------|--------|
| **样式** | 硬编码颜色/字号/间距/圆角 |
| **样式** | 在 scoped 中定义全局重置 |
| **样式** | scoped 超过 100 行 |
| **组件** | 自己写布局外壳(页头/侧栏/TabBar |
| **组件** | 裸用 `<img>` 代替图标组件 |
| **网络** | 直调 `fetch` / `axios` / `uni.request` |
| **网络** | 不处理错误态 |
| **状态** | 无加载态/空态 |
---
## 检查表
每完成一个前端任务,逐项自检:
- [ ] 使用项目统一布局组件
- [ ] 无硬编码颜色/字号/间距/圆角
- [ ] 图标全部走统一图标组件
- [ ] API 调用走统一封装,无裸调
- [ ] 页面/组件有加载态 + 空态 + 错误态
- [ ] 关键操作有 loading 态(防重复点击)
- [ ] `<style scoped>` 不超过 100 行
- [ ] PC/移动端均正常(如项目需响应式)
- [ ] 无禁止清单中的违规项

246
.claude/git.md Normal file
View File

@ -0,0 +1,246 @@
# 05 — Git 协作规则
> **适用**:所有 AI 和人工开发任务。分支怎么拉、什么时候合、冲突怎么处理。
---
## 分支模型
```
master ──────────────────────────────────────────→ 生产
└── test ──────────────────────────────────────→ 测试主线
└── dev ──────────────────────────────────────→ 开发主线
├── feature/任务编号-简短描述 → 新功能
├── bugfix/任务编号-简短描述 → Bug修复
└── hotfix/任务编号-简短描述 (从 master 拉) → 紧急修复
```
### 什么时候从哪拉
| 场景 | 从哪拉 | 合回哪 |
|------|--------|--------|
| 新功能 | `dev` | `dev` |
| Bug 修复 | `dev` | `dev` |
| 紧急线上修复 | `master` | `master` + `dev`(两边都要合) |
### 分支命名
```
feature/2688-用户批量导入 ← 禅道任务号 + 功能描述
bugfix/2690-任务列表分页错误
hotfix/2691-登录验证码绕过
禁止:
feature/xxx ← 没任务号
zhangsan-test ← 没意义
dev-backup-20260603 ← 备份不是分支
```
---
## 日常操作
### 开始一个新任务
```bash
# 1. 切到 dev拉最新
git checkout dev
git pull origin dev
# 2. 从 dev 拉分支
git checkout -b feature/任务号-功能描述
# 3. 开发...
# 4. 定期提交(不要攒到最后一次性交)
git add .
git commit -m "feat: xxx"
# 5. 推送到远程(每天至少推一次,防止本地丢失)
git push origin feature/任务号-功能描述
```
### 合并回 dev
```bash
# 1. 确保本地 dev 最新
git checkout dev
git pull origin dev
# 2. 切回自己的分支rebase dev让历史干净
git checkout feature/任务号-功能描述
git rebase dev
# 如果有冲突:
# - 逐个文件解决冲突
# - git add <解决的文件>
# - git rebase --continue
# - 直到完成
# 3. 推 rebase 后的分支(需要 --force-with-lease不是 --force
git push --force-with-lease origin feature/任务号-功能描述
# 4. 到 Git 平台提 PR/MR 到 dev
# PR 标题feat: 用户管理增加批量导入
# PR 描述:改了什么、怎么测试
```
### 冲突处理
```
1. git status 看哪些文件冲突
2. 打开冲突文件,找到 <<<<<<< ======= >>>>>>> 标记
3. 逐行确认保留哪边的代码
4. 删掉标记符号
5. git add <文件>
6. git rebase --continue 或 git merge --continue
7. 编译验证
```
**常见错误**:看到冲突整块接受一边,把别人的代码覆盖了。必须逐行看。
---
## AI 开发特殊规则
### AI 提交必须标注
```
feat: 用户管理增加批量导入 [AI: Claude Code]
- 新增 UserBatchImportController
- 新增 UserImportService
- 新增 import_batch 表
- 前端增加导入按钮和上传弹窗
```
**为什么**review 时知道这是 AI 写的,检查更仔细(尤其是边界条件和安全性)。
### AI 解决冲突
AI 遇到冲突时,不能自己决定保留哪边。必须:
1. 列出冲突内容和两边来源
2. 让人类决定
3. 人类确认后再继续
### AI 禁止操作
```bash
# ❌ AI 绝对禁止
git push --force origin dev # 强制推共享分支
git push --force origin master # 同上
git reset --hard HEAD~10 && git push -f # 回退远程历史
git commit --amend && git push -f # 修改已推送的 commit
```
---
## Commit 格式
```
<type>: <简短描述> [来源标注]
类型:
feat: 新功能
fix: Bug 修复
refactor: 重构(不改功能)
style: 格式调整
docs: 文档
test: 测试
chore: 构建/依赖/工具
来源标注AI 写的必须加):
[AI: Claude Code]
[AI: Kimi]
[AI: Codex]
正确示例:
feat: 用户管理增加批量导入 [AI: Claude Code]
fix: 任务列表分页总数计算错误
refactor: 提取公共校验逻辑到 BaseService
chore: 升级 Spring Boot 到 3.5.6
禁止示例:
update ← 无意义
fix bug ← 不知道修的哪个 bug
修改 ← 中文且无具体内容
WIP / temp / test ← 临时提交应该 squash 掉
```
---
## PR / MR 规范
### PR 必须包含
```markdown
## 改了什么
简要描述改动内容
## 关联任务
禅道 #2688 / 需求文档链接
## 测试方式
- [ ] 本地编译通过
- [ ] 接口测试通过
- [ ] 前端页面正常
## 契约变更
- [ ] 无接口变更
- [ ] 有接口变更 → 已更新 contract/ 目录 → 已通知前端
```
### Review 规则
- 至少一人 approve 才能合
- AI 生成的代码必须至少一人 review
- 改动超过 500 行 → 拆分 PR
---
## .gitignore 必备
```
# Java
target/
*.class
*.jar
*.war
# IDE
.idea/
*.iml
.vscode/
.settings/
.project
.classpath
# 前端
node_modules/
dist/
.env.local
# 系统
.DS_Store
Thumbs.db
# 敏感信息
*.pem
*.key
application-prod.yml (如果有密码)
```
---
## 检查表
- [ ] 分支从正确的源头拉(功能/Bug → dev紧急 → master
- [ ] 分支名包含任务号
- [ ] commit message 有意义
- [ ] AI 提交标注了来源
- [ ] push 前 rebase 了 dev
- [ ] 没用 `--force` 推共享分支
- [ ] 冲突逐行解决,没整块覆盖
- [ ] PR 有描述和测试说明

125
.claude/security.md Normal file
View File

@ -0,0 +1,125 @@
# 06 — 安全与日志规则
> **适用**:所有后端开发任务。
---
## 安全规则
### 认证检查(不变式)
每个需要登录的接口必须通过 Shiro 认证。JeecgBoot 默认全局拦截。
```java
// JeecgBoot 认证机制:
// 1. 所有 /sys/** /admin/** 默认需要登录
// 2. 白名单在 application.yml 配置
// 3. 权限注解:@RequiresPermissions / @RequiresRoles
// ✅ 接口加权限
@RequiresPermissions("user:add")
@PostMapping("/add")
public Result<?> add(...) { ... }
// ❌ 不要为了调试临时关认证
// ❌ 不要把需要认证的路径加到 shiro.excludeUrls
```
### 数据权限(租户隔离)
JeecgBoot 已通过 MyBatis-Plus 拦截器自动注入 tenant_id**但如果手写 SQL 必须手动加**。
```java
// ✅ 用 MyBatis-Plus 自动处理
taskService.list(new LambdaQueryWrapper<Task>().eq(Task::getStatus, "pending"));
// ❌ 手写 SQL 忘了加租户 → 租户A 看到租户B 的数据
@Select("SELECT * FROM task WHERE status = #{status}")
// ✅ 手写 SQL 正确写法
@Select("SELECT * FROM task WHERE status = #{status} AND tenant_id = #{tenantId}")
```
### 敏感信息保护
```java
// ✅ 密码字段不返回
@Entity
public class SysUser {
@JsonIgnore // ← 关键
private String password;
}
// ✅ 手机号脱敏返回VO 层处理)
userVO.setPhone(phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"));
```
### 文件上传安全
```java
// ✅ 必须校验JeecgBoot 已内置部分,确认开启)
- 文件类型白名单:只允许 jpg/png/pdf/doc/xlsx
- 文件大小上限:单文件 ≤ 10MB
- 文件名处理:用 UUID 重命名,防路径穿越
- 存储路径:不暴露原始文件名给前端
```
### 接口防刷
```java
// ✅ 敏感接口(登录/短信/支付)加限流
// JeecgBoot 使用 Sentinel 或自定义注解
@RateLimiter(count = 5, time = 60) // 60秒内最多5次
@PostMapping("/send-sms")
public Result<?> sendSms(...) { ... }
```
---
## 日志规则
### 日志级别
| 级别 | 场景 |
|------|------|
| ERROR | 数据库连不上、第三方接口失败、业务异常需要人工介入 |
| WARN | 降级处理、重试成功、配置缺失但有默认值 |
| INFO | 用户登录、任务创建、状态变更、定时任务执行 |
| DEBUG | 开发调试用,不入生产 |
### 日志内容
```java
// ✅ 关键操作
log.info("任务状态变更 taskId={} from={} to={} operator={}", taskId, oldStatus, newStatus, userId);
// ✅ 异常
log.error("调用第三方接口失败 url={}", url, e); // 最后一个参数是异常对象
// ❌ 不要这样
log.info("进来了");
log.info("user=" + user.toString()); // 可能打印密码
System.out.println("debug: " + xxx); // 永远不
```
### 禁止
| ❌ | 原因 |
|----|------|
| 循环里打 INFO | 日志爆炸 |
| 打印完整大对象 | 刷屏且可能泄露敏感数据 |
| 用 System.out.println | 不入日志文件 |
| 用 e.printStackTrace() | 不入日志框架 |
---
## 检查表
- [ ] 新接口需要认证(不在白名单中)
- [ ] 手写 SQL 加了租户隔离
- [ ] 敏感字段不返回给前端
- [ ] 文件上传有类型/大小校验
- [ ] 关键操作有 INFO 日志
- [ ] 异常有 ERROR 日志(带栈)
- [ ] 无硬编码密码/密钥

146
.claude/testing.md Normal file
View File

@ -0,0 +1,146 @@
# 07 — 测试与错误处理规则
> **适用**所有后端开发任务。AI 最容易忽略的两件事:写测试、统一错误码。
---
## 测试规则
### 必须写测试的场景
| 场景 | 测试类型 | 要求 |
|------|----------|------|
| 新增 Service 方法 | 单元测试 | 必须 |
| 新增 Controller 接口 | 集成测试 | 必须 |
| 涉及金额计算 | 单元测试 | 必须(多组边界值) |
| 涉及状态流转 | 单元测试 | 必须(覆盖所有状态路径) |
| 涉及外部接口调用 | 集成测试 | 必须Mock 外部依赖) |
| 修 Bug | 回归测试 | 必须(防止复现) |
| 简单 CRUD | 可选 | 不强制 |
### AI 测试规则
**AI 完成任务后,必须同时交付测试代码。** 禁止说"功能写好了但测试你自己补"。
```java
// ✅ AI 必须产出完整测试
@SpringBootTest
class TaskServiceTest {
@Autowired
private TaskService taskService;
@Test
void shouldCreateTask() { ... }
@Test
void shouldNotCreateTaskWithoutTitle() { ... }
@Test
void shouldCalculateRewardCorrectly() { ... }
}
// ❌ AI 不能这样
// "功能已完成,测试代码未编写,建议后续补充"
```
### 测试命名
```
方法名_场景_预期结果
shouldCreateTask_whenValidInput
shouldNotCreateTask_whenTitleIsEmpty
shouldCalculateReward_whenDiscountApplied
shouldReturnFail_whenTaskNotFound
```
### 禁止
- 测试只测正常路径,不测异常路径
- 测试依赖执行顺序(每个测试必须独立)
- 测试里有 `Thread.sleep()`(用 Awaitility
- 测试代码复制粘贴业务代码(测了个寂寞)
---
## 错误处理规则
### 返回格式(不变式)
```java
// ✅ 统一返回
Result.ok(data) // 成功
Result.error("原因") // 业务失败(用户可见)
Result.error("系统错误") // 系统异常(用户不可见,只记录日志)
// JSON 格式
{ "code": 200, "message": "success", "data": {...} } // 成功
{ "code": 500, "message": "任务不存在", "data": null } // 业务失败
{ "code": 500, "message": "系统繁忙,请稍后重试", "data": null } // 系统异常
```
### 什么情况返回什么
```java
// 查不到 → 业务失败
if (task == null) return Result.error("任务不存在");
// 参数不合法 → 业务失败
if (page < 1) return Result.error("页码必须大于0");
// 没权限 → 业务失败
if (!hasPermission) return Result.error("无权操作");
// 数据库连接失败 → 系统异常(全局异常处理器统一捕获)
// 第三方接口超时 → 系统异常(记录日志 + 返回通用错误)
// 不需要手动 try-catch 每个地方,用全局异常处理器统一处理
```
### 全局异常处理器
```java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result<?> handleBusiness(BusinessException e) {
log.warn("业务异常: {}", e.getMessage());
return Result.error(e.getMessage()); // 用户可见
}
@ExceptionHandler(Exception.class)
public Result<?> handleException(Exception e) {
log.error("系统异常", e);
return Result.error("系统繁忙,请稍后重试"); // 用户不可见原文
}
}
```
### 禁止
| ❌ | ✅ |
|----|-----|
| `return Result.error(e.getMessage())` 把栈打印给用户 | `return Result.error("任务不存在")` |
| 每个方法都 try-catch 一遍 | 全局异常处理器统一处理 |
| 异常被吞掉 `catch(Exception e) {}` | 至少打日志 `log.error("xxx", e)` |
| `return null` 让前端爆 NPE | `return Result.error("原因")` |
| 成功和失败都用 code=200 | 失败用 code=500 |
---
## 检查表
### 测试
- [ ] 新增的非 CRUD 方法有测试
- [ ] 测试覆盖了异常路径(不是只测正常路径)
- [ ] 金额计算有边界值测试
- [ ] AI 交付包含测试代码
### 错误处理
- [ ] 错误信息用户能看懂(不是技术术语)
- [ ] 系统异常不暴露内部信息给用户
- [ ] 查不到/null 有明确 fail 返回
- [ ] 没有空 catch 块
- [ ] 没有 `return null` 代替错误返回

57
.claudeignore Normal file
View File

@ -0,0 +1,57 @@
# Git
.git/
.gitignore
.gitmodules
# SVN
.svn/
# IntelliJ IDEA
.idea/
*.iml
*.iws
*.ipr
out/
# Eclipse
.classpath
.project
.settings/
# VS Code
.vscode/
# Maven / Gradle build output
target/
build/
!.mvn/wrapper/maven-wrapper.jar
# OS files
.DS_Store
Thumbs.db
desktop.ini
# Logs
*.log
logs/
# Node (frontend artifacts if any)
node_modules/
dist/
# Docker volumes / data
docker/data/
# Compiled classes
*.class
# Custom
*.qqy
代码修改.log
代码修改日志
*.zip
backup/
.history/
.cursor/
doc/
docs/

121
.cursorrules Normal file
View File

@ -0,0 +1,121 @@
# ghb-base — AI 开发总纲
> coverage: 聚合版,可直接粘贴到 Kimi / DeepSeek / GPT 等无自动加载的 AI 对话中。自动加载工具Claude Code / Cursor / Codex / Copilot / Windsurf会从对应入口文件读取。
>
> 所有 AI 编码助手首先读本文件。读完本文件后,必须继续读取 `.claude/` 目录下的全部规则文件,再开始编写代码。
## 项目信息
- **名称**ghb-base基于 JeecgBoot 3.9.2 精简)
- **技术栈**Spring Boot 3.5.5 / Java 17 / MyBatis-Plus / Shiro+JWT / MySQL / Redis
- **包名**`com.ghb.base` | 数据库:`ghb_base` | 上下文路径:`/ghb`
- **前端**Vue 3 + Vite + Ant Design Vue + TypeScript
## 模块
```
ghb-base-parent/
├── ghb-base-core/ # 核心框架(不改)
├── ghb-module-system/ # 租户/用户/角色/菜单/字典/日志(不改)
├── ghb-module-business/ # 业务代码 ← 写这里
└── ghb-server-cloud/ # 微服务(网关/Nacos/监控)
```
## 必读规则(全部读完再写代码)
1. `.claude/contract.md` — 返回格式 · 分页 · 状态值 · 契约生命周期 · 同步铁律
2. `.claude/backend.md` — 分层 · 命名 · 注释 · CRUD 模板 · 禁止清单 · 检查表
3. `.claude/frontend.md` — 样式 · 命名 · 注释 · 组件铁律 · 页面模板 · 检查表
4. `.claude/database.md` — 表设计 · 字段类型 · 索引 · 必有字段 · 检查表
5. `.claude/git.md` — 分支命名 · commit 格式 · AI 提交标注 · 禁止操作
6. `.claude/security.md` — 认证 · 数据权限 · 敏感信息 · 文件上传 · 日志
7. `.claude/testing.md` — 测试要求 · 错误码 · 全局异常处理 · 检查表
8. `.claude/encoding.md` — 字符编码UTF-8 无 BOM · 禁 GBK · 批量改写须 UTF-8 感知)
## 接口契约
契约文件在 `contract/` 目录(独立 git 仓库,前后端 submodule 引用)。修改接口前必须先更新契约。
## 环境要求
- JDK 17+
- Maven 3.8+
- MySQL 8.0+
- Redis 6.0+
- Node.js 18+ / pnpm
```bash
# 后端启动
cd test-module-system/test-system-start
mvn spring-boot:run -Pdev
# 前端启动
cd ghb-frontend
pnpm install
pnpm dev
```
## 多工具兼容
| 工具 | 入口文件 | 操作 |
|------|----------|------|
| Claude Code | `CLAUDE.md` | 自动加载,无需操作 |
| Cursor | `.cursorrules` | 自动加载,无需操作 |
| Codex / OpenCode | `AGENTS.md` | 自动加载,无需操作 |
| GitHub Copilot | `.github/copilot-instructions.md` | 自动加载,无需操作 |
| Windsurf | `.windsurfrules` | 自动加载(把 CLAUDE.md 复制一份改名为 .windsurfrules |
| **Kimi / DeepSeek / GPT** | 无自动 | **手动粘贴**:把本文件 + `.claude/` 全部内容一次性贴到对话开头 |
| **人工开发** | 无自动 | 阅读 `开发规范-人工版.md`(同级目录) |
---
## 审查机制 — 怎么检查代码是否合规
### 自动化检查(提交前自动跑)
```bash
# 后端代码检查
mvn checkstyle:check # Java 代码风格
mvn test # 单元测试
# 前端代码检查
pnpm lint # ESLint
pnpm stylelint # 样式检查
# 数据库 SQL 检查
# 人工 review金额字段是不是 DECIMAL、有没有 COMMENT、有没有 create_time
```
### AI 产出专项检查
| 检查项 | 怎么查 | 不合格的表现 |
|--------|--------|-------------|
| 返回值是否包装 | `grep -r "return [^R]" --include="*.java" | grep -v Result` | 出现裸 `return data;` |
| 手写 SQL 是否带租户 | `grep -r "@Select" --include="*.java"` | SQL 里没有 `tenant_id` |
| Controller 是否写了业务逻辑 | 人工 review Controller 方法体 | 超过 20 行或有 `if/else` 嵌套 |
| Commit 是否标注 AI | `git log --oneline -20` | 没有 `[AI: xxx]` 标记 |
| 是否包含测试 | `git diff --stat` 看是否有测试文件 | 新功能没测试类 |
### PR Review 必查清单
```
□ 接口变更 → contract/ 目录有对应更新
□ 返回格式 → 全部用 Result 包装
□ 手写 SQL → 有 tenant_id 条件
□ 新增表 → 有 create_time / update_time / COMMENT
□ 金额字段 → 类型是 DECIMAL
□ 硬编码 → 无魔法数字、无裸色值、无裸字号
□ Commit → 格式正确AI 代码有标注
□ 测试 → 新功能有测试代码
```
### 常见违规信号
| 信号 | 可能的问题 |
|------|-----------|
| `return data;`(裸返回) | 没读规则 |
| `@Select("SELECT * FROM xxx WHERE ...")` 缺 tenant_id | 数据泄露风险 |
| 类名超过 35 字符 | 没缩写 |
| `color: #` 硬编码 | 没读前端规则 |
| `git commit -m "update"` | 没读 Git 规则 |
| Controller 超过 100 行 | 业务逻辑泄漏到接口层 |

10
.dockerignore Normal file
View File

@ -0,0 +1,10 @@
.git
deploy
*.md
.github
.claude
.cursorrules
.windsurfrules
.idea
.vscode
*.iml

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
[*.md]
trim_trailing_whitespace = false

121
.github/copilot-instructions.md vendored Normal file
View File

@ -0,0 +1,121 @@
# ghb-base — AI 开发总纲
> coverage: 聚合版,可直接粘贴到 Kimi / DeepSeek / GPT 等无自动加载的 AI 对话中。自动加载工具Claude Code / Cursor / Codex / Copilot / Windsurf会从对应入口文件读取。
>
> 所有 AI 编码助手首先读本文件。读完本文件后,必须继续读取 `.claude/` 目录下的全部规则文件,再开始编写代码。
## 项目信息
- **名称**ghb-base基于 JeecgBoot 3.9.2 精简)
- **技术栈**Spring Boot 3.5.5 / Java 17 / MyBatis-Plus / Shiro+JWT / MySQL / Redis
- **包名**`com.ghb.base` | 数据库:`ghb_base` | 上下文路径:`/ghb`
- **前端**Vue 3 + Vite + Ant Design Vue + TypeScript
## 模块
```
ghb-base-parent/
├── ghb-base-core/ # 核心框架(不改)
├── ghb-module-system/ # 租户/用户/角色/菜单/字典/日志(不改)
├── ghb-module-business/ # 业务代码 ← 写这里
└── ghb-server-cloud/ # 微服务(网关/Nacos/监控)
```
## 必读规则(全部读完再写代码)
1. `.claude/contract.md` — 返回格式 · 分页 · 状态值 · 契约生命周期 · 同步铁律
2. `.claude/backend.md` — 分层 · 命名 · 注释 · CRUD 模板 · 禁止清单 · 检查表
3. `.claude/frontend.md` — 样式 · 命名 · 注释 · 组件铁律 · 页面模板 · 检查表
4. `.claude/database.md` — 表设计 · 字段类型 · 索引 · 必有字段 · 检查表
5. `.claude/git.md` — 分支命名 · commit 格式 · AI 提交标注 · 禁止操作
6. `.claude/security.md` — 认证 · 数据权限 · 敏感信息 · 文件上传 · 日志
7. `.claude/testing.md` — 测试要求 · 错误码 · 全局异常处理 · 检查表
8. `.claude/encoding.md` — 字符编码UTF-8 无 BOM · 禁 GBK · 批量改写须 UTF-8 感知)
## 接口契约
契约文件在 `contract/` 目录(独立 git 仓库,前后端 submodule 引用)。修改接口前必须先更新契约。
## 环境要求
- JDK 17+
- Maven 3.8+
- MySQL 8.0+
- Redis 6.0+
- Node.js 18+ / pnpm
```bash
# 后端启动
cd test-module-system/test-system-start
mvn spring-boot:run -Pdev
# 前端启动
cd ghb-frontend
pnpm install
pnpm dev
```
## 多工具兼容
| 工具 | 入口文件 | 操作 |
|------|----------|------|
| Claude Code | `CLAUDE.md` | 自动加载,无需操作 |
| Cursor | `.cursorrules` | 自动加载,无需操作 |
| Codex / OpenCode | `AGENTS.md` | 自动加载,无需操作 |
| GitHub Copilot | `.github/copilot-instructions.md` | 自动加载,无需操作 |
| Windsurf | `.windsurfrules` | 自动加载(把 CLAUDE.md 复制一份改名为 .windsurfrules |
| **Kimi / DeepSeek / GPT** | 无自动 | **手动粘贴**:把本文件 + `.claude/` 全部内容一次性贴到对话开头 |
| **人工开发** | 无自动 | 阅读 `开发规范-人工版.md`(同级目录) |
---
## 审查机制 — 怎么检查代码是否合规
### 自动化检查(提交前自动跑)
```bash
# 后端代码检查
mvn checkstyle:check # Java 代码风格
mvn test # 单元测试
# 前端代码检查
pnpm lint # ESLint
pnpm stylelint # 样式检查
# 数据库 SQL 检查
# 人工 review金额字段是不是 DECIMAL、有没有 COMMENT、有没有 create_time
```
### AI 产出专项检查
| 检查项 | 怎么查 | 不合格的表现 |
|--------|--------|-------------|
| 返回值是否包装 | `grep -r "return [^R]" --include="*.java" | grep -v Result` | 出现裸 `return data;` |
| 手写 SQL 是否带租户 | `grep -r "@Select" --include="*.java"` | SQL 里没有 `tenant_id` |
| Controller 是否写了业务逻辑 | 人工 review Controller 方法体 | 超过 20 行或有 `if/else` 嵌套 |
| Commit 是否标注 AI | `git log --oneline -20` | 没有 `[AI: xxx]` 标记 |
| 是否包含测试 | `git diff --stat` 看是否有测试文件 | 新功能没测试类 |
### PR Review 必查清单
```
□ 接口变更 → contract/ 目录有对应更新
□ 返回格式 → 全部用 Result 包装
□ 手写 SQL → 有 tenant_id 条件
□ 新增表 → 有 create_time / update_time / COMMENT
□ 金额字段 → 类型是 DECIMAL
□ 硬编码 → 无魔法数字、无裸色值、无裸字号
□ Commit → 格式正确AI 代码有标注
□ 测试 → 新功能有测试代码
```
### 常见违规信号
| 信号 | 可能的问题 |
|------|-----------|
| `return data;`(裸返回) | 没读规则 |
| `@Select("SELECT * FROM xxx WHERE ...")` 缺 tenant_id | 数据泄露风险 |
| 类名超过 35 字符 | 没缩写 |
| `color: #` 硬编码 | 没读前端规则 |
| `git commit -m "update"` | 没读 Git 规则 |
| Controller 超过 100 行 | 业务逻辑泄漏到接口层 |

33
.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
# 依赖与构建产物
**/node_modules/
**/dist/
**/target/
**/logs/
**/.cache/
**/docs/tasks/
**/docs/tasks/**
**/.eslintcache
# IDE / 编辑器
.idea/
**/*.iml
.vscode/
.history/
*.suo
*.sw?
# 独立子仓库(接口契约按规则单独管理,不并入主仓库)
# 本地环境与日志
**/.env.local
**/.env.*.local
**/*-debug.log*
**/docs/tasks/**
rebel.xml
derby.log
# 系统
.DS_Store
Thumbs.db
os_del.cmd
os_del_doc.cmd

121
.windsurfrules Normal file
View File

@ -0,0 +1,121 @@
# ghb-base — AI 开发总纲
> coverage: 聚合版,可直接粘贴到 Kimi / DeepSeek / GPT 等无自动加载的 AI 对话中。自动加载工具Claude Code / Cursor / Codex / Copilot / Windsurf会从对应入口文件读取。
>
> 所有 AI 编码助手首先读本文件。读完本文件后,必须继续读取 `.claude/` 目录下的全部规则文件,再开始编写代码。
## 项目信息
- **名称**ghb-base基于 JeecgBoot 3.9.2 精简)
- **技术栈**Spring Boot 3.5.5 / Java 17 / MyBatis-Plus / Shiro+JWT / MySQL / Redis
- **包名**`com.ghb.base` | 数据库:`ghb_base` | 上下文路径:`/ghb`
- **前端**Vue 3 + Vite + Ant Design Vue + TypeScript
## 模块
```
ghb-base-parent/
├── ghb-base-core/ # 核心框架(不改)
├── ghb-module-system/ # 租户/用户/角色/菜单/字典/日志(不改)
├── ghb-module-business/ # 业务代码 ← 写这里
└── ghb-server-cloud/ # 微服务(网关/Nacos/监控)
```
## 必读规则(全部读完再写代码)
1. `.claude/contract.md` — 返回格式 · 分页 · 状态值 · 契约生命周期 · 同步铁律
2. `.claude/backend.md` — 分层 · 命名 · 注释 · CRUD 模板 · 禁止清单 · 检查表
3. `.claude/frontend.md` — 样式 · 命名 · 注释 · 组件铁律 · 页面模板 · 检查表
4. `.claude/database.md` — 表设计 · 字段类型 · 索引 · 必有字段 · 检查表
5. `.claude/git.md` — 分支命名 · commit 格式 · AI 提交标注 · 禁止操作
6. `.claude/security.md` — 认证 · 数据权限 · 敏感信息 · 文件上传 · 日志
7. `.claude/testing.md` — 测试要求 · 错误码 · 全局异常处理 · 检查表
8. `.claude/encoding.md` — 字符编码UTF-8 无 BOM · 禁 GBK · 批量改写须 UTF-8 感知)
## 接口契约
契约文件在 `contract/` 目录(独立 git 仓库,前后端 submodule 引用)。修改接口前必须先更新契约。
## 环境要求
- JDK 17+
- Maven 3.8+
- MySQL 8.0+
- Redis 6.0+
- Node.js 18+ / pnpm
```bash
# 后端启动
cd test-module-system/test-system-start
mvn spring-boot:run -Pdev
# 前端启动
cd ghb-frontend
pnpm install
pnpm dev
```
## 多工具兼容
| 工具 | 入口文件 | 操作 |
|------|----------|------|
| Claude Code | `CLAUDE.md` | 自动加载,无需操作 |
| Cursor | `.cursorrules` | 自动加载,无需操作 |
| Codex / OpenCode | `AGENTS.md` | 自动加载,无需操作 |
| GitHub Copilot | `.github/copilot-instructions.md` | 自动加载,无需操作 |
| Windsurf | `.windsurfrules` | 自动加载(把 CLAUDE.md 复制一份改名为 .windsurfrules |
| **Kimi / DeepSeek / GPT** | 无自动 | **手动粘贴**:把本文件 + `.claude/` 全部内容一次性贴到对话开头 |
| **人工开发** | 无自动 | 阅读 `开发规范-人工版.md`(同级目录) |
---
## 审查机制 — 怎么检查代码是否合规
### 自动化检查(提交前自动跑)
```bash
# 后端代码检查
mvn checkstyle:check # Java 代码风格
mvn test # 单元测试
# 前端代码检查
pnpm lint # ESLint
pnpm stylelint # 样式检查
# 数据库 SQL 检查
# 人工 review金额字段是不是 DECIMAL、有没有 COMMENT、有没有 create_time
```
### AI 产出专项检查
| 检查项 | 怎么查 | 不合格的表现 |
|--------|--------|-------------|
| 返回值是否包装 | `grep -r "return [^R]" --include="*.java" | grep -v Result` | 出现裸 `return data;` |
| 手写 SQL 是否带租户 | `grep -r "@Select" --include="*.java"` | SQL 里没有 `tenant_id` |
| Controller 是否写了业务逻辑 | 人工 review Controller 方法体 | 超过 20 行或有 `if/else` 嵌套 |
| Commit 是否标注 AI | `git log --oneline -20` | 没有 `[AI: xxx]` 标记 |
| 是否包含测试 | `git diff --stat` 看是否有测试文件 | 新功能没测试类 |
### PR Review 必查清单
```
□ 接口变更 → contract/ 目录有对应更新
□ 返回格式 → 全部用 Result 包装
□ 手写 SQL → 有 tenant_id 条件
□ 新增表 → 有 create_time / update_time / COMMENT
□ 金额字段 → 类型是 DECIMAL
□ 硬编码 → 无魔法数字、无裸色值、无裸字号
□ Commit → 格式正确AI 代码有标注
□ 测试 → 新功能有测试代码
```
### 常见违规信号
| 信号 | 可能的问题 |
|------|-----------|
| `return data;`(裸返回) | 没读规则 |
| `@Select("SELECT * FROM xxx WHERE ...")` 缺 tenant_id | 数据泄露风险 |
| 类名超过 35 字符 | 没缩写 |
| `color: #` 硬编码 | 没读前端规则 |
| `git commit -m "update"` | 没读 Git 规则 |
| Controller 超过 100 行 | 业务逻辑泄漏到接口层 |

121
AGENTS.md Normal file
View File

@ -0,0 +1,121 @@
# ghb-base — AI 开发总纲
> coverage: 聚合版,可直接粘贴到 Kimi / DeepSeek / GPT 等无自动加载的 AI 对话中。自动加载工具Claude Code / Cursor / Codex / Copilot / Windsurf会从对应入口文件读取。
>
> 所有 AI 编码助手首先读本文件。读完本文件后,必须继续读取 `.claude/` 目录下的全部规则文件,再开始编写代码。
## 项目信息
- **名称**ghb-base基于 JeecgBoot 3.9.2 精简)
- **技术栈**Spring Boot 3.5.5 / Java 17 / MyBatis-Plus / Shiro+JWT / MySQL / Redis
- **包名**`com.ghb.base` | 数据库:`ghb_base` | 上下文路径:`/ghb`
- **前端**Vue 3 + Vite + Ant Design Vue + TypeScript
## 模块
```
ghb-base-parent/
├── ghb-base-core/ # 核心框架(不改)
├── ghb-module-system/ # 租户/用户/角色/菜单/字典/日志(不改)
├── ghb-module-business/ # 业务代码 ← 写这里
└── ghb-server-cloud/ # 微服务(网关/Nacos/监控)
```
## 必读规则(全部读完再写代码)
1. `.claude/contract.md` — 返回格式 · 分页 · 状态值 · 契约生命周期 · 同步铁律
2. `.claude/backend.md` — 分层 · 命名 · 注释 · CRUD 模板 · 禁止清单 · 检查表
3. `.claude/frontend.md` — 样式 · 命名 · 注释 · 组件铁律 · 页面模板 · 检查表
4. `.claude/database.md` — 表设计 · 字段类型 · 索引 · 必有字段 · 检查表
5. `.claude/git.md` — 分支命名 · commit 格式 · AI 提交标注 · 禁止操作
6. `.claude/security.md` — 认证 · 数据权限 · 敏感信息 · 文件上传 · 日志
7. `.claude/testing.md` — 测试要求 · 错误码 · 全局异常处理 · 检查表
8. `.claude/encoding.md` — 字符编码UTF-8 无 BOM · 禁 GBK · 批量改写须 UTF-8 感知)
## 接口契约
契约文件在 `contract/` 目录(独立 git 仓库,前后端 submodule 引用)。修改接口前必须先更新契约。
## 环境要求
- JDK 17+
- Maven 3.8+
- MySQL 8.0+
- Redis 6.0+
- Node.js 18+ / pnpm
```bash
# 后端启动
cd test-module-system/test-system-start
mvn spring-boot:run -Pdev
# 前端启动
cd ghb-frontend
pnpm install
pnpm dev
```
## 多工具兼容
| 工具 | 入口文件 | 操作 |
|------|----------|------|
| Claude Code | `CLAUDE.md` | 自动加载,无需操作 |
| Cursor | `.cursorrules` | 自动加载,无需操作 |
| Codex / OpenCode | `AGENTS.md` | 自动加载,无需操作 |
| GitHub Copilot | `.github/copilot-instructions.md` | 自动加载,无需操作 |
| Windsurf | `.windsurfrules` | 自动加载(把 CLAUDE.md 复制一份改名为 .windsurfrules |
| **Kimi / DeepSeek / GPT** | 无自动 | **手动粘贴**:把本文件 + `.claude/` 全部内容一次性贴到对话开头 |
| **人工开发** | 无自动 | 阅读 `开发规范-人工版.md`(同级目录) |
---
## 审查机制 — 怎么检查代码是否合规
### 自动化检查(提交前自动跑)
```bash
# 后端代码检查
mvn checkstyle:check # Java 代码风格
mvn test # 单元测试
# 前端代码检查
pnpm lint # ESLint
pnpm stylelint # 样式检查
# 数据库 SQL 检查
# 人工 review金额字段是不是 DECIMAL、有没有 COMMENT、有没有 create_time/update_time/del_flag
```
### AI 产出专项检查
| 检查项 | 怎么查 | 不合格的表现 |
|--------|--------|-------------|
| 返回值是否包装 | `grep -r "return [^R]" --include="*.java" | grep -v Result` | 出现裸 `return data;` |
| 手写 SQL 是否带租户 | `grep -r "@Select" --include="*.java"` | SQL 里没有 `tenant_id` |
| Controller 是否写了业务逻辑 | 人工 review Controller 方法体 | 超过 20 行或有 `if/else` 嵌套 |
| Commit 是否标注 AI | `git log --oneline -20` | 没有 `[AI: xxx]` 标记 |
| 是否包含测试 | `git diff --stat` 看是否有测试文件 | 新功能没测试类 |
### PR Review 必查清单
```
□ 接口变更 → contract/ 目录有对应更新
□ 返回格式 → 全部用 Result 包装
□ 手写 SQL → 有 tenant_id 条件
□ 新增表 → 有 create_time / update_time / del_flag / COMMENT
□ 金额字段 → 类型是 DECIMAL
□ 硬编码 → 无魔法数字、无裸色值、无裸字号
□ Commit → 格式正确AI 代码有标注
□ 测试 → 新功能有测试代码
```
### 常见违规信号
| 信号 | 可能的问题 |
|------|-----------|
| `return data;`(裸返回) | 没读规则 |
| `@Select("SELECT * FROM xxx WHERE ...")` 缺 tenant_id | 数据泄露风险 |
| 类名超过 35 字符 | 没缩写 |
| `color: #` 硬编码 | 没读前端规则 |
| `git commit -m "update"` | 没读 Git 规则 |
| Controller 超过 100 行 | 业务逻辑泄漏到接口层 |

119
CLAUDE.md Normal file
View File

@ -0,0 +1,119 @@
# ghb-base — AI 开发总纲
> 所有 AI 编码助手首先读本文件。读完本文件后,必须继续读取 `.claude/` 目录下的全部规则文件,再开始编写代码。
## 项目信息
- **名称**ghb-base基于 JeecgBoot 3.9.2 精简)
- **技术栈**Spring Boot 3.5.5 / Java 17 / MyBatis-Plus / Shiro+JWT / MySQL / Redis
- **包名**`com.ghb.base` | 数据库:`ghb_base` | 上下文路径:`/ghb`
- **前端**Vue 3 + Vite + Ant Design Vue + TypeScript
## 模块
```
ghb-base-parent/
├── ghb-base-core/ # 核心框架(不改)
├── ghb-module-system/ # 租户/用户/角色/菜单/字典/日志(不改)
├── ghb-module-business/ # 业务代码 ← 写这里
└── ghb-server-cloud/ # 微服务(网关/Nacos/监控)
```
## 必读规则(全部读完再写代码)
1. `.claude/contract.md` — 返回格式 · 分页 · 状态值 · 契约生命周期 · 同步铁律
2. `.claude/backend.md` — 分层 · 命名 · 注释 · CRUD 模板 · 禁止清单 · 检查表
3. `.claude/frontend.md` — 样式 · 命名 · 注释 · 组件铁律 · 页面模板 · 检查表
4. `.claude/database.md` — 表设计 · 字段类型 · 索引 · 必有字段 · 检查表
5. `.claude/git.md` — 分支命名 · commit 格式 · AI 提交标注 · 禁止操作
6. `.claude/security.md` — 认证 · 数据权限 · 敏感信息 · 文件上传 · 日志
7. `.claude/testing.md` — 测试要求 · 错误码 · 全局异常处理 · 检查表
8. `.claude/encoding.md` — 字符编码(一律 UTF-8 无 BOM · 禁 GBK · 批量改写须 UTF-8 感知 · mojibake 还原)
## 接口契约
契约文件在 `contract/` 目录(独立 git 仓库,前后端 submodule 引用)。修改接口前必须先更新契约。
## 环境要求
- JDK 17+
- Maven 3.8+
- MySQL 8.0+
- Redis 6.0+
- Node.js 18+ / pnpm
```bash
# 后端启动
cd test-module-system/test-system-start
mvn spring-boot:run -Pdev
# 前端启动
cd ghb-frontend
pnpm install
pnpm dev
```
## 多工具兼容
| 工具 | 入口文件 | 操作 |
|------|----------|------|
| Claude Code | `CLAUDE.md` | 自动加载,无需操作 |
| Cursor | `.cursorrules` | 自动加载,无需操作 |
| Codex / OpenCode | `AGENTS.md` | 自动加载,无需操作 |
| GitHub Copilot | `.github/copilot-instructions.md` | 自动加载,无需操作 |
| Windsurf | `.windsurfrules` | 自动加载(把 CLAUDE.md 复制一份改名为 .windsurfrules |
| **Kimi / DeepSeek / GPT** | 无自动 | **手动粘贴**:把本文件 + `.claude/` 全部内容一次性贴到对话开头 |
| **人工开发** | 无自动 | 阅读 `开发规范-人工版.md`(同级目录) |
---
## 审查机制 — 怎么检查代码是否合规
### 自动化检查(提交前自动跑)
```bash
# 后端代码检查
mvn checkstyle:check # Java 代码风格
mvn test # 单元测试
# 前端代码检查
pnpm lint # ESLint
pnpm stylelint # 样式检查
# 数据库 SQL 检查
# 人工 review金额字段是不是 DECIMAL、有没有 COMMENT、有没有 create_time
```
### AI 产出专项检查
| 检查项 | 怎么查 | 不合格的表现 |
|--------|--------|-------------|
| 返回值是否包装 | `grep -r "return [^R]" --include="*.java" | grep -v Result` | 出现裸 `return data;` |
| 手写 SQL 是否带租户 | `grep -r "@Select" --include="*.java"` | SQL 里没有 `tenant_id` |
| Controller 是否写了业务逻辑 | 人工 review Controller 方法体 | 超过 20 行或有 `if/else` 嵌套 |
| Commit 是否标注 AI | `git log --oneline -20` | 没有 `[AI: xxx]` 标记 |
| 是否包含测试 | `git diff --stat` 看是否有测试文件 | 新功能没测试类 |
### PR Review 必查清单
```
□ 接口变更 → contract/ 目录有对应更新
□ 返回格式 → 全部用 Result 包装
□ 手写 SQL → 有 tenant_id 条件
□ 新增表 → 有 create_time / update_time / COMMENT
□ 金额字段 → 类型是 DECIMAL
□ 硬编码 → 无魔法数字、无裸色值、无裸字号
□ Commit → 格式正确AI 代码有标注
□ 测试 → 新功能有测试代码
```
### 常见违规信号
| 信号 | 可能的问题 |
|------|-----------|
| `return data;`(裸返回) | 没读规则 |
| `@Select("SELECT * FROM xxx WHERE ...")` 缺 tenant_id | 数据泄露风险 |
| 类名超过 35 字符 | 没缩写 |
| `color: #` 硬编码 | 没读前端规则 |
| `git commit -m "update"` | 没读 Git 规则 |
| Controller 超过 100 行 | 业务逻辑泄漏到接口层 |

52
Dockerfile Normal file
View File

@ -0,0 +1,52 @@
# ============================================
# 后端 Docker 镜像 — 多阶段构建
# Stage 1: Maven 编译(不污染宿主)
# Stage 2: JRE 运行(精简镜像)
# ============================================
# ---- Stage 1: 编译 ----
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
# 先复制 pom.xml利用 Docker 缓存加速依赖下载
COPY pom.xml ./
COPY test-base-core/pom.xml test-base-core/
COPY test-module-system/pom.xml test-module-system/
COPY test-module-system/test-system-api/pom.xml test-module-system/test-system-api/
COPY test-module-system/test-system-biz/pom.xml test-module-system/test-system-biz/
COPY test-module-system/test-system-start/pom.xml test-module-system/test-system-start/
COPY test-module-business/pom.xml test-module-business/
# 下载依赖(这一层在 pom 不变时会被缓存)
RUN mvn dependency:go-offline -B || true
# 复制全部源码
COPY . .
# 编译打包(跳过测试)
RUN mvn package -Dmaven.test.skip=true -T 1C
# ---- Stage 2: 运行 ----
FROM eclipse-temurin:17-jre
LABEL maintainer="ghb-base deploy"
# 时区Debian 系)
ENV TZ=Asia/Shanghai
RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime
WORKDIR /app
# 从编译阶段复制 fat jar
COPY --from=builder /build/test-module-system/test-system-start/target/*.jar app.jar
# 上传文件目录
RUN mkdir -p /opt/upFiles
EXPOSE 8081
# JVM 参数可通过环境变量 JAVA_OPTS 覆盖
ENV JAVA_OPTS="-Xms512m -Xmx512m"
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

213
LICENSE Normal file
View File

@ -0,0 +1,213 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (c) 2019 <a href="http://www.jeecg.com">Jeecg Boot</a> All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
In any case, you must not make any such use of this software as to develop software which may be considered competitive with this software.
JeecgBoot 是由 北京国炬信息技术有限公司 发行的软件。 总部位于北京地址中国·北京·朝阳区科荟前街1号院奥林佳泰大厦。邮箱jeecgos@163.com
本软件受适用的国家软件著作权法(包括国际条约)和开源协议 双重保护许可。
开源协议中文释意如下:
1.JeecgBoot开源版本无任何限制在遵循本开源协议条款下允许商用使用不会造成侵权行为。
2.允许基于本平台软件开展业务系统开发。
3.在任何情况下,您不得使用本软件开发可能被认为与本软件竞争的软件。
最终解释权归http://www.jeecg.com

44
README.md Normal file
View File

@ -0,0 +1,44 @@
# ghb-base 后端jeecg-boot
> 这是 **ghb-base 基础框架**的后端。以后每个新系统都基于本框架开发——业务代码只写在 `ghb-module-business`框架层core/system不改。
> 基于 JeecgBoot 3.9.2 精简。
## 技术栈
- Spring Boot 3.5.5 / Java **17**
- MyBatis-Plus / Shiro + JWT / MySQL 8 / Redis 6
- 包名 `com.ghb.base` 数据库 `ghb_base` 上下文路径 `/ghb`
## 环境要求
- JDK 17+、Maven 3.8+、MySQL 8.0+、Redis 6.0+
## 第一次启动
1. 建库 `ghb_base`,导入初始化脚本:`../ghb_base_init.sql`(在 base 根目录)
2. 改连接:`ghb-module-system/ghb-system-start/src/main/resources/application-dev.yml`MySQL、Redis 地址)
3. 启动:
```bash
cd ghb-module-system/ghb-system-start
mvn spring-boot:run -Pdev
```
主类 `com.ghb.base.GhbSystemApplication`,端口 **8080**,访问前缀 `/ghb`(如 `http://localhost:8080/ghb`)。
## 模块结构(哪能改哪不能改)
```
jeecg-boot/
├── ghb-base-core/ 核心框架 ← 不改
├── ghb-module-system/ 租户/用户/角色/菜单/字典/日志 ← 不改
│ └── ghb-system-start/ 启动模块(含 application*.yml
├── ghb-module-business/ 业务代码 ← 写这里
└── ghb-server-cloud/ 微服务(网关/Nacos/监控)
```
## 新人必读(开始写代码前全部读完)
1. **`CLAUDE.md`**(本目录)/ base 根 `../CLAUDE.md` — AI 开发总纲
2. **`../.claude/`** 下全部规则:`contract.md`(契约/返回格式/同步铁律)、`backend.md`(分层/命名/CRUD 模板/检查表)、`database.md`、`git.md`、`security.md`、`testing.md`
3. **`../contract/`** — 接口契约(独立 git submodule。**改接口前必须先更新契约。**
## 铁律速记(详见 .claude/
- 返回一律 `Result` 包装,禁止裸 `return data;`
- Controller 只做参数校验 + 调 Service + 返回,方法体别超 20 行、别写 if/else 业务
- 手写 `@Select` SQL 必须带 `tenant_id` 条件(多租户,否则数据泄露)
- 新增表必须有 `create_time`/`update_time`/`del_flag`/`COMMENT`,金额字段用 `DECIMAL`
- commit 要带 `[AI: xxx]` 标注;改 entity/controller 后重新打包重启

11
db/Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM mysql:8.0
ENV TZ=Asia/Shanghai
RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
COPY ./tables_nacos.sql /docker-entrypoint-initdb.d
COPY ./ghb_base_init.sql /docker-entrypoint-initdb.d
COPY ./tables_xxl_job.sql /docker-entrypoint-initdb.d

357
db/tables_nacos.bak.sql Normal file

File diff suppressed because one or more lines are too long

310
db/tables_nacos.sql Normal file

File diff suppressed because one or more lines are too long

360
db/tables_xxl_job.sql Normal file
View File

@ -0,0 +1,360 @@
#
# XXL-JOB v2.4.0
# Copyright (c) 2015-present, xuxueli.
CREATE database if NOT EXISTS `xxl_job` default character set utf8mb4 collate utf8mb4_general_ci;
use `xxl_job`;
/*
Navicat Premium Data Transfer
Source Server : mysql5.7
Source Server Type : MySQL
Source Server Version : 50738 (5.7.38)
Source Host : 127.0.0.1:3306
Source Schema : xxl_job
Target Server Type : MySQL
Target Server Version : 50738 (5.7.38)
File Encoding : 65001
Date: 10/02/2025 13:49:31
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for xxl_job_group
-- ----------------------------
DROP TABLE IF EXISTS `xxl_job_group`;
CREATE TABLE `xxl_job_group` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '执行器AppName',
`title` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '执行器名称',
`address_type` tinyint(4) NOT NULL DEFAULT 0 COMMENT '执行器地址类型0=自动注册、1=手动录入',
`address_list` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '执行器地址列表,多地址逗号分隔',
`update_time` datetime NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of xxl_job_group
-- ----------------------------
INSERT INTO `xxl_job_group` VALUES (1, 'xxl-job-executor-sample', '示例执行器', 0, NULL, '2025-02-10 13:49:04');
INSERT INTO `xxl_job_group` VALUES (2, 'jeecg-demo', '测试Demo模块', 0, NULL, '2025-02-10 13:49:04');
INSERT INTO `xxl_job_group` VALUES (3, 'jeecg-system', '系统System模块', 0, NULL, '2025-02-10 13:49:04');
-- ----------------------------
-- Table structure for xxl_job_info
-- ----------------------------
DROP TABLE IF EXISTS `xxl_job_info`;
CREATE TABLE `xxl_job_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`add_time` datetime NULL DEFAULT NULL,
`update_time` datetime NULL DEFAULT NULL,
`author` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '作者',
`alarm_email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '报警邮件',
`schedule_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NONE' COMMENT '调度类型',
`schedule_conf` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '调度配置,值含义取决于调度类型',
`misfire_strategy` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'DO_NOTHING' COMMENT '调度过期策略',
`executor_route_strategy` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '执行器路由策略',
`executor_handler` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '执行器任务handler',
`executor_param` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '执行器任务参数',
`executor_block_strategy` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '阻塞处理策略',
`executor_timeout` int(11) NOT NULL DEFAULT 0 COMMENT '任务执行超时时间,单位秒',
`executor_fail_retry_count` int(11) NOT NULL DEFAULT 0 COMMENT '失败重试次数',
`glue_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'GLUE类型',
`glue_source` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT 'GLUE源代码',
`glue_remark` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime NULL DEFAULT NULL COMMENT 'GLUE更新时间',
`child_jobid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '子任务ID多个逗号分隔',
`trigger_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '调度状态0-停止1-运行',
`trigger_last_time` bigint(13) NOT NULL DEFAULT 0 COMMENT '上次调度时间',
`trigger_next_time` bigint(13) NOT NULL DEFAULT 0 COMMENT '下次调度时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of xxl_job_info
-- ----------------------------
INSERT INTO `xxl_job_info` VALUES (1, 1, '测试任务1', '2018-11-03 22:21:31', '2024-08-21 22:30:30', 'XXL', '', 'CRON', '0 0 0 * * ? *', 'DO_NOTHING', 'FIRST', 'demoJob', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '', 1, 1729353600000, 1739203200000);
INSERT INTO `xxl_job_info` VALUES (2, 3, '测试jeecg xxljob', '2024-08-21 22:41:10', '2024-08-21 22:41:30', 'JEECG', '', 'CRON', '* * * * * ?', 'DO_NOTHING', 'FIRST', 'demoJob', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2024-08-21 22:41:10', '', 1, 1739166572000, 1739166573000);
-- ----------------------------
-- Table structure for xxl_job_lock
-- ----------------------------
DROP TABLE IF EXISTS `xxl_job_lock`;
CREATE TABLE `xxl_job_lock` (
`lock_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '锁名称',
PRIMARY KEY (`lock_name`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of xxl_job_lock
-- ----------------------------
INSERT INTO `xxl_job_lock` VALUES ('schedule_lock');
-- ----------------------------
-- Table structure for xxl_job_log
-- ----------------------------
DROP TABLE IF EXISTS `xxl_job_log`;
CREATE TABLE `xxl_job_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务主键ID',
`executor_address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '执行器地址,本次执行的地址',
`executor_handler` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '执行器任务handler',
`executor_param` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '执行器任务参数',
`executor_sharding_param` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '执行器任务分片参数,格式如 1/2',
`executor_fail_retry_count` int(11) NOT NULL DEFAULT 0 COMMENT '失败重试次数',
`trigger_time` datetime NULL DEFAULT NULL COMMENT '调度-时间',
`trigger_code` int(11) NOT NULL COMMENT '调度-结果',
`trigger_msg` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '调度-日志',
`handle_time` datetime NULL DEFAULT NULL COMMENT '执行-时间',
`handle_code` int(11) NOT NULL COMMENT '执行-状态',
`handle_msg` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '执行-日志',
`alarm_status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '告警状态0-默认、1-无需告警、2-告警成功、3-告警失败',
PRIMARY KEY (`id`) USING BTREE,
INDEX `I_trigger_time`(`trigger_time`) USING BTREE,
INDEX `I_handle_code`(`handle_code`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6761 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of xxl_job_log
-- ----------------------------
INSERT INTO `xxl_job_log` VALUES (6618, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:09', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6619, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:10', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6620, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:11', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6621, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:12', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6622, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:13', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6623, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:14', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6624, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:15', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6625, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:16', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6626, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:17', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6627, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:18', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6628, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:19', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6629, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:20', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6630, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:21', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6631, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:22', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6632, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:23', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6633, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:24', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6634, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:25', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6635, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:26', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6636, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:27', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6637, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:28', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6638, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:29', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6639, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:30', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6640, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:31', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6641, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:32', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6642, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:33', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6643, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:34', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6644, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:35', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6645, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:36', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6646, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:37', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6647, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:38', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6648, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:39', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6649, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:40', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6650, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:41', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6651, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:42', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6652, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:43', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6653, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:44', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6654, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:45', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6655, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:46', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6656, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:47', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6657, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:48', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6658, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:49', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6659, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:50', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6660, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:51', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6661, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:52', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6662, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:53', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6663, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:54', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6664, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:55', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6665, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:56', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6666, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:57', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6667, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:58', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6668, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:47:59', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6669, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:00', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6670, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:01', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6671, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:02', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6672, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:03', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6673, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:04', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6674, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:05', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6675, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:06', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6676, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:07', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6677, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:08', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6678, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:09', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6679, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:10', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6680, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:11', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6681, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:12', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6682, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:13', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6683, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:14', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6684, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:15', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6685, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:16', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6686, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:17', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6687, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:18', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6688, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:19', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6689, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:20', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6690, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:21', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6691, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:22', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6692, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:23', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6693, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:24', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6694, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:25', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6695, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:26', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6696, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:27', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6697, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:28', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6698, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:29', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6699, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:30', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6700, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:31', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6701, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:32', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6702, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:33', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6703, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:34', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6704, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:35', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6705, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:36', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6706, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:37', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6707, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:38', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6708, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:39', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6709, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:40', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6710, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:41', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6711, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:42', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6712, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:43', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6713, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:44', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6714, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:45', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6715, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:46', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6716, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:47', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6717, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:48', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6718, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:49', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6719, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:50', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6720, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:51', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6721, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:52', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6722, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:53', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6723, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:54', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6724, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:55', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6725, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:56', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6726, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:57', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6727, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:58', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6728, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:48:59', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6729, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:00', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6730, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:01', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6731, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:02', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6732, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:03', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6733, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:04', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6734, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:05', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6735, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:06', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6736, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:07', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6737, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:08', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6738, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:09', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6739, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:10', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6740, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:11', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6741, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:12', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6742, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:13', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6743, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:14', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6744, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:15', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6745, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:16', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6746, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:17', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6747, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:18', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6748, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:19', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6749, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:20', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6750, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:21', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6751, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:22', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6752, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:23', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6753, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:24', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 2);
INSERT INTO `xxl_job_log` VALUES (6754, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:25', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 0);
INSERT INTO `xxl_job_log` VALUES (6755, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:26', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 0);
INSERT INTO `xxl_job_log` VALUES (6756, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:27', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 0);
INSERT INTO `xxl_job_log` VALUES (6757, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:28', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 0);
INSERT INTO `xxl_job_log` VALUES (6758, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:29', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 0);
INSERT INTO `xxl_job_log` VALUES (6759, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:30', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 0);
INSERT INTO `xxl_job_log` VALUES (6760, 3, 2, NULL, 'demoJob', '', NULL, 0, '2025-02-10 13:49:31', 500, '任务触发类型Cron触发<br>调度机器192.168.1.11<br>执行器-注册方式:自动注册<br>执行器-地址列表null<br>路由策略:第一个<br>阻塞处理策略:单机串行<br>任务超时时间0<br>失败重试次数0<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>调度失败:执行器地址为空<br><br>', NULL, 0, NULL, 0);
-- ----------------------------
-- Table structure for xxl_job_log_report
-- ----------------------------
DROP TABLE IF EXISTS `xxl_job_log_report`;
CREATE TABLE `xxl_job_log_report` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`trigger_day` datetime NULL DEFAULT NULL COMMENT '调度-时间',
`running_count` int(11) NOT NULL DEFAULT 0 COMMENT '运行中-日志数量',
`suc_count` int(11) NOT NULL DEFAULT 0 COMMENT '执行成功-日志数量',
`fail_count` int(11) NOT NULL DEFAULT 0 COMMENT '执行失败-日志数量',
`update_time` datetime NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `i_trigger_day`(`trigger_day`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of xxl_job_log_report
-- ----------------------------
INSERT INTO `xxl_job_log_report` VALUES (1, '2024-08-21 00:00:00', 70, 0, 5, NULL);
INSERT INTO `xxl_job_log_report` VALUES (2, '2024-08-20 00:00:00', 0, 0, 0, NULL);
INSERT INTO `xxl_job_log_report` VALUES (3, '2024-08-19 00:00:00', 0, 0, 0, NULL);
INSERT INTO `xxl_job_log_report` VALUES (4, '2024-09-10 00:00:00', 0, 0, 56, NULL);
INSERT INTO `xxl_job_log_report` VALUES (5, '2024-09-09 00:00:00', 0, 0, 0, NULL);
INSERT INTO `xxl_job_log_report` VALUES (6, '2024-09-08 00:00:00', 0, 0, 0, NULL);
INSERT INTO `xxl_job_log_report` VALUES (7, '2024-10-19 00:00:00', 0, 0, 6391, NULL);
INSERT INTO `xxl_job_log_report` VALUES (8, '2024-10-18 00:00:00', 0, 0, 0, NULL);
INSERT INTO `xxl_job_log_report` VALUES (9, '2024-10-17 00:00:00', 0, 0, 0, NULL);
INSERT INTO `xxl_job_log_report` VALUES (10, '2025-02-10 00:00:00', 0, 0, 116, NULL);
INSERT INTO `xxl_job_log_report` VALUES (11, '2025-02-09 00:00:00', 0, 0, 0, NULL);
INSERT INTO `xxl_job_log_report` VALUES (12, '2025-02-08 00:00:00', 0, 0, 0, NULL);
-- ----------------------------
-- Table structure for xxl_job_logglue
-- ----------------------------
DROP TABLE IF EXISTS `xxl_job_logglue`;
CREATE TABLE `xxl_job_logglue` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务主键ID',
`glue_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'GLUE类型',
`glue_source` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT 'GLUE源代码',
`glue_remark` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'GLUE备注',
`add_time` datetime NULL DEFAULT NULL,
`update_time` datetime NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of xxl_job_logglue
-- ----------------------------
-- ----------------------------
-- Table structure for xxl_job_registry
-- ----------------------------
DROP TABLE IF EXISTS `xxl_job_registry`;
CREATE TABLE `xxl_job_registry` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`registry_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`registry_value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`update_time` datetime NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `i_g_k_v`(`registry_group`, `registry_key`, `registry_value`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of xxl_job_registry
-- ----------------------------
-- ----------------------------
-- Table structure for xxl_job_user
-- ----------------------------
DROP TABLE IF EXISTS `xxl_job_user`;
CREATE TABLE `xxl_job_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '账号',
`password` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
`role` tinyint(4) NOT NULL COMMENT '角色0-普通用户、1-管理员',
`permission` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限执行器ID列表多个逗号分割',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `i_username`(`username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of xxl_job_user
-- ----------------------------
INSERT INTO `xxl_job_user` VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
SET FOREIGN_KEY_CHECKS = 1;

10917
db/test_base_init.sql Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
oracle导出编码 export NLS_LANG=AMERICAN_AMERICA.ZHS16GBK
导出用户: jeecgbootos
导入命令: imp scott/tiger@orcl file=jeecgboot-oracle11g.dmp

View File

@ -0,0 +1,39 @@
# JeecgBoot 数据库支持与转库说明
JeecgBoot 平台支持以下数据库,默认只提供 MySQL 脚本,其他数据库可参考 [Navicat 工具 mysql 转库 oracle 步骤](https://my.oschina.net/jeecg/blog/4905722)自行转换,其他数据库转换类似。
| 数据库 | 支持 |
|--------|------|
| MySQL | √ |
| Oracle 11g | √ |
| SQL Server 2017 | √ |
| PostgreSQL | √ |
| MariaDB | √ |
| 达梦DM | √ |
| 人大金仓KingbaseES | √ |
| TiDB | √ |
| Kingbase8 | √ |
---
## 一、Navicat 工具 MySQL 迁移 Oracle
1. 使用 Navicat 工具连接源数据库 MySQL用户名 jeecg-m和目标数据库 Oracle用户名 jeecg-o
2. 使用 Navicat 的数据传输功能将 jeecg-m 拷贝到 jeecg-o
1选中源数据库右键点击"数据传输"
2在常规选项卡上选择源数据库并勾选数据库对象选择目标连接数据库名模式
3在高级选项卡中勾选**转换对象名为大写**,其他选项处勾选"遇到错误继续"
4表选项的**外键限制要勾掉**,由于在转换过程中每个表是单独写入数据,如果保持外键限制,会导致有外键的表写入失败。外键请在迁移后手动补充。
5点击开始按钮在信息日志选项卡中会打出日志完成后记录下出错的表出错的表需要后续使用其他工具迁移。
## 二、特殊表处理
定时任务的表 `qrtz_*`,通过 Navicat 转的表还是有问题,需要删除相关表,手工执行下原始初始化脚本。
quartz 初始化脚本下载:[quartz-2.2.3-distribution.tar.gz](http://pan.baidu.com/s/1WrmZdUuAPg3iBwJ-LoHWyg?pwd=8mdz)(百度网盘),找到自己需要的数据库类型脚本即可。

23
db/版本升级说明.md Normal file
View File

@ -0,0 +1,23 @@
# 版本升级方法
> JeecgBoot属于平台级产品每次升级改动较大目前做不到平滑升级。
### 增量升级方案
#### 1.代码合并
本地通过svn或git做好主干在分支上做业务开发jeecg每次版本发布可以手工覆盖主干的代码对比合并代码
#### 2.数据库升级
- 从3.6.2+版本增加flyway自动升级数据库机制支持 mysql5.7、mysql8;
- 其他库请手工执行SQL, 目录: `jeecg-module-system\jeecg-system-start\src\main\resources\flyway\sql\mysql`
> 注意: 升级sql只提供mysql版本如果有权限升级, 还需要手工角色授权,退出重新登录才好使。
#### 3.其他数据库脚本说明
原先官方默认提供oracle和SqlServer的脚本但是维护成本太高未提供脚本的数据库可以参考下面的文档自己转
https://my.oschina.net/jeecg/blog/4905722
注意定时任务的表qrtz_*,需要删掉用原始的脚本重新执行一下)
quartz-2.2.3-distribution.tar.gz放到百度网盘中大家自己下载执行所需数据库脚本
https://pan.baidu.com/s/1WrmZdUuAPg3iBwJ-LoHWyg?pwd=8mdz
#### 4.兼容问题
每次发版,会针对不兼容地方重点说明。

79
deploy/.env.example Normal file
View File

@ -0,0 +1,79 @@
# ============================================
# 部署配置模板 — 复制为 .env 后修改
# 快速开始: bash deploy.sh init交互式生成
# 手动配置: cp .env.example .env && vim .env
# ============================================
# 部署架构: 1=单体 3=SpringCloud
DEPLOY_MODE=1
COMPOSE_FILE=docker-compose.yml
# 应用
APP_NAME=test
APP_PORT=8008
JAVA_OPTS="-Xms512m -Xmx512m"
# 数据库
DB_NAME=test_base
# 网络
NETWORK_NAME=ghb-net
# ---- 中间件策略 ----
# docker = Docker 一起安装 | external = 连接外部服务 | none = 不使用(仅 MinIO
MYSQL_MODE=docker
REDIS_MODE=docker
MINIO_MODE=none
# ---- MySQLdocker 模式) ----
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=ChangeMe123
# ---- MySQLexternal 模式) ----
# MYSQL_HOST=127.0.0.1
# MYSQL_USER=root
# MYSQL_PASS='ChangeMe123'
# MYSQL_URL='jdbc:mysql://127.0.0.1:3306/test_base?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true'
# ---- Redisdocker 模式) ----
REDIS_PORT=6379
REDIS_PASS=''
# ---- Redisexternal 模式) ----
# REDIS_HOST=127.0.0.1
# REDIS_DB=0
# ---- MinIOdocker 模式) ----
# MINIO_HOST=minio
# MINIO_PORT=9000
# MINIO_CONSOLE_PORT=9001
# MINIO_ACCESS_KEY=minioadmin
# MINIO_SECRET_KEY=minioadmin
# MINIO_BUCKET=ghb-base
# MINIO_ENDPOINT=http://minio:9000
# ---- MinIOexternal 模式) ----
# MINIO_HOST=127.0.0.1
# MINIO_PORT=9000
# MINIO_ACCESS_KEY=minioadmin
# MINIO_SECRET_KEY=minioadmin
# MINIO_BUCKET=ghb-base
# MINIO_ENDPOINT=http://127.0.0.1:9000
# ---- Spring Cloud仅 DEPLOY_MODE=3 ----
# NACOS_PORT=8848
# GATEWAY_PORT=9999
# SYSTEM_PORT=7001
# 数据库初始化(仅 external MySQLtrue 时启动前自动建表)
DB_INIT=false
# 跳过连通性检查true 时不检查外接服务是否可达)
SKIP_CONNECTIVITY_CHECK=false
# 上传方式: local(本地) / minio(MinIO)
JEECG_UPLOAD_TYPE=local
# ---- 前端集成(可选) ----
FRONTEND_ENABLED=false
# FRONTEND_PORT=80
# JWT 密钥(自动生成或手动填写)
JWT_SECRET='your-jwt-secret-here'

1
deploy/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.env

177
deploy/README.md Normal file
View File

@ -0,0 +1,177 @@
# 后端 Docker 部署
## 部署架构
| 架构 | compose 文件 | 说明 |
|------|-------------|------|
| 单体 | `docker-compose.yml` | 一个 App 容器 |
| Spring Cloud | `docker-compose.cloud.yml` | Nacos + Gateway + System |
中间件MySQL / Redis / MinIO**独立选择**,不绑定架构:
| 中间件 | 选项 | 说明 |
|--------|------|------|
| MySQL | `docker` / `external` | Docker 安装 or 连接外部服务 |
| Redis | `docker` / `external` | Docker 安装 or 连接外部服务 |
| MinIO | `docker` / `external` / `none` | Docker 安装 / 外部 / 不使用 |
## 前置条件
- Docker + Docker Compose
- **外部 MySQL 场景**:需提前建好 `nacos``ghb_base` 两个库(见下文)
---
## 快速开始
```bash
cd deploy
# 1. 交互式配置
bash deploy.sh init
# 2. 编译镜像(首次 10-20 分钟)
bash deploy.sh build
# 3. 启动
bash deploy.sh start
```
---
## 完整步骤Spring Cloud + 外部 MySQL/Redis
### 第一步:初始化数据库
```bash
# Nacos 内部存储库(必须)
mysql -h <MySQL地址> -u root -p < db/tables_nacos.sql
# 业务库
mysql -h <MySQL地址> -u root -p -e "CREATE DATABASE IF NOT EXISTS ghb_base DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
mysql -h <MySQL地址> -u root -p ghb_base < db/ghb_base_init.sql
```
### 第二步:交互式配置
```bash
cd deploy
bash deploy.sh init
```
按提示填写:
```
部署架构: 2. Spring Cloud
MySQL: 2. 连接外部服务 → 10.13.13.1:3306
Redis: 2. 连接外部服务 → 10.13.13.1:56379
MinIO: 3. 不使用
外接数据库是否已建表? Y
是否集成前端? Y需先 pnpm build
```
### 第三步:编译 + 启动
```bash
bash deploy.sh build && bash deploy.sh start
```
`start` 在 Spring Cloud 模式下自动执行:
```
Phase 1: 启动 Nacos → 等待健康检查 → 无配置则自动导入
Phase 2: 启动 System → Gateway
```
### 第四步:验证
| 服务 | 地址 |
|------|------|
| Nacos | `http://localhost:8848/nacos` |
| Gateway 文档 | `http://localhost:9999/doc.html` |
| System 文档 | `http://localhost:7001/doc.html` |
| 前端 | `http://localhost:3001`(如启用) |
Nacos 服务列表应有 `jeecg-gateway`、`jeecg-system`。
---
## 架构链路
```
前端 :3001
Gateway :9999 ──┐
↓ │ ghb-netDocker 内部网络)
System :7001 ────┤
↓ │
Nacos :8848 ─────┘ ← init-nacos.sh 自动推送配置
外部 MySQL / Redis ← Nacos 配置中地址已被替换
```
---
## Nacos 配置原理
`deploy/nacos/config/*.yaml` 模板中用占位符,`init-nacos.sh` 运行时从 `.env` 读取实际值替换后推送到 Nacos
```yaml
# 模板nacos/config/jeecg-dev.yaml
url: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${DB_NAME}?...
host: ${REDIS_HOST}
port: ${REDIS_PORT}
# ↓ init-nacos.sh 替换后推送到 Nacos ↓
# Nacos 中实际配置
url: jdbc:mysql://10.13.13.1:3306/ghb_base?...
host: 10.13.13.1
port: 56379
```
服务启动时从 Nacos 拉取配置,自动连到外部 MySQL/Redis。
### 数据库要求
| 库 | 用途 | 谁建 | SQL |
|----|------|------|-----|
| `nacos` | Nacos 内部存储 | **手动** | `db/tables_nacos.sql` |
| `ghb_base` | 业务数据 | 手动 or `deploy.sh init-db` | `db/ghb_base_init.sql` |
---
## 命令速查
| 命令 | 作用 |
|------|------|
| `bash deploy.sh init` | 交互式生成 .env |
| `bash deploy.sh start` | 停旧容器 → 启动(不编译) |
| `bash deploy.sh build` | 编译镜像 |
| `bash deploy.sh rebuild [分支]` | 拉代码 + 编译 + 启动 |
| `bash deploy.sh stop` | 停止所有容器 |
| `bash deploy.sh logs` | 查看日志 |
| `bash deploy.sh status` | 容器状态 |
| `bash deploy.sh config` | 查看配置 |
| `bash deploy.sh init-db` | 初始化外部业务数据库 |
| `bash deploy.sh init-nacos` | 强制重新导入 Nacos 配置 |
---
## 目录结构
```
deploy/
├── deploy.sh # 主脚本
├── init-nacos.sh # Nacos 配置导入
├── .env # 实际配置gitignore
├── docker-compose.yml # 单体编排
├── docker-compose.cloud.yml # Spring Cloud 编排
├── config/
│ └── application-prod.yml # 单体外部配置
└── nacos/config/
├── jeecg.yaml # 公共配置
├── jeecg-dev.yaml # 环境配置(含占位符)
├── jeecg-gateway-dev.yaml # 网关配置
└── jeecg-gateway-router.json # 路由表
```

View File

@ -0,0 +1,43 @@
server:
port: 8081
servlet:
context-path: /test
spring:
datasource:
dynamic:
datasource:
master:
url: ${MYSQL_URL}
username: ${MYSQL_USER}
password: ${MYSQL_PASS}
driver-class-name: com.mysql.cj.jdbc.Driver
data:
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASS}
database: ${REDIS_DB:0}
flyway:
enabled: false
app:
jwt:
secret: ${JWT_SECRET:defaultSecretPleaseChangeMe}
knife4j:
enable: true
# ==== MinIO可选MINIO_ENABLED=true 时生效)====
jeecg:
uploadType: ${JEECG_UPLOAD_TYPE:local}
minio:
minio_url: ${MINIO_ENDPOINT:http://127.0.0.1:9000}
minio_name: ${MINIO_ACCESS_KEY:minioadmin}
minio_pass: ${MINIO_SECRET_KEY:minioadmin}
bucketName: ${MINIO_BUCKET:ghb-base}
logging:
level:
root: INFO
com.ghb: INFO

1278
deploy/deploy.sh Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,201 @@
# ============================================
# Spring Cloud 微服务编排
# Nacos + Gateway + System + MySQL + Redis + MinIO(可选)
# 中间件通过 profiles 按需启用:
# --profile mysql → Docker MySQLexternal 时不激活)
# --profile redis → Docker Redisexternal 时不激活)
# --profile minio → Docker MinIO
# --profile frontend → 前端 Nginx
# 启动顺序由 deploy.sh 控制
# Demo 服务默认注释,按需取消
# ============================================
services:
mysql:
build: ../db
image: ${APP_NAME}-mysql:latest
container_name: ${APP_NAME}-mysql
restart: unless-stopped
ports:
- "0.0.0.0:${MYSQL_PORT:-3306}:3306"
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${DB_NAME}
TZ: Asia/Shanghai
command: --lower_case_table_names=1
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 10
start_period: 60s
profiles:
- mysql
networks:
- default
redis:
image: redis:7-alpine
container_name: ${APP_NAME}-redis
restart: unless-stopped
ports:
- "0.0.0.0:${REDIS_HOST_PORT:-6379}:6379"
command: redis-server --appendonly yes ${REDIS_PASS:+--requirepass $REDIS_PASS}
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
profiles:
- redis
networks:
- default
# MinIO 文件存储(可选,通过 --profile minio 启用)
minio:
image: minio/minio:RELEASE.2025-04-08T15-41-24Z
container_name: ${APP_NAME}-minio
restart: unless-stopped
ports:
- "${MINIO_PORT:-9000}:9000"
- "${MINIO_CONSOLE_PORT:-9001}:9001"
environment:
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:-minioadmin}
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:-minioadmin}
command: server /data --console-address ":9001"
volumes:
- minio-data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 10s
retries: 3
profiles:
- minio
networks:
- default
nacos:
build:
context: ..
dockerfile: ghb-server-cloud/ghb-cloud-nacos/Dockerfile
image: ${APP_NAME}-nacos:latest
container_name: ${APP_NAME}-nacos
restart: unless-stopped
ports:
- "0.0.0.0:${NACOS_PORT:-8848}:8848"
environment:
MYSQL_HOST: ${MYSQL_HOST:-mysql}
MYSQL_PORT: "${MYSQL_PORT:-3306}"
MYSQL_DB: nacos
MYSQL_USER: ${MYSQL_USER:-root}
MYSQL_PWD: ${MYSQL_PASS:-${MYSQL_ROOT_PASSWORD}}
JAVA_OPTS: "-Xms512m -Xmx512m"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8848/nacos/v1/console/health/liveness"]
interval: 15s
timeout: 10s
retries: 20
start_period: 90s
networks:
- default
system:
build:
context: ..
dockerfile: ghb-server-cloud/ghb-system-cloud-start/Dockerfile
image: ${APP_NAME}-system-cloud:latest
container_name: ${APP_NAME}-system-cloud
restart: unless-stopped
ports:
- "0.0.0.0:${SYSTEM_PORT:-7001}:7001"
env_file:
- .env
environment:
SPRING_PROFILES_ACTIVE: dev
SPRING_CLOUD_NACOS_DISCOVERY_SERVER-ADDR: nacos:8848
SPRING_CLOUD_NACOS_CONFIG_SERVER-ADDR: nacos:8848
volumes:
- /opt/upFiles:/opt/upFiles
depends_on:
nacos:
condition: service_healthy
networks:
- default
gateway:
build:
context: ..
dockerfile: ghb-server-cloud/ghb-cloud-gateway/Dockerfile
image: ${APP_NAME}-gateway:latest
container_name: ${APP_NAME}-gateway
restart: unless-stopped
ports:
- "0.0.0.0:${GATEWAY_PORT:-9999}:9999"
env_file:
- .env
environment:
SPRING_PROFILES_ACTIVE: dev
SPRING_CLOUD_NACOS_DISCOVERY_SERVER-ADDR: nacos:8848
SPRING_CLOUD_NACOS_CONFIG_SERVER-ADDR: nacos:8848
depends_on:
nacos:
condition: service_healthy
system:
condition: service_started
networks:
- default
# 前端(可选,通过 --profile frontend 启用)
# 启用前需先在 ghb-frontend 目录执行 pnpm build 生成 dist/
frontend:
build:
context: ../../ghb-frontend
dockerfile: deploy/Dockerfile
image: ${APP_NAME}-frontend:latest
container_name: ${APP_NAME}-frontend
restart: unless-stopped
ports:
- "${FRONTEND_PORT:-80}:80"
environment:
BACKEND_PROXY: http://${APP_NAME}-gateway:${GATEWAY_PORT:-9999}
depends_on:
- gateway
profiles:
- frontend
networks:
- default
# Demo 服务(默认不启用,需要时取消注释)
#demo:
# build:
# context: ..
# dockerfile: ghb-server-cloud/ghb-demo-cloud-start/Dockerfile
# image: ${APP_NAME}-demo-cloud:latest
# container_name: ${APP_NAME}-demo-cloud
# restart: unless-stopped
# ports:
# - "0.0.0.0:${DEMO_PORT:-7002}:7002"
# env_file:
# - .env
# environment:
# SPRING_PROFILES_ACTIVE: dev
# SPRING_CLOUD_NACOS_DISCOVERY_SERVER-ADDR: nacos:8848
# SPRING_CLOUD_NACOS_CONFIG_SERVER-ADDR: nacos:8848
# depends_on:
# nacos:
# condition: service_healthy
# networks:
# - default
volumes:
mysql-data:
redis-data:
minio-data:
networks:
default:
name: ${NETWORK_NAME:-ghb-net}

123
deploy/docker-compose.yml Normal file
View File

@ -0,0 +1,123 @@
# ============================================
# 单体部署编排
# 中间件通过 profiles 按需启用:
# --profile mysql → Docker MySQL
# --profile redis → Docker Redis
# --profile minio → Docker MinIO
# --profile frontend → 前端 Nginx
# 启动顺序由 deploy.sh 控制app 不写 depends_on
# ============================================
services:
mysql:
build: ../db
image: ${APP_NAME}-mysql:latest
container_name: ${APP_NAME}-mysql
restart: unless-stopped
ports:
- "${MYSQL_PORT:-3306}:3306"
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${DB_NAME}
TZ: Asia/Shanghai
command: --lower_case_table_names=1
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 10
start_period: 60s
profiles:
- mysql
networks:
- default
redis:
image: redis:7-alpine
container_name: ${APP_NAME}-redis
restart: unless-stopped
ports:
- "0.0.0.0:${REDIS_HOST_PORT:-6379}:6379"
command: redis-server --appendonly yes ${REDIS_PASS:+--requirepass $REDIS_PASS}
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
profiles:
- redis
networks:
- default
# MinIO 文件存储(可选,通过 --profile minio 启用)
minio:
image: minio/minio:RELEASE.2025-04-08T15-41-24Z
container_name: ${APP_NAME}-minio
restart: unless-stopped
ports:
- "${MINIO_PORT:-9000}:9000"
- "${MINIO_CONSOLE_PORT:-9001}:9001"
environment:
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:-minioadmin}
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:-minioadmin}
command: server /data --console-address ":9001"
volumes:
- minio-data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 10s
retries: 3
profiles:
- minio
networks:
- default
app:
build:
context: ..
dockerfile: Dockerfile
image: ${APP_NAME}-backend:latest
container_name: ${APP_NAME}-backend
restart: unless-stopped
ports:
- "0.0.0.0:${APP_PORT}:8081"
env_file:
- .env
environment:
SPRING_PROFILES_ACTIVE: prod
volumes:
- ./config/application-prod.yml:/app/config/application-prod.yml:ro
- /opt/upFiles:/opt/upFiles
networks:
- default
# 前端(可选,通过 --profile frontend 启用)
# 启用前需先在 ghb-frontend 目录执行 pnpm build 生成 dist/
frontend:
build:
context: ../../ghb-frontend
dockerfile: deploy/Dockerfile
image: ${APP_NAME}-frontend:latest
container_name: ${APP_NAME}-frontend
restart: unless-stopped
ports:
- "${FRONTEND_PORT:-80}:80"
environment:
BACKEND_PROXY: http://${APP_NAME}-backend:8081/ghb
profiles:
- frontend
networks:
- default
volumes:
mysql-data:
redis-data:
minio-data:
networks:
default:
name: ${NETWORK_NAME:-ghb-net}

177
deploy/init-nacos.sh Executable file
View File

@ -0,0 +1,177 @@
#!/bin/bash
# ============================================
# Nacos 配置初始化脚本
# 1. 等待 Nacos 就绪
# 2. 替换占位符 → 导入配置到 Nacos
#
# 用法:
# bash init-nacos.sh 首次导入(配置已存在则跳过)
# bash init-nacos.sh --force 强制覆盖已有配置
# ============================================
set -e
FORCE="${1:-}"
if [ "$FORCE" = "--force" ] || [ "$FORCE" = "-f" ]; then
FORCE=true
else
FORCE=false
fi
DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$DIR"
NACOS_URL="${NACOS_URL:-http://localhost:8848}"
NACOS_USER="${NACOS_USER:-nacos}"
NACOS_PASS="${NACOS_PASS:-nacos}"
NAMESPACE="${NACOS_NAMESPACE:-springboot3}" # 默认与 Maven profile 保持一致
GROUP="${NACOS_GROUP:-DEFAULT_GROUP}"
CONFIG_DIR="${DIR}/nacos/config"
MAX_RETRIES=60
RETRY_INTERVAL=5
# ============================================
# 0. 加载 .env
# ============================================
if [ -f "${DIR}/.env" ]; then
set -a
source "${DIR}/.env"
set +a
fi
# 默认值
MYSQL_HOST="${MYSQL_HOST:-mysql}"
MYSQL_PORT="${MYSQL_PORT:-3306}"
MYSQL_USER="${MYSQL_USER:-root}"
MYSQL_PASS="${MYSQL_PASS:-root}"
REDIS_HOST="${REDIS_HOST:-redis}"
REDIS_PORT="${REDIS_PORT:-6379}"
REDIS_PASS="${REDIS_PASS:-}"
DB_NAME="${DB_NAME:-ghb_base}"
MINIO_HOST="${MINIO_HOST:-minio}"
MINIO_PORT="${MINIO_PORT:-9000}"
MINIO_ACCESS_KEY="${MINIO_ACCESS_KEY:-minioadmin}"
MINIO_SECRET_KEY="${MINIO_SECRET_KEY:-minioadmin}"
MINIO_BUCKET_NAME="${MINIO_BUCKET_NAME:-ghb-base}"
# ============================================
# 1. 等待 Nacos 就绪
# ============================================
echo ">>> 等待 Nacos 就绪(${NACOS_URL}..."
for i in $(seq 1 $MAX_RETRIES); do
if curl -s "${NACOS_URL}/nacos/v1/console/health/liveness" | grep -q "OK"; then
echo "✅ Nacos 已就绪"
break
fi
if [ "$i" -eq "$MAX_RETRIES" ]; then
echo "❌ Nacos 启动超时"
exit 1
fi
echo " 等待中...${i}/${MAX_RETRIES}"
sleep $RETRY_INTERVAL
done
# ============================================
# 2. 登录(如启用认证)
# ============================================
ACCESS_TOKEN=""
AUTH_CHECK=$(curl -s -o /dev/null -w "%{http_code}" "${NACOS_URL}/nacos/v1/auth/login" -X POST 2>/dev/null || echo "404")
if [ "$AUTH_CHECK" = "200" ]; then
echo ">>> Nacos 认证已启用,正在登录..."
TOKEN_RESP=$(curl -s -X POST "${NACOS_URL}/nacos/v1/auth/login" \
-d "username=${NACOS_USER}&password=${NACOS_PASS}")
ACCESS_TOKEN=$(echo "$TOKEN_RESP" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4 || echo "")
if [ -n "$ACCESS_TOKEN" ]; then
echo "✅ 登录成功"
else
echo "⚠️ 登录失败,尝试无认证模式"
fi
fi
# ============================================
# 3. 检查配置是否已初始化
# ============================================
CHECK_DATA_ID="jeecg.yaml"
CHECK_RESP=$(curl -s "${NACOS_URL}/nacos/v1/cs/configs?dataId=${CHECK_DATA_ID}&group=${GROUP}&tenant=${NAMESPACE}")
if [ "$FORCE" = false ] && [ -n "$CHECK_RESP" ] && [ "$CHECK_RESP" != "config data not exist" ]; then
echo ">>> Nacos 配置已存在,跳过初始化"
echo " 如需强制覆盖,请执行: bash deploy.sh init-nacos --force"
exit 0
fi
if [ "$FORCE" = true ]; then
echo ">>> --force将覆盖已有配置"
fi
# ============================================
# 4. 创建命名空间(如指定且非 public
# ============================================
if [ -n "$NAMESPACE" ] && [ "$NAMESPACE" != "public" ]; then
echo ">>> 创建命名空间: ${NAMESPACE}"
TOKEN_PARAM=""
[ -n "$ACCESS_TOKEN" ] && TOKEN_PARAM="&accessToken=${ACCESS_TOKEN}"
curl -s -X POST "${NACOS_URL}/nacos/v1/namespace" \
-d "customNamespaceId=${NAMESPACE}&namespaceName=${NAMESPACE}${TOKEN_PARAM}" > /dev/null || true
fi
# ============================================
# 5. 导入配置
# ============================================
echo ">>> 开始导入 Nacos 配置..."
publish_config() {
local data_id="$1"
local type="$2"
local file="$3"
if [ ! -f "$file" ]; then
echo " ⚠️ 跳过(文件不存在): $file"
return
fi
# 替换占位符 → 写入临时文件(避免 shell 特殊字符问题)
local tmpfile
tmpfile=$(mktemp)
sed \
-e "s|\${MYSQL_HOST}|${MYSQL_HOST}|g" \
-e "s|\${MYSQL_PORT}|${MYSQL_PORT}|g" \
-e "s|\${MYSQL_USER}|${MYSQL_USER}|g" \
-e "s|\${MYSQL_PASS}|${MYSQL_PASS}|g" \
-e "s|\${REDIS_HOST}|${REDIS_HOST}|g" \
-e "s|\${REDIS_PORT}|${REDIS_PORT}|g" \
-e "s|\${REDIS_PASS}|${REDIS_PASS}|g" \
-e "s|\${DB_NAME}|${DB_NAME}|g" \
-e "s|\${MINIO_HOST}|${MINIO_HOST}|g" \
-e "s|\${MINIO_PORT}|${MINIO_PORT}|g" \
-e "s|\${MINIO_ACCESS_KEY}|${MINIO_ACCESS_KEY}|g" \
-e "s|\${MINIO_SECRET_KEY}|${MINIO_SECRET_KEY}|g" \
-e "s|\${MINIO_BUCKET_NAME}|${MINIO_BUCKET_NAME}|g" \
"$file" > "$tmpfile"
local token_param=""
[ -n "$ACCESS_TOKEN" ] && token_param="&accessToken=${ACCESS_TOKEN}"
local http_code
http_code=$(curl -s -o /dev/null -w "%{http_code}" -X POST "${NACOS_URL}/nacos/v1/cs/configs" \
-d "tenant=${NAMESPACE}&dataId=${data_id}&group=${GROUP}&type=${type}${token_param}" \
--data-urlencode "content@${tmpfile}")
rm -f "$tmpfile"
if [ "$http_code" = "200" ]; then
echo "${data_id}"
else
echo "${data_id}HTTP ${http_code}"
fi
}
# yaml 配置
publish_config "jeecg.yaml" "yaml" "${CONFIG_DIR}/jeecg.yaml"
publish_config "jeecg-dev.yaml" "yaml" "${CONFIG_DIR}/jeecg-dev.yaml"
publish_config "jeecg-gateway-dev.yaml" "yaml" "${CONFIG_DIR}/jeecg-gateway-dev.yaml"
# json 配置(路由表)
publish_config "test-gateway-router" "json" "${CONFIG_DIR}/test-gateway-router.json"
echo ""
echo "✅ Nacos 配置初始化完成"
echo " Nacos 地址: ${NACOS_URL}/nacos"

View File

@ -0,0 +1,147 @@
spring:
datasource:
druid:
stat-view-servlet:
enabled: true
loginUsername: admin
loginPassword: 123456
allow:
web-stat-filter:
enabled: true
dynamic:
druid:
initial-size: 5
min-idle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat,wall,slf4j
connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
datasource:
master:
url: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${DB_NAME}?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: ${MYSQL_USER}
password: ${MYSQL_PASS}
driver-class-name: com.mysql.cj.jdbc.Driver
data:
redis:
database: 0
host: ${REDIS_HOST}
password: ${REDIS_PASS}
port: ${REDIS_PORT}
rabbitmq:
host: jeecg-boot-rabbitmq
username: guest
password: guest
port: 5672
publisher-confirms: true
publisher-returns: true
virtual-host: /
listener:
simple:
acknowledge-mode: manual
concurrency: 1
max-concurrency: 1
retry:
enabled: true
minidao:
base-package: org.jeecg.modules.jmreport.*,org.jeecg.modules.drag.*
ghb:
signatureSecret: dd05f1c54d63749eda95f9fa6d49v442a
signUrls: /sys/dict/getDictItems/*,/sys/dict/loadDict/*,/sys/dict/loadDictOrderByValue/*,/sys/dict/loadDictItem/*,/sys/dict/loadTreeData,/sys/api/queryTableDictItemsByCode,/sys/api/queryFilterTableDictInfo,/sys/api/queryTableDictByKeys,/sys/api/translateDictFromTable,/sys/api/translateDictFromTableByKeys
uploadType: local
domainUrl:
pc: http://localhost:3100
app: http://localhost:8051
path:
upload: /opt/upFiles
webapp: /opt/webapp
shiro:
excludeUrls: /test/jeecgDemo/demo3,/test/jeecgDemo/redisDemo/**,/category/**,/visual/**,/map/**,/jmreport/bigscreen2/**
oss:
endpoint: oss-cn-beijing.aliyuncs.com
accessKey: ??
secretKey: ??
bucketName: jeecgdev
staticDomain: ??
elasticsearch:
cluster-name: jeecg-ES
cluster-nodes: jeecg-boot-es:9200
check-enabled: false
file-view-domain: 127.0.0.1:8012
minio:
minio_url: http://${MINIO_HOST}:${MINIO_PORT}
minio_name: ${MINIO_ACCESS_KEY}
minio_pass: ${MINIO_SECRET_KEY}
bucketName: ${MINIO_BUCKET_NAME}
jmreport:
mode: dev
is_verify_token: false
verify_methods: remove,delete,save,add,update
wps:
domain: https://wwo.wps.cn/office/
appid: ??
appsecret: ??
xxljob:
enabled: false
adminAddresses: http://jeecg-boot-xxljob:9080/xxl-job-admin
appname: ${spring.application.name}
accessToken: ''
logPath: logs/jeecg/job/jobhandler/
logRetentionDays: 30
redisson:
address: ${REDIS_HOST}:${REDIS_PORT}
password: ${REDIS_PASS}
type: STANDALONE
enabled: true
logging:
level:
com.ghb.base.modules.system.mapper : info
cas:
prefixUrl: http://localhost:8888/cas
knife4j:
production: false
basic:
enable: false
username: jeecg
password: jeecg1314
justauth:
enabled: true
type:
GITHUB:
client-id: ??
client-secret: ??
redirect-uri: http://sso.test.com:8080/jeecg-boot/thirdLogin/github/callback
WECHAT_ENTERPRISE:
client-id: ??
client-secret: ??
redirect-uri: http://sso.test.com:8080/jeecg-boot/thirdLogin/wechat_enterprise/callback
agent-id: ??
DINGTALK:
client-id: ??
client-secret: ??
redirect-uri: http://sso.test.com:8080/jeecg-boot/thirdLogin/dingtalk/callback
cache:
type: default
prefix: 'demo::'
timeout: 1h
third-app:
enabled: false
type:
WECHAT_ENTERPRISE:
enabled: false
client-id: ??
client-secret: ??
agent-id: ??
DINGTALK:
enabled: false
client-id: ??
client-secret: ??
agent-id: ??

View File

@ -0,0 +1,14 @@
ghb:
route:
config:
#type:database nacos yml
data-type: nacos
group: DEFAULT_GROUP
data-id: test-gateway-router
spring:
data:
redis:
database: 0
host: ${REDIS_HOST}
port: ${REDIS_PORT}
password: ${REDIS_PASS}

View File

@ -0,0 +1,100 @@
server:
tomcat:
max-swallow-size: -1
error:
include-exception: true
include-stacktrace: ALWAYS
include-message: ALWAYS
compression:
enabled: true
min-response-size: 1024
mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/*
management:
health:
mail:
enabled: false
endpoints:
web:
exposure:
include: "*"
health:
sensitive: true
endpoint:
health:
show-details: ALWAYS
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
mail:
host: smtp.163.com
username: jeecgos@163.com
password: ??
properties:
mail:
smtp:
auth: true
starttls:
enable: true
required: true
quartz:
job-store-type: jdbc
initialize-schema: embedded
auto-startup: false
startup-delay: 1s
overwrite-existing-jobs: true
properties:
org:
quartz:
scheduler:
instanceName: MyScheduler
instanceId: AUTO
jobStore:
class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
tablePrefix: QRTZ_
isClustered: true
misfireThreshold: 12000
clusterCheckinInterval: 15000
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 10
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
aop:
proxy-target-class: true
activiti:
check-process-definitions: false
async-executor-activate: false
job-executor-activate: false
jpa:
open-in-view: false
freemarker:
suffix: .ftl
content-type: text/html
charset: UTF-8
cache: false
prefer-file-system-access: false
template-loader-path:
- classpath:/templates
mvc:
static-path-pattern: /**
pathmatch:
matching-strategy: ant_path_matcher
resource:
static-locations: classpath:/static/,classpath:/public/
autoconfigure:
exclude: com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure
mybatis-plus:
mapper-locations: classpath*:org/jeecg/modules/**/xml/*Mapper.xml
global-config:
banner: false
db-config:
id-type: ASSIGN_ID
table-underline: true
configuration:
call-setters-on-nulls: true

View File

@ -0,0 +1,52 @@
[{
"id": "test-system",
"order": 0,
"predicates": [{
"name": "Path",
"args": {
"_genkey_0": "/sys/**",
"_genkey_1": "/jmreport/**",
"_genkey_3": "/online/**",
"_genkey_4": "/generic/**"
}
}],
"filters": [],
"uri": "lb://test-system"
}, {
"id": "test-demo",
"order": 1,
"predicates": [{
"name": "Path",
"args": {
"_genkey_0": "/mock/**",
"_genkey_1": "/test/**",
"_genkey_2": "/bigscreen/template1/**",
"_genkey_3": "/bigscreen/template2/**"
}
}],
"filters": [],
"uri": "lb://test-demo"
}, {
"id": "test-system-websocket",
"order": 2,
"predicates": [{
"name": "Path",
"args": {
"_genkey_0": "/websocket/**",
"_genkey_1": "/newsWebsocket/**"
}
}],
"filters": [],
"uri": "lb:ws://test-system"
}, {
"id": "test-demo-websocket",
"order": 3,
"predicates": [{
"name": "Path",
"args": {
"_genkey_0": "/vxeSocket/**"
}
}],
"filters": [],
"uri": "lb:ws://test-demo"
}]

82
docker-compose.yml Normal file
View File

@ -0,0 +1,82 @@
version: '2'
services:
jeecg-boot-mysql:
build:
context: ./db
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_ROOT_HOST: '%'
TZ: Asia/Shanghai
restart: always
container_name: jeecg-boot-mysql
image: jeecg-boot-mysql
command:
--character-set-server=utf8mb4
--collation-server=utf8mb4_general_ci
--explicit_defaults_for_timestamp=true
--lower_case_table_names=1
--max_allowed_packet=128M
--default-authentication-plugin=caching_sha2_password
ports:
- 13306:3306
networks:
- jeecg-boot
jeecg-boot-redis:
image: registry.cn-hangzhou.aliyuncs.com/jeecgdocker/redis:5.0
# ports:
# - 6379:6379
restart: always
hostname: jeecg-boot-redis
container_name: jeecg-boot-redis
networks:
- jeecg-boot
jeecg-boot-pgvector:
image: registry.cn-hangzhou.aliyuncs.com/jeecgdocker/pgvector
container_name: jeecg-boot-pgvector
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: vector_db
# ports:
# - 5432:5432
restart: always
networks:
- jeecg-boot
# jeecg-boot-rabbitmq:
# image: rabbitmq:3.7.7-management
## ports:
## - 5672:5672
## - 15672:15672
# restart: always
# container_name: jeecg-boot-rabbitmq
# hostname: jeecg-boot-rabbitmq
# environment:
# RABBITMQ_DEFAULT_USER: guest
# RABBITMQ_DEFAULT_PASS: guest
# networks:
# - jeecg-boot
jeecg-boot-system:
build:
context: ./jeecg-module-system/test-system-start
restart: on-failure
mac_address: 02:42:ac:11:00:02
depends_on:
- jeecg-boot-mysql
- jeecg-boot-redis
container_name: jeecg-boot-system
image: jeecg-boot-system
hostname: jeecg-boot-system
ports:
- 8080:8080
networks:
- jeecg-boot
volumes:
- ./config:/jeecg-boot/config
networks:
jeecg-boot:
name: jeecg_boot

757
pom.xml Normal file
View File

@ -0,0 +1,757 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" 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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ghb</groupId>
<artifactId>test-base-parent</artifactId>
<version>3.9.2</version>
<packaging>pom</packaging>
<name>test-base ${project.version}</name>
<developers>
<developer>
<name>北京国炬信息技术有限公司</name>
<email>jeecgos@163.com</email>
<url>http://www.guojusoft.com</url>
</developer>
</developers>
<scm>
<connection>http://www.jeecg.com</connection>
<developerConnection>http://guojusoft.com</developerConnection>
<url>http://www.jeecg.com/vip</url>
</scm>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.5</version>
<relativePath/>
</parent>
<properties>
<jeecgboot.version>3.9.2</jeecgboot.version>
<!-- JDK 版本支持 17、21、24、25 -->
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 微服务 -->
<spring-cloud.version>2025.0.0</spring-cloud.version>
<spring-cloud-alibaba.version>2023.0.3.3</spring-cloud-alibaba.version>
<alibaba.nacos.version>2.0.4</alibaba.nacos.version>
<xxl-job-core.version>2.4.1</xxl-job-core.version>
<fastjson.version>2.0.57</fastjson.version>
<aviator.version>5.2.6</aviator.version>
<pegdown.version>1.6.0</pegdown.version>
<commonmark.version>0.17.0</commonmark.version>
<knife4j-spring-boot-starter.version>4.5.0</knife4j-spring-boot-starter.version>
<!-- 数据库驱动 -->
<postgresql.version>42.2.25</postgresql.version>
<ojdbc6.version>11.2.0.3</ojdbc6.version>
<sqljdbc4.version>12.6.1.jre8</sqljdbc4.version>
<mysql-connector-java.version>8.0.27</mysql-connector-java.version>
<hutool.version>5.8.25</hutool.version>
<!-- 国产数据库驱动 -->
<kingbase8.version>9.0.0</kingbase8.version>
<dm8.version>8.1.3.140</dm8.version>
<!-- 积木报表 -->
<jimureport-spring-boot-starter.version>2.3.4</jimureport-spring-boot-starter.version>
<jimubi-spring-boot-starter.version>2.3.2</jimubi-spring-boot-starter.version>
<minidao.version>1.10.20</minidao.version>
<autopoi-web.version>2.0.4</autopoi-web.version>
<!-- 持久层 -->
<mybatis-plus.version>3.5.12</mybatis-plus.version>
<dynamic-datasource-spring-boot-starter.version>4.3.1</dynamic-datasource-spring-boot-starter.version>
<druid.version>1.2.24</druid.version>
<commons-io.version>2.20.0</commons-io.version>
<commons-fileupload.version>1.5</commons-fileupload.version>
<commons.version>2.6</commons.version>
<aliyun-java-sdk-dysmsapi.version>2.1.0</aliyun-java-sdk-dysmsapi.version>
<aliyun.oss.version>3.17.3</aliyun.oss.version>
<tencentcloud-sdk-java-sms.version>3.1.407</tencentcloud-sdk-java-sms.version>
<!-- shiro -->
<shiro.version>2.0.5</shiro.version>
<shiro-redis.version>3.2.3</shiro-redis.version>
<java-jwt.version>4.5.0</java-jwt.version>
<codegenerate.version>1.5.6</codegenerate.version>
<minio.version>8.5.7</minio.version>
<justauth-spring-boot-starter.version>1.4.0</justauth-spring-boot-starter.version>
<dom4j.version>1.6.1</dom4j.version>
<qiniu-java-sdk.version>7.4.0</qiniu-java-sdk.version>
<jedis.version>3.8.0</jedis.version>
<baidu-java-sdk.version>4.16.19</baidu-java-sdk.version>
</properties>
<modules>
<module>test-base-core</module>
<module>test-module-system</module>
<module>test-module-business</module>
</modules>
<repositories>
<repository>
<id>aliyun</id>
<name>aliyun Repository</name>
<url>https://maven.aliyun.com/repository/public</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>jeecg</id>
<name>jeecg Repository</name>
<url>https://maven.jeecg.org/nexus/content/repositories/jeecg</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>jeecg-snapshots</id>
<name>jeecg-snapshots Repository</name>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>asm</artifactId>
<groupId>org.ow2.asm</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- markdown -->
<!--<dependency>
<groupId>org.pegdown</groupId>
<artifactId>pegdown</artifactId>
<version>${pegdown.version}</version>
</dependency>-->
<!--markdown 解析 https://github.com/commonmark/commonmark-java -->
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>${commonmark.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- spring-cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring-cloud-alibaba -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- system 模块-->
<dependency>
<groupId>com.ghb</groupId>
<artifactId>test-system-biz</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!-- business 模块-->
<dependency>
<groupId>com.ghb</groupId>
<artifactId>test-module-business</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!-- jeecg tools -->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-common</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!-- jeecg core -->
<dependency>
<groupId>com.ghb</groupId>
<artifactId>test-base-core</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!-- system 单体 api -->
<dependency>
<groupId>com.ghb</groupId>
<artifactId>test-system-local-api</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!-- system 微服务 api -->
<dependency>
<groupId>com.ghb</groupId>
<artifactId>test-system-cloud-api</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!-- 微服务启动依赖 -->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-cloud</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!-- xxl-job -->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-job</artifactId>
<version>3.9.2</version>
</dependency>
<!--redis分布式锁-->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-lock</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!--rabbitmq消息队列-->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-rabbitmq</artifactId>
<version>${jeecgboot.version}</version>
<exclusions>
<exclusion>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--rocketmq-->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-rocketmq</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!--分库分表shardingsphere-->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-shardingsphere</artifactId>
<version>3.9.2</version>
</dependency>
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-shardingsphere-nacos</artifactId>
<version>3.9.2</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.6.7.Final</version>
<exclusions>
<exclusion>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-online</artifactId>
<version>3.9.2-beta</version>
</dependency>
<!--mongon db-->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-mongon</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!--人大金仓驱动 版本号V008R006C005B0013 -->
<dependency>
<groupId>org.jeecgframework</groupId>
<artifactId>kingbase8</artifactId>
<version>9.0.0</version>
<scope>runtime</scope>
</dependency>
<!-- 达梦8 数据库最新驱动 版本号 -3-26-2023.07.26-197096-20046-ENT -->
<dependency>
<groupId>com.dameng</groupId>
<artifactId>DmJdbcDriver18</artifactId>
<version>${dm8.version}</version>
</dependency>
<dependency>
<groupId>com.dameng</groupId>
<artifactId>DmDialect-for-hibernate5.0</artifactId>
<version>${dm8.version}</version>
</dependency>
<!-- 七牛云SDK -->
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>${qiniu-java-sdk.version}</version>
<exclusions>
<exclusion>
<artifactId>okhttp</artifactId>
<groupId>com.squareup.okhttp3</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 百度SDKOCR -->
<dependency>
<groupId>com.baidu.aip</groupId>
<artifactId>java-sdk</artifactId>
<version>${baidu-java-sdk.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>${dom4j.version}</version>
</dependency>
<!-- update-begin-author:chenrui -date:20240104 for[issue/5723]指定jaxb-runtime版本 -->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>
<!-- update-end-author:chenrui -date:20240104 for[issue/5723]指定jaxb-runtime版本 -->
<!-- fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons-fileupload.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-io</artifactId>
<groupId>commons-io</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- justauth 第三方登录 -->
<dependency>
<groupId>com.xkcoding.justauth</groupId>
<artifactId>justauth-spring-boot-starter</artifactId>
<version>${justauth-spring-boot-starter.version}</version>
<exclusions>
<exclusion>
<artifactId>hutool-core</artifactId>
<groupId>cn.hutool</groupId>
</exclusion>
<exclusion>
<artifactId>fastjson</artifactId>
<groupId>com.alibaba</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.4.1</version>
</dependency>
<!-- hutool 工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>${hutool.version}</version>
</dependency>
<!--minio-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
<exclusions>
<exclusion>
<artifactId>okio</artifactId>
<groupId>com.squareup.okio</groupId>
</exclusion>
<exclusion>
<artifactId>okhttp</artifactId>
<groupId>com.squareup.okhttp3</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 企业微信和钉钉 SDK -->
<dependency>
<groupId>org.jeecgframework</groupId>
<artifactId>weixin4j</artifactId>
<version>2.0.4</version>
<exclusions>
<exclusion>
<artifactId>commons-beanutils</artifactId>
<groupId>commons-beanutils</groupId>
</exclusion>
<exclusion>
<artifactId>commons-lang</artifactId>
<groupId>commons-lang</groupId>
</exclusion>
<exclusion>
<artifactId>commons-collections</artifactId>
<groupId>commons-collections</groupId>
</exclusion>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
<exclusion>
<artifactId>commons-io</artifactId>
<groupId>commons-io</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- minidao -->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>minidao-spring-boot-starter-jsqlparser-4.9</artifactId>
<version>${minidao.version}</version>
<exclusions>
<exclusion>
<artifactId>druid</artifactId>
<groupId>com.alibaba</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 积木报表 -->
<dependency>
<groupId>org.jeecgframework.jimureport</groupId>
<artifactId>jimureport-spring-boot3-starter</artifactId>
<version>${jimureport-spring-boot-starter.version}</version>
<exclusions>
<exclusion>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</exclusion>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</exclusion>
<exclusion>
<artifactId>commons-lang</artifactId>
<groupId>commons-lang</groupId>
</exclusion>
<exclusion>
<artifactId>checker-qual</artifactId>
<groupId>org.checkerframework</groupId>
</exclusion>
<exclusion>
<artifactId>druid</artifactId>
<groupId>com.alibaba</groupId>
</exclusion>
<exclusion>
<artifactId>jsqlparser</artifactId>
<groupId>com.github.jsqlparser</groupId>
</exclusion>
<exclusion>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- AutoPoi Excel 工具类 -->
<dependency>
<groupId>org.jeecgframework</groupId>
<artifactId>autopoi-spring-boot-3-starter</artifactId>
<version>${autopoi-web.version}</version>
<exclusions>
<exclusion>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</exclusion>
<exclusion>
<artifactId>xercesImpl</artifactId>
<groupId>xerces</groupId>
</exclusion>
<exclusion>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
<exclusion>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- mongo、redis 和文件数据集支持包,按需引入 -->
<dependency>
<groupId>org.jeecgframework.jimureport</groupId>
<artifactId>jimureport-nosql-starter3</artifactId>
<version>2.3.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.calcite</groupId>
<artifactId>calcite-elasticsearch</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 后台导出接口 Echart 图表支持包,按需引入 -->
<dependency>
<groupId>org.jeecgframework.jimureport</groupId>
<artifactId>jimureport-echarts-starter</artifactId>
<version>2.3.0</version>
</dependency>
<!-- 积木BI 大屏和仪表盘 -->
<dependency>
<groupId>org.jeecgframework.jimureport</groupId>
<artifactId>jimubi-spring-boot3-starter</artifactId>
<version>${jimubi-spring-boot-starter.version}</version>
<exclusions>
<exclusion>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- chatgpt -->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-ai</artifactId>
<version>3.9.2.4</version>
</dependency>
<!-- flyway 支持 mysql5.7+、MariaDB10.3.16 -->
<!-- mysql5.6,需要把版本号改成 9.2.1 -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>7.15.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.27.1</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<!-- 指定JDK编译版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- 打包跳过测试 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>false</skipTests>
</configuration>
</plugin>
<!-- 避免font文件的二进制文件格式压缩破坏 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<encoding>UTF-8</encoding>
<nonFilteredFileExtensions>
<!-- 常见的二进制文件类型 -->
<nonFilteredFileExtension>woff</nonFilteredFileExtension>
<nonFilteredFileExtension>woff2</nonFilteredFileExtension>
<nonFilteredFileExtension>eot</nonFilteredFileExtension>
<nonFilteredFileExtension>ttf</nonFilteredFileExtension>
<nonFilteredFileExtension>svg</nonFilteredFileExtension>
<nonFilteredFileExtension>glb</nonFilteredFileExtension>
<nonFilteredFileExtension>wasm</nonFilteredFileExtension>
<nonFilteredFileExtension>ico</nonFilteredFileExtension>
<nonFilteredFileExtension>swf</nonFilteredFileExtension>
<nonFilteredFileExtension>mp3</nonFilteredFileExtension>
<nonFilteredFileExtension>mp4</nonFilteredFileExtension>
<nonFilteredFileExtension>jpg</nonFilteredFileExtension>
<nonFilteredFileExtension>png</nonFilteredFileExtension>
<nonFilteredFileExtension>gif</nonFilteredFileExtension>
<nonFilteredFileExtension>pdf</nonFilteredFileExtension>
<nonFilteredFileExtension>bcmap</nonFilteredFileExtension>
<nonFilteredFileExtension>cmap</nonFilteredFileExtension>
<nonFilteredFileExtension>cur</nonFilteredFileExtension>
<nonFilteredFileExtension>zip</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.json</include>
<include>**/*.ftl</include>
</includes>
</resource>
</resources>
</build>
<distributionManagement>
<repository>
<id>jeecg</id>
<name>jeecg Repository</name>
<url>http://maven.jeecg.com:8090/nexus/content/repositories/jeecg</url>
</repository>
<snapshotRepository>
<id>jeecg-snapshots</id>
<name>jeecg Snapshot Repository</name>
<url>http://maven.jeecg.com:8090/nexus/content/repositories/snapshots/</url>
</snapshotRepository>
</distributionManagement>
<!-- 环境 -->
<profiles>
<!-- 开发 -->
<profile>
<id>dev</id>
<activation>
<!-- 默认激活配置 -->
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<!-- 当前环境 -->
<profile.name>dev</profile.name>
<!--Nacos服务地址-->
<config.server-addr>test-nacos:8848</config.server-addr>
<!-- Nacos 配置中心命名空间用于支持多环境这里必须使用ID不能使用名称默认为空 -->
<config.namespace>springboot3</config.namespace>
<!--Nacos配置分组名称-->
<config.group>DEFAULT_GROUP</config.group>
<!-- Nacos 用户名 -->
<config.username></config.username>
<!-- Nacos 密码 -->
<config.password></config.password>
</properties>
</profile>
<!-- 测试 Nacos 开启鉴权、设置分组和命名空间
<profile>
<id>dev</id>
<properties>
<profile.name>dev</profile.name>
<config.server-addr>test-nacos:8848</config.server-addr>
<config.namespace>ac14ab82-51f8-4f0c-aa5b-25fb8384bfb6</config.namespace>
<config.group>JEECGDEV_GROUP</config.group>
<config.username>nacos</config.username>
<config.password>nacos</config.password>
</properties>
</profile> -->
<!-- 测试 -->
<profile>
<id>test</id>
<properties>
<!-- 当前环境 -->
<profile.name>test</profile.name>
<!--Nacos服务地址-->
<config.server-addr>test-nacos:8848</config.server-addr>
<!-- Nacos配置中心命名空间用于支持多环境这里必须使用ID不能使用名称默认为空 -->
<config.namespace>springboot3</config.namespace>
<!--Nacos配置分组名称-->
<config.group>DEFAULT_GROUP</config.group>
<!-- Nacos用户名 -->
<config.username></config.username>
<!--Nacos密码-->
<config.password></config.password>
</properties>
</profile>
<!-- docker 打包用 -->
<profile>
<id>docker</id>
<properties>
<!-- 当前环境 -->
<profile.name>docker</profile.name>
<!--Nacos服务地址-->
<config.server-addr>test-nacos:8848</config.server-addr>
<!-- Nacos配置中心命名空间用于支持多环境这里必须使用ID不能使用名称默认为空 -->
<config.namespace>springboot3</config.namespace>
<!--Nacos配置分组名称-->
<config.group>DEFAULT_GROUP</config.group>
<!-- Nacos用户名 -->
<config.username></config.username>
<!--Nacos密码-->
<config.password></config.password>
</properties>
</profile>
<!-- 生产 -->
<profile>
<id>prod</id>
<properties>
<!-- 当前环境 -->
<profile.name>prod</profile.name>
<!--Nacos服务地址-->
<config.server-addr>test-nacos:8848</config.server-addr>
<!-- Nacos配置中心命名空间用于支持多环境这里必须使用ID不能使用名称默认为空 -->
<config.namespace>springboot3</config.namespace>
<!--Nacos配置分组名称-->
<config.group>DEFAULT_GROUP</config.group>
<!-- Nacos用户名 -->
<config.username></config.username>
<!--Nacos密码-->
<config.password></config.password>
</properties>
</profile>
<!-- SpringCloud 运行环境 -->
<profile>
<id>SpringCloud</id>
<activation>
<!-- 默认激活配置 -->
<activeByDefault>false</activeByDefault>
</activation>
<modules>
<!-- 微服务模块 -->
<module>test-server-cloud</module>
</modules>
</profile>
</profiles>
</project>

750
pom.xml.bak Normal file
View File

@ -0,0 +1,750 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" 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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ghb</groupId>
<artifactId>ghb-base-parent</artifactId>
<version>3.9.2</version>
<packaging>pom</packaging>
<name>ghb-base ${project.version}</name>
<developers>
<developer>
<name>北京国炬信息技术有限公司</name>
<email>jeecgos@163.com</email>
<url>http://www.guojusoft.com</url>
</developer>
</developers>
<scm>
<connection>http://www.jeecg.com</connection>
<developerConnection>http://guojusoft.com</developerConnection>
<url>http://www.jeecg.com/vip</url>
</scm>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.5</version>
<relativePath/>
</parent>
<properties>
<jeecgboot.version>3.9.2</jeecgboot.version>
<!-- JDK鐗堟湰鏀寔17銆?1銆?4銆?5 -->
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 寰湇鍔?-->
<spring-cloud.version>2025.0.0</spring-cloud.version>
<spring-cloud-alibaba.version>2023.0.3.3</spring-cloud-alibaba.version>
<alibaba.nacos.version>2.0.4</alibaba.nacos.version>
<xxl-job-core.version>2.4.1</xxl-job-core.version>
<fastjson.version>2.0.57</fastjson.version>
<aviator.version>5.2.6</aviator.version>
<pegdown.version>1.6.0</pegdown.version>
<commonmark.version>0.17.0</commonmark.version>
<knife4j-spring-boot-starter.version>4.5.0</knife4j-spring-boot-starter.version>
<!-- 鏁版嵁搴撻┍鍔?-->
<postgresql.version>42.2.25</postgresql.version>
<ojdbc6.version>11.2.0.3</ojdbc6.version>
<sqljdbc4.version>12.6.1.jre8</sqljdbc4.version>
<mysql-connector-java.version>8.0.27</mysql-connector-java.version>
<hutool.version>5.8.25</hutool.version>
<!-- 鍥戒骇鏁版嵁搴撻┍鍔?-->
<kingbase8.version>9.0.0</kingbase8.version>
<dm8.version>8.1.3.140</dm8.version>
<!-- 绉湪鎶ヨ〃-->
<jimureport-spring-boot-starter.version>2.3.4</jimureport-spring-boot-starter.version>
<jimubi-spring-boot-starter.version>2.3.2</jimubi-spring-boot-starter.version>
<minidao.version>1.10.20</minidao.version>
<autopoi-web.version>2.0.4</autopoi-web.version>
<!-- 鎸佷箙灞?-->
<mybatis-plus.version>3.5.12</mybatis-plus.version>
<dynamic-datasource-spring-boot-starter.version>4.3.1</dynamic-datasource-spring-boot-starter.version>
<druid.version>1.2.24</druid.version>
<commons-io.version>2.20.0</commons-io.version>
<commons-fileupload.version>1.5</commons-fileupload.version>
<commons.version>2.6</commons.version>
<aliyun-java-sdk-dysmsapi.version>2.1.0</aliyun-java-sdk-dysmsapi.version>
<aliyun.oss.version>3.17.3</aliyun.oss.version>
<tencentcloud-sdk-java-sms.version>3.1.407</tencentcloud-sdk-java-sms.version>
<!-- shiro -->
<shiro.version>2.0.5</shiro.version>
<shiro-redis.version>3.2.3</shiro-redis.version>
<java-jwt.version>4.5.0</java-jwt.version>
<codegenerate.version>1.5.6</codegenerate.version>
<minio.version>8.5.7</minio.version>
<justauth-spring-boot-starter.version>1.4.0</justauth-spring-boot-starter.version>
<dom4j.version>1.6.1</dom4j.version>
<qiniu-java-sdk.version>7.4.0</qiniu-java-sdk.version>
<jedis.version>3.8.0</jedis.version>
<baidu-java-sdk.version>4.16.19</baidu-java-sdk.version>
</properties>
<modules>
<module>ghb-base-core</module>
<module>ghb-module-system</module>
<module>ghb-module-business</module>
</modules>
<repositories>
<repository>
<id>aliyun</id>
<name>aliyun Repository</name>
<url>https://maven.aliyun.com/repository/public</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>jeecg</id>
<name>jeecg Repository</name>
<url>https://maven.jeecg.org/nexus/content/repositories/jeecg</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>jeecg-snapshots</id>
<name>jeecg-snapshots Repository</name>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>asm</artifactId>
<groupId>org.ow2.asm</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- markdown -->
<!--<dependency>
<groupId>org.pegdown</groupId>
<artifactId>pegdown</artifactId>
<version>${pegdown.version}</version>
</dependency>-->
<!--markdown 瑙f瀽 https://github.com/commonmark/commonmark-java -->
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>${commonmark.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- spring-cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring-cloud-alibaba -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- system 妯″潡-->
<dependency>
<groupId>com.ghb</groupId>
<artifactId>ghb-system-biz</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!-- jeecg tools -->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-common</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!-- jeecg core -->
<dependency>
<groupId>com.ghb</groupId>
<artifactId>ghb-base-core</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!-- system 鍗曚綋 api -->
<dependency>
<groupId>com.ghb</groupId>
<artifactId>ghb-system-local-api</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!-- system 寰湇鍔?api -->
<dependency>
<groupId>com.ghb</groupId>
<artifactId>ghb-system-cloud-api</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!--寰湇鍔″惎鍔ㄤ緷璧-->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-cloud</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!-- xxl-job -->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-job</artifactId>
<version>3.9.2</version>
</dependency>
<!--redis鍒嗗竷寮忛攣-->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-lock</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!--rabbitmq娑堟伅闃熷垪-->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-rabbitmq</artifactId>
<version>${jeecgboot.version}</version>
<exclusions>
<exclusion>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--rocketmq-->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-rocketmq</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!--鍒嗗簱鍒嗚〃shardingsphere-->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-shardingsphere</artifactId>
<version>3.9.2</version>
</dependency>
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-shardingsphere-nacos</artifactId>
<version>3.9.2</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.6.7.Final</version>
<exclusions>
<exclusion>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-online</artifactId>
<version>3.9.2-beta</version>
</dependency>
<!--mongon db-->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-mongon</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!--浜哄ぇ閲戜粨椹卞姩 鐗堟湰鍙稸008R006C005B0013 -->
<dependency>
<groupId>org.jeecgframework</groupId>
<artifactId>kingbase8</artifactId>
<version>9.0.0</version>
<scope>runtime</scope>
</dependency>
<!--杈炬ⅵ8鏁版嵁搴撴渶鏂伴┍鍔?鐗堟湰鍙?-3-26-2023.07.26-197096-20046-ENT -->
<dependency>
<groupId>com.dameng</groupId>
<artifactId>DmJdbcDriver18</artifactId>
<version>${dm8.version}</version>
</dependency>
<dependency>
<groupId>com.dameng</groupId>
<artifactId>DmDialect-for-hibernate5.0</artifactId>
<version>${dm8.version}</version>
</dependency>
<!-- 涓冪墰浜慡DK -->
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>${qiniu-java-sdk.version}</version>
<exclusions>
<exclusion>
<artifactId>okhttp</artifactId>
<groupId>com.squareup.okhttp3</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 鐧惧害SDK 锛圤CR锛?-->
<dependency>
<groupId>com.baidu.aip</groupId>
<artifactId>java-sdk</artifactId>
<version>${baidu-java-sdk.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>${dom4j.version}</version>
</dependency>
<!-- update-begin-author:chenrui -date:20240104 for锛歔issue/5723]鎸囧畾jaxb-runtime鐗堟湰 -->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>
<!-- update-end-author:chenrui -date:20240104 for锛歔issue/5723]鎸囧畾jaxb-runtime鐗堟湰 -->
<!-- fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons-fileupload.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-io</artifactId>
<groupId>commons-io</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- justauth绗笁鏂圭櫥褰? -->
<dependency>
<groupId>com.xkcoding.justauth</groupId>
<artifactId>justauth-spring-boot-starter</artifactId>
<version>${justauth-spring-boot-starter.version}</version>
<exclusions>
<exclusion>
<artifactId>hutool-core</artifactId>
<groupId>cn.hutool</groupId>
</exclusion>
<exclusion>
<artifactId>fastjson</artifactId>
<groupId>com.alibaba</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.4.1</version>
</dependency>
<!-- hutool宸ュ叿绫-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>${hutool.version}</version>
</dependency>
<!--minio-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
<exclusions>
<exclusion>
<artifactId>okio</artifactId>
<groupId>com.squareup.okio</groupId>
</exclusion>
<exclusion>
<artifactId>okhttp</artifactId>
<groupId>com.squareup.okhttp3</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 浼佷笟寰俊鍜岄拤閽?SDK -->
<dependency>
<groupId>org.jeecgframework</groupId>
<artifactId>weixin4j</artifactId>
<version>2.0.4</version>
<exclusions>
<exclusion>
<artifactId>commons-beanutils</artifactId>
<groupId>commons-beanutils</groupId>
</exclusion>
<exclusion>
<artifactId>commons-lang</artifactId>
<groupId>commons-lang</groupId>
</exclusion>
<exclusion>
<artifactId>commons-collections</artifactId>
<groupId>commons-collections</groupId>
</exclusion>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
<exclusion>
<artifactId>commons-io</artifactId>
<groupId>commons-io</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- minidao -->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>minidao-spring-boot-starter-jsqlparser-4.9</artifactId>
<version>${minidao.version}</version>
<exclusions>
<exclusion>
<artifactId>druid</artifactId>
<groupId>com.alibaba</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 绉湪鎶ヨ〃-->
<dependency>
<groupId>org.jeecgframework.jimureport</groupId>
<artifactId>jimureport-spring-boot3-starter</artifactId>
<version>${jimureport-spring-boot-starter.version}</version>
<exclusions>
<exclusion>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</exclusion>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</exclusion>
<exclusion>
<artifactId>commons-lang</artifactId>
<groupId>commons-lang</groupId>
</exclusion>
<exclusion>
<artifactId>checker-qual</artifactId>
<groupId>org.checkerframework</groupId>
</exclusion>
<exclusion>
<artifactId>druid</artifactId>
<groupId>com.alibaba</groupId>
</exclusion>
<exclusion>
<artifactId>jsqlparser</artifactId>
<groupId>com.github.jsqlparser</groupId>
</exclusion>
<exclusion>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- AutoPoi Excel宸ュ叿绫-->
<dependency>
<groupId>org.jeecgframework</groupId>
<artifactId>autopoi-spring-boot-3-starter</artifactId>
<version>${autopoi-web.version}</version>
<exclusions>
<exclusion>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</exclusion>
<exclusion>
<artifactId>xercesImpl</artifactId>
<groupId>xerces</groupId>
</exclusion>
<exclusion>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
<exclusion>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- mongo銆乺edis鍜屾枃浠舵暟鎹泦鏀寔鍖咃紝鎸夐渶寮曞叆 -->
<dependency>
<groupId>org.jeecgframework.jimureport</groupId>
<artifactId>jimureport-nosql-starter3</artifactId>
<version>2.3.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.calcite</groupId>
<artifactId>calcite-elasticsearch</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 鍚庡彴瀵煎嚭鎺ュ彛Echart鍥捐〃鏀寔鍖咃紝鎸夐渶寮曞叆 -->
<dependency>
<groupId>org.jeecgframework.jimureport</groupId>
<artifactId>jimureport-echarts-starter</artifactId>
<version>2.3.0</version>
</dependency>
<!-- 绉湪BI澶у睆鍜屼华琛ㄧ洏 -->
<dependency>
<groupId>org.jeecgframework.jimureport</groupId>
<artifactId>jimubi-spring-boot3-starter</artifactId>
<version>${jimubi-spring-boot-starter.version}</version>
<exclusions>
<exclusion>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- chatgpt -->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-ai</artifactId>
<version>3.9.2.4</version>
</dependency>
<!--flyway 鏀寔 mysql5.7+銆丮ariaDB10.3.16-->
<!--mysql5.6锛岄渶瑕佹妸鐗堟湰鍙锋敼鎴?.2.1-->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>7.15.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.27.1</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<!-- 鎸囧畾JDK缂栬瘧鐗堟湰 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- 鎵撳寘璺宠繃娴嬭瘯 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<!-- 閬垮厤font鏂囦欢鐨勪簩杩涘埗鏂囦欢鏍煎紡鍘嬬缉鐮村潖 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<encoding>UTF-8</encoding>
<nonFilteredFileExtensions>
<!-- 甯歌鐨勪簩杩涘埗鏂囦欢绫诲瀷 -->
<nonFilteredFileExtension>woff</nonFilteredFileExtension>
<nonFilteredFileExtension>woff2</nonFilteredFileExtension>
<nonFilteredFileExtension>eot</nonFilteredFileExtension>
<nonFilteredFileExtension>ttf</nonFilteredFileExtension>
<nonFilteredFileExtension>svg</nonFilteredFileExtension>
<nonFilteredFileExtension>glb</nonFilteredFileExtension>
<nonFilteredFileExtension>wasm</nonFilteredFileExtension>
<nonFilteredFileExtension>ico</nonFilteredFileExtension>
<nonFilteredFileExtension>swf</nonFilteredFileExtension>
<nonFilteredFileExtension>mp3</nonFilteredFileExtension>
<nonFilteredFileExtension>mp4</nonFilteredFileExtension>
<nonFilteredFileExtension>jpg</nonFilteredFileExtension>
<nonFilteredFileExtension>png</nonFilteredFileExtension>
<nonFilteredFileExtension>gif</nonFilteredFileExtension>
<nonFilteredFileExtension>pdf</nonFilteredFileExtension>
<nonFilteredFileExtension>bcmap</nonFilteredFileExtension>
<nonFilteredFileExtension>cmap</nonFilteredFileExtension>
<nonFilteredFileExtension>cur</nonFilteredFileExtension>
<nonFilteredFileExtension>zip</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.json</include>
<include>**/*.ftl</include>
</includes>
</resource>
</resources>
</build>
<distributionManagement>
<repository>
<id>jeecg</id>
<name>jeecg Repository</name>
<url>http://maven.jeecg.com:8090/nexus/content/repositories/jeecg</url>
</repository>
<snapshotRepository>
<id>jeecg-snapshots</id>
<name>jeecg Snapshot Repository</name>
<url>http://maven.jeecg.com:8090/nexus/content/repositories/snapshots/</url>
</snapshotRepository>
</distributionManagement>
<!-- 鐜 -->
<profiles>
<!-- 寮€鍙?-->
<profile>
<id>dev</id>
<activation>
<!--榛樿婵€娲婚厤缃-->
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<!--褰撳墠鐜-->
<profile.name>dev</profile.name>
<!--Nacos鏈嶅姟鍦板潃-->
<config.server-addr>ghb-nacos:8848</config.server-addr>
<!--Nacos閰嶇疆涓績鍛藉悕绌洪棿,鐢ㄤ簬鏀寔澶氱幆澧?杩欓噷蹇呴』浣跨敤ID锛屼笉鑳戒娇鐢ㄥ悕绉?榛樿涓虹┖-->
<config.namespace>springboot3</config.namespace>
<!--Nacos閰嶇疆鍒嗙粍鍚嶇О-->
<config.group>DEFAULT_GROUP</config.group>
<!--Nacos鐢ㄦ埛鍚-->
<config.username></config.username>
<!--Nacos瀵嗙爜-->
<config.password></config.password>
</properties>
</profile>
<!-- 娴嬭瘯Nacos寮€鍚壌鏉冦€佽缃垎缁勫拰鍛藉悕绌洪棿
<profile>
<id>dev</id>
<properties>
<profile.name>dev</profile.name>
<config.server-addr>ghb-nacos:8848</config.server-addr>
<config.namespace>ac14ab82-51f8-4f0c-aa5b-25fb8384bfb6</config.namespace>
<config.group>JEECGDEV_GROUP</config.group>
<config.username>nacos</config.username>
<config.password>nacos</config.password>
</properties>
</profile> -->
<!-- 娴嬭瘯 -->
<profile>
<id>test</id>
<properties>
<!--褰撳墠鐜-->
<profile.name>test</profile.name>
<!--Nacos鏈嶅姟鍦板潃-->
<config.server-addr>ghb-nacos:8848</config.server-addr>
<!--Nacos閰嶇疆涓績鍛藉悕绌洪棿,鐢ㄤ簬鏀寔澶氱幆澧?杩欓噷蹇呴』浣跨敤ID锛屼笉鑳戒娇鐢ㄥ悕绉?榛樿涓虹┖-->
<config.namespace>springboot3</config.namespace>
<!--Nacos閰嶇疆鍒嗙粍鍚嶇О-->
<config.group>DEFAULT_GROUP</config.group>
<!--Nacos鐢ㄦ埛鍚-->
<config.username></config.username>
<!--Nacos瀵嗙爜-->
<config.password></config.password>
</properties>
</profile>
<!-- docker鎵撳寘鐢?-->
<profile>
<id>docker</id>
<properties>
<!--褰撳墠鐜-->
<profile.name>docker</profile.name>
<!--Nacos鏈嶅姟鍦板潃-->
<config.server-addr>ghb-nacos:8848</config.server-addr>
<!--Nacos閰嶇疆涓績鍛藉悕绌洪棿,鐢ㄤ簬鏀寔澶氱幆澧?杩欓噷蹇呴』浣跨敤ID锛屼笉鑳戒娇鐢ㄥ悕绉?榛樿涓虹┖-->
<config.namespace>springboot3</config.namespace>
<!--Nacos閰嶇疆鍒嗙粍鍚嶇О-->
<config.group>DEFAULT_GROUP</config.group>
<!--Nacos鐢ㄦ埛鍚-->
<config.username></config.username>
<!--Nacos瀵嗙爜-->
<config.password></config.password>
</properties>
</profile>
<!-- 鐢熶骇 -->
<profile>
<id>prod</id>
<properties>
<!--褰撳墠鐜-->
<profile.name>prod</profile.name>
<!--Nacos鏈嶅姟鍦板潃-->
<config.server-addr>ghb-nacos:8848</config.server-addr>
<!--Nacos閰嶇疆涓績鍛藉悕绌洪棿,鐢ㄤ簬鏀寔澶氱幆澧?杩欓噷蹇呴』浣跨敤ID锛屼笉鑳戒娇鐢ㄥ悕绉?榛樿涓虹┖-->
<config.namespace>springboot3</config.namespace>
<!--Nacos閰嶇疆鍒嗙粍鍚嶇О-->
<config.group>DEFAULT_GROUP</config.group>
<!--Nacos鐢ㄦ埛鍚-->
<config.username></config.username>
<!--Nacos瀵嗙爜-->
<config.password></config.password>
</properties>
</profile>
<!-- SpringCloud杩愯鐜 -->
<profile>
<id>SpringCloud</id>
<activation>
<!--榛樿婵€娲婚厤缃-->
<activeByDefault>false</activeByDefault>
</activation>
<modules>
<!-- 寰湇鍔℃ā鍧?-->
<module>ghb-server-cloud</module>
</modules>
</profile>
</profiles>
</project>

405
test-base-core/pom.xml Normal file
View File

@ -0,0 +1,405 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
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>
<groupId>com.ghb</groupId>
<artifactId>test-base-parent</artifactId>
<version>3.9.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>test-base-core</artifactId>
<repositories>
<repository>
<id>aliyun</id>
<name>aliyun Repository</name>
<url>https://maven.aliyun.com/repository/public</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>jeecg</id>
<name>jeecg Repository</name>
<url>https://maven.jeecg.org/nexus/content/repositories/jeecg</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>jeecg-snapshots</id>
<name>jeecg-snapshots Repository</name>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<!--jeecg-tools-->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-common</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--集成springmvc框架并实现自动配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--springboot2.3+ 需引入validation对应的包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--springboot2.6+解决metrics端点不显示jvm信息的问题-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<!-- commons -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commons.version}</version>
</dependency>
<!-- freemarker -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser-4.9</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- minidao -->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>minidao-spring-boot-starter-jsqlparser-4.9</artifactId>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- 动态数据源 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>${dynamic-datasource-spring-boot-starter.version}</version>
</dependency>
<!-- 数据库驱动 -->
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
<scope>runtime</scope>
</dependency>
<!-- sqlserver-->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>${sqljdbc4.version}</version>
<scope>runtime</scope>
</dependency>
<!-- oracle驱动 -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>${ojdbc6.version}</version>
<scope>runtime</scope>
</dependency>
<!-- postgresql驱动 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
<scope>runtime</scope>
</dependency>
<!--人大金仓驱动 版本号V008R006C005B0013 -->
<dependency>
<groupId>org.jeecgframework</groupId>
<artifactId>kingbase8</artifactId>
<version>${kingbase8.version}</version>
<scope>runtime</scope>
</dependency>
<!--达梦数据库驱动 版本号1-3-26-2023.07.26-197096-20046-ENT -->
<dependency>
<groupId>com.dameng</groupId>
<artifactId>DmJdbcDriver18</artifactId>
<version>${dm8.version}</version>
</dependency>
<dependency>
<groupId>com.dameng</groupId>
<artifactId>DmDialect-for-hibernate5.0</artifactId>
<version>${dm8.version}</version>
</dependency>
<!-- Quartz定时任务 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!--JWT-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${java-jwt.version}</version>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<classifier>jakarta</classifier>
<version>${shiro.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<classifier>jakarta</classifier>
<version>${shiro.version}</version>
<!-- 排除仍使用了javax.servlet的依赖 -->
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入适配jakarta的依赖包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<classifier>jakarta</classifier>
<version>${shiro.version}</version>
<exclusions>
<exclusion>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<classifier>jakarta</classifier>
<version>${shiro.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- shiro-redis -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>${shiro-redis.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</exclusion>
<exclusion>
<artifactId>checkstyle</artifactId>
<groupId>com.puppycrawl.tools</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-ui</artifactId>
<version>${knife4j-spring-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.7.0</version>
</dependency>
<!-- 代码生成器 -->
<!-- 如下载失败,请参考此文档 https://help.jeecg.com/java/setup/maven.html -->
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>codegenerate</artifactId>
<version>${codegenerate.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-io</artifactId>
<groupId>commons-io</groupId>
</exclusion>
<exclusion>
<artifactId>mysql-connector-java</artifactId>
<groupId>mysql</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- AutoPoi Excel工具类-->
<dependency>
<groupId>org.jeecgframework</groupId>
<artifactId>autopoi-spring-boot-3-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.jeecgframework.jimureport</groupId>
<artifactId>jimureport-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.12.2</version>
<optional>true</optional>
</dependency>
<!-- minio文件存储服务 -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<exclusions>
<exclusion>
<artifactId>checker-qual</artifactId>
<groupId>org.checkerframework</groupId>
</exclusion>
<exclusion>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_annotations</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 阿里云短信 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>${aliyun-java-sdk-dysmsapi.version}</version>
</dependency>
<!-- aliyun oss -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${aliyun.oss.version}</version>
</dependency>
<!-- 第三方登录 -->
<dependency>
<groupId>com.xkcoding.justauth</groupId>
<artifactId>justauth-spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<!-- 解决okhttp引用了kotlin,应用启动有警告日志问题 -->
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<!--加载hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
</dependency>
<!-- chatgpt -->
<dependency>
<groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-ai</artifactId>
</dependency>
<!-- 腾讯云 -->
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java-sms</artifactId>
<version>${tencentcloud-sdk-java-sms.version}</version>
<exclusions>
<exclusion>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</exclusion>
<exclusion>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,145 @@
package com.ghb.base.common.api;
import com.ghb.base.common.system.vo.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 通用api
* @author: Ghb-boot
*/
public interface CommonAPI {
/**
* 1查询用户角色信息
* @param username
* @return
*/
Set<String> queryUserRoles(String username);
/**
* 1查询用户角色信息
* @param userId
* @return
*/
Set<String> queryUserRolesById(String userId);
/**
* 2查询用户权限信息
* @param userId
* @return
*/
Set<String> queryUserAuths(String userId);
/**
* 3根据 id 查询数据库中存储的 DynamicDataSourceModel
*
* @param dbSourceId
* @return
*/
DynamicDataSourceModel getDynamicDbSourceById(String dbSourceId);
/**
* 4根据 code 查询数据库中存储的 DynamicDataSourceModel
*
* @param dbSourceCode
* @return
*/
DynamicDataSourceModel getDynamicDbSourceByCode(String dbSourceCode);
/**
* 5根据用户账号查询用户信息
* @param username
* @return
*/
public LoginUser getUserByName(String username);
/**
* 5根据用户账号查询用户Id
* @param username
* @return
*/
public String getUserIdByName(String username);
/**
* 6字典表的 翻译
* @param table
* @param text
* @param code
* @param key
* @return
*/
String translateDictFromTable(String table, String text, String code, String key);
/**
* 7普通字典的翻译
* @param code
* @param key
* @return
*/
String translateDict(String code, String key);
/**
* 8查询数据权限
* @param component 组件
* @param username 用户名
* @param requestPath 前段请求地址
* @return
*/
List<SysPermissionDataRuleModel> queryPermissionDataRule(String component, String requestPath, String username);
/**
* 9查询用户信息
* @param username
* @return
*/
SysUserCacheInfo getCacheUser(String username);
/**
* 10获取数据字典
* @param code
* @return
*/
public List<DictModel> queryDictItemsByCode(String code);
/**
* 获取有效的数据字典项
* @param code
* @return
*/
public List<DictModel> queryEnableDictItemsByCode(String code);
/**
* 13获取表数据字典
* @param tableFilterSql
* @param text
* @param code
* @return
*/
List<DictModel> queryTableDictItemsByCode(String tableFilterSql, String text, String code);
/**
* 14 普通字典的翻译根据多个dictCode和多条数据多个以逗号分割
* @param dictCodes 例如user_status,sex
* @param keys 例如1,2,0
* @return
*/
Map<String, List<DictModel>> translateManyDict(String dictCodes, String keys);
/**
* 15 字典表的 翻译可批量
* @param table
* @param text
* @param code
* @param keys 多个用逗号分割
* @param dataSource 数据源
* @return
*/
List<DictModel> translateDictFromTableByKeys(String table, String text, String code, String keys, String dataSource);
}

View File

@ -0,0 +1,42 @@
package com.ghb.base.common.api.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Map;
/**
* 调用AI流程入参
* for [QQYUN-13634]在baseapi里面封装方法方便其他模块调用
* @author chenrui
* @date 2025/9/2 14:11
*/
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class AiragFlowDTO implements Serializable {
private static final long serialVersionUID = 7431775881170684867L;
/**
* 流程id
*/
private String flowId;
/**
* 输入参数
*/
private Map<String, Object> inputParams;
/**
* 是否流式返回
*/
private boolean isStream;
}

View File

@ -0,0 +1,48 @@
package com.ghb.base.common.api.dto;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 流程审批意见DTO
* @author scott
* @date 2025-01-29
*/
@Data
public class ApprovalCommentDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 任务ID
*/
private String taskId;
/**
* 任务名称
*/
private String taskName;
/**
* 审批人ID
*/
private String approverId;
/**
* 审批人姓名
*/
private String approverName;
/**
* 审批意见
*/
private String approvalComment;
/**
* 审批时间
*/
private Date approvalTime;
}

View File

@ -0,0 +1,38 @@
package com.ghb.base.common.api.dto;
import lombok.Data;
/**
* @Author taoYan
* @Date 2022/7/26 14:44
**/
@Data
public class DataLogDTO {
private String tableName;
private String dataId;
private String content;
private String type;
private String createName;
public DataLogDTO(){
}
public DataLogDTO(String tableName, String dataId, String content, String type) {
this.tableName = tableName;
this.dataId = dataId;
this.content = content;
this.type = type;
}
public DataLogDTO(String tableName, String dataId, String type) {
this.tableName = tableName;
this.dataId = dataId;
this.type = type;
}
}

View File

@ -0,0 +1,31 @@
package com.ghb.base.common.api.dto;
import lombok.Data;
import jakarta.servlet.http.HttpServletResponse;
import java.io.Serializable;
/**
* 文件下载
* cloud api 用到的接口传输对象
* @author: Ghb-boot
*/
@Data
public class FileDownDTO implements Serializable {
private static final long serialVersionUID = 6749126258686446019L;
private String filePath;
private String uploadpath;
private String uploadType;
private HttpServletResponse response;
public FileDownDTO(){}
public FileDownDTO(String filePath, String uploadpath, String uploadType,HttpServletResponse response){
this.filePath = filePath;
this.uploadpath = uploadpath;
this.uploadType = uploadType;
this.response = response;
}
}

View File

@ -0,0 +1,56 @@
package com.ghb.base.common.api.dto;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import java.io.Serializable;
/**
* 文件上传
* cloud api 用到的接口传输对象
* @author: Ghb-boot
*/
@Data
public class FileUploadDTO implements Serializable {
private static final long serialVersionUID = -4111953058578954386L;
private MultipartFile file;
private String bizPath;
private String uploadType;
private String customBucket;
public FileUploadDTO(){
}
/**
* 简单上传 构造器1
* @param file
* @param bizPath
* @param uploadType
*/
public FileUploadDTO(MultipartFile file,String bizPath,String uploadType){
this.file = file;
this.bizPath = bizPath;
this.uploadType = uploadType;
}
/**
* 申明桶 文件上传 构造器2
* @param file
* @param bizPath
* @param uploadType
* @param customBucket
*/
public FileUploadDTO(MultipartFile file,String bizPath,String uploadType,String customBucket){
this.file = file;
this.bizPath = bizPath;
this.uploadType = uploadType;
this.customBucket = customBucket;
}
}

View File

@ -0,0 +1,80 @@
package com.ghb.base.common.api.dto;
import lombok.Data;
import com.ghb.base.common.aspect.annotation.Dict;
import com.ghb.base.common.system.vo.LoginUser;
import java.io.Serializable;
import java.util.Date;
/**
* 日志对象
* cloud api 用到的接口传输对象
* @author: Ghb-boot
*/
@Data
public class LogDTO implements Serializable {
private static final long serialVersionUID = 8482720462943906924L;
/**内容*/
private String logContent;
/**日志类型(0:操作日志;1:登录日志;2:定时任务) */
private Integer logType;
/**操作类型(1:添加;2:修改;3:删除;) */
private Integer operateType;
/**登录用户 */
private LoginUser loginUser;
private String id;
private String createBy;
private Date createTime;
private Long costTime;
private String ip;
/**请求参数 */
private String requestParam;
/**请求类型*/
private String requestType;
/**请求路径*/
private String requestUrl;
/**请求方法 */
private String method;
/**操作人用户名称*/
private String username;
/**操作人用户账户*/
private String userid;
/**
* 租户ID
*/
private Integer tenantId;
/**
* 客户终端类型 pc:电脑端 app:手机端 h5:移动网页端
*/
private String clientType;
public LogDTO(){
}
public LogDTO(String logContent, Integer logType, Integer operatetype){
this.logContent = logContent;
this.logType = logType;
this.operateType = operatetype;
}
public LogDTO(String logContent, Integer logType, Integer operatetype, LoginUser loginUser){
this.logContent = logContent;
this.logType = logType;
this.operateType = operatetype;
this.loginUser = loginUser;
}
}

View File

@ -0,0 +1,47 @@
package com.ghb.base.common.api.dto;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* online 拦截器权限判断
* cloud api 用到的接口传输对象
* @author: Ghb-boot
*/
@Data
public class OnlineAuthDTO implements Serializable {
private static final long serialVersionUID = 1771827545416418203L;
/**
* 用户名
*/
private String username;
/**
* 可能的请求地址
*/
private List<String> possibleUrl;
/**
* online开发的菜单地址
*/
private String onlineFormUrl;
/**
* online工单的地址
*/
private String onlineWorkOrderUrl;
public OnlineAuthDTO(){
}
public OnlineAuthDTO(String username, List<String> possibleUrl, String onlineFormUrl){
this.username = username;
this.possibleUrl = possibleUrl;
this.onlineFormUrl = onlineFormUrl;
}
}

View File

@ -0,0 +1,55 @@
package com.ghb.base.common.api.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* 移动端消息推送
* @author liusq
* @date 2025/11/12 14:11
*/
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class PushMessageDTO implements Serializable {
private static final long serialVersionUID = 7431775881170684867L;
/**
* 消息标题
*/
private String title;
/**
* 消息内容
*/
private String content;
/**
* 推送形式all全推送 single单用户推送
*/
private String pushType;
/**
* 用户名usernameList
*/
List<String> usernames;
/**
* 用户名idList
*/
List<String> userIds;
/**
* 消息附加参数
*/
Map<String,Object> payload;
}

View File

@ -0,0 +1,46 @@
package com.ghb.base.common.api.dto.message;
import lombok.Data;
import java.io.Serializable;
/**
* 带业务参数的消息
*
* @author: taoyan
* @date: 2022/8/17
*/
@Data
public class BusMessageDTO extends MessageDTO implements Serializable {
private static final long serialVersionUID = 9104793287983367669L;
/**
* 业务类型
*/
private String busType;
/**
* 业务id
*/
private String busId;
public BusMessageDTO(){
}
/**
* 构造 带业务参数的消息
* @param fromUser
* @param toUser
* @param title
* @param msgContent
* @param msgCategory
* @param busType
* @param busId
*/
public BusMessageDTO(String fromUser, String toUser, String title, String msgContent, String msgCategory, String busType, String busId){
super(fromUser, toUser, title, msgContent, msgCategory);
this.busId = busId;
this.busType = busType;
}
}

View File

@ -0,0 +1,46 @@
package com.ghb.base.common.api.dto.message;
import lombok.Data;
import java.io.Serializable;
import java.util.Map;
/**
* 带业务参数的模板消息
* @author: Ghb-boot
*/
@Data
public class BusTemplateMessageDTO extends TemplateMessageDTO implements Serializable {
private static final long serialVersionUID = -4277810906346929459L;
/**
* 业务类型
*/
private String busType;
/**
* 业务id
*/
private String busId;
public BusTemplateMessageDTO(){
}
/**
* 构造 带业务参数的模板消息
* @param fromUser
* @param toUser
* @param title
* @param templateParam
* @param templateCode
* @param busType
* @param busId
*/
public BusTemplateMessageDTO(String fromUser, String toUser, String title, Map<String, String> templateParam, String templateCode, String busType, String busId){
super(fromUser, toUser, title, templateParam, templateCode);
this.busId = busId;
this.busType = busType;
}
}

View File

@ -0,0 +1,134 @@
package com.ghb.base.common.api.dto.message;
import lombok.Data;
import com.ghb.base.common.constant.CommonConstant;
import java.io.Serializable;
import java.util.*;
/**
* 普通消息
* @author: Ghb-boot
*/
@Data
public class MessageDTO implements Serializable {
private static final long serialVersionUID = -5690444483968058442L;
/**
* 发送人(用户登录账户)
*/
protected String fromUser;
/**
* 发送给(用户登录账户)
*/
protected String toUser;
/**
* 发送给所有人
*/
protected Boolean toAll;
/**
* 消息主题
*/
protected String title;
/**
* 消息内容
*/
protected String content;
/**
* 消息类型 1:消息 2:系统消息
*/
protected String category;
/**
* 消息类型com.ghb.base.common.constant.enums.MessageTypeEnum
* XT("system", "系统消息")
* YJ("email", "邮件消息")
* DD("dingtalk", "钉钉消息")
* QYWX("wechat_enterprise", "企业微信")
*/
protected String type;
//---推送模板相关参数-------------------------------------------------------------
/**
* 是否发送Markdown格式的消息
*/
protected boolean isMarkdown;
/**
* 模板消息对应的模板编码
*/
protected String templateCode;
/**
* 解析模板内容 对应的数据
*/
protected Map<String, Object> data;
//---推送模板相关参数-------------------------------------------------------------
//---邮件相关参数-------------------------------------------------------------
/**
* 邮件抄送人
*/
private String copyToUser;
/**
* 邮件推送地址
*/
protected Set<String> toEmailList;
/**
* 邮件抄送地址
*/
protected Set<String> ccEmailList;
/**
* 是否为定时任务推送email
*/
private Boolean isTimeJob = false;
//---邮件相关参数-------------------------------------------------------------
/**
* 枚举com.ghb.base.common.constant.enums.NoticeTypeEnum
* 通知类型(system:系统消息file:知识库flow:流程plan:日程计划meeting:会议)
*/
private String noticeType;
public MessageDTO(){
}
/**
* 构造器1 系统消息
*/
public MessageDTO(String fromUser,String toUser,String title, String content){
this.fromUser = fromUser;
this.toUser = toUser;
this.title = title;
this.content = content;
//默认 都是2系统消息
this.category = CommonConstant.MSG_CATEGORY_2;
}
/**
* 构造器2 支持设置category 1:消息 2:系统消息
*/
public MessageDTO(String fromUser,String toUser,String title, String content, String category){
this.fromUser = fromUser;
this.toUser = toUser;
this.title = title;
this.content = content;
this.category = category;
}
public boolean isMarkdown() {
return this.isMarkdown;
}
public void setIsMarkdown(boolean isMarkdown) {
this.isMarkdown = isMarkdown;
}
}

View File

@ -0,0 +1,38 @@
package com.ghb.base.common.api.dto.message;
import lombok.Data;
import java.io.Serializable;
import java.util.Map;
/**
* 消息模板dto
* @author: Ghb-boot
*/
@Data
public class TemplateDTO implements Serializable {
private static final long serialVersionUID = 5848247133907528650L;
/**
* 模板编码
*/
protected String templateCode;
/**
* 模板参数
*/
protected Map<String, String> templateParam;
/**
* 构造器 通过设置模板参数和模板编码 作为参数获取消息内容
*/
public TemplateDTO(String templateCode, Map<String, String> templateParam){
this.templateCode = templateCode;
this.templateParam = templateParam;
}
public TemplateDTO(){
}
}

View File

@ -0,0 +1,49 @@
package com.ghb.base.common.api.dto.message;
import lombok.Data;
import java.io.Serializable;
import java.util.Map;
/**
* 模板消息
* @author: Ghb-boot
*/
@Data
public class TemplateMessageDTO extends TemplateDTO implements Serializable {
private static final long serialVersionUID = 411137565170647585L;
/**
* 发送人(用户登录账户)
*/
protected String fromUser;
/**
* 发送给(用户登录账户)
*/
protected String toUser;
/**
* 消息主题
*/
protected String title;
public TemplateMessageDTO(){
}
/**
* 构造器1 发模板消息用
*/
public TemplateMessageDTO(String fromUser, String toUser,String title, Map<String, String> templateParam, String templateCode){
super(templateCode, templateParam);
this.fromUser = fromUser;
this.toUser = toUser;
this.title = title;
}
}

View File

@ -0,0 +1,177 @@
package com.ghb.base.common.api.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import com.ghb.base.common.constant.CommonConstant;
import java.io.Serializable;
/**
* 接口返回数据格式
* @author scott
* @email Ghbos@163.com
* @date 2019年1月19日
*/
@Data
@Schema(description="接口返回对象")
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 成功标志
*/
@Schema(description = "成功标志")
private boolean success = true;
/**
* 返回处理消息
*/
@Schema(description = "返回处理消息")
private String message = "";
/**
* 返回代码
*/
@Schema(description = "返回代码")
private Integer code = 0;
/**
* 返回数据对象 data
*/
@Schema(description = "返回数据对象")
private T result;
/**
* 时间戳
*/
@Schema(description = "时间戳")
private long timestamp = System.currentTimeMillis();
public Result() {
}
/**
* 兼容VUE3版token失效不跳转登录页面
* @param code
* @param message
*/
public Result(Integer code, String message) {
this.code = code;
this.message = message;
}
public Result<T> success(String message) {
this.message = message;
this.code = CommonConstant.SC_OK_200;
this.success = true;
return this;
}
public static<T> Result<T> ok() {
Result<T> r = new Result<T>();
r.setSuccess(true);
r.setCode(CommonConstant.SC_OK_200);
return r;
}
public static<T> Result<T> ok(String msg) {
Result<T> r = new Result<T>();
r.setSuccess(true);
r.setCode(CommonConstant.SC_OK_200);
//Result OK(String msg)方法会造成兼容性问题 issues/I4IP3D
r.setResult((T) msg);
r.setMessage(msg);
return r;
}
public static<T> Result<T> ok(T data) {
Result<T> r = new Result<T>();
r.setSuccess(true);
r.setCode(CommonConstant.SC_OK_200);
r.setResult(data);
return r;
}
public static<T> Result<T> OK() {
Result<T> r = new Result<T>();
r.setSuccess(true);
r.setCode(CommonConstant.SC_OK_200);
return r;
}
/**
* 此方法是为了兼容升级所创建
*
* @param msg
* @param <T>
* @return
*/
public static<T> Result<T> OK(String msg) {
Result<T> r = new Result<T>();
r.setSuccess(true);
r.setCode(CommonConstant.SC_OK_200);
r.setMessage(msg);
//Result OK(String msg)方法会造成兼容性问题 issues/I4IP3D
r.setResult((T) msg);
return r;
}
public static<T> Result<T> OK(T data) {
Result<T> r = new Result<T>();
r.setSuccess(true);
r.setCode(CommonConstant.SC_OK_200);
r.setResult(data);
return r;
}
public static<T> Result<T> OK(String msg, T data) {
Result<T> r = new Result<T>();
r.setSuccess(true);
r.setCode(CommonConstant.SC_OK_200);
r.setMessage(msg);
r.setResult(data);
return r;
}
public static<T> Result<T> error(String msg, T data) {
Result<T> r = new Result<T>();
r.setSuccess(false);
r.setCode(CommonConstant.SC_INTERNAL_SERVER_ERROR_500);
r.setMessage(msg);
r.setResult(data);
return r;
}
public static<T> Result<T> error(String msg) {
return error(CommonConstant.SC_INTERNAL_SERVER_ERROR_500, msg);
}
public static<T> Result<T> error(int code, String msg) {
Result<T> r = new Result<T>();
r.setCode(code);
r.setMessage(msg);
r.setSuccess(false);
return r;
}
public Result<T> error500(String message) {
this.message = message;
this.code = CommonConstant.SC_INTERNAL_SERVER_ERROR_500;
this.success = false;
return this;
}
/**
* 无权限访问返回结果
*/
public static<T> Result<T> noauth(String msg) {
return error(CommonConstant.SC_Ghb_NO_AUTHZ, msg);
}
@JsonIgnore
private String onlTable;
}

View File

@ -0,0 +1,258 @@
package com.ghb.base.common.aspect;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.PropertyFilter;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import com.ghb.base.common.api.dto.LogDTO;
import com.ghb.base.common.api.vo.Result;
import com.ghb.base.common.aspect.annotation.AutoLog;
import com.ghb.base.common.constant.CommonConstant;
import com.ghb.base.common.constant.enums.ModuleType;
import com.ghb.base.common.constant.enums.OperateTypeEnum;
import com.ghb.base.modules.base.service.BaseCommonService;
import com.ghb.base.common.system.vo.LoginUser;
import com.ghb.base.common.util.IpUtils;
import com.ghb.base.common.util.SpringContextUtils;
import com.ghb.base.common.util.oConvertUtils;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
/**
* 系统日志切面处理类
*
* @Author scott
* @email Ghbos@163.com
* @Date 2018年1月14日
*/
@Aspect
@Component
public class AutoLogAspect {
@Resource
private BaseCommonService baseCommonService;
@Pointcut("@annotation(com.ghb.base.common.aspect.annotation.AutoLog)")
public void logPointCut() {
}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
//执行方法
Object result = point.proceed();
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//保存日志
saveSysLog(point, time, result);
return result;
}
private void saveSysLog(ProceedingJoinPoint joinPoint, long time, Object obj) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
LogDTO dto = new LogDTO();
AutoLog syslog = method.getAnnotation(AutoLog.class);
if(syslog != null){
//update-begin-author:taoyan date:
String content = syslog.value();
if(syslog.module()== ModuleType.ONLINE){
content = getOnlineLogContent(obj, content);
}
//注解上的描述,操作日志内容
dto.setLogType(syslog.logType());
dto.setLogContent(content);
}
//请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
dto.setMethod(className + "." + methodName + "()");
//设置操作类型
if (CommonConstant.LOG_TYPE_2 == dto.getLogType()) {
dto.setOperateType(getOperateType(methodName, syslog.operateType()));
}
//获取request
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
//请求的参数
dto.setRequestParam(getReqestParams(request,joinPoint));
//设置IP地址
dto.setIp(IpUtils.getIpAddr(request));
//获取登录用户信息
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
if(sysUser!=null){
dto.setUserid(sysUser.getUsername());
dto.setUsername(sysUser.getRealname());
}
//耗时
dto.setCostTime(time);
dto.setCreateTime(new Date());
//保存系统日志
baseCommonService.addLog(dto);
}
/**
* 获取操作类型
*/
private int getOperateType(String methodName,int operateType) {
if (operateType > 0) {
return operateType;
}
// 代码逻辑说明: 阿里云代码扫描规范(不允许任何魔法值出现在代码中)------------
return OperateTypeEnum.getTypeByMethodName(methodName);
}
/**
* @Description: 获取请求参数
* @author: scott
* @date: 2020/4/16 0:10
* @param request: request
* @param joinPoint: joinPoint
* @Return: java.lang.String
*/
private String getReqestParams(HttpServletRequest request, JoinPoint joinPoint) {
String httpMethod = request.getMethod();
String params = "";
if (CommonConstant.HTTP_POST.equals(httpMethod) || CommonConstant.HTTP_PUT.equals(httpMethod) || CommonConstant.HTTP_PATCH.equals(httpMethod)) {
Object[] paramsArray = joinPoint.getArgs();
// java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
// https://my.oschina.net/mengzhang6/blog/2395893
Object[] arguments = new Object[paramsArray.length];
for (int i = 0; i < paramsArray.length; i++) {
if (paramsArray[i] instanceof BindingResult || paramsArray[i] instanceof ServletRequest || paramsArray[i] instanceof ServletResponse || paramsArray[i] instanceof MultipartFile || paramsArray[i] instanceof MultipartFile[]) {
//ServletRequest不能序列化从入参里排除否则报异常java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
//ServletResponse不能序列化 从入参里排除否则报异常java.lang.IllegalStateException: getOutputStream() has already been called for this response
//MultipartFile和MultipartFile[]不能序列化从入参里排除
continue;
}
arguments[i] = paramsArray[i];
}
// 代码逻辑说明: 日志数据太长的直接过滤掉
PropertyFilter profilter = new PropertyFilter() {
@Override
public boolean apply(Object o, String name, Object value) {
int length = 500;
if(value!=null && value.toString().length()>length){
return false;
}
if(value instanceof MultipartFile){
return false;
}
return true;
}
};
params = JSONObject.toJSONString(arguments, profilter);
} else {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 请求的方法参数值
Object[] args = joinPoint.getArgs();
// 请求的方法参数名称
StandardReflectionParameterNameDiscoverer u= new StandardReflectionParameterNameDiscoverer();
String[] paramNames = u.getParameterNames(method);
if (args != null && paramNames != null) {
for (int i = 0; i < args.length; i++) {
params += " " + paramNames[i] + ": " + args[i];
}
}
}
return params;
}
/**
* online日志内容拼接
* @param obj
* @param content
* @return
*/
private String getOnlineLogContent(Object obj, String content){
if (Result.class.isInstance(obj)){
Result res = (Result)obj;
String msg = res.getMessage();
String tableName = res.getOnlTable();
if(oConvertUtils.isNotEmpty(tableName)){
content+=",表名:"+tableName;
}
if(res.isSuccess()){
content+= ","+(oConvertUtils.isEmpty(msg)?"操作成功":msg);
}else{
content+= ","+(oConvertUtils.isEmpty(msg)?"操作失败":msg);
}
}
return content;
}
/* private void saveSysLog(ProceedingJoinPoint joinPoint, long time, Object obj) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SysLog sysLog = new SysLog();
AutoLog syslog = method.getAnnotation(AutoLog.class);
if(syslog != null){
//update-begin-author:taoyan date:
String content = syslog.value();
if(syslog.module()== ModuleType.ONLINE){
content = getOnlineLogContent(obj, content);
}
//注解上的描述,操作日志内容
sysLog.setLogContent(content);
sysLog.setLogType(syslog.logType());
}
//请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysLog.setMethod(className + "." + methodName + "()");
//设置操作类型
if (sysLog.getLogType() == CommonConstant.LOG_TYPE_2) {
sysLog.setOperateType(getOperateType(methodName, syslog.operateType()));
}
//获取request
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
//请求的参数
sysLog.setRequestParam(getReqestParams(request,joinPoint));
//设置IP地址
sysLog.setIp(IPUtils.getIpAddr(request));
//获取登录用户信息
LoginUser sysUser = (LoginUser)SecurityUtils.getSubject().getPrincipal();
if(sysUser!=null){
sysLog.setUserid(sysUser.getUsername());
sysLog.setUsername(sysUser.getRealname());
}
//耗时
sysLog.setCostTime(time);
sysLog.setCreateTime(new Date());
//保存系统日志
sysLogService.save(sysLog);
}*/
}

View File

@ -0,0 +1,466 @@
package com.ghb.base.common.aspect;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import com.ghb.base.common.api.CommonAPI;
import com.ghb.base.common.api.vo.Result;
import com.ghb.base.common.aspect.annotation.Dict;
import com.ghb.base.common.constant.CommonConstant;
import com.ghb.base.common.system.vo.DictModel;
import com.ghb.base.common.util.oConvertUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @Description: 字典aop类
* @Author: dangzhenghui
* @Date: 2019-3-17 21:50
* @Version: 1.0
*/
@Aspect
@Component
@Slf4j
public class DictAspect {
@Lazy
@Autowired
private CommonAPI commonApi;
@Autowired
public RedisTemplate redisTemplate;
private static final String JAVA_UTIL_DATE = "java.util.Date";
/**
* 定义切点Pointcut
*/
@Pointcut("(@within(org.springframework.web.bind.annotation.RestController) || " +
"@within(org.springframework.stereotype.Controller) || @annotation(com.ghb.base.common.aspect.annotation.AutoDict)) " +
"&& execution(public com.ghb.base.common.api.vo.Result com.ghb.base..*.*(..))")
public void excudeService() {
}
@Around("excudeService()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
long time1=System.currentTimeMillis();
Object result = pjp.proceed();
long time2=System.currentTimeMillis();
log.debug("获取JSON数据 耗时:"+(time2-time1)+"ms");
long start=System.currentTimeMillis();
result=this.parseDictText(result);
long end=System.currentTimeMillis();
log.debug("注入字典到JSON数据 耗时"+(end-start)+"ms");
return result;
}
/**
* 本方法针对返回对象为Result 的IPage的分页列表数据进行动态字典注入
* 字典注入实现 通过对实体类添加注解@dict 来标识需要的字典内容,字典分为单字典code即可 table字典 code table text配合使用与原来Ghb的用法相同
* 示例为SysUser 字段为sex 添加了注解@Dict(dicCode = "sex") 会在字典服务立马查出来对应的text 然后在请求list的时候将这个字典text已字段名称加_dictText形式返回到前端
* 例输入当前返回值的就会多出一个sex_dictText字段
* {
* sex:1,
* sex_dictText:""
* }
* 前端直接取值sext_dictText在table里面无需再进行前端的字典转换了
* customRender:function (text) {
* if(text==1){
* return "";
* }else if(text==2){
* return "";
* }else{
* return text;
* }
* }
* 目前vue是这么进行字典渲染到table上的多了就很麻烦了 这个直接在服务端渲染完成前端可以直接用
* @param result
*/
private Object parseDictText(Object result) {
//if (result instanceof Result) {
if (true) {
if (((Result) result).getResult() instanceof IPage) {
List<JSONObject> items = new ArrayList<>();
//step.1 筛选出加了 Dict 注解的字段列表
List<Field> dictFieldList = new ArrayList<>();
// 字典数据列表 key = 字典codevalue=数据列表
Map<String, List<String>> dataListMap = new HashMap<>(5);
//取出结果集
List<Object> records=((IPage) ((Result) result).getResult()).getRecords();
// 代码逻辑说明: VUEN-1230 判断是否含有字典注解,没有注解返回-----
Boolean hasDict= checkHasDict(records);
if(!hasDict){
return result;
}
log.debug(" __ 进入字典翻译切面 DictAspect —— " );
for (Object record : records) {
//update-begin---author:scott ---date:2026-04-15 forissues/9543改用反射直接读取字段构建 JSONObject避免 ObjectMapper 对循环引用实体进行全量序列化导致 OOM合并字典字段收集逻辑为同一次循环避免对 getAllFields 遍历两遍保留 issues/#3629@JsonFormat Date 格式化兼容保留 issues/3303字段顺序LinkedHashMap-----------
JSONObject item = new JSONObject(true);
for (Field field : oConvertUtils.getAllFields(record)) {
if (Modifier.isStatic(field.getModifiers())) {
continue;
}
//update-begin---author:scott ---date:2026-04-16 forissues/9543优先通过 getter 方法读取字段值兼容实体重写 getter 的场景getter 不存在时 fallback 到直接读字段-----------
Object fieldValue = getFieldValue(record, field);
//update-end---author:scott ---date:2026-04-16 forissues/9543优先通过 getter 方法读取字段值兼容实体重写 getter 的场景getter 不存在时 fallback 到直接读字段-----------
// 解决@JsonFormat注解解析不了的问题详见SysAnnouncement类的@JsonFormat
if (fieldValue instanceof Date) {
JsonFormat jsonFormat = field.getAnnotation(JsonFormat.class);
if (jsonFormat != null && oConvertUtils.isNotEmpty(jsonFormat.pattern())) {
fieldValue = new SimpleDateFormat(jsonFormat.pattern()).format((Date) fieldValue);
}
}
item.put(field.getName(), fieldValue);
// 遍历所有字段把字典Code取出来放到 map
String value = item.getString(field.getName());
if (oConvertUtils.isEmpty(value)) {
continue;
}
if (field.getAnnotation(Dict.class) != null) {
if (!dictFieldList.contains(field)) {
dictFieldList.add(field);
}
String code = field.getAnnotation(Dict.class).dicCode();
String text = field.getAnnotation(Dict.class).dicText();
String table = field.getAnnotation(Dict.class).dictTable();
// 代码逻辑说明: [issues/#5643]解决分布式下表字典跨库无法查询问题------------
String dataSource = field.getAnnotation(Dict.class).ds();
List<String> dataList;
String dictCode = code;
if (!StringUtils.isEmpty(table)) {
// 代码逻辑说明: [issues/#5643]解决分布式下表字典跨库无法查询问题------------
dictCode = String.format("%s,%s,%s,%s", table, text, code, dataSource);
}
dataList = dataListMap.computeIfAbsent(dictCode, k -> new ArrayList<>());
this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(",")));
}
//date类型默认转换string格式化日期
//if (JAVA_UTIL_DATE.equals(field.getType().getName())&&field.getAnnotation(JsonFormat.class)==null&&item.get(field.getName())!=null){
//SimpleDateFormat aDate=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// item.put(field.getName(), aDate.format(new Date((Long) item.get(field.getName()))));
//}
}
//update-end---author:scott ---date:2026-04-15 forissues/9543改用反射直接读取字段构建 JSONObject避免 ObjectMapper 对循环引用实体进行全量序列化导致 OOM合并字典字段收集逻辑为同一次循环避免对 getAllFields 遍历两遍保留 issues/#3629@JsonFormat Date 格式化兼容保留 issues/3303字段顺序LinkedHashMap-----------
items.add(item);
}
//step.2 调用翻译方法一次性翻译
Map<String, List<DictModel>> translText = this.translateAllDict(dataListMap);
//step.3 将翻译结果填充到返回结果里
for (JSONObject record : items) {
for (Field field : dictFieldList) {
String code = field.getAnnotation(Dict.class).dicCode();
String text = field.getAnnotation(Dict.class).dicText();
String table = field.getAnnotation(Dict.class).dictTable();
// 自定义的字典表数据源
String dataSource = field.getAnnotation(Dict.class).ds();
String fieldDictCode = code;
if (!StringUtils.isEmpty(table)) {
// 代码逻辑说明: [issues/#5643]解决分布式下表字典跨库无法查询问题------------
fieldDictCode = String.format("%s,%s,%s,%s", table, text, code, dataSource);
}
String value = record.getString(field.getName());
if (oConvertUtils.isNotEmpty(value)) {
List<DictModel> dictModels = translText.get(fieldDictCode);
if(dictModels==null || dictModels.size()==0){
continue;
}
String textValue = this.translDictText(dictModels, value);
log.debug(" 字典Val : " + textValue);
log.debug(" __翻译字典字段__ " + field.getName() + CommonConstant.DICT_TEXT_SUFFIX + " " + textValue);
// TODO-sun 测试输出待删
log.debug(" ---- dictCode: " + fieldDictCode);
log.debug(" ---- value: " + value);
log.debug(" ----- text: " + textValue);
log.debug(" ---- dictModels: " + JSON.toJSONString(dictModels));
record.put(field.getName() + CommonConstant.DICT_TEXT_SUFFIX, textValue);
}
}
}
((IPage) ((Result) result).getResult()).setRecords(items);
}
}
return result;
}
/**
* list 去重添加
*/
private void listAddAllDeduplicate(List<String> dataList, List<String> addList) {
// 筛选出dataList中没有的数据
List<String> filterList = addList.stream().filter(i -> !dataList.contains(i)).collect(Collectors.toList());
dataList.addAll(filterList);
}
/**
* 一次性把所有的字典都翻译了
* 1. 所有的普通数据字典的所有数据只执行一次SQL
* 2. 表字典相同的所有数据只执行一次SQL
* @param dataListMap
* @return
*/
private Map<String, List<DictModel>> translateAllDict(Map<String, List<String>> dataListMap) {
// 翻译后的字典文本key=dictCode
Map<String, List<DictModel>> translText = new HashMap<>(5);
// 需要翻译的数据有些可以从redis缓存中获取就不走数据库查询
List<String> needTranslData = new ArrayList<>();
//step.1 先通过redis中获取缓存字典数据
for (String dictCode : dataListMap.keySet()) {
List<String> dataList = dataListMap.get(dictCode);
if (dataList.size() == 0) {
continue;
}
// 表字典需要翻译的数据
List<String> needTranslDataTable = new ArrayList<>();
for (String s : dataList) {
String data = s.trim();
if (data.length() == 0) {
continue; //跳过循环
}
if (dictCode.contains(",")) {
String keyString = String.format("sys:cache:dictTable::SimpleKey [%s,%s]", dictCode, data);
if (redisTemplate.hasKey(keyString)) {
try {
String text = oConvertUtils.getString(redisTemplate.opsForValue().get(keyString));
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
list.add(new DictModel(data, text));
} catch (Exception e) {
log.warn(e.getMessage());
}
} else if (!needTranslDataTable.contains(data)) {
// 去重添加
needTranslDataTable.add(data);
}
} else {
String keyString = String.format("sys:cache:dict::%s:%s", dictCode, data);
if (redisTemplate.hasKey(keyString)) {
try {
String text = oConvertUtils.getString(redisTemplate.opsForValue().get(keyString));
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
list.add(new DictModel(data, text));
} catch (Exception e) {
log.warn(e.getMessage());
}
} else if (!needTranslData.contains(data)) {
// 去重添加
needTranslData.add(data);
}
}
}
//step.2 调用数据库翻译表字典
if (needTranslDataTable.size() > 0) {
String[] arr = dictCode.split(",");
String table = arr[0], text = arr[1], code = arr[2];
String values = String.join(",", needTranslDataTable);
// 自定义的数据源
String dataSource = null;
if (arr.length > 3) {
dataSource = arr[3];
}
log.debug("translateDictFromTableByKeys.dictCode:" + dictCode);
log.debug("translateDictFromTableByKeys.values:" + values);
// 代码逻辑说明: 微服务下为空报错没有参数需要传递空字符串---
if(null == dataSource){
dataSource = "";
}
List<DictModel> texts = commonApi.translateDictFromTableByKeys(table, text, code, values, dataSource);
log.debug("translateDictFromTableByKeys.result:" + texts);
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
list.addAll(texts);
// redis 缓存
for (DictModel dict : texts) {
String redisKey = String.format("sys:cache:dictTable::SimpleKey [%s,%s]", dictCode, dict.getValue());
try {
// 保留5分钟
redisTemplate.opsForValue().set(redisKey, dict.getText(), 300, TimeUnit.SECONDS);
} catch (Exception e) {
log.warn(e.getMessage(), e);
}
}
}
}
//step.3 调用数据库进行翻译普通字典
if (needTranslData.size() > 0) {
List<String> dictCodeList = Arrays.asList(dataListMap.keySet().toArray(new String[]{}));
// 将不包含逗号的字典code筛选出来因为带逗号的是表字典而不是普通的数据字典
List<String> filterDictCodes = dictCodeList.stream().filter(key -> !key.contains(",")).collect(Collectors.toList());
String dictCodes = String.join(",", filterDictCodes);
String values = String.join(",", needTranslData);
log.debug("translateManyDict.dictCodes:" + dictCodes);
log.debug("translateManyDict.values:" + values);
Map<String, List<DictModel>> manyDict = commonApi.translateManyDict(dictCodes, values);
log.debug("translateManyDict.result:" + manyDict);
for (String dictCode : manyDict.keySet()) {
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
List<DictModel> newList = manyDict.get(dictCode);
list.addAll(newList);
// redis 缓存
for (DictModel dict : newList) {
String redisKey = String.format("sys:cache:dict::%s:%s", dictCode, dict.getValue());
try {
redisTemplate.opsForValue().set(redisKey, dict.getText());
} catch (Exception e) {
log.warn(e.getMessage(), e);
}
}
}
}
return translText;
}
/**
* 字典值替换文本
*
* @param dictModels
* @param values
* @return
*/
private String translDictText(List<DictModel> dictModels, String values) {
List<String> result = new ArrayList<>();
// 允许多个逗号分隔允许传数组对象
String[] splitVal = values.split(",");
for (String val : splitVal) {
String dictText = val;
for (DictModel dict : dictModels) {
if (val.equals(dict.getValue())) {
dictText = dict.getText();
break;
}
}
result.add(dictText);
}
return String.join(",", result);
}
/**
* 翻译字典文本
* @param code
* @param text
* @param table
* @param key
* @return
*/
@Deprecated
private String translateDictValue(String code, String text, String table, String key) {
if(oConvertUtils.isEmpty(key)) {
return null;
}
StringBuffer textValue=new StringBuffer();
String[] keys = key.split(",");
for (String k : keys) {
String tmpValue = null;
log.debug(" 字典 key : "+ k);
if (k.trim().length() == 0) {
continue; //跳过循环
}
// 代码逻辑说明: !56 优化微服务应用下存在表字段需要字典翻译时加载缓慢问题-----
if (!StringUtils.isEmpty(table)){
log.debug("--DictAspect------dicTable="+ table+" ,dicText= "+text+" ,dicCode="+code);
String keyString = String.format("sys:cache:dictTable::SimpleKey [%s,%s,%s,%s]",table,text,code,k.trim());
if (redisTemplate.hasKey(keyString)){
try {
tmpValue = oConvertUtils.getString(redisTemplate.opsForValue().get(keyString));
} catch (Exception e) {
log.warn(e.getMessage());
}
}else {
tmpValue= commonApi.translateDictFromTable(table,text,code,k.trim());
}
}else {
String keyString = String.format("sys:cache:dict::%s:%s",code,k.trim());
if (redisTemplate.hasKey(keyString)){
try {
tmpValue = oConvertUtils.getString(redisTemplate.opsForValue().get(keyString));
} catch (Exception e) {
log.warn(e.getMessage());
}
}else {
tmpValue = commonApi.translateDict(code, k.trim());
}
}
if (tmpValue != null) {
if (!"".equals(textValue.toString())) {
textValue.append(",");
}
textValue.append(tmpValue);
}
}
return textValue.toString();
}
//update-begin---author:scott ---date:2026-04-16 forissues/9543优先通过 getter 方法读取字段值兼容实体重写 getter 的场景getter 不存在时 fallback 到直接读字段-----------
/**
* 优先通过 PropertyDescriptor 获取 getter 方法读取字段值兼容实体重写 getter 的场景
* getter 不存在或调用异常时 fallback 到直接反射读字段
*/
private Object getFieldValue(Object record, Field field) {
try {
PropertyDescriptor pd = new PropertyDescriptor(field.getName(), record.getClass());
Method readMethod = pd.getReadMethod();
if (readMethod != null) {
return readMethod.invoke(record);
}
} catch (Exception ignored) {
}
try {
field.setAccessible(true);
return field.get(record);
} catch (IllegalAccessException e) {
log.error("反射读取字段失败: " + field.getName(), e);
return null;
}
}
//update-end---author:scott ---date:2026-04-16 forissues/9543优先通过 getter 方法读取字段值兼容实体重写 getter 的场景getter 不存在时 fallback 到直接读字段-----------
/**
* 检测返回结果集中是否包含Dict注解
* @param records
* @return
*/
private Boolean checkHasDict(List<Object> records){
if(oConvertUtils.isNotEmpty(records) && records.size()>0){
for (Field field : oConvertUtils.getAllFields(records.get(0))) {
if (oConvertUtils.isNotEmpty(field.getAnnotation(Dict.class))) {
return true;
}
}
}
return false;
}
}

View File

@ -0,0 +1,137 @@
package com.ghb.base.common.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import com.ghb.base.common.api.CommonAPI;
import com.ghb.base.common.aspect.annotation.PermissionData;
import com.ghb.base.common.constant.CommonConstant;
import com.ghb.base.common.constant.SymbolConstant;
import com.ghb.base.common.system.query.QueryRuleEnum;
import com.ghb.base.common.system.util.GhbDataAutorUtils;
import com.ghb.base.common.system.util.JwtUtil;
import com.ghb.base.common.system.vo.SysPermissionDataRuleModel;
import com.ghb.base.common.system.vo.SysUserCacheInfo;
import com.ghb.base.common.util.SpringContextUtils;
import com.ghb.base.common.util.oConvertUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.List;
/**
* 数据权限切面处理类
* 当被请求的方法有注解PermissionData时,会在往当前request中写入数据权限信息
* @Date 2019年4月10日
* @Version: 1.0
* @author: Ghb-boot
*/
@Aspect
@Component
@Slf4j
public class PermissionDataAspect {
@Lazy
@Autowired
private CommonAPI commonApi;
private static final String SPOT_DO = ".do";
@Pointcut("@annotation(com.ghb.base.common.aspect.annotation.PermissionData)")
public void pointCut() {
}
@Around("pointCut()")
public Object arround(ProceedingJoinPoint point) throws Throwable{
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
PermissionData pd = method.getAnnotation(PermissionData.class);
String component = pd.pageComponent();
String requestMethod = request.getMethod();
String requestPath = request.getRequestURI().substring(request.getContextPath().length());
requestPath = filterUrl(requestPath);
//先判断是否online报表请求
if(requestPath.indexOf(UrlMatchEnum.CGREPORT_DATA.getMatchUrl())>=0 || requestPath.indexOf(UrlMatchEnum.CGREPORT_ONLY_DATA.getMatchUrl())>=0){
// 获取地址栏参数
String urlParamString = request.getParameter(CommonConstant.ONL_REP_URL_PARAM_STR);
if(oConvertUtils.isNotEmpty(urlParamString)){
requestPath+="?"+urlParamString;
}
}
log.debug("拦截请求 >> {} ; 请求类型 >> {} . ", requestPath, requestMethod);
String username = JwtUtil.getUserNameByToken(request);
//查询数据权限信息
//TODO 微服务情况下也得支持缓存机制
List<SysPermissionDataRuleModel> dataRules = commonApi.queryPermissionDataRule(component, requestPath, username);
if(dataRules!=null && dataRules.size()>0) {
//临时存储
GhbDataAutorUtils.installDataSearchConditon(request, dataRules);
//TODO 微服务情况下也得支持缓存机制
SysUserCacheInfo userinfo = commonApi.getCacheUser(username);
GhbDataAutorUtils.installUserInfo(request, userinfo);
}
return point.proceed();
}
private String filterUrl(String requestPath){
String url = "";
if(oConvertUtils.isNotEmpty(requestPath)){
url = requestPath.replace("\\", "/");
url = url.replace("//", "/");
if(url.indexOf(SymbolConstant.DOUBLE_SLASH)>=0){
url = filterUrl(url);
}
/*if(url.startsWith("/")){
url=url.substring(1);
}*/
}
return url;
}
/**
* 获取请求地址
* @param request
* @return
*/
@Deprecated
private String getJgAuthRequsetPath(HttpServletRequest request) {
String queryString = request.getQueryString();
String requestPath = request.getRequestURI();
if(oConvertUtils.isNotEmpty(queryString)){
requestPath += "?" + queryString;
}
// 去掉其他参数(保留一个参数) 例如loginController.do?login
if (requestPath.indexOf(SymbolConstant.AND) > -1) {
requestPath = requestPath.substring(0, requestPath.indexOf("&"));
}
if(requestPath.indexOf(QueryRuleEnum.EQ.getValue())!=-1){
if(requestPath.indexOf(SPOT_DO)!=-1){
requestPath = requestPath.substring(0,requestPath.indexOf(".do")+3);
}else{
requestPath = requestPath.substring(0,requestPath.indexOf("?"));
}
}
// 去掉项目路径
requestPath = requestPath.substring(request.getContextPath().length() + 1);
return filterUrl(requestPath);
}
@Deprecated
private boolean moHuContain(List<String> list,String key){
for(String str : list){
if(key.contains(str)){
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,68 @@
package com.ghb.base.common.aspect;
/**
* @Author scott
* @Date 2020/1/14 13:36
* @Description: 请求URL与菜单路由URL转换规则方便于采用菜单路由URL来配置数据权限规则
*/
public enum UrlMatchEnum {
/**求URL与菜单路由URL转换规则 /online/cgform/api/getData/ */
CGFORM_DATA("/online/cgform/api/getData/", "/online/cgformList/"),
/**求URL与菜单路由URL转换规则 /online/cgform/api/exportXls/ */
CGFORM_EXCEL_DATA("/online/cgform/api/exportXls/", "/online/cgformList/"),
/**求URL与菜单路由URL转换规则 /online/cgform/api/getTreeData/ */
CGFORM_TREE_DATA("/online/cgform/api/getTreeData/", "/online/cgformList/"),
/**求URL与菜单路由URL转换规则 /online/cgreport/api/getColumnsAndData/ */
CGREPORT_DATA("/online/cgreport/api/getColumnsAndData/", "/online/cgreport/"),
/** 求URL与菜单路由URL转换规则/online/cgreport/api/getData/ 【vue3报表数据请求地址】 */
CGREPORT_ONLY_DATA("/online/cgreport/api/getData/", "/online/cgreport/"),
/**求URL与菜单路由URL转换规则 /online/cgreport/api/exportXls/ */
CGREPORT_EXCEL_DATA("/online/cgreport/api/exportXls/", "/online/cgreport/"),
/**求URL与菜单路由URL转换规则 /online/cgreport/api/exportManySheetXls/ */
CGREPORT_EXCEL_DATA2("/online/cgreport/api/exportManySheetXls/", "/online/cgreport/");
UrlMatchEnum(String url, String matchUrl) {
this.url = url;
this.matchUrl = matchUrl;
}
/**
* Request 请求 URL前缀
*/
private String url;
/**
* 菜单路由 URL前缀 (对应菜单路径)
*/
private String matchUrl;
/**
* 根据req url 获取到菜单配置路径前端页面路由URL
*
* @param url
* @return
*/
public static String getMatchResultByUrl(String url) {
//获取到枚举
UrlMatchEnum[] values = UrlMatchEnum.values();
//加强for循环进行遍历操作
for (UrlMatchEnum lr : values) {
//如果遍历获取的type和参数type一致
if (url.indexOf(lr.url) != -1) {
//返回type对象的desc
return url.replace(lr.url, lr.matchUrl);
}
}
return null;
}
public String getMatchUrl() {
return matchUrl;
}
// public static void main(String[] args) {
// /**
// * 比如request真实请求URL: /online/cgform/api/getData/81fcf7d8922d45069b0d5ba983612d3a
// * 转换匹配路由URL后对应配置的菜单路径:/online/cgformList/81fcf7d8922d45069b0d5ba983612d3a
// */
// System.out.println(UrlMatchEnum.getMatchResultByUrl("/online/cgform/api/getData/81fcf7d8922d45069b0d5ba983612d3a"));
// }
}

View File

@ -0,0 +1,23 @@
package com.ghb.base.common.aspect.annotation;
import java.lang.annotation.*;
/**
* 通过此注解声明的接口自动实现字典翻译
*
* @Author scott
* @email Ghbos@163.com
* @Date 2022年01月05日
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoDict {
/**
* 暂时无用
* @return
*/
String value() default "";
}

View File

@ -0,0 +1,46 @@
package com.ghb.base.common.aspect.annotation;
import com.ghb.base.common.constant.CommonConstant;
import com.ghb.base.common.constant.enums.ModuleType;
import java.lang.annotation.*;
/**
* 系统日志注解
*
* @Author scott
* @email Ghbos@163.com
* @Date 2019年1月14日
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoLog {
/**
* 日志内容
*
* @return
*/
String value() default "";
/**
* 日志类型
*
* @return 0:操作日志;1:登录日志;2:定时任务;
*/
int logType() default CommonConstant.LOG_TYPE_2;
/**
* 操作日志类型
*
* @return 1查询2添加3修改4删除
*/
int operateType() default 0;
/**
* 模块类型 默认为common
* @return
*/
ModuleType module() default ModuleType.COMMON;
}

View File

@ -0,0 +1,52 @@
package com.ghb.base.common.aspect.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 字典注解
* @author: dangzhenghui
* @date: 2019年03月17日-下午9:37:16
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Dict {
/**
* 方法描述: 数据code
* dangzhenghui
* 2019年03月17日-下午9:37:16
*
* @return 返回类型 String
*/
String dicCode();
/**
* 方法描述: 数据Text
* dangzhenghui
* 2019年03月17日-下午9:37:16
*
* @return 返回类型 String
*/
String dicText() default "";
/**
* 方法描述: 数据字典表
* dangzhenghui
* 2019年03月17日-下午9:37:16
*
* @return 返回类型 String
*/
String dictTable() default "";
/**
* 方法描述: 数据字典表所在数据源名称
* chenrui
* 2023年12月20日-下午4:58
*
* @return 返回类型 String
*/
String ds() default "";
}

View File

@ -0,0 +1,20 @@
package com.ghb.base.common.aspect.annotation;
import java.lang.annotation.*;
/**
* 动态table切换
*
* @author :zyf
* @date:2020-04-25
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicTable {
/**
* 需要动态解析的表名
* @return
*/
String value();
}

View File

@ -0,0 +1,19 @@
package com.ghb.base.common.aspect.annotation;
import java.lang.annotation.*;
/**
* online请求拦截专用注解
* @author: Ghb-boot
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
public @interface OnlineAuth {
/**
* 请求关键字在xxx/code之前的字符串
* @return
*/
String value();
}

View File

@ -0,0 +1,29 @@
package com.ghb.base.common.aspect.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 数据权限注解
* @Author taoyan
* @Date 2019年4月11日
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
public @interface PermissionData {
/**
* 暂时没用
* @return
*/
String value() default "";
/**
* 配置菜单的组件路径,用于数据权限
*/
String pageComponent() default "";
}

View File

@ -0,0 +1,744 @@
package com.ghb.base.common.constant;
/**
* @Description: 通用常量
* @author: Ghb-boot
*/
public interface CommonConstant {
/**
* 正常状态
*/
Integer STATUS_NORMAL = 0;
/**
* 禁用状态
*/
Integer STATUS_DISABLE = -1;
/**
* 删除标志
*/
Integer DEL_FLAG_1 = 1;
/**
* 未删除
*/
Integer DEL_FLAG_0 = 0;
/**
* 系统日志类型 登录
*/
int LOG_TYPE_1 = 1;
/**
* 系统日志类型 操作
*/
int LOG_TYPE_2 = 2;
/**
* 系统日志类型 租户操作日志
*/
int LOG_TYPE_3 = 3;
/**
* 系统日志类型 异常
*/
int LOG_TYPE_4 = 4;
/**
* 操作日志类型 查询
*/
int OPERATE_TYPE_1 = 1;
/**
* 操作日志类型 添加
*/
int OPERATE_TYPE_2 = 2;
/**
* 操作日志类型 更新
*/
int OPERATE_TYPE_3 = 3;
/**
* 操作日志类型 删除
*/
int OPERATE_TYPE_4 = 4;
/**
* 操作日志类型 倒入
*/
int OPERATE_TYPE_5 = 5;
/**
* 操作日志类型 导出
*/
int OPERATE_TYPE_6 = 6;
/** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */
Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
/** {@code 404 Not Found} (HTTP/1.0 - RFC 1945) */
Integer SC_INTERNAL_NOT_FOUND_404 = 404;
/** {@code 200 OK} (HTTP/1.0 - RFC 1945) */
Integer SC_OK_200 = 200;
/**访问权限认证未通过 510*/
Integer SC_Ghb_NO_AUTHZ=510;
/** 登录用户Shiro权限缓存KEY前缀 */
public static String PREFIX_USER_SHIRO_CACHE = "shiro:cache:com.ghb.base.config.shiro.ShiroRealm.authorizationCache:";
/** 登录用户Token令牌缓存KEY前缀 */
String PREFIX_USER_TOKEN = "prefix_user_token:";
/** 登录用户Token令牌作废提示信息比如 “不允许同一账号多地同时登录,会往这个变量存提示信息” */
String PREFIX_USER_TOKEN_ERROR_MSG = "prefix_user_token:error:msg_";
/**============================== 【是否允许同一账号多地同时登录】登录客户端类型常量 ==============================*/
/** 客户端类型PC端 */
String CLIENT_TYPE_PC = "PC";
/** 客户端类型APP端 */
String CLIENT_TYPE_APP = "APP";
/** 客户端类型:手机号登录 */
String CLIENT_TYPE_PHONE = "PHONE";
String PREFIX_USER_TOKEN_PC = "prefix_user_token:single_login:pc:";
/** 单点登录用户在APP端的Token缓存KEY前缀 (username -> token) */
String PREFIX_USER_TOKEN_APP = "prefix_user_token:single_login:app:";
/** 单点登录用户在手机号登录的Token缓存KEY前缀 (username -> token) */
String PREFIX_USER_TOKEN_PHONE = "prefix_user_token:single_login:phone:";
/**============================== 【是否允许同一账号多地同时登录】登录客户端类型常量 ==============================*/
// /** Token缓存时间3600秒即一小时 */
// int TOKEN_EXPIRE_TIME = 3600;
/** 登录二维码 */
String LOGIN_QRCODE_PRE = "QRCODELOGIN:";
String LOGIN_QRCODE = "LQ:";
/** 登录二维码token */
String LOGIN_QRCODE_TOKEN = "LQT:";
/**
* 0一级菜单
*/
Integer MENU_TYPE_0 = 0;
/**
* 1子菜单
*/
Integer MENU_TYPE_1 = 1;
/**
* 2按钮权限
*/
Integer MENU_TYPE_2 = 2;
/**通告对象类型USER:指定用户ALL:全体用户)*/
String MSG_TYPE_UESR = "USER";
String MSG_TYPE_ALL = "ALL";
/**发布状态0未发布1已发布2已撤销*/
String NO_SEND = "0";
String HAS_SEND = "1";
String HAS_CANCLE = "2";
/**阅读状态0未读1已读*/
Integer HAS_READ_FLAG = 1;
Integer NO_READ_FLAG = 0;
/**优先级L低M中H高*/
String PRIORITY_L = "L";
String PRIORITY_M = "M";
String PRIORITY_H = "H";
/**
* 短信模板方式 0 .登录模板1.注册模板2.忘记密码模板
*/
String SMS_TPL_TYPE_0 = "0";
String SMS_TPL_TYPE_1 = "1";
String SMS_TPL_TYPE_2 = "2";
/**
* 状态(0无效1有效)
*/
String STATUS_0 = "0";
String STATUS_1 = "1";
Integer STATUS_0_INT = 0;
Integer STATUS_1_INT = 1;
/**
* 同步工作流引擎1同步0不同步
*/
Integer ACT_SYNC_1 = 1;
Integer ACT_SYNC_0 = 0;
/**
* 消息类型1:通知公告2:系统消息
*/
String MSG_CATEGORY_1 = "1";
String MSG_CATEGORY_2 = "2";
/**
* 是否配置菜单的数据权限 1是0否
*/
Integer RULE_FLAG_0 = 0;
Integer RULE_FLAG_1 = 1;
/**
* 是否用户已被冻结 1正常(解冻) 2冻结 3离职
*/
Integer USER_UNFREEZE = 1;
Integer USER_FREEZE = 2;
Integer USER_QUIT = 3;
/**字典翻译文本后缀*/
String DICT_TEXT_SUFFIX = "_dictText";
/**字典翻译颜色后缀*/
String DICT_COLOR_SUFFIX = "_dictColor";
/**
* 表单设计器主表类型
*/
Integer DESIGN_FORM_TYPE_MAIN = 1;
/**
* 表单设计器子表表类型
*/
Integer DESIGN_FORM_TYPE_SUB = 2;
/**
* 表单设计器URL授权通过
*/
Integer DESIGN_FORM_URL_STATUS_PASSED = 1;
/**
* 表单设计器URL授权未通过
*/
Integer DESIGN_FORM_URL_STATUS_NOT_PASSED = 2;
/**
* 表单设计器新增 Flag
*/
String DESIGN_FORM_URL_TYPE_ADD = "add";
/**
* 表单设计器修改 Flag
*/
String DESIGN_FORM_URL_TYPE_EDIT = "edit";
/**
* 表单设计器详情 Flag
*/
String DESIGN_FORM_URL_TYPE_DETAIL = "detail";
/**
* 表单设计器复用数据 Flag
*/
String DESIGN_FORM_URL_TYPE_REUSE = "reuse";
/**
* 表单设计器编辑 Flag 已弃用
*/
String DESIGN_FORM_URL_TYPE_VIEW = "view";
/**
* online参数值设置Y, N
*/
String ONLINE_PARAM_VAL_IS_TURE = "Y";
String ONLINE_PARAM_VAL_IS_FALSE = "N";
/**
* 文件上传类型本地localMiniominio阿里云alioss
*/
String UPLOAD_TYPE_LOCAL = "local";
String UPLOAD_TYPE_MINIO = "minio";
String UPLOAD_TYPE_OSS = "alioss";
/**
* 文档上传自定义桶名称 (私有加密桶名) 知识库功能
*/
String UPLOAD_CUSTOM_BUCKET = "eoafile";
/**
* 文档上传自定义路径
*/
String UPLOAD_CUSTOM_PATH = "eoafile";
/**
* 文件外链接有效天数
*/
Integer UPLOAD_EFFECTIVE_DAYS = 1;
/**
* 员工身份 1:普通员工 2:上级
*/
Integer USER_IDENTITY_1 = 1;
Integer USER_IDENTITY_2 = 2;
/** sys_user 表 username 唯一键索引 */
String SQL_INDEX_UNIQ_SYS_USER_USERNAME = "uniq_sys_user_username";
/** sys_user 表 work_no 唯一键索引 */
String SQL_INDEX_UNIQ_SYS_USER_WORK_NO = "uniq_sys_user_work_no";
/** sys_user 表 phone 唯一键索引 */
String SQL_INDEX_UNIQ_SYS_USER_PHONE = "uniq_sys_user_phone";
/** 达梦数据库升提示。违反表[SYS_USER]唯一性约束 */
String SQL_INDEX_UNIQ_SYS_USER = "唯一性约束";
/** sys_user 表 email 唯一键索引 */
String SQL_INDEX_UNIQ_SYS_USER_EMAIL = "uniq_sys_user_email";
/** sys_quartz_job 表 job_class_name 唯一键索引 */
String SQL_INDEX_UNIQ_JOB_CLASS_NAME = "uniq_job_class_name";
/** sys_position 表 code 唯一键索引 */
String SQL_INDEX_UNIQ_CODE = "uniq_code";
/** sys_role 表 code 唯一键索引 */
String SQL_INDEX_UNIQ_SYS_ROLE_CODE = "uniq_sys_role_role_code";
/** sys_depart 表 code 唯一键索引 */
String SQL_INDEX_UNIQ_DEPART_ORG_CODE = "uniq_depart_org_code";
/** sys_category 表 code 唯一键索引 */
String SQL_INDEX_UNIQ_CATEGORY_CODE = "idx_sc_code";
/**
* 在线聊天 是否为默认分组
*/
String IM_DEFAULT_GROUP = "1";
/**
* 在线聊天 图片文件保存路径
*/
String IM_UPLOAD_CUSTOM_PATH = "biz/user_imgs";
/**
* 在线聊天 用户状态
*/
String IM_STATUS_ONLINE = "online";
/**
* 在线聊天 SOCKET消息类型
*/
String IM_SOCKET_TYPE = "chatMessage";
/**
* 在线聊天 是否开启默认添加好友 1是 0否
*/
String IM_DEFAULT_ADD_FRIEND = "1";
/**
* 在线聊天 用户好友缓存前缀
*/
String IM_PREFIX_USER_FRIEND_CACHE = "sys:cache:im:im_prefix_user_friend_";
/**
* 缓存用户id与用户名关系
*/
String SYS_USER_ID_MAPPING_CACHE = "sys:cache:user:id_mapping";
/**
* 系统角色管理员编码
*/
String SYS_ROLE_ADMIN = "admin";
/**
* 考勤补卡业务状态 0处理中
*/
String SIGN_PATCH_BIZ_STATUS_0 = "0";
/**
* 考勤补卡业务状态 1同意 2不同意
*/
String SIGN_PATCH_BIZ_STATUS_1 = "1";
String SIGN_PATCH_BIZ_STATUS_2 = "2";
/**
* 公文文档上传自定义路径
*/
String UPLOAD_CUSTOM_PATH_OFFICIAL = "officialdoc";
/**
* 公文文档下载自定义路径
*/
String DOWNLOAD_CUSTOM_PATH_OFFICIAL = "officaldown";
/**
* WPS存储值类别(1 code文号 2 textWPS模板还是公文发文模板)
*/
String WPS_TYPE_1="1";
String WPS_TYPE_2="2";
/**===============================================================================================*/
/**
* ::非常重要::
* 注意这四个常量值如果修改需要与 Ghb-boot-starter/Ghb-boot-common/com.ghb.base.config.FeignConfig 类中的值保持一致
*/
String X_ACCESS_TOKEN = "X-Access-Token";
String X_SIGN = "X-Sign";
String X_TIMESTAMP = "X-TIMESTAMP";
/** 租户请求头 更名为X-Tenant-Id */
String TENANT_ID = "X-Tenant-Id";
/** 简流接口请求头,用于排除不支持的控件字段 */
String X_MiniFlowExclusionFieldMode = "X-Miniflowexclusionfieldmode";
/**===============================================================================================*/
String TOKEN_IS_INVALID_MSG = "Token失效请重新登录!";
String X_FORWARDED_SCHEME = "X-Forwarded-Scheme";
/**
* 微服务读取配置文件属性 服务地址
*/
String CLOUD_SERVER_KEY = "spring.cloud.nacos.discovery.server-addr";
/**
* 第三方登录 验证密码/创建用户 都需要设置一个操作码 防止被恶意调用
*/
String THIRD_LOGIN_CODE = "third_login_code";
/**
* 第三方APP同步方向本地 --> 第三方APP
*/
String THIRD_SYNC_TO_APP = "SYNC_TO_APP";
/**
* 第三方APP同步方向第三方APP --> 本地
*/
String THIRD_SYNC_TO_LOCAL = "SYNC_TO_LOCAL";
/** 系统通告消息状态0=未发布 */
String ANNOUNCEMENT_SEND_STATUS_0 = "0";
/** 系统通告消息状态1=已发布 */
String ANNOUNCEMENT_SEND_STATUS_1 = "1";
/** 系统通告消息状态2=已撤销 */
String ANNOUNCEMENT_SEND_STATUS_2 = "2";
/**ONLINE 报表权限用 从request中获取地址栏后的参数*/
String ONL_REP_URL_PARAM_STR="onlRepUrlParamStr";
/**POST请求*/
String HTTP_POST = "POST";
/**PUT请求*/
String HTTP_PUT = "PUT";
/**PATCH请求*/
String HTTP_PATCH = "PATCH";
/**未知的*/
String UNKNOWN = "unknown";
/**字符串http*/
String STR_HTTP = "http";
/**String 类型的空值*/
String STRING_NULL = "null";
/**前端vue3版本Header参数名*/
String VERSION="X-Version";
String VERSION_V3 = "v3";
/**存储在线程变量里的动态表名*/
String DYNAMIC_TABLE_NAME="DYNAMIC_TABLE_NAME";
/**
* http:// http协议
*/
String HTTP_PROTOCOL = "http://";
/**
* https:// https协议
*/
String HTTPS_PROTOCOL = "https://";
/** 部门表唯一keyid */
String DEPART_KEY_ID = "id";
/** 部门表唯一keyorgCode */
String DEPART_KEY_ORG_CODE = "orgCode";
/**======【消息推送相关】==============================================================================*/
/**
* 发消息 会传递一些信息到map
*/
String NOTICE_MSG_SUMMARY = "NOTICE_MSG_SUMMARY";
/**
* 发消息 会传递一个业务ID到map
*/
String NOTICE_MSG_BUS_ID = "NOTICE_MSG_BUS_ID";
/**
* 发消息 消息业务类型
*/
String NOTICE_MSG_BUS_TYPE = "NOTICE_MSG_BUS_TYPE";
/**
* 通知类型用于区分来源 file 知识 flow 流程 plan 日程 system 系统消息
*/
String NOTICE_TYPE = "noticeType";
/**
* 邮箱消息中地址登录时地址后携带的token,需要替换成真实的token值
*/
String LOGIN_TOKEN = "{LOGIN_TOKEN}";
/**
* 模板消息中 跳转地址的对应的key
*/
String MSG_HREF_URL = "url";
/**
* sys_data_log表的类型 用于区别评论区域的日志数据
*/
String DATA_LOG_TYPE_COMMENT = "comment";
/**
* sys_data_log表的类型 老的数据比较 类型都设置为json
*/
String DATA_LOG_TYPE_JSON = "json";
/** 消息模板markdown */
String MSG_TEMPLATE_TYPE_MD = "5";
/**========【消息推送相关】==========================================================================*/
/**
* 短信验证码redis-key的前缀
*/
String PHONE_REDIS_KEY_PRE = "phone_msg";
/**
* 是文件夹
*/
String IT_IS_FOLDER = "1";
/**
* 文件拥有者
*/
String FILE_OWNER = "owner";
/**
* 文件管理员
*/
String FILE_ADMIN = "admin";
/**
* 只允许编辑
*/
String FILE_EDITABLE = "editable";
/**
* 文件 只读
*/
String FILE_READONLY = "readonly";
/**
* 登录失败用于记录失败次数的key
*/
String LOGIN_FAIL = "LOGIN_FAIL_";
/**
* 入职事件
*/
Integer BPM_USER_EVENT_ADD = 1;
/**
* 离职事件
*/
Integer BPM_USER_EVENT_LEVEL = 2;
/**
* 用户租户状态(正常/已通过审核的)
*/
String USER_TENANT_NORMAL = "1";
/**
* 用户租户状态(离职)
*/
String USER_TENANT_QUIT = "2";
/**
* 用户租户状态(审核中)
*/
String USER_TENANT_UNDER_REVIEW = "3";
/**
* 用户租户状态(拒绝)
*/
String USER_TENANT_REFUSE = "4";
/**
* 用户租户状态(邀请)
*/
String USER_TENANT_INVITE = "5";
/**
* 不是叶子节点
*/
Integer NOT_LEAF = 0;
/**
* 是叶子节点
*/
Integer IS_LEAF = 1;
/**
* 钉钉
*/
String DINGTALK = "DINGTALK";
/**
* 企业微信
*/
String WECHAT_ENTERPRISE = "WECHAT_ENTERPRISE";
/**
* 系统默认租户id 0
*/
Integer TENANT_ID_DEFAULT_VALUE = 0;
/**
* low-app用 应用级别的复制
*/
String COPY_LEVEL_APP = "app";
/**
* low-app用 菜单级别的复制
*/
String COPY_LEVEL_MENU = "menu";
/**
* low-app用 应用备份
*/
String COPY_LEVEL_BAK = "backup";
/**
* low-app用 从备份还原
*/
String COPY_LEVEL_COVER = "cover";
/** 【QQYUN-6034】关联字段变更历史值缓存半个小时 */
String CACHE_REL_FIELD_OLD_VAL = "sys:cache:desform:relFieldOldVal:";
/**
* 排序类型升序
*/
String ORDER_TYPE_ASC = "ASC";
/**
* 排序类型降序
*/
String ORDER_TYPE_DESC = "DESC";
/**
* 报表允许设计开发的角色
*/
public static String[] allowDevRoles = new String[]{"lowdeveloper", "admin"};
/**
* 对应积木报表的常量
* 数据隔离模式 按照创建人隔离
*/
public static final String SAAS_MODE_CREATED = "created";
/**
* 对应积木报表的常量
* 数据隔离模式 按照租户隔离
*/
public static final String SAAS_MODE_TENANT = "tenant";
/**
* 修改手机号短信验证码redis-key的前缀
*/
String CHANGE_PHONE_REDIS_KEY_PRE = "sys:cache:phone:change_phone_msg:";
/**
* 手机号短信验证码redis-key的前缀
*/
String LOG_OFF_PHONE_REDIS_KEY_PRE = "sys:cache:phone:qqy_log_off_user_msg:";
/**
* 缓存用户最后一次收到消息通知的时间 KEY
*/
String CACHE_KEY_USER_LAST_ANNOUNT_TIME_1HOUR = "sys:cache:userinfo:user_last_annount_time::%s";
/**
* 验证原手机号
*/
String VERIFY_ORIGINAL_PHONE = "verifyOriginalPhone";
/**
* 修改手机号
*/
String UPDATE_PHONE = "updatePhone";
/**
* 修改手机号验证码请求次数超出
*/
Integer PHONE_SMS_FAIL_CODE = 40002;
/**
* 自定义首页关联关系(ROLE:表示角色 USER:表示用户 DEFAULT:默认首页)
*
*/
String HOME_RELATION_ROLE = "ROLE";
String HOME_RELATION_USER = "USER";
String HOME_RELATION_DEFAULT = "DEFAULT";
/**
* 是否置顶(0否 1是)
*/
Integer IZ_TOP_1 = 1;
Integer IZ_TOP_0 = 0;
//关注流程缓存前缀
String FLOW_FOCUS_NOTICE_PREFIX = "flow:runtimeData:focus:notice:";
//任务缓办时间缓存前缀
String FLOW_TASK_DELAY_PREFIX = "flow:runtimeData:task:delay:";
/**
* 用户代理类型离职quit 代理agent
*/
String USER_AGENT_TYPE_QUIT = "quit";
String USER_AGENT_TYPE_AGENT = "agent";
/**
* 督办流程首节点任务taskKey
*/
String SUPERVISE_FIRST_TASK_KEY = "Task_1bhxpt0";
/**
* wps模板预览数据缓存前缀
*/
String EOA_WPS_TEMPLATE_VIEW_DATA ="eoa:wps:templateViewData:";
/**
* wps模板预览版本号缓存前缀
*/
String EOA_WPS_TEMPLATE_VIEW_VERSION ="eoa:wps:templateViewVersion:";
/**
* 表单设计器oa新增字段
* x_oa_timeout_date:逾期时间
* x_oa_archive_status:归档状态
*/
String X_OA_TIMEOUT_DATE ="x_oa_timeout_date";
String X_OA_ARCHIVE_STATUS ="x_oa_archive_status";
/**
* 流程状态
* 待提交: 1
* 处理中: 2
* 已完成: 3
* 已作废: 4
* 已挂起: 5
*/
String BPM_STATUS_1 ="1";
String BPM_STATUS_2 ="2";
String BPM_STATUS_3 ="3";
String BPM_STATUS_4 ="4";
String BPM_STATUS_5 ="5";
/**
* 默认租户产品包
*/
String TENANT_PACK_DEFAULT = "default";
/**
* 部门名称redisKey(全路径)
*/
String DEPART_NAME_REDIS_KEY_PRE = "sys:cache:departPathName:";
/**
* 默认用户排序值
*/
Integer DEFAULT_USER_SORT = 1000;
/**
* 发送短信方式腾讯
*/
String SMS_SEND_TYPE_TENCENT = "tencent";
/**
* 发送短信方式阿里云
*/
String SMS_SEND_TYPE_ALI_YUN = "aliyun";
}

View File

@ -0,0 +1,48 @@
package com.ghb.base.common.constant;
/**
* 系统通告 - 发布状态
* @Author LeeShaoQing
*
*/
public interface CommonSendStatus {
/**
* 未发布
*/
public static final String UNPUBLISHED_STATUS_0 = "0";
/**
* 已发布
*/
public static final String PUBLISHED_STATUS_1 = "1";
/**
* 撤销
*/
public static final String REVOKE_STATUS_2 = "2";
/**
* app端推送会话标识后缀
*/
public static final String APP_SESSION_SUFFIX = "_app";
/**-----【流程相关通知模板code】------------------------------------------------------------*/
/**流程催办——系统通知消息模板*/
public static final String TZMB_BPM_CUIBAN = "bpm_cuiban";
/**流程抄送——系统通知消息模板*/
public static final String TZMB_BPM_CC = "bpm_cc";
/**流程催办——邮件通知消息模板*/
public static final String TZMB_BPM_CUIBAN_EMAIL = "bpm_cuiban_email";
/**标准模板—系统消息通知*/
public static final String TZMB_SYS_TS_NOTE = "sys_ts_note";
/**流程超时提醒——系统通知消息模板*/
public static final String TZMB_BPM_CHAOSHI_TIP = "bpm_chaoshi_tip";
/**-----【流程相关通知模板code】-----------------------------------------------------------*/
/**
* 系统通知拓展参数比如用于流程抄送和催办通知这里额外传递流程跳转页面所需要的路由参数
*/
public static final String MSG_ABSTRACT_JSON = "msg_abstract";
}

View File

@ -0,0 +1,196 @@
package com.ghb.base.common.constant;
/**
* 数据库上下文常量
* @author: Ghb-boot
*/
public interface DataBaseConstant {
/**
* 内置的系统变量键列表
*/
public static final String[] SYSTEM_KEYS = {
DataBaseConstant.SYS_ORG_CODE, DataBaseConstant.SYS_ORG_CODE_TABLE, DataBaseConstant.SYS_MULTI_ORG_CODE,
DataBaseConstant.SYS_MULTI_ORG_CODE_TABLE, DataBaseConstant.SYS_ORG_ID, DataBaseConstant.SYS_ORG_ID_TABLE,
DataBaseConstant.SYS_ROLE_CODE, DataBaseConstant.SYS_ROLE_CODE_TABLE, DataBaseConstant.SYS_USER_CODE,
DataBaseConstant.SYS_USER_CODE_TABLE, DataBaseConstant.SYS_USER_ID, DataBaseConstant.SYS_USER_ID_TABLE,
DataBaseConstant.SYS_USER_NAME, DataBaseConstant.SYS_USER_NAME_TABLE, DataBaseConstant.SYS_DATE,
DataBaseConstant.SYS_DATE_TABLE, DataBaseConstant.SYS_TIME, DataBaseConstant.SYS_TIME_TABLE,
DataBaseConstant.SYS_BASE_PATH
};
//*********数据库类型****************************************
/**MYSQL数据库*/
public static final String DB_TYPE_MYSQL = "MYSQL";
/** ORACLE*/
public static final String DB_TYPE_ORACLE = "ORACLE";
/**达梦数据库*/
public static final String DB_TYPE_DM = "DM";
/**postgreSQL达梦数据库*/
public static final String DB_TYPE_POSTGRESQL = "POSTGRESQL";
/**人大金仓数据库*/
public static final String DB_TYPE_KINGBASEES = "KINGBASEES";
/**sqlserver数据库*/
public static final String DB_TYPE_SQLSERVER = "SQLSERVER";
/**mariadb 数据库*/
public static final String DB_TYPE_MARIADB = "MARIADB";
/**DB2 数据库*/
public static final String DB_TYPE_DB2 = "DB2";
/**HSQL 数据库*/
public static final String DB_TYPE_HSQL = "HSQL";
// // 数据库类型对应 database_type 字典
// public static final String DB_TYPE_MYSQL_NUM = "1";
// public static final String DB_TYPE_MYSQL7_NUM = "6";
// public static final String DB_TYPE_ORACLE_NUM = "2";
// public static final String DB_TYPE_SQLSERVER_NUM = "3";
// public static final String DB_TYPE_POSTGRESQL_NUM = "4";
// public static final String DB_TYPE_MARIADB_NUM = "5";
//*********系统上下文变量****************************************
/**
* 数据-所属机构编码
*/
public static final String SYS_ORG_CODE = "sysOrgCode";
/**
* 数据-所属机构编码
*/
public static final String SYS_ORG_CODE_TABLE = "sys_org_code";
/**
* 数据-所属机构编码
*/
public static final String SYS_MULTI_ORG_CODE = "sysMultiOrgCode";
/**
* 数据-所属机构编码
*/
public static final String SYS_MULTI_ORG_CODE_TABLE = "sys_multi_org_code";
/**
* 数据-所属机构ID
*/
public static final String SYS_ORG_ID = "sysOrgId";
/**
* 数据-所属机构ID
*/
public static final String SYS_ORG_ID_TABLE = "sys_org_id";
/**
* 数据-所属角色code多个逗号分割
*/
public static final String SYS_ROLE_CODE = "sysRoleCode";
/**
* 数据-所属角色code多个逗号分割
*/
public static final String SYS_ROLE_CODE_TABLE = "sys_role_code";
/**
* 数据-系统用户编码对应登录用户账号
*/
public static final String SYS_USER_CODE = "sysUserCode";
/**
* 数据-系统用户编码对应登录用户账号
*/
public static final String SYS_USER_CODE_TABLE = "sys_user_code";
/**
* 登录用户ID
*/
public static final String SYS_USER_ID = "sysUserId";
/**
* 登录用户ID
*/
public static final String SYS_USER_ID_TABLE = "sys_user_id";
/**
* 登录用户真实姓名
*/
public static final String SYS_USER_NAME = "sysUserName";
/**
* 登录用户真实姓名
*/
public static final String SYS_USER_NAME_TABLE = "sys_user_name";
/**
* 系统日期"yyyy-MM-dd"
*/
public static final String SYS_DATE = "sysDate";
/**
* 系统日期"yyyy-MM-dd"
*/
public static final String SYS_DATE_TABLE = "sys_date";
/**
* 系统时间"yyyy-MM-dd HH:mm"
*/
public static final String SYS_TIME = "sysTime";
/**
* 系统时间"yyyy-MM-dd HH:mm"
*/
public static final String SYS_TIME_TABLE = "sys_time";
/**
* 数据-所属机构编码
*/
public static final String SYS_BASE_PATH = "sys_base_path";
//*********系统上下文变量****************************************
//*********系统建表标准字段****************************************
/**
* 创建者登录名称
*/
public static final String CREATE_BY_TABLE = "create_by";
/**
* 创建者登录名称
*/
public static final String CREATE_BY = "createBy";
/**
* 创建日期时间
*/
public static final String CREATE_TIME_TABLE = "create_time";
/**
* 创建日期时间
*/
public static final String CREATE_TIME = "createTime";
/**
* 更新用户登录名称
*/
public static final String UPDATE_BY_TABLE = "update_by";
/**
* 更新用户登录名称
*/
public static final String UPDATE_BY = "updateBy";
/**
* 更新日期时间
*/
public static final String UPDATE_TIME = "updateTime";
/**
* 更新日期时间
*/
public static final String UPDATE_TIME_TABLE = "update_time";
/**
* 业务流程状态
*/
public static final String BPM_STATUS = "bpmStatus";
/**
* 业务流程状态
*/
public static final String BPM_STATUS_TABLE = "bpm_status";
//*********系统建表标准字段****************************************
/**
* sql语句 where
*/
String SQL_WHERE = "where";
/**
* sql语句 asc
*/
String SQL_ASC = "asc";
/**
* sqlserver数据库,中间有空格
*/
String DB_TYPE_SQL_SERVER_BLANK = "sql server";
}

View File

@ -0,0 +1,16 @@
package com.ghb.base.common.constant;
/**
* 动态切换表配置常量
*
* @author: scott
* @date: 2022年04月25日 22:30
*/
public class DynamicTableConstant {
/**
* 角色首页配置表
* vue2表名: sys_role_index
* vue3表名: sys_role_index_vue3
*/
public static final String SYS_ROLE_INDEX = "sys_role_index";
}

View File

@ -0,0 +1,25 @@
package com.ghb.base.common.constant;
/**
* 规则值生成 编码常量类
* @author: taoyan
* @date: 2020年04月02日
*/
public class FillRuleConstant {
/**
* 公文发文编码
*/
public static final String DOC_SEND = "doc_send_code";
/**
* 部门编码
*/
public static final String DEPART = "org_num_role";
/**
* 分类字典编码
*/
public static final String CATEGORY = "category_code_rule";
}

View File

@ -0,0 +1,15 @@
package com.ghb.base.common.constant;
/**
* @Description: 密码常量类
*
* @author: wangshuai
* @date: 2025/8/27 20:10
*/
public interface PasswordConstant {
/**
* 导入用户默认密码
*/
String DEFAULT_PASSWORD = "123456";
}

View File

@ -0,0 +1,219 @@
package com.ghb.base.common.constant;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import com.ghb.base.common.util.oConvertUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.Set;
import java.util.List;
/**
* @Description: 省市区
* @author: Ghb-boot
*/
@Component("pca")
public class ProvinceCityArea {
List<Area> areaList;
public String getText(String code){
if(StringUtils.isNotBlank(code)){
this.initAreaList();
if(this.areaList!=null || this.areaList.size()>0){
List<String> ls = new ArrayList<String>();
getAreaByCode(code,ls);
return String.join("/",ls);
}
}
return "";
}
public String getCode(String text){
if(StringUtils.isNotBlank(text)){
this.initAreaList();
if(areaList!=null && areaList.size()>0){
for(int i=areaList.size()-1;i>=0;i--){
// 代码逻辑说明: VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区
String areaText = areaList.get(i).getText();
String cityText = areaList.get(i).getAheadText();
if(text.indexOf(areaText)>=0 && (cityText!=null && text.indexOf(cityText)>=0)){
return areaList.get(i).getId();
}
}
}
}
return null;
}
/**
* 获取省市区code精准匹配
* @param texts 文本数组
* @return 返回 省市区的code
*/
public String[] getCode(String[] texts) {
if (texts == null || texts.length == 0) {
return null;
}
this.initAreaList();
if (areaList == null || areaList.size() == 0) {
return null;
}
String[] codes = new String[texts.length];
String code = null;
for (int i = 0; i < texts.length; i++) {
String text = texts[i];
Area area;
if (code == null) {
area = getAreaByText(text);
} else {
area = getAreaByPidAndText(code, text);
}
if (area != null) {
code = area.id;
codes[i] = code;
} else {
return null;
}
}
return codes;
}
/**
* 根据text获取area
* @param text
* @return
*/
public Area getAreaByText(String text) {
for (Area area : areaList) {
if (text.equals(area.getText())) {
return area;
}
}
return null;
}
/**
* 通过pid获取 area 对象
* @param pCode 父级编码
* @param text
* @return
*/
public Area getAreaByPidAndText(String pCode, String text) {
this.initAreaList();
if (this.areaList != null && this.areaList.size() > 0) {
for (Area area : this.areaList) {
if (area.getPid().equals(pCode) && area.getText().equals(text)) {
return area;
}
}
}
return null;
}
public void getAreaByCode(String code,List<String> ls){
for(Area area: areaList){
if(null != area && area.getId().equals(code)){
String pid = area.getPid();
ls.add(0,area.getText());
getAreaByCode(pid,ls);
}
}
}
private void initAreaList(){
//System.out.println("=====================");
if(this.areaList==null || this.areaList.size()==0){
this.areaList = new ArrayList<Area>();
try {
String jsonData = oConvertUtils.readStatic("classpath:static/pca.json");
JSONObject baseJson = JSONObject.parseObject(jsonData);
//第一层
JSONObject provinceJson = baseJson.getJSONObject("86");
for(String provinceKey: provinceJson.keySet()){
//System.out.println("===="+provinceKey);
Area province = new Area(provinceKey,provinceJson.getString(provinceKey),"86");
this.areaList.add(province);
//第二层
JSONObject cityJson = baseJson.getJSONObject(provinceKey);
for(String cityKey:cityJson.keySet()){
//System.out.println("-----"+cityKey);
Area city = new Area(cityKey,cityJson.getString(cityKey),provinceKey);
this.areaList.add(city);
//第三层
JSONObject areaJson = baseJson.getJSONObject(cityKey);
if(areaJson!=null){
for(String areaKey:areaJson.keySet()){
//System.out.println("········"+areaKey);
Area area = new Area(areaKey,areaJson.getString(areaKey),cityKey);
// 代码逻辑说明: VUEN-1088 online 导入 省市区导入后 导入数据错乱 北京市/市辖区/西城区-->山西省/晋城市/城区
area.setAheadText(cityJson.getString(cityKey));
this.areaList.add(area);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private String jsonRead(File file){
Scanner scanner = null;
StringBuilder buffer = new StringBuilder();
try {
scanner = new Scanner(file, "utf-8");
while (scanner.hasNextLine()) {
buffer.append(scanner.nextLine());
}
} catch (Exception e) {
} finally {
if (scanner != null) {
scanner.close();
}
}
return buffer.toString();
}
class Area{
String id;
String text;
String pid;
// 用于存储上级文本数据区的上级文本 是市的数据
String aheadText;
public Area(String id,String text,String pid){
this.id = id;
this.text = text;
this.pid = pid;
}
public String getId() {
return id;
}
public String getText() {
return text;
}
public String getPid() {
return pid;
}
public String getAheadText() {
return aheadText;
}
public void setAheadText(String aheadText) {
this.aheadText = aheadText;
}
}
}

View File

@ -0,0 +1,59 @@
/*
*
* * Copyright (c) 2019-2020, 冷冷 (wangiegie@gmail.com).
* * <p>
* * Licensed under the GNU Lesser General Public License 3.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* * <p>
* * https://www.gnu.org/licenses/lgpl.html
* * <p>
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package com.ghb.base.common.constant;
/**
* @author scott
* @date 2019年05月18日
* 服务名称
*/
public interface ServiceNameConstants {
/**
* 微服务名系统管理模块
*/
String SERVICE_SYSTEM = "Ghb-system";
/**
* 微服务名 demo模块
*/
String SERVICE_DEMO = "Ghb-demo";
/**
* 微服务名joa模块
*/
String SERVICE_JOA = "Ghb-joa";
// /**
// * 微服务名online在线模块
// */
// String SERVICE_ONLINE = "Ghb-online";
// /**
// * 微服务名OA模块
// */
// String SERVICE_EOA = "Ghb-eoa";
// /**
// * 微服务名表单设计模块
// */
// String SERVICE_FORM = "Ghb-desform";
/**
* gateway通过header传递根路径 basePath
*/
String X_GATEWAY_BASE_PATH = "X_GATEWAY_BASE_PATH";
}

View File

@ -0,0 +1,123 @@
package com.ghb.base.common.constant;
/**
* @Description: 符号和特殊符号常用类
* @author: wangshuai
* @date: 2022年03月30日 17:44
*/
public class SymbolConstant {
/**
* 符号
*/
public static final String SPOT = ".";
/**
* 符号双斜杠
*/
public static final String DOUBLE_BACKSLASH = "\\";
/**
* 符号冒号
*/
public static final String COLON = ":";
/**
* 符号逗号
*/
public static final String COMMA = ",";
/**
* 符号左花括号 }
*/
public static final String LEFT_CURLY_BRACKET = "{";
/**
* 符号右花括号 }
*/
public static final String RIGHT_CURLY_BRACKET = "}";
/**
* 符号井号 #
*/
public static final String WELL_NUMBER = "#";
/**
* 符号单斜杠
*/
public static final String SINGLE_SLASH = "/";
/**
* 符号双斜杠
*/
public static final String DOUBLE_SLASH = "//";
/**
* 符号感叹号
*/
public static final String EXCLAMATORY_MARK = "!";
/**
* 符号下划线
*/
public static final String UNDERLINE = "_";
/**
* 符号单引号
*/
public static final String SINGLE_QUOTATION_MARK = "'";
/**
* 符号星号
*/
public static final String ASTERISK = "*";
/**
* 符号百分号
*/
public static final String PERCENT_SIGN = "%";
/**
* 符号美元 $
*/
public static final String DOLLAR = "$";
/**
* 符号 &
*/
public static final String AND = "&";
/**
* 符号../
*/
public static final String SPOT_SINGLE_SLASH = "../";
/**
* 符号..\\
*/
public static final String SPOT_DOUBLE_BACKSLASH = "..\\";
/**
* 系统变量前缀 #{
*/
public static final String SYS_VAR_PREFIX = "#{";
/**
* 符号 {{
*/
public static final String DOUBLE_LEFT_CURLY_BRACKET = "{{";
/**
* 符号[
*/
public static final String SQUARE_BRACKETS_LEFT = "[";
/**
* 符号]
*/
public static final String SQUARE_BRACKETS_RIGHT = "]";
/**
* 拼接字符串符号 分号 ;
*/
public static final String SEMICOLON = ";";
}

View File

@ -0,0 +1,54 @@
package com.ghb.base.common.constant;
/**
* @Description: TenantConstant
* @author: scott
* @date: 2022年08月29日 15:29
*/
public interface TenantConstant {
/*------【低代码应用参数】----------------------------------------------*/
/**
* header的lowAppId标识
*/
String X_LOW_APP_ID = "X-Low-App-ID";
/**
* 应用ID实体字段
*/
String FIELD_LOW_APP_ID = "lowAppId";
/**
* 应用ID表字段
*/
String DB_FIELD_LOW_APP_ID = "low_app_id";
/*------【低代码应用参数】---------------------------------------------*/
/*--------【租户参数】-----------------------------------------------*/
/**
* 租户ID实体字段名 url参数名
*/
String TENANT_ID = "tenantId";
/**
* 租户ID 数据库字段名
*/
String TENANT_ID_TABLE = "tenant_id";
/*-------【租户参数】-----------------------------------------------*/
/**
* 超级管理员
*/
String SUPER_ADMIN = "superAdmin";
/**
* 组织账户管理员
*/
String ACCOUNT_ADMIN = "accountAdmin";
/**
* 组织应用管理员
*/
String APP_ADMIN = "appAdmin";
/**
* 增加SignatureCheck注解POST请求的URL
*/
String[] SIGNATURE_CHECK_POST_URL = { "/sys/tenant/joinTenantByHouseNumber", "/sys/tenant/invitationUser" };
}

View File

@ -0,0 +1,31 @@
package com.ghb.base.common.constant;
/**
* VXESocket 常量
* @author: Ghb-boot
*/
public class VxeSocketConst {
/**
* 消息类型
*/
public static final String TYPE = "type";
/**
* 消息数据
*/
public static final String DATA = "data";
/**
* 消息类型心跳检测
*/
public static final String TYPE_HB = "heart_beat";
/**
* 消息类型通用数据传递
*/
public static final String TYPE_CSD = "common_send_date";
/**
* 消息类型更新vxe table数据
*/
public static final String TYPE_UVT = "update_vxe_table";
}

View File

@ -0,0 +1,66 @@
package com.ghb.base.common.constant;
/**
* @Description: Websocket常量类
* @author: taoyan
* @date: 2020年03月23日
*/
public class WebsocketConst {
/**
* 消息json key:cmd
*/
public static final String MSG_CMD = "cmd";
/**
* 消息json key:msgId
*/
public static final String MSG_ID = "msgId";
/**
* 消息json key:msgTxt
*/
public static final String MSG_TXT = "msgTxt";
/**
* 消息json key:userId
*/
public static final String MSG_USER_ID = "userId";
/**
* 消息json key:chat
*/
public static final String MSG_CHAT = "chat";
/**
* 消息类型 heartcheck
*/
public static final String CMD_CHECK = "heartcheck";
/**
* 消息类型 user 用户消息
*/
public static final String CMD_USER = "user";
/**
* 消息类型 topic 系统通知
*/
public static final String CMD_TOPIC = "topic";
/**
* 消息类型 email
*/
public static final String CMD_EMAIL = "email";
/**
* 消息类型 meetingsign 会议签到
*/
public static final String CMD_SIGN = "sign";
/**
* 消息类型 新闻发布/取消
*/
public static final String NEWS_PUBLISH = "publish";
}

View File

@ -0,0 +1,167 @@
package com.ghb.base.common.constant.enums;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* online表单枚举 代码生成器用到
* @author: Ghb-boot
*/
public enum CgformEnum {
/**
* 单表
*/
ONE(1, "one", "/Ghb/code-template-online", "default.one", "经典风格", new String[]{"vue3","vue","vue3Native"}),
/**
* 多表
*/
MANY(2, "many", "/Ghb/code-template-online", "default.onetomany", "经典风格" ,new String[]{"vue"}),
/**
* 多表jvxe风格
* */
JVXE_TABLE(2, "jvxe", "/Ghb/code-template-online", "jvxe.onetomany", "默认风格" ,new String[]{"vue3","vue","vue3Native"}),
/**
* 多表 (erp风格)
*/
ERP(2, "erp", "/Ghb/code-template-online", "erp.onetomany", "ERP风格" ,new String[]{"vue3","vue","vue3Native"}),
/**
* 多表内嵌子表风格
*/
INNER_TABLE(2, "innerTable", "/Ghb/code-template-online", "inner-table.onetomany", "内嵌子表风格" ,new String[]{"vue3","vue"}),
/**
* 多表tab风格
* */
TAB(2, "tab", "/Ghb/code-template-online", "tab.onetomany", "Tab风格" ,new String[]{"vue3","vue"}),
/**
* 树形列表
*/
TREE(3, "tree", "/Ghb/code-template-online", "default.tree", "树形列表" ,new String[]{"vue3","vue","vue3Native"});
/**
* 类型 1/单表 2/一对多 3/
*/
int type;
/**
* 编码标识
*/
String code;
/**
* 代码生成器模板路径
*/
String templatePath;
/**
* 代码生成器模板路径
*/
String stylePath;
/**
* 模板风格名称
*/
String note;
/**
* 支持代码风格 vue3:vue3包装代码 vue3Native:vue3原生代码 vue:vue2代码
*/
String[] vueStyle;
/**
* 构造器
*
* @param type 类型 1/单表 2/一对多 3/
* @param code 模板编码
* @param templatePath 模板路径
* @param stylePath 模板子路径
* @param note
* @param vueStyle 支持代码风格
*/
CgformEnum(int type, String code, String templatePath, String stylePath, String note, String[] vueStyle) {
this.type = type;
this.code = code;
this.templatePath = templatePath;
this.stylePath = stylePath;
this.note = note;
this.vueStyle = vueStyle;
}
/**
* 根据code获取模板路径
*
* @param code
* @return
*/
public static String getTemplatePathByConfig(String code) {
return getCgformEnumByConfig(code).templatePath;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getTemplatePath() {
return templatePath;
}
public void setTemplatePath(String templatePath) {
this.templatePath = templatePath;
}
public String getStylePath() {
return stylePath;
}
public void setStylePath(String stylePath) {
this.stylePath = stylePath;
}
public String[] getVueStyle() {
return vueStyle;
}
public void setVueStyle(String[] vueStyle) {
this.vueStyle = vueStyle;
}
/**
* 根据code找枚举
*
* @param code
* @return
*/
public static CgformEnum getCgformEnumByConfig(String code) {
for (CgformEnum e : CgformEnum.values()) {
if (e.code.equals(code)) {
return e;
}
}
return null;
}
/**
* 根据类型找所有
*
* @param type
* @return
*/
public static List<Map<String, Object>> getJspModelList(int type) {
List<Map<String, Object>> ls = new ArrayList<Map<String, Object>>();
for (CgformEnum e : CgformEnum.values()) {
if (e.type == type) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("code", e.code);
map.put("note", e.note);
ls.add(map);
}
}
return ls;
}
}

View File

@ -0,0 +1,23 @@
package com.ghb.base.common.constant.enums;
/**
* 客户终端类型
*/
public enum ClientTerminalTypeEnum {
PC("pc", "电脑终端"),
H5("h5", "移动网页端"),
APP("app", "手机app端");
private String key;
private String text;
ClientTerminalTypeEnum(String value, String text) {
this.key = value;
this.text = text;
}
public String getKey() {
return this.key;
}
}

View File

@ -0,0 +1,27 @@
package com.ghb.base.common.constant.enums;
/**
* 日期预设范围枚举
*/
public enum DateRangeEnum {
// 今天
TODAY,
// 昨天
YESTERDAY,
// 明天
TOMORROW,
// 本周
THIS_WEEK,
// 上周
LAST_WEEK,
// 下周
NEXT_WEEK,
// 过去七天
LAST_7_DAYS,
// 本月
THIS_MONTH,
// 上月
LAST_MONTH,
// 下月
NEXT_MONTH,
}

View File

@ -0,0 +1,97 @@
package com.ghb.base.common.constant.enums;
import com.ghb.base.common.util.oConvertUtils;
/**
* @Description: 部门类型枚举类
*
* @author: wangshuai
* @date: 2025/8/19 21:37
*/
public enum DepartCategoryEnum {
DEPART_CATEGORY_COMPANY("部门类型:公司","公司","1"),
DEPART_CATEGORY_DEPART("部门类型:部门","部门","2"),
DEPART_CATEGORY_POST("部门类型:岗位","岗位","3"),
DEPART_CATEGORY_SUB_COMPANY("部门类型:子公司","子公司","4");
DepartCategoryEnum(String described, String name, String value) {
this.value = value;
this.name = name;
this.described = described;
}
/**
* 描述
*/
private String described;
/**
*
*/
private String value;
/**
* 名称
*/
private String name;
public String getDescribed() {
return described;
}
public void setDescribed(String described) {
this.described = described;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 根据值获取名称
*
* @param value
* @return
*/
public static String getNameByValue(String value){
if (oConvertUtils.isEmpty(value)) {
return null;
}
for (DepartCategoryEnum val : values()) {
if (val.getValue().equals(value)) {
return val.getName();
}
}
return value;
}
/**
* 根据名称获取值
*
* @param name
* @return
*/
public static String getValueByName(String name){
if (oConvertUtils.isEmpty(name)) {
return null;
}
for (DepartCategoryEnum val : values()) {
if (val.getName().equals(name)) {
return val.getValue();
}
}
return name;
}
}

View File

@ -0,0 +1,75 @@
package com.ghb.base.common.constant.enums;
import org.apache.commons.lang3.StringUtils;
/**
* @Description: 短信枚举类
* @author: Ghb-boot
*/
public enum DySmsEnum {
/**登录短信模板编码*/
LOGIN_TEMPLATE_CODE("SMS_175435174","敲敲云","code"),
/**忘记密码短信模板编码*/
FORGET_PASSWORD_TEMPLATE_CODE("SMS_175435174","敲敲云","code"),
/**修改密码短信模板编码*/
CHANGE_PASSWORD_TEMPLATE_CODE("SMS_465391221","敲敲云","code"),
/**注册账号短信模板编码*/
REGISTER_TEMPLATE_CODE("SMS_175430166","敲敲云","code");
/**
* 短信模板编码
*/
private String templateCode;
/**
* 签名
*/
private String signName;
/**
* 短信模板必需的数据名称多个key以逗号分隔此处配置作为校验
*/
private String keys;
private DySmsEnum(String templateCode,String signName,String keys) {
this.templateCode = templateCode;
this.signName = signName;
this.keys = keys;
}
public String getTemplateCode() {
return templateCode;
}
public void setTemplateCode(String templateCode) {
this.templateCode = templateCode;
}
public String getSignName() {
return signName;
}
public void setSignName(String signName) {
this.signName = signName;
}
public String getKeys() {
return keys;
}
public void setKeys(String keys) {
this.keys = keys;
}
public static DySmsEnum toEnum(String templateCode) {
if(StringUtils.isEmpty(templateCode)){
return null;
}
for(DySmsEnum item : DySmsEnum.values()) {
if(item.getTemplateCode().equals(templateCode)) {
return item;
}
}
return null;
}
}

View File

@ -0,0 +1,70 @@
package com.ghb.base.common.constant.enums;
import com.ghb.base.common.util.oConvertUtils;
/**
* 邮件html模板配置地址美剧
*
* @author: liusq
* @Date: 2023-10-13
*/
public enum EmailTemplateEnum {
/**
* 流程催办
*/
BPM_CUIBAN_EMAIL("bpm_cuiban_email", "/templates/email/bpm_cuiban_email.ftl"),
/**
* 流程抄送
*/
BPM_CC_EMAIL("bpm_cc_email", "/templates/email/bpm_cc_email.ftl"),
/**
* 流程新任务
*/
BPM_NEW_TASK_EMAIL("bpm_new_task_email", "/templates/email/bpm_new_task_email.ftl"),
/**
* 表单新增记录
*/
DESFORM_NEW_DATA_EMAIL("desform_new_data_email", "/templates/email/desform_new_data_email.ftl");
/**
* 模板名称
*/
private String name;
/**
* 模板地址
*/
private String url;
EmailTemplateEnum(String name, String url) {
this.name = name;
this.url = url;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public static EmailTemplateEnum getByName(String name) {
if (oConvertUtils.isEmpty(name)) {
return null;
}
for (EmailTemplateEnum val : values()) {
if (val.getName().equals(name)) {
return val;
}
}
return null;
}
}

View File

@ -0,0 +1,77 @@
package com.ghb.base.common.constant.enums;
import com.ghb.base.common.util.oConvertUtils;
/**
* 文件类型
*/
public enum FileTypeEnum {
// 文档类型folder:文件夹 excel:excel doc:word pp:ppt image:图片 archive:其他文档 video:视频 voice:语音
// FOLDER
xls(".xls","excel","excel"),
xlsx(".xlsx","excel","excel"),
doc(".doc","doc","word"),
docx(".docx","doc","word"),
ppt(".ppt","pp","ppt"),
pptx(".pptx","pp","ppt"),
gif(".gif","image","图片"),
jpg(".jpg","image","图片"),
jpeg(".jpeg","image","图片"),
png(".png","image","图片"),
txt(".txt","text","文本"),
avi(".avi","video","视频"),
mov(".mov","video","视频"),
rmvb(".rmvb","video","视频"),
rm(".rm","video","视频"),
flv(".flv","video","视频"),
mp4(".mp4","video","视频"),
zip(".zip","zip","压缩包"),
pdf(".pdf","pdf","pdf"),
mp3(".mp3","mp3","语音"),
wav(".wav","wav","语音");
private String type;
private String value;
private String text;
private FileTypeEnum(String type,String value,String text){
this.type = type;
this.value = value;
this.text = text;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public static FileTypeEnum getByType(String type){
if (oConvertUtils.isEmpty(type)) {
return null;
}
for (FileTypeEnum val : values()) {
if (val.getType().equals(type)) {
return val;
}
}
return null;
}
}

View File

@ -0,0 +1,98 @@
package com.ghb.base.common.constant.enums;
import com.ghb.base.common.system.annotation.EnumDict;
import com.ghb.base.common.system.vo.DictModel;
import java.util.ArrayList;
import java.util.List;
/**
* 消息类型
*
* @author: Ghb-boot
*/
@EnumDict("messageType")
public enum MessageTypeEnum {
/**
* 系统消息
*/
XT("system", "系统消息"),
/**
* 邮件消息
*/
YJ("email", "邮件消息"),
/**
* 钉钉消息
*/
DD("dingtalk", "钉钉消息"),
/**
* 企业微信
*/
QYWX("wechat_enterprise", "企业微信");
MessageTypeEnum(String type, String note) {
this.type = type;
this.note = note;
}
/**
* 消息类型
*/
String type;
/**
* 类型说明
*/
String note;
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
/**
* 获取字典数据
*
* @return
*/
public static List<DictModel> getDictList() {
List<DictModel> list = new ArrayList<>();
DictModel dictModel = null;
for (MessageTypeEnum e : MessageTypeEnum.values()) {
dictModel = new DictModel();
dictModel.setValue(e.getType());
dictModel.setText(e.getNote());
list.add(dictModel);
}
return list;
}
/**
* 根据type获取枚举
*
* @param type
* @return
*/
public static MessageTypeEnum valueOfType(String type) {
for (MessageTypeEnum e : MessageTypeEnum.values()) {
if (e.getType().equals(type)) {
return e;
}
}
return null;
}
}

View File

@ -0,0 +1,18 @@
package com.ghb.base.common.constant.enums;
/**
* 日志按模块分类
* @author: Ghb-boot
*/
public enum ModuleType {
/**
* 普通
*/
COMMON,
/**
* online
*/
ONLINE;
}

View File

@ -0,0 +1,88 @@
package com.ghb.base.common.constant.enums;
/**
* @Description: 文件类型枚举类
*
* @author: wangshuai
* @date: 2025/6/26 17:29
*/
public enum NoticeTypeEnum {
//VUE3专用
NOTICE_TYPE_FILE("知识库消息","file"),
NOTICE_TYPE_FLOW("工作流消息","flow"),
NOTICE_TYPE_PLAN("日程消息","plan"),
//暂时没用到
NOTICE_TYPE_MEETING("会议消息","meeting"),
NOTICE_TYPE_SYSTEM("系统消息","system"),
/**
* 协同工作
* for [JHHB-136]vue3协同工作系统消息需要添加一个类型
*/
NOTICE_TYPE_COLLABORATION("协同工作", "collab"),
/**
* 督办
*/
NOTICE_TYPE_SUPERVISE("督办管理", "supe"),
/**
* 考勤
*/
NOTICE_TYPE_ATTENDANCE("考勤消息", "attendance");
/**
* 文件类型名称
*/
private String name;
/**
* 文件类型值
*/
private String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
NoticeTypeEnum(String name, String value) {
this.name = name;
this.value = value;
}
/**
* 获取聊天通知类型
*
* @param value
* @return
*/
public static String getChatNoticeType(String value){
return value + "Notice";
}
/**
* 获取通知名称
*
* @param value
* @return
*/
public static String getNoticeNameByValue(String value){
value = value.replace("Notice","");
for (NoticeTypeEnum e : NoticeTypeEnum.values()) {
if (e.getValue().equals(value)) {
return e.getName();
}
}
return "系统消息";
}
}

View File

@ -0,0 +1,95 @@
package com.ghb.base.common.constant.enums;
import com.ghb.base.common.constant.CommonConstant;
/**
* @Description: 操作类型
* @author: Ghb-boot
* @date: 2022/3/31 10:05
*/
public enum OperateTypeEnum {
/**
* 列表
*/
LIST(CommonConstant.OPERATE_TYPE_1, "list"),
/**
* 新增
*/
ADD(CommonConstant.OPERATE_TYPE_2, "add"),
/**
* 编辑
*/
EDIT(CommonConstant.OPERATE_TYPE_3, "edit"),
/**
* 删除
*/
DELETE(CommonConstant.OPERATE_TYPE_4, "delete"),
/**
* 导入
*/
IMPORT(CommonConstant.OPERATE_TYPE_5, "import"),
/**
* 导出
*/
EXPORT(CommonConstant.OPERATE_TYPE_6, "export");
/**
* 类型 1列表,2新增,3编辑,4删除,5导入,6导出
*/
int type;
/**
* 编码(请求方式)
*/
String code;
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
/**
* 构造器
*
* @param type 类型
* @param code 编码(请求方式)
*/
OperateTypeEnum(int type, String code) {
this.type = type;
this.code = code;
}
/**
* 根据请求名称匹配
*
* @param methodName 请求名称
* @return Integer 类型
*/
public static Integer getTypeByMethodName(String methodName) {
for (OperateTypeEnum e : OperateTypeEnum.values()) {
if (methodName.startsWith(e.getCode())) {
return e.getType();
}
}
return CommonConstant.OPERATE_TYPE_1;
}
}

View File

@ -0,0 +1,180 @@
package com.ghb.base.common.constant.enums;
import java.util.Arrays;
import java.util.List;
/**
* 职级枚举类
*
* 注意此枚举仅适用于天津临港控股OA项目,职级的名称和等级均为写死需要与数据库配置一致
* @date 2025-08-26
* @author scott
*/
public enum PositionLevelEnum {
// 领导层级等级1-3
CHAIRMAN("董事长", 1, PositionType.LEADER),
GENERAL_MANAGER("总经理", 2, PositionType.LEADER),
VICE_GENERAL_MANAGER("副总经理", 3, PositionType.LEADER),
// 职员层级等级4-6
MINISTER("部长", 4, PositionType.STAFF),
VICE_MINISTER("副部长", 5, PositionType.STAFF),
STAFF("职员", 6, PositionType.STAFF);
private final String name;
private final int level;
private final PositionType type;
PositionLevelEnum(String name, int level, PositionType type) {
this.name = name;
this.level = level;
this.type = type;
}
public String getName() {
return name;
}
public int getLevel() {
return level;
}
public PositionType getType() {
return type;
}
/**
* 职级类型枚举
*/
public enum PositionType {
STAFF("职员层级"),
LEADER("领导层级");
private final String desc;
PositionType(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
}
/**
* 根据职级名称获取枚举
* @param name 职级名称
* @return 职级枚举
*/
public static PositionLevelEnum getByName(String name) {
for (PositionLevelEnum position : values()) {
if (position.getName().equals(name)) {
return position;
}
}
return null;
}
/**
* 根据职级等级获取枚举
* @param level 职级等级
* @return 职级枚举
*/
public static PositionLevelEnum getByLevel(int level) {
for (PositionLevelEnum position : values()) {
if (position.getLevel() == level) {
return position;
}
}
return null;
}
/**
* 根据职级名称判断是否为职员层级
* @param name 职级名称
* @return true-职员层级false-非职员层级
*/
public static boolean isStaffLevel(String name) {
PositionLevelEnum position = getByName(name);
return position != null && position.getType() == PositionType.STAFF;
}
/**
* 根据职级名称判断是否为领导层级
* @param name 职级名称
* @return true-领导层级false-非领导层级
*/
public static boolean isLeaderLevel(String name) {
PositionLevelEnum position = getByName(name);
return position != null && position.getType() == PositionType.LEADER;
}
/**
* 比较两个职级的等级高低
* @param name1 职级名称1
* @param name2 职级名称2
* @return 正数表示name1等级更高负数表示name2等级更高0表示等级相同
*/
public static int compareLevel(String name1, String name2) {
PositionLevelEnum pos1 = getByName(name1);
PositionLevelEnum pos2 = getByName(name2);
if (pos1 == null || pos2 == null) {
return 0;
}
// 等级数字越小代表职级越高
return pos2.getLevel() - pos1.getLevel();
}
/**
* 判断是否为更高等级
* @param currentName 当前职级名称
* @param targetName 目标职级名称
* @return true-目标职级更高false-目标职级不高于当前职级
*/
public static boolean isHigherLevel(String currentName, String targetName) {
return compareLevel(targetName, currentName) > 0;
}
/**
* 获取所有职员层级名称
* @return 职员层级名称列表
*/
public static List<String> getStaffLevelNames() {
return Arrays.asList(MINISTER.getName(), VICE_MINISTER.getName(), STAFF.getName());
}
/**
* 获取所有领导层级名称
* @return 领导层级名称列表
*/
public static List<String> getLeaderLevelNames() {
return Arrays.asList(CHAIRMAN.getName(), GENERAL_MANAGER.getName(), VICE_GENERAL_MANAGER.getName());
}
/**
* 获取所有职级名称按等级排序
* @return 所有职级名称列表
*/
public static List<String> getAllPositionNames() {
return Arrays.asList(
CHAIRMAN.getName(), GENERAL_MANAGER.getName(), VICE_GENERAL_MANAGER.getName(),
MINISTER.getName(), VICE_MINISTER.getName(), STAFF.getName()
);
}
/**
* 获取指定等级范围的职级
* @param minLevel 最小等级
* @param maxLevel 最大等级
* @return 职级名称列表
*/
public static List<String> getPositionsByLevelRange(int minLevel, int maxLevel) {
return Arrays.stream(values())
.filter(p -> p.getLevel() >= minLevel && p.getLevel() <= maxLevel)
.map(PositionLevelEnum::getName)
.collect(java.util.stream.Collectors.toList());
}
}

View File

@ -0,0 +1,97 @@
package com.ghb.base.common.constant.enums;
import com.ghb.base.common.util.oConvertUtils;
import java.util.List;
/**
* 首页自定义
* 通过角色编码与首页组件路径配置
* 枚举的顺序有权限高低权重作用也就是配置多个角色在前面的角色首页会优先生效
* @author: Ghb-boot
*/
public enum RoleIndexConfigEnum {
/**首页自定义 admin*/
// ADMIN("admin", "dashboard/Analysis"),
//TEST("test", "dashboard/IndexChart"),
/**首页自定义 hr*/
// HR("hr", "dashboard/IndexBdc");
//DM("dm", "dashboard/IndexTask"),
// 此值仅为防止报错无任何实际意义
ROLE_INDEX_CONFIG_ENUM("RoleIndexConfigEnumDefault", "dashboard/Analysis");
/**
* 角色编码
*/
String roleCode;
/**
* 路由index
*/
String componentUrl;
/**
* 构造器
*
* @param roleCode 角色编码
* @param componentUrl 首页组件路径规则跟菜单配置一样
*/
RoleIndexConfigEnum(String roleCode, String componentUrl) {
this.roleCode = roleCode;
this.componentUrl = componentUrl;
}
/**
* 根据code找枚举
* @param roleCode 角色编码
* @return
*/
private static RoleIndexConfigEnum getEnumByCode(String roleCode) {
for (RoleIndexConfigEnum e : RoleIndexConfigEnum.values()) {
if (e.roleCode.equals(roleCode)) {
return e;
}
}
return null;
}
/**
* 根据code找index
* @param roleCode 角色编码
* @return
*/
private static String getIndexByCode(String roleCode) {
for (RoleIndexConfigEnum e : RoleIndexConfigEnum.values()) {
if (e.roleCode.equals(roleCode)) {
return e.componentUrl;
}
}
return null;
}
public static String getIndexByRoles(List<String> roles) {
String[] rolesArray = roles.toArray(new String[roles.size()]);
for (RoleIndexConfigEnum e : RoleIndexConfigEnum.values()) {
if (oConvertUtils.isIn(e.roleCode,rolesArray)){
return e.componentUrl;
}
}
return null;
}
public String getRoleCode() {
return roleCode;
}
public void setRoleCode(String roleCode) {
this.roleCode = roleCode;
}
public String getComponentUrl() {
return componentUrl;
}
public void setComponentUrl(String componentUrl) {
this.componentUrl = componentUrl;
}
}

View File

@ -0,0 +1,100 @@
package com.ghb.base.common.constant.enums;
import com.ghb.base.common.util.oConvertUtils;
/**
* 系统公告自定义跳转方式
* @author: Ghb-boot
*/
public enum SysAnnmentTypeEnum {
/**
* 邮件跳转组件
*/
EMAIL("email", "component", "modules/eoa/email/modals/EoaEmailInForm"),
/**
* 流程跳转到我的任务
*/
BPM("bpm", "url", "/bpm/task/MyTaskList"),
/**
* 流程抄送任务
*/
BPM_VIEW("bpm_cc", "url", "/bpm/task/MyTaskList"),
/**
* 邀请用户跳转到个人设置
*/
TENANT_INVITE("tenant_invite", "url", "/system/usersetting"),
/**
* 协同工作-待办通知
* for [JHHB-136]vue3协同工作系统消息需要添加一个类型
*/
EOA_CO_NOTIFY("eoa_co_notify", "url", "/collaboration/pending"),
/**
* 协同工作-催办通知
* for [JHHB-136]vue3协同工作系统消息需要添加一个类型
*/
EOA_CO_REMIND("eoa_co_remind", "url", "/collaboration/pending"),
/**
* 督办管理-催办
*/
EOA_SUP_REMIND("eoa_sup_remind", "url", "/superivse/list"),
/**
* 督办管理-通知
*/
EOA_SUP_NOTIFY("eoa_sup_notify", "url", "/superivse/list");
/**
* 业务类型(email:邮件 bpm:流程)
*/
private String type;
/**
* 打开方式 组件component 路由url
*/
private String openType;
/**
* 组件/路由 地址
*/
private String openPage;
SysAnnmentTypeEnum(String type, String openType, String openPage) {
this.type = type;
this.openType = openType;
this.openPage = openPage;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getOpenType() {
return openType;
}
public void setOpenType(String openType) {
this.openType = openType;
}
public String getOpenPage() {
return openPage;
}
public void setOpenPage(String openPage) {
this.openPage = openPage;
}
public static SysAnnmentTypeEnum getByType(String type) {
if (oConvertUtils.isEmpty(type)) {
return null;
}
for (SysAnnmentTypeEnum val : values()) {
if (val.getType().equals(type)) {
return val;
}
}
return null;
}
}

View File

@ -0,0 +1,86 @@
package com.ghb.base.common.constant.enums;
import com.ghb.base.common.util.oConvertUtils;
/**
* UniPush 消息推送枚举
* @author: Ghb-boot
*/
public enum UniPushTypeEnum {
/**
* 聊天
*/
CHAT("chat", "聊天消息", "收到%s发来的聊天消息"),
/**
* 流程跳转到我的任务
*/
BPM("bpm_task", "待办任务", "收到%s待办任务"),
/**
* 流程抄送任务
*/
BPM_VIEW("bpm_cc", "知会任务", "收到%s知会任务"),
/**
* 系统消息
*/
SYS_MSG("system", "系统消息", "收到一条系统通告"),
/**
* 协同工作
*/
COLLABORATION_MSG("collaboration", "系统消息", "收到一条协同工作消息");
/**
* 业务类型(chat:聊天 bpm_task:流程 bpm_cc:流程抄送)
*/
private String type;
/**
* 消息标题
*/
private String title;
/**
* 消息内容
*/
private String content;
UniPushTypeEnum(String type, String title, String content) {
this.type = type;
this.title = title;
this.content = content;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getTitle() {
return title ;
}
public void setTitle(String openType) {
this.title = openType;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public static UniPushTypeEnum getByType(String type) {
if (oConvertUtils.isEmpty(type)) {
return null;
}
for (UniPushTypeEnum val : values()) {
if (val.getType().equals(type)) {
return val;
}
}
return null;
}
}

View File

@ -0,0 +1,75 @@
package com.ghb.base.common.constant.enums;
import com.ghb.base.common.system.annotation.EnumDict;
import com.ghb.base.common.system.vo.DictModel;
import java.util.ArrayList;
import java.util.List;
/**
* 消息跳转vue3
* @Author taoYan
* @Date 2022/8/19 20:41
**/
@EnumDict("messageHref")
public enum Vue3MessageHrefEnum {
/**
* 流程催办
*/
BPM("bpm", "/task/myHandleTaskInfo"),
/**
* 系统消息通知
*/
BPM_SYSTEM_MSG("bpm_msg_node", ""),
/**
* 流程抄送任务
*/
BPM_VIEW("bpm_cc", "/task/myHandleTaskInfo"),
/**
* 节点通知
*/
BPM_TASK("bpm_task", "/task/myHandleTaskInfo"),
/**
* 邮件消息
*/
EMAIL("email", "/eoa/email");
String busType;
String path;
Vue3MessageHrefEnum(String busType, String path) {
this.busType = busType;
this.path = path;
}
public String getBusType() {
return busType;
}
public String getPath() {
return path;
}
/**
* 获取字典数据
* @return
*/
public static List<DictModel> getDictList(){
List<DictModel> list = new ArrayList<>();
DictModel dictModel = null;
for(Vue3MessageHrefEnum e: Vue3MessageHrefEnum.values()){
dictModel = new DictModel();
dictModel.setValue(e.getBusType());
dictModel.setText(e.getPath());
list.add(dictModel);
}
return list;
}
}

View File

@ -0,0 +1,87 @@
package com.ghb.base.common.desensitization;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.ghb.base.common.desensitization.annotation.Sensitive;
import com.ghb.base.common.desensitization.enums.SensitiveEnum;
import com.ghb.base.common.desensitization.util.SensitiveInfoUtil;
import com.ghb.base.common.util.encryption.AesEncryptUtil;
import java.io.IOException;
import java.util.Objects;
/**
* @author eightmonth@qq.com
* @date 2024/6/19 10:43
*/
@NoArgsConstructor
@AllArgsConstructor
@Slf4j
public class SensitiveSerialize extends JsonSerializer<String> implements ContextualSerializer {
private SensitiveEnum type;
@Override
public void serialize(String data, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
switch (type){
case ENCODE:
try {
jsonGenerator.writeString(AesEncryptUtil.encrypt(data));
} catch (Exception exception) {
log.error("数据加密错误", exception.getMessage());
jsonGenerator.writeString(data);
}
break;
case CHINESE_NAME:
jsonGenerator.writeString(SensitiveInfoUtil.chineseName(data));
break;
case ID_CARD:
jsonGenerator.writeString(SensitiveInfoUtil.idCardNum(data));
break;
case FIXED_PHONE:
jsonGenerator.writeString(SensitiveInfoUtil.fixedPhone(data));
break;
case MOBILE_PHONE:
jsonGenerator.writeString(SensitiveInfoUtil.mobilePhone(data));
break;
case ADDRESS:
jsonGenerator.writeString(SensitiveInfoUtil.address(data, 3));
break;
case EMAIL:
jsonGenerator.writeString(SensitiveInfoUtil.email(data));
break;
case BANK_CARD:
jsonGenerator.writeString(SensitiveInfoUtil.bankCard(data));
break;
case CNAPS_CODE:
jsonGenerator.writeString(SensitiveInfoUtil.cnapsCode(data));
break;
default:
jsonGenerator.writeString(data);
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
if (beanProperty != null) {
if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
Sensitive sensitive = beanProperty.getAnnotation(Sensitive.class);
if (sensitive == null) {
sensitive = beanProperty.getContextAnnotation(Sensitive.class);
}
if (sensitive != null) {
return new SensitiveSerialize(sensitive.type());
}
}
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
}
return serializerProvider.findNullValueSerializer(null);
}
}

View File

@ -0,0 +1,26 @@
package com.ghb.base.common.desensitization.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.ghb.base.common.desensitization.SensitiveSerialize;
import com.ghb.base.common.desensitization.enums.SensitiveEnum;
import java.lang.annotation.*;
/**
* 在字段上定义 标识字段存储的信息是敏感的
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveSerialize.class)
public @interface Sensitive {
/**
* 不同类型处理不同
* @return
*/
SensitiveEnum type() default SensitiveEnum.ENCODE;
}

View File

@ -0,0 +1,20 @@
package com.ghb.base.common.desensitization.annotation;
import java.lang.annotation.*;
/**
* 解密注解
*
* 在方法上定义 将方法返回对象中的敏感字段 解密需要注意的是如果没有加密过解密会出问题返回原字符串
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SensitiveDecode {
/**
* 指明需要脱敏的实体类class
* @return
*/
Class entity() default Object.class;
}

Some files were not shown because too many files have changed in this diff Show More