Compare commits

..

327 Commits

Author SHA1 Message Date
dengqichen
640cbf9c99 1.42 2025-12-18 10:14:59 +08:00
dengqichen
f619654b1a 1.41 2025-12-17 20:07:35 +08:00
dengqichen
d16b1d59ed 1.41 2025-12-17 19:34:51 +08:00
dengqichen
c3c9f9fabb 1.41 2025-12-17 19:33:47 +08:00
dengqichen
f5154b3c66 1.40 2025-12-17 17:23:14 +08:00
dengqichen
c1ac2201c1 1.40 2025-12-17 17:22:33 +08:00
dengqichen
96625fc77a 1.40 2025-12-17 12:15:17 +08:00
dengqichen
17b8f69c19 1.40 2025-12-17 12:13:43 +08:00
dengqichen
718bbf9ddd 1.40 2025-12-17 09:55:11 +08:00
dengqichen
8117b61fb1 1.40 2025-12-17 09:54:23 +08:00
dengqichen
8f09e63ea1 1.33 日志通用查询 2025-12-16 17:55:45 +08:00
dengqichen
82eb9ca6d6 1.33 日志通用查询 2025-12-16 17:55:13 +08:00
dengqichen
5a7970da36 1.33 日志通用查询 2025-12-16 16:36:04 +08:00
dengqichen
af53992713 1.33 日志通用查询 2025-12-16 16:35:19 +08:00
dengqichen
beb2cd6544 1.33 日志通用查询 2025-12-16 16:34:48 +08:00
dengqichen
3469c8ccb1 1.33 日志通用查询 2025-12-16 15:27:12 +08:00
dengqichen
5804d6e2e6 1.33 日志通用查询 2025-12-16 10:55:17 +08:00
dengqichen
9225414a87 1.33 日志通用查询 2025-12-16 10:54:54 +08:00
dengqichen
5056b133ca 1.33 日志通用查询 2025-12-16 10:54:40 +08:00
dengqichen
e70f6c5d41 1.32 2025-12-15 16:40:16 +08:00
dengqichen
a19b4b8ec8 1.32 k8s开发 2025-12-15 16:39:46 +08:00
dengqichen
d5188bc94d 1.31 k8s开发 2025-12-15 15:34:59 +08:00
dengqichen
6762a94abc 1.30 2025-12-15 15:16:58 +08:00
dengqichen
828461d46b 1.31 k8s开发 2025-12-15 14:54:22 +08:00
dengqichen
7e9587c6e7 1.30 2025-12-15 14:53:48 +08:00
dengqichen
4ca37a506d 1.31 k8s开发 2025-12-15 14:53:20 +08:00
dengqichen
8c9aec2e0c 1.31 k8s开发 2025-12-15 13:28:16 +08:00
dengqichen
7cbc1359b3 初始化数据补充 2025-12-15 09:57:55 +08:00
dengqichen
bc6eb294d9 1.30 k8s管理 2025-12-14 13:48:25 +08:00
dengqichen
dfbf8e4f0a 1.30 k8s pods查询 2025-12-14 13:38:50 +08:00
dengqichen
4e87fc1b98 1.30 k8s管理 2025-12-14 01:10:34 +08:00
dengqichen
e54d6834c3 1.30 k8s pods查询 2025-12-14 01:00:32 +08:00
dengqichen
06d4be5e8a 1.30 k8s pods查询 2025-12-14 00:51:20 +08:00
dengqichen
2a9b896f3f 1.30 k8s pods查询 2025-12-14 00:25:33 +08:00
dengqichen
daeb880bff 1.30 k8s pods查询 2025-12-14 00:16:27 +08:00
dengqichen
39c116a608 1.30 k8s pods查询 2025-12-13 23:56:13 +08:00
dengqichen
53ff53738d 1.30 k8s pods查询 2025-12-13 23:31:48 +08:00
dengqichen
b7fd18876c 1.30 k8s pods查询 2025-12-13 22:57:15 +08:00
dengqichen
7e1d78a898 1.30 k8s管理 2025-12-13 22:39:03 +08:00
dengqichen
7471571e8c 1.30 k8s pods查询 2025-12-13 22:31:52 +08:00
dengqichen
c440a91782 1.30 k8s pods查询 2025-12-13 22:08:49 +08:00
dengqichen
bd1f7fe89e 1.30 k8s pods查询 2025-12-13 22:00:22 +08:00
dengqichen
2ce817b978 1.30 k8s pods查询 2025-12-13 21:57:16 +08:00
dengqichen
e5f4fee51d 1.30 k8s pods查询 2025-12-13 21:31:01 +08:00
dengqichen
69a4dc8e1f 1.30 k8s pods查询 2025-12-13 17:40:24 +08:00
dengqichen
d81dfe9efa 1.30 k8s管理 2025-12-13 17:40:16 +08:00
dengqichen
81294bc1dc 1.30 k8s pods查询 2025-12-13 15:54:35 +08:00
dengqichen
55c8428454 1.30 k8s管理 2025-12-13 14:33:31 +08:00
dengqichen
faf983afe0 1.30 k8s管理 2025-12-13 13:58:12 +08:00
dengqichen
be6b6d75ac 1.30 k8s pods查询 2025-12-13 13:40:35 +08:00
dengqichen
07eb96e582 1.30 2025-12-12 18:39:04 +08:00
dengqichen
4d0c23fb96 1.30增加K8S支持 2025-12-12 18:28:25 +08:00
dengqichen
c226f89ebe 1.30 2025-12-12 18:27:58 +08:00
dengqichen
0ed6c1e126 1.30增加K8S支持 2025-12-12 18:26:31 +08:00
dengqichen
4c461ea42a 1.20升级 2025-12-12 09:39:59 +08:00
dengqichen
22a32c78e7 1.22 2025-12-12 09:39:44 +08:00
dengqichen
dffafa07a7 1.20升级 2025-12-12 09:29:23 +08:00
dengqichen
1eb7a6eb6b 1.20升级 2025-12-11 18:03:47 +08:00
dengqichen
1c3c148221 1.20 2025-12-11 17:17:59 +08:00
dengqichen
d372f5edfc 1.20升级 2025-12-11 17:17:18 +08:00
dengqichen
a25ee2c9a6 1.20升级 2025-12-11 16:43:51 +08:00
dengqichen
a99476794c 1.20升级 2025-12-11 16:15:32 +08:00
dengqichen
fc204bf2a1 1.18升级 2025-12-11 14:58:45 +08:00
dengqichen
eebd3240b3 1.18升级 2025-12-11 13:44:26 +08:00
dengqichen
38b6e873c8 1.18升级 2025-12-11 13:43:58 +08:00
dengqichen
8b66e7d100 1.19升级 2025-12-11 13:43:32 +08:00
dengqichen
19ea644e6e 1.18升级 2025-12-11 09:38:56 +08:00
dengqichen
2badfa63e3 1.18升级 2025-12-11 09:38:31 +08:00
dengqichen
af8e07839b 告警规则表单优化:数字输入框使用本地状态管理,解决清空后回退到默认值问题 2025-12-10 18:35:47 +08:00
dengqichen
c1e0763472 告警规则表单优化:数字输入框使用本地状态管理,解决清空后回退到默认值问题 2025-12-10 18:04:29 +08:00
dengqichen
a5dda91f20 告警规则表单优化:数字输入框使用本地状态管理,解决清空后回退到默认值问题 2025-12-10 18:03:38 +08:00
dengqichen
b9b684b778 告警规则表单优化:数字输入框使用本地状态管理,解决清空后回退到默认值问题 2025-12-10 15:04:11 +08:00
dengqichen
1e93fffecc 【后端】
新增:服务器离线告警连续失败检测机制(避免临时网络波动误报)
    - 数据库:deploy_server_monitor 表增加 status 字段记录采集状态(SUCCESS/FAILURE)
    - 框架层:新增 StatusEnum 通用状态枚举类,MonitorMetricEnum 增加 SERVER_STATUS 监控类型
    - 实体层:ServerMonitor 实体增加 status 字段用于标识采集状态
    - Repository:IServerMonitorRepository 增加 findRecentMonitorRecords 方法查询最近N条监控记录
    - Service:IServerMonitorService 增加 saveMonitorRecord(保存单条记录)和 countConsecutiveFailures(统计连续失败次数)方法
    - 告警服务:IServerAlertService 增加 checkServerStatusAlert(检查状态告警)和 resolveServerStatusAlert(解除状态告警)方法
    - 告警实现:ServerAlertServiceImpl 实现服务器状态告警的创建、级别升级(WARNING→CRITICAL)、自动解除逻辑
    - 调度器:ServerMonitorScheduler 集成连续失败检测,连接成功时插入SUCCESS记录并解除告警,连接失败时插入FAILURE记录并触发告警检测
    - 数据初始化:增加全局服务器状态告警规则(连续3次失败触发警告,5次触发严重并标记离线)

优化:统一监控告警通知模板,简化配置参数
    - ServerMonitorNotificationConfig 删除 serverOfflineTemplateId 字段,所有监控告警(CPU/内存/磁盘/网络/服务器状态)统一使用 resourceAlertTemplateId
    - ServerMonitorScheduler.collectServerMetrics 方法删除 serverOfflineTemplateId 参数
    - ServerAlertServiceImpl.sendServerStatusNotification 改用统一的资源告警模板,模板参数与其他监控告警保持一致

修复:删除冗余代码和未使用的类
    - 删除 CollectionStatusEnum.java(已被 StatusEnum 取代)
    - 删除 ServerMonitorScheduler.sendServerOfflineNotification 方法(改由 ServerAlertService 统一处理)

优化:告警通知模板支持百分比和次数两种单位
    - 通知模板使用 FreeMarker 条件判断,根据 alertType 自动显示"连续失败X次"或"使用率X%"
    - 严重级别告警显示红色并提示"请立即处理",警告级别提示"请注意观察"

    【前端】
修复:告警规则表单规则范围下拉框无法滚动选择问题(移除嵌套滚动容器干扰)
优化:规则范围选择器升级为带搜索的Popover组件(支持按服务器名称/IP搜索,固定高度可滚动)
优化:规则范围数据加载从分页接口改为列表接口
2025-12-10 14:42:37 +08:00
dengqichen
ef7efe0497 增加系统版本维护页面 2025-12-09 18:34:46 +08:00
dengqichen
2d08651f62 重新维护初始化数据 2025-12-09 18:17:31 +08:00
dengqichen
14a2a97ad9 重新维护初始化数据 2025-12-09 18:15:39 +08:00
dengqichen
edf455f127 重新维护初始化数据 2025-12-09 18:14:55 +08:00
dengqichen
b2fff8ec8a 修改LIQUIBASE的加载方式 2025-12-09 18:07:27 +08:00
dengqichen
f77f01689f 增加系统版本维护页面 2025-12-09 18:06:48 +08:00
dengqichen
a0d38e1d00 修改LIQUIBASE的加载方式 2025-12-09 17:19:34 +08:00
dengqichen
e898b25976 增加系统版本通知功能 2025-12-09 15:17:12 +08:00
dengqichen
35c65b0510 增加系统版本维护页面 2025-12-09 15:16:54 +08:00
dengqichen
051a3c827c 增加系统版本通知功能 2025-12-09 15:16:24 +08:00
dengqichen
59e114fd6b 增加系统版本维护页面 2025-12-09 14:49:03 +08:00
dengqichen
4eb82800df 增加系统版本通知功能 2025-12-09 14:48:31 +08:00
dengqichen
d7d34a314e 增加系统版本通知功能 2025-12-09 14:05:50 +08:00
dengqichen
94ef1ef9eb 增加系统版本通知功能 2025-12-09 14:04:59 +08:00
dengqichen
78c2ef0dd9 优化SSH线程池 2025-12-09 11:13:53 +08:00
dengqichen
81a11c4594 服务器拆分接口 2025-12-09 11:10:21 +08:00
dengqichen
44de0ca028 优化SSH线程池 2025-12-09 11:09:30 +08:00
dengqichen
1a135d26cf 优化SSH线程池 2025-12-09 10:00:36 +08:00
dengqichen
81f1c6407b 增加服务器阈值规则 2025-12-08 18:31:55 +08:00
dengqichen
257b8921f5 整理下初始化数据表 2025-12-08 18:10:12 +08:00
dengqichen
a584aa02d6 整理下初始化数据表 2025-12-08 17:22:36 +08:00
dengqichen
d9caf505c1 增加服务器阈值规则 2025-12-08 17:16:20 +08:00
dengqichen
279c19ad7a 整理下初始化数据表 2025-12-08 17:09:43 +08:00
dengqichen
c36ee0808c 增加服务器阈值规则 2025-12-08 17:02:05 +08:00
dengqichen
e298dcf83c 整理下初始化数据表 2025-12-08 16:09:28 +08:00
dengqichen
1a4d4edfca 整理下初始化数据表 2025-12-08 16:06:21 +08:00
dengqichen
ab052b14de 整理下初始化数据表 2025-12-08 16:02:23 +08:00
dengqichen
798691d068 整理下初始化数据表 2025-12-08 15:54:09 +08:00
dengqichen
b5fe3c96f6 整理下初始化数据表 2025-12-08 15:53:43 +08:00
dengqichen
dc56a1e3c0 整理下初始化数据表 2025-12-08 14:32:55 +08:00
dengqichen
4794e6ddcf 整理下初始化数据表 2025-12-08 13:47:13 +08:00
dengqichen
16b8db336f 重写ssh前端组件,通用化 2025-12-07 23:29:52 +08:00
dengqichen
03f2146546 增加ssh链接框架 2025-12-07 23:29:36 +08:00
dengqichen
e5bd51b4b5 重写ssh前端组件,通用化 2025-12-07 21:12:15 +08:00
dengqichen
460f237211 增加ssh链接框架 2025-12-07 21:12:08 +08:00
dengqichen
ff149be46f 增加ssh链接框架 2025-12-07 18:26:42 +08:00
dengqichen
4d4ffabe05 重写ssh前端组件,通用化 2025-12-07 18:26:26 +08:00
dengqichen
7309d05bcb 重写ssh前端组件,通用化 2025-12-07 16:38:14 +08:00
dengqichen
b8412314a8 增加ssh链接框架 2025-12-07 15:29:59 +08:00
dengqichen
c9b8157754 重写ssh前端组件,通用化 2025-12-07 15:29:46 +08:00
dengqichen
bb6985341f 重写ssh前端组件,通用化 2025-12-07 02:21:22 +08:00
dengqichen
04f1cbd251 增加ssh链接框架 2025-12-07 02:21:05 +08:00
dengqichen
ba7663ebd6 重写ssh前端组件,通用化 2025-12-07 02:01:45 +08:00
dengqichen
308a5587cc 增加ssh链接框架 2025-12-07 01:05:25 +08:00
dengqichen
d83a87b259 重写ssh前端组件,通用化 2025-12-07 00:17:11 +08:00
dengqichen
1cddee8d42 重写ssh前端组件,通用化 2025-12-07 00:06:02 +08:00
dengqichen
7025eebd51 重写ssh前端组件,通用化 2025-12-06 23:47:24 +08:00
dengqichen
6d2f0cb95c 重写ssh前端组件,通用化 2025-12-06 23:47:20 +08:00
dengqichen
743255d5c5 重写ssh前端组件,通用化 2025-12-06 23:27:26 +08:00
dengqichen
202d1b52e5 增加ssh链接框架 2025-12-06 23:27:17 +08:00
dengqichen
e768188ca5 重写ssh前端组件,通用化 2025-12-06 23:10:37 +08:00
dengqichen
83c36866cf 重写ssh前端组件,通用化 2025-12-06 22:48:26 +08:00
dengqichen
004e1adcf5 重写ssh前端组件,通用化 2025-12-06 22:32:57 +08:00
dengqichen
1fd72bf007 重写ssh前端组件,通用化 2025-12-06 22:12:34 +08:00
dengqichen
f6abb48333 重写ssh前端组件,通用化 2025-12-06 19:14:25 +08:00
dengqichen
980c827feb 重写ssh前端组件,通用化 2025-12-06 18:53:40 +08:00
dengqichen
9325166132 修改SSH链接属性字段 2025-12-06 18:01:05 +08:00
dengqichen
24f62c5719 增加ssh链接框架 2025-12-06 18:00:58 +08:00
dengqichen
80c40bb6ab 增加ssh链接框架 2025-12-06 17:22:40 +08:00
dengqichen
5b6a06016e 修改SSH链接属性字段 2025-12-06 17:01:42 +08:00
dengqichen
00628d2811 增加ssh链接框架 2025-12-06 17:01:15 +08:00
dengqichen
d4eb907536 增加SSH连接。 2025-12-05 18:02:26 +08:00
dengqichen
86276b2ffd 增加SSH功能 2025-12-05 18:02:21 +08:00
dengqichen
366935c575 增加SSH功能 2025-12-05 17:22:20 +08:00
dengqichen
cca2fd8c4a 增加SSH连接。 2025-12-05 17:21:50 +08:00
dengqichen
598ab2503d 增加SSH功能 2025-12-05 16:47:02 +08:00
dengqichen
b69c3f58c5 增加SSH连接。 2025-12-05 16:16:06 +08:00
dengqichen
0e88904267 增加GIT代码检测节点 2025-12-05 13:58:51 +08:00
dengqichen
2eaca114a4 增加团队应用下的JENKINS JOB绑定校验 2025-12-05 13:20:00 +08:00
dengqichen
241eb3914b 增加GIT代码检测节点 2025-12-05 13:19:36 +08:00
dengqichen
61ff98737d 增加GIT代码检测节点 2025-12-05 13:10:19 +08:00
dengqichen
ee65e204c6 增加团队应用下的JENKINS JOB绑定校验 2025-12-05 11:17:57 +08:00
dengqichen
612717654b 增加GIT同步时的最大数量,分页 2025-12-04 20:03:12 +08:00
dengqichen
909337044f 增加GIT代码检测节点 2025-12-04 18:35:15 +08:00
dengqichen
f49c0915ec 增加GIT检测节点 2025-12-04 18:34:50 +08:00
dengqichen
485bf949b2 增加GIT分支检测节点逻辑 2025-12-04 17:40:01 +08:00
dengqichen
6b83597d47 增加GIT代码检测节点 2025-12-04 17:39:04 +08:00
dengqichen
e03671c5de 增加GIT代码检测节点 2025-12-04 16:26:36 +08:00
dengqichen
d65e82b0e8 增加图标展示,区分清楚项目类型 2025-12-04 15:26:56 +08:00
dengqichen
456a4bede2 增加图标展示,区分清楚项目类型 2025-12-04 13:59:52 +08:00
dengqichen
a1ab19b7f3 增加同步锁 2025-12-04 13:59:21 +08:00
dengqichen
8711e0ef48 重构消息通知弹窗 2025-12-04 13:23:02 +08:00
dengqichen
a5276ca524 增加同步锁 2025-12-02 17:45:40 +08:00
dengqichen
2a6785d97d 增加同步锁 2025-12-02 14:20:56 +08:00
dengqichen
0ccb068bfd 增加同步锁 2025-12-02 13:54:26 +08:00
dengqichen
20670df44c 增加同步锁 2025-12-01 18:08:12 +08:00
dengqichen
e71711b4a0 增加同步锁 2025-12-01 18:07:38 +08:00
dengqichen
35fb294879 增加构建通知 2025-12-01 17:54:29 +08:00
dengqichen
433887c3cf 增加构建通知 2025-12-01 10:23:10 +08:00
dengqichen
c3325c379d 重构消息通知弹窗 2025-11-28 18:16:45 +08:00
dengqichen
36b04d50b8 增加构建通知 2025-11-28 18:01:27 +08:00
dengqichen
69fc7fe163 重构消息通知弹窗 2025-11-28 17:51:29 +08:00
dengqichen
0a10f1def1 重构消息通知弹窗 2025-11-28 17:40:41 +08:00
dengqichen
f66524bac6 重构消息通知弹窗 2025-11-28 17:37:42 +08:00
dengqichen
763d14f3d3 重构消息通知弹窗 2025-11-28 17:31:33 +08:00
dengqichen
cb5741f487 增加构建通知 2025-11-28 17:23:00 +08:00
dengqichen
13b2a6ac53 重构消息通知弹窗 2025-11-28 17:22:20 +08:00
dengqichen
f1f8b963f1 重构消息通知弹窗 2025-11-28 17:06:57 +08:00
dengqichen
6de57aff67 重构消息通知弹窗 2025-11-28 16:57:57 +08:00
dengqichen
7a4f5f37c2 重构消息通知弹窗 2025-11-28 16:52:43 +08:00
dengqichen
38ebe57a13 重构消息通知弹窗 2025-11-28 16:47:06 +08:00
dengqichen
52d1f0ef27 重构消息通知弹窗 2025-11-28 16:42:53 +08:00
dengqichen
9292cde650 重构消息通知弹窗 2025-11-28 16:41:03 +08:00
dengqichen
b3b6cb7f19 重构消息通知弹窗 2025-11-28 15:33:29 +08:00
dengqichen
b2edf5404f 增加构建通知 2025-11-28 14:44:30 +08:00
dengqichen
6c9df6dd58 重构消息通知弹窗 2025-11-28 14:40:50 +08:00
dengqichen
0795570470 增加构建通知 2025-11-28 14:21:55 +08:00
dengqichen
33f648d79f 增加构建通知 2025-11-28 13:53:02 +08:00
dengqichen
44ea1353b2 重构消息通知弹窗 2025-11-28 13:17:07 +08:00
dengqichen
9275f5d4ae 重构消息通知弹窗 2025-11-28 13:09:24 +08:00
dengqichen
ba9192e8bb 增加构建通知 2025-11-28 12:40:29 +08:00
dengqichen
f2d4fd75e4 重构消息通知弹窗 2025-11-28 12:30:56 +08:00
dengqichen
c88426c7ad 增加构建通知 2025-11-28 12:25:48 +08:00
dengqichen
46fb8fb5de 重构消息通知弹窗 2025-11-28 11:24:20 +08:00
dengqichen
7bf536cb41 增加构建通知 2025-11-28 11:23:59 +08:00
dengqichen
e7211f8699 增加构建通知 2025-11-28 09:36:54 +08:00
dengqichen
730bb94926 重构消息通知弹窗 2025-11-28 09:26:33 +08:00
dengqichen
d14f80ce51 重构消息通知弹窗 2025-11-28 09:13:21 +08:00
dengqichen
a872e8d290 增加构建通知 2025-11-28 09:05:03 +08:00
dengqichen
1590760616 重构消息通知弹窗 2025-11-28 09:04:35 +08:00
dengqichen
d572e5115b 增加构建通知 2025-11-27 18:53:44 +08:00
dengqichen
e09d8d4be2 重构消息通知弹窗 2025-11-27 18:47:19 +08:00
dengqichen
cbe701a5af 增加构建通知 2025-11-27 18:46:07 +08:00
dengqichen
b9d5fbe360 重构消息通知弹窗 2025-11-27 18:14:52 +08:00
dengqichen
6f218dca6a 增加构建通知 2025-11-27 18:14:35 +08:00
dengqichen
360b794bff 增加构建通知 2025-11-27 17:29:47 +08:00
dengqichen
fe66ee4d5f 增加构建通知 2025-11-27 17:06:25 +08:00
dengqichen
3fc6ddc8fc 增加构建通知 2025-11-27 17:02:28 +08:00
dengqichen
f6569fe020 增加构建通知 2025-11-27 16:07:38 +08:00
dengqichen
1a5786502a 重构消息通知弹窗 2025-11-27 15:40:58 +08:00
dengqichen
1af85b2940 增加构建通知 2025-11-27 15:40:17 +08:00
dengqichen
71e20202da 重构消息通知弹窗 2025-11-21 13:06:49 +08:00
dengqichen
df369d0d0c 重构消息通知弹窗 2025-11-21 12:24:38 +08:00
dengqichen
5b8e12b82f 增加构建通知 2025-11-20 17:16:48 +08:00
dengqichen
1838a26494 重构消息通知弹窗 2025-11-20 17:16:13 +08:00
dengqichen
21f0b965bc 增加构建通知 2025-11-20 16:33:06 +08:00
dengqichen
58ad4b2643 重构消息通知弹窗 2025-11-20 16:32:33 +08:00
dengqichen
8e2d39107f 重构消息通知弹窗 2025-11-20 15:02:59 +08:00
dengqichen
0991fb487f 增加构建通知 2025-11-20 14:46:09 +08:00
dengqichen
bbcc11b511 增加构建通知 2025-11-20 14:15:55 +08:00
dengqichen
06d4c799b2 增加构建通知 2025-11-20 14:04:46 +08:00
dengqichen
4654395a56 增加构建通知 2025-11-20 13:52:40 +08:00
dengqichen
85bebb7fc3 重构消息通知弹窗 2025-11-20 13:52:03 +08:00
dengqichen
e409101fbf 增加构建通知 2025-11-20 12:13:53 +08:00
dengqichen
97f6e8c8e2 增加代码编辑器表单组件 2025-11-18 23:44:16 +08:00
dengqichen
7c6275c03a 增加构建通知 2025-11-18 14:53:07 +08:00
dengqichen
7a3bfac44e 增加构建通知 2025-11-18 13:56:00 +08:00
dengqichen
0128d7b03b 重构消息通知弹窗 2025-11-15 18:01:08 +08:00
dengqichen
f108781983 重构消息通知弹窗 2025-11-15 17:37:43 +08:00
dengqichen
96517b809e 重构消息通知弹窗 2025-11-15 11:26:01 +08:00
dengqichen
88f5ec1d5c 增加构建通知 2025-11-15 11:24:45 +08:00
dengqichen
c09d82e7b5 增加构建通知 2025-11-15 10:51:12 +08:00
dengqichen
56a9e39bbe 增加构建通知 2025-11-15 10:21:20 +08:00
dengqichen
f9633e23b1 重构消息通知弹窗 2025-11-14 18:27:42 +08:00
dengqichen
09fe61e9a6 重构消息通知弹窗 2025-11-14 18:08:41 +08:00
dengqichen
2dd4e11191 重构消息通知弹窗 2025-11-14 18:08:31 +08:00
dengqichen
5e23c7b17f 重构消息通知弹窗 2025-11-14 17:39:35 +08:00
dengqichen
0c8d8f187c 重构消息通知弹窗 2025-11-14 17:18:15 +08:00
dengqichen
72a08a7950 增加构建通知 2025-11-14 16:49:37 +08:00
dengqichen
22ad888c9f 增加构建通知 2025-11-14 16:37:22 +08:00
dengqichen
c81006177f 增加构建通知 2025-11-14 15:54:35 +08:00
dengqichen
d8e65d8855 增加构建通知 2025-11-14 15:01:09 +08:00
dengqichen
46f23104f9 重构消息通知弹窗 2025-11-14 14:45:45 +08:00
dengqichen
3a7492ac91 增加构建通知 2025-11-14 14:35:46 +08:00
dengqichen
566425869d 增加构建通知 2025-11-14 13:14:59 +08:00
dengqichen
3645d8d062 增加构建通知 2025-11-14 11:18:00 +08:00
dengqichen
123b9054da 重构消息通知弹窗 2025-11-13 19:11:02 +08:00
dengqichen
8f8b90abb2 增加构建通知 2025-11-13 19:10:53 +08:00
dengqichen
7a8c7f7762 增加构建通知 2025-11-13 18:56:30 +08:00
dengqichen
8e6dbec07e 增加构建通知 2025-11-13 17:58:25 +08:00
dengqichen
694e6690ca 增加构建通知 2025-11-13 17:13:39 +08:00
dengqichen
1fa033cac2 重构消息通知弹窗 2025-11-13 17:13:21 +08:00
dengqichen
3b6cc1b65d 增加构建通知 2025-11-13 17:03:34 +08:00
dengqichen
8b26c0d196 重构消息通知弹窗 2025-11-13 16:55:47 +08:00
dengqichen
828a0da526 重构消息通知弹窗 2025-11-13 14:00:02 +08:00
dengqichen
a0ac1c5205 增加构建通知 2025-11-13 13:59:42 +08:00
dengqichen
41c7a88542 增加构建通知 2025-11-13 13:27:04 +08:00
dengqichen
49c65b6886 增加构建通知 2025-11-13 12:39:42 +08:00
dengqichen
9d425aa1f7 增加构建通知 2025-11-13 12:14:01 +08:00
dengqichen
5205c6aaa1 增加构建通知 2025-11-13 11:15:50 +08:00
dengqichen
8b6b4652ce 增加构建通知 2025-11-13 10:31:58 +08:00
dengqichen
e5e20a4dbb 增加代码编辑器表单组件 2025-11-12 22:50:49 +08:00
dengqichen
826c2d1a76 增加代码编辑器表单组件 2025-11-12 22:46:32 +08:00
dengqichen
c2e1d69ee4 增加部署日志 2025-11-12 22:36:55 +08:00
dengqichen
1791eb13cb 增加代码编辑器表单组件 2025-11-12 22:18:58 +08:00
dengqichen
54545a91ec 增加部署日志 2025-11-12 22:18:50 +08:00
dengqichen
87d3a55c17 增加代码编辑器表单组件 2025-11-12 21:54:42 +08:00
dengqichen
160917c074 增加部署日志 2025-11-12 21:36:48 +08:00
dengqichen
dd04c93e9b 增加代码编辑器表单组件 2025-11-12 21:36:33 +08:00
dengqichen
79b6f4bade 重构消息通知弹窗 2025-11-12 20:25:03 +08:00
dengqichen
8647997e63 重构消息通知弹窗 2025-11-12 18:14:47 +08:00
dengqichen
b1cbf52044 增加构建通知 2025-11-12 18:14:20 +08:00
dengqichen
c00153082c 重构消息通知弹窗 2025-11-12 16:54:21 +08:00
dengqichen
13f8be9146 增加构建通知 2025-11-12 16:53:53 +08:00
dengqichen
62c1a8b0d1 重构消息通知弹窗 2025-11-12 14:25:23 +08:00
dengqichen
0de08cb09b 增加构建通知 2025-11-12 14:23:57 +08:00
dengqichen
8e860443d2 重构消息通知弹窗 2025-11-12 14:11:53 +08:00
dengqichen
92eb82583f 三方系统密码加密 2025-11-12 13:18:14 +08:00
dengqichen
adf83458a5 增加构建通知 2025-11-12 13:17:48 +08:00
dengqichen
5a924f7aa5 三方系统密码加密 2025-11-12 09:53:10 +08:00
dengqichen
5cc4958eb0 增加代码编辑器表单组件 2025-11-11 22:29:42 +08:00
dengqichen
e672dd7e27 增加部署日志 2025-11-11 22:29:13 +08:00
dengqichen
edd34ae5db 增加代码编辑器表单组件 2025-11-11 21:50:18 +08:00
dengqichen
a8a9e2625e 增加部署日志 2025-11-11 21:49:59 +08:00
dengqichen
62523bc382 三方系统密码加密 2025-11-11 18:08:04 +08:00
dengqichen
3f061e996e 三方系统密码加密 2025-11-11 18:07:49 +08:00
dengqichen
a974b6fea4 增加构建通知 2025-11-11 18:07:29 +08:00
dengqichen
8b15f8ae71 三方系统密码加密 2025-11-11 16:05:30 +08:00
dengqichen
d0538de830 应用迁移到团队应用里配置GIT 2025-11-11 16:05:26 +08:00
dengqichen
4f28a9d3e5 应用迁移到团队应用里配置GIT 2025-11-11 15:34:16 +08:00
dengqichen
9d42dd1ba3 增加密码加密 2025-11-11 14:44:22 +08:00
dengqichen
3d454a0802 三方系统密码加密 2025-11-11 14:06:53 +08:00
dengqichen
10e75b9769 增加密码加密 2025-11-11 14:06:52 +08:00
dengqichen
f75a4b5f7e 重构前端逻辑 2025-11-11 11:19:38 +08:00
dengqichen
dc01b87943 打印了JENKINS节点日志 2025-11-11 11:19:05 +08:00
dengqichen
8b940229fc 重构前端逻辑 2025-11-11 10:57:42 +08:00
dengqichen
8712f199a1 打印了JENKINS节点日志 2025-11-11 10:56:00 +08:00
dengqichen
63e0df6085 打印了JENKINS节点日志 2025-11-11 10:10:19 +08:00
dengqichen
13ae354929 重构前端逻辑 2025-11-11 10:10:11 +08:00
dengqichen
161978b1be 打印了JENKINS节点日志 2025-11-11 10:09:34 +08:00
dengqichen
36f8f92f85 打印了JENKINS节点日志 2025-11-11 09:17:36 +08:00
dengqichen
532e43f736 重构前端逻辑 2025-11-10 18:10:49 +08:00
dengqichen
d9d9f2968d 打印了JENKINS节点日志 2025-11-10 17:56:18 +08:00
dengqichen
d9f084908a 重构前端逻辑 2025-11-10 17:55:05 +08:00
dengqichen
c285d224e4 重构前端逻辑 2025-11-10 17:11:09 +08:00
dengqichen
a682d5718b 重构前端逻辑 2025-11-10 16:50:28 +08:00
dengqichen
a964dac6b0 重构前端逻辑 2025-11-10 16:29:29 +08:00
dengqichen
5245edc88e 重构前端逻辑 2025-11-10 16:29:18 +08:00
dengqichen
f14aa984b1 重构前端逻辑 2025-11-10 15:55:25 +08:00
dengqichen
fcdf005e22 重构前端逻辑 2025-11-10 13:56:26 +08:00
dengqichen
11c44bc95d 重构前端逻辑 2025-11-10 13:41:39 +08:00
dengqichen
a80eea3a1e 重构前端逻辑 2025-11-10 13:40:01 +08:00
dengqichen
341062cef5 打印了JENKINS节点日志 2025-11-10 11:30:54 +08:00
dengqichen
ec7b0aa67c 重构前端逻辑 2025-11-10 11:09:06 +08:00
dengqichen
d0f2e78ee7 打印了JENKINS节点日志 2025-11-10 09:49:41 +08:00
dengqichen
e8f6102067 打印了JENKINS节点日志 2025-11-07 18:32:20 +08:00
dengqichen
af95532706 打印了JENKINS节点日志 2025-11-07 17:50:06 +08:00
dengqichen
3b77cf7539 重构前端逻辑 2025-11-07 17:02:44 +08:00
dengqichen
ccc8bc591e 打印了JENKINS节点日志 2025-11-07 17:02:18 +08:00
dengqichen
66efeb6058 重构前端逻辑 2025-11-07 16:02:41 +08:00
dengqichen
28cae45e51 打印了JENKINS节点日志 2025-11-07 16:02:18 +08:00
dengqichen
344ba25284 重构前端逻辑 2025-11-07 13:15:14 +08:00
dengqichen
ee9c0124fd 打印了JENKINS节点日志 2025-11-07 13:14:42 +08:00
dengqichen
011e13dbfa 重构前端逻辑 2025-11-07 11:22:26 +08:00
dengqichen
d9807a1816 打印了JENKINS节点日志 2025-11-07 11:21:58 +08:00
dengqichen
03814728f1 重构前端逻辑 2025-11-06 18:34:57 +08:00
dengqichen
9acae67438 打印了JENKINS节点日志 2025-11-06 18:34:40 +08:00
dengqichen
99161d3f7d 重构前端逻辑 2025-11-06 18:07:41 +08:00
dengqichen
e34248f3ea 打印了JENKINS节点日志 2025-11-06 18:07:23 +08:00
dengqichen
bb1faa1495 重构前端逻辑 2025-11-06 17:51:55 +08:00
dengqichen
0ab4472a2f 重构前端逻辑 2025-11-06 16:10:37 +08:00
dengqichen
6318ca6241 打印了JENKINS节点日志 2025-11-06 16:10:25 +08:00
dengqichen
69a41c8aaf 打印了JENKINS节点日志 2025-11-06 11:07:12 +08:00
dengqichen
c6558ee00d 重构前端逻辑 2025-11-06 10:56:17 +08:00
734 changed files with 75668 additions and 15995 deletions

245
.cursor/rules/project.mdc Normal file
View File

@ -0,0 +1,245 @@
---
alwaysApply: true
---
# 身份定义
你是一位资深的软件架构师和工程师,具备丰富的项目经验和系统思维能力。你的核心优势在于:
- 上下文工程专家:构建完整的任务上下文,而非简单的提示响应
- 规范驱动思维:将模糊需求转化为精确、可执行的规范
- 质量优先理念:每个阶段都确保高质量输出
- 项目对齐能力:深度理解现有项目架构和约束
# 6A工作流执行规则
## 阶段1: Align (对齐阶段)
**目标:** 模糊需求 → 精确规范
### 执行步骤
### 1. 项目上下文分析
- 分析现有项目结构、技术栈、架构模式、依赖关系
- 分析现有代码模式、现有文档和约定
- 理解业务域和数据模型
### 2. 需求理解确认
- 创建 docs/任务名/ALIGNMENT_[任务名].md
- 包含项目和任务特性规范
- 包含原始需求、边界确认(明确任务范围)、需求理解(对现有项目的理解)、疑问澄清(存在歧义的地方)
### 3. 智能决策策略
- 自动识别歧义和不确定性
- 生成结构化问题清单(按优先级排序)
- 优先基于现有项目内容和查找类似工程和行业知识进行决策和在文档中回答
- 有人员倾向或不确定的问题主动中断并询问关键决策点
- 基于回答更新理解和规范
### 4. 中断并询问关键决策点
- 主动中断询问,迭代执行智能决策策略
### 5. 最终共识
生成 docs/任务名/CONSENSUS_[任务名].md 包含:
- 明确的需求描述和验收标准
- 技术实现方案和技术约束和集成方案
- 任务边界限制和验收标准
- 确认所有不确定性已解决
### 质量门控
- 需求边界清晰无歧义
- 技术方案与现有架构对齐
- 验收标准具体可测试
- 所有关键假设已确认
- 项目特性规范已对齐
## 阶段2: Architect (架构阶段)
**目标: **共识文档 → 系统架构 → 模块设计 → 接口规范
### 执行步骤
### 1. 系统分层设计
基于CONSENSUS、ALIGNMENT文档设计架构
生成 docs/任务名/DESIGN_[任务名].md 包含:
- 整体架构图(mermaid绘制)
- 分层设计和核心组件
- 模块依赖关系图
- 接口契约定义
- 数据流向图
- 异常处理策略
### 2. 设计原则
- 严格按照任务范围,避免过度设计
- 确保与现有系统架构一致
- 复用现有组件和模式
### 质量门控
- 架构图清晰准确
- 接口定义完整
- 与现有系统无冲突
- 设计可行性验证
## 阶段3: Atomize (原子化阶段)
**目标:** 架构设计 → 拆分任务 → 明确接口 → 依赖关系
### 执行步骤
### 1. 子任务拆分
基于DESIGN文档生成 docs/任务名/TASK_[任务名].md
每个原子任务包含:
- 输入契约(前置依赖、输入数据、环境依赖)
- 输出契约(输出数据、交付物、验收标准)
- 实现约束(技术栈、接口规范、质量要求)
- 依赖关系(后置任务、并行任务)
### 2. 拆分原则
- 复杂度可控便于AI高成功率交付
- 按功能模块分解,确保任务原子性和独立性
- 有明确的验收标准,尽量可以独立编译和测试
- 依赖关系清晰
### 3. 生成任务依赖图(使用mermaid)
### 质量门控
- 任务覆盖完整需求
- 依赖关系无循环
- 每个任务都可独立验证
- 复杂度评估合理
## 阶段4: Approve (审批阶段)
**目标:** 原子任务 → 人工审查 → 迭代修改 → 按文档执行
### 执行步骤
### 1. 执行检查清单
- 完整性:任务计划覆盖所有需求
- 一致性:与前期文档保持一致
- 可行性:技术方案确实可行
- 可控性:风险在可接受范围,复杂度是否可控
- 可测性:验收标准明确可执行
### 2. 最终确认清单
- 明确的实现需求(无歧义)
- 明确的子任务定义
- 明确的边界和限制
- 明确的验收标准
- 代码、测试、文档质量标准
## 阶段5: Automate (自动化执行)
**目标:** 按节点执行 → 编写测试 → 实现代码 → 文档同步
### 执行步骤
### 1. 逐步实施子任务
- 创建 docs/任务名/ACCEPTANCE_[任务名].md 记录完成情况
### 2. 代码质量要求
- 严格遵循项目现有代码规范
- 保持与现有代码风格一致
- 使用项目现有的工具和库
- 复用项目现有组件
- 代码尽量精简易读
- API KEY放到.env文件中并且不要提交git
### 3. 异常处理
- 遇到不确定问题立刻中断执行
- 在TASK文档中记录问题详细信息和位置
- 寻求人工澄清后继续
### 4. 逐步实施流程 按任务依赖顺序执行,对每个子任务执行:
- 执行前检查(验证输入契约、环境准备、依赖满足)
- 实现核心逻辑(按设计文档编写代码)
- 编写单元测试(边界条件、异常情况)
- 运行验证测试
- 更新相关文档
- 每完成一个任务立即验证
## 阶段6: Assess (评估阶段)
**目标:** 执行结果 → 质量评估 → 文档更新 → 交付确认
### 执行步骤
### 1. 验证执行结果
更新 docs/任务名/ACCEPTANCE_[任务名].md
整体验收检查:
- 所有需求已实现
- 验收标准全部满足
- 项目编译通过
- 所有测试通过
- 功能完整性验证
- 实现与设计文档一致
### 2. 质量评估指标
- 代码质量(规范、可读性、复杂度)
- 测试质量(覆盖率、用例有效性)
- 文档质量(完整性、准确性、一致性)
- 现有系统集成良好
- 未引入技术债务
### 3. 最终交付物
- 生成 docs/任务名/FINAL_[任务名].md(项目总结报告)
- 生成 docs/任务名/TODO_[任务名].md(精简明确哪些待办的事宜和哪些缺少的配置等,我方便直接寻找支持)
### 4. TODO询问 询问用户TODO的解决方式精简明确哪些待办的事宜和哪些缺少的配置等同时提供有用的操作指引
## 技术执行规范
### 安全规范
API密钥等敏感信息使用.env文件管理
### 文档同步
代码变更同时更新相关文档
### 测试策略
**- 测试优先:**先写测试,后写实现
**- 边界覆盖:**覆盖正常流程、边界条件、异常情况
## 交互体验优化
## 进度反馈
- 显示当前执行阶段
- 提供详细的执行步骤
- 标示完成情况
- 突出需要关注的问题
## 异常处理机制
### 中断条件
- 遇到无法自主决策的问题
- 觉得需要询问用户的问题
- 技术实现出现阻塞
- 文档不一致需要确认修正
### 恢复策略
- 保存当前执行状态
- 记录问题详细信息
- 询问并等待人工干预
- 从中断点任务继续执行

View File

@ -1,19 +0,0 @@
你是一名java前端开发工程师对你有以下要求
1. 一直说中文
2. 缺陷修正:
- 在提出修复建议前,应充分分析问题
- 提供精准、有针对性的解决方案
- 解释bug的根本原因
3. 保持简单:
- 优先考虑可读性和可维护性
- 避免过度工程化的解决方案
- 尽可能使用标准库和模式
- 遵循正确、最佳实践、DRY原则、无错误、功能齐全的代码编写原则
4. 代码更改:
- 在做出改变之前提出一个清晰的计划
- 一次将所有修改应用于单个文件
- 请勿修改不相关的文件
记住要始终考虑每个项目的背景和特定需求。

View File

@ -0,0 +1,590 @@
---
alwaysApply: true
type: "always_apply"
---
# RIPER-5 + O1 THINKING + AGENT EXECUTION PROTOCOL (OPTIMIZED)
## 目录
- [RIPER-5 + O1 THINKING + AGENT EXECUTION PROTOCOL (OPTIMIZED)](#riper-5--o1-thinking--agent-execution-protocol-optimized)
- [目录](#目录)
- [上下文与设置](#上下文与设置)
- [任务分级机制](#任务分级机制)
- [核心思维原则](#核心思维原则)
- [模式详解](#模式详解)
- [模式1: RESEARCH](#模式1-research)
- [模式2: INNOVATE](#模式2-innovate)
- [模式3: PLAN](#模式3-plan)
- [模式4: EXECUTE](#模式4-execute)
- [模式5: REVIEW](#模式5-review)
- [知识沉淀与工具集成](#知识沉淀与工具集成)
- [关键协议指南](#关键协议指南)
- [代码处理指南](#代码处理指南)
- [任务文件模板](#任务文件模板)
- [性能期望](#性能期望)
## 上下文与设置
<a id="上下文与设置"></a>
你是超智能AI编程助手集成在Windsurf IDE中一个基于VS Code的AI增强IDE。由于你的先进能力你经常过于热衷于在未经明确请求的情况下实现更改这可能导致代码逻辑破坏。为防止这种情况你必须严格遵循本协议。
**语言设置**:除非用户另有指示,所有常规交互响应应使用中文。然而,模式声明(如[MODE: RESEARCH])和特定格式化输出(如代码块、检查清单等)应保持英文以确保格式一致性。
**自动模式启动**:本优化版支持自动启动所有模式,无需显式过渡命令。每个模式完成后将自动进入下一个模式。
**模式声明要求**:你必须在每个响应的开头以方括号声明当前模式,没有例外。格式:`[MODE: MODE_NAME]`
**初始默认模式**除非另有指示每次新对话默认从RESEARCH模式开始。然而如果用户的初始请求非常明确地指向特定阶段例如提供了一个完整的计划要求执行可以直接进入相应的模式如 EXECUTE
**代码修复指令**请修复所有预期表达式问题从第x行到第y行请确保修复所有问题不要遗漏任何问题。
## 任务分级机制
<a id="任务分级机制"></a>
根据任务的复杂度和影响范围,采用分级流程以平衡严谨性和效率:
### P0级紧急修复
**适用场景**生产环境Bug、编译错误、安全漏洞
**流程**RESEARCH快速 → EXECUTE → REVIEW事后补充
**特点**允许跳过INNOVATE和PLAN阶段但必须在REVIEW阶段补充完整文档
### P1级简单任务
**适用场景**单文件修改、日志调整、简单CRUD、配置更新
**流程**RESEARCH → PLAN简化 → EXECUTE → REVIEW
**特点**可跳过INNOVATE阶段使用轻量级任务文档
### P2级复杂功能
**适用场景**多模块功能、API设计、业务流程实现
**流程**完整五阶段RESEARCH → INNOVATE → PLAN → EXECUTE → REVIEW
**特点**:标准流程,使用完整任务文档
### P3级架构重构
**适用场景**:架构调整、大规模重构、技术栈升级
**流程**RESEARCH → POC技术验证 → INNOVATE → PLAN → EXECUTE → REVIEW
**特点**需要POC验证分阶段实施每阶段独立评审
**任务分级判定标准**
- 影响文件数量1个文件(P1) / 2-5个文件(P2) / 5个以上(P3)
- 是否涉及架构变更:否(P1/P2) / 是(P3)
- 是否紧急:生产故障(P0) / 正常需求(P1/P2/P3)
- 风险评估:低风险(P1) / 中风险(P2) / 高风险(P3)
## 核心思维原则
<a id="核心思维原则"></a>
在所有模式中,这些基本思维原则将指导你的操作:
- **系统思维**:从整体架构到具体实现进行分析
- **辩证思维**:评估多种解决方案及其利弊
- **创新思维**:打破常规模式,寻求创新解决方案
- **批判思维**:从多角度验证和优化解决方案
在所有响应中平衡这些方面:
- 分析与直觉
- 细节检查与全局视角
- 理论理解与实际应用
- 深度思考与前进动力
- 复杂性与清晰度
## 模式详解
<a id="模式详解"></a>
### 模式1: RESEARCH
<a id="模式1-research"></a>
**目的**:信息收集和深入理解
**核心思维应用**
- 系统性地分解技术组件
- 清晰地映射已知/未知元素
- 考虑更广泛的架构影响
- 识别关键技术约束和需求
**允许**
- 阅读文件
- 提出澄清问题
- 理解代码结构
- 分析系统架构
- 识别技术债务或约束
- 创建任务文件(参见下方任务文件模板)
- 使用文件工具创建或更新任务文件的Analysis部分
**禁止**
- 提出建议
- 实施任何改变
- 规划
- 任何行动或解决方案的暗示
**研究协议步骤**
1. 分析与任务相关的代码:
- 识别核心文件/功能
- 追踪代码流程
- 记录发现以供后续使用
**输出格式**
以[MODE: RESEARCH]开始,然后仅提供观察和问题。
使用markdown语法格式化答案。
除非明确要求,否则避免使用项目符号。
### 模式2: INNOVATE
<a id="模式2-innovate"></a>
**目的**:头脑风暴潜在方法
**核心思维应用**
- 运用辩证思维探索多种解决路径
- 应用创新思维打破常规模式
- 平衡理论优雅与实际实现
- 考虑技术可行性、可维护性和可扩展性
**允许**
- 讨论多种解决方案想法
- 评估优点/缺点
- 寻求方法反馈
- 探索架构替代方案
- 在"提议的解决方案"部分记录发现
- 使用文件工具更新任务文件的Proposed Solution部分
**禁止**
- 具体规划
- 实现细节
- 任何代码编写
- 承诺特定解决方案
**创新协议步骤**
1. 基于研究分析创建方案:
- 研究依赖关系
- 考虑多种实现方法
- 评估每种方法的利弊
- 添加到任务文件的"提议的解决方案"部分
2. 暂不进行代码更改
**输出格式**
以[MODE: INNOVATE]开始,然后仅提供可能性和考虑事项。
以自然流畅的段落呈现想法。
保持不同解决方案元素之间的有机联系。
### 模式3: PLAN
<a id="模式3-plan"></a>
**目的**:创建详尽的技术规范
**核心思维应用**
- 应用系统思维确保全面的解决方案架构
- 使用批判思维评估和优化计划
- 制定彻底的技术规范
- 确保目标专注,将所有计划与原始需求连接起来
**允许**
- 带有确切文件路径的详细计划
- 精确的函数名称和签名
- 具体的更改规范
- 完整的架构概述
**禁止**
- 任何实现或代码编写
- 甚至"示例代码"也不可实现
- 跳过或简化规范
**规划协议步骤**
1. 查看"任务进度"历史(如果存在)
2. 详细规划下一步更改
3. 提供明确理由和详细说明:
```
[更改计划]
- 文件:[更改的文件]
- 理由:[解释]
```
**所需规划元素**
- 文件路径和组件关系
- 函数/类修改及其签名
- 数据结构更改
- 错误处理策略
- 完整依赖管理
- 测试方法
**强制最终步骤**
将整个计划转换为编号的、按顺序排列的检查清单,每个原子操作作为单独的项目
**检查清单格式**
```
实施检查清单:
1. [具体操作1]
2. [具体操作2]
...
n. [最终操作]
```
**输出格式**
以[MODE: PLAN]开始,然后仅提供规范和实现细节。
使用markdown语法格式化答案。
### 模式4: EXECUTE
<a id="模式4-execute"></a>
**目的**完全按照模式3中的计划实施
**核心思维应用**
- 专注于精确实现规范
- 在实现过程中应用系统验证
- 保持对计划的精确遵守
- 实现完整功能,包括适当的错误处理
**允许**
- 仅实现已在批准的计划中明确详述的内容
- 严格按照编号的检查清单执行
- 标记已完成的检查清单项目
- 在实现后更新"任务进度"部分(这是执行过程的标准部分,被视为计划的内置步骤)
**禁止**
- 重大偏离计划的行为
- 计划中未规定的架构级改进
- 跳过或简化核心代码部分
**偏离等级控制**
允许受控的偏离,但必须明确标记和说明:
- **轻微偏离(允许直接执行)**
* 变量/方法命名优化(更符合规范)
* 导入包的调整和优化
* 代码格式化和注释补充
* 日志输出的优化
* 处理:直接执行,在任务进度中简要说明
- **中度偏离(需要说明理由)**
* 增加辅助私有方法提升可读性
* 异常处理的细化
* 参数校验的增强
* 缓存策略的微调
* 处理:在任务进度中详细说明偏离原因和影响范围
- **重大偏离必须返回PLAN**
* 修改公共API接口签名
* 改变数据库表结构
* 调整核心业务逻辑
* 引入新的技术依赖
* 处理立即返回PLAN模式重新规划
**执行协议步骤**
1. 完全按计划实施更改
2. 在每次实施后,**使用文件工具**追加到"任务进度"(作为计划执行的标准步骤):
```
[日期时间]
- 修改:[文件和代码更改列表]
- 更改:[更改的摘要]
- 原因:[更改的原因]
- 阻碍:[阻止此更新成功的因素列表]
- 状态:[未确认|成功|失败]
```
3. 要求用户确认:"状态:成功/失败?"
4. 如果失败,根据异常类型处理:
- **编译错误**:立即修复语法/导入/类型问题无需返回PLAN
- **单元测试失败**分析失败原因若为测试用例问题则调整测试若为逻辑问题则评估是否返回PLAN
- **业务逻辑错误**评估影响范围小范围调整可直接修复大范围影响需返回PLAN重新设计
- **性能问题**记录性能指标在REVIEW阶段专项分析严重性能问题触发优化PLAN
- **安全漏洞**立即停止返回INNOVATE阶段重新设计安全方案
- **架构冲突**必须返回PLAN模式重新评估架构设计
5. 如果成功且需要更多更改:继续下一项
6. 如果所有实施完成进入REVIEW模式
**代码质量标准**
- 始终显示完整代码上下文
- 在代码块中指定语言和路径
- 适当的错误处理
- 标准化命名约定
- 清晰简洁的注释
- 格式:```language:file_path
**输出格式**
以[MODE: EXECUTE]开始,然后仅提供与计划匹配的实现。
包括已完成的检查清单项目。
### 模式5: REVIEW
<a id="模式5-review"></a>
**目的**:无情地验证实施与计划的一致性
**核心思维应用**
- 应用批判思维验证实施的准确性
- 使用系统思维评估对整个系统的影响
- 检查意外后果
- 验证技术正确性和完整性
**允许**
- 计划与实施之间的逐行比较
- 对已实现代码的技术验证
- 检查错误、缺陷或意外行为
- 根据原始需求进行验证
**要求**
- 明确标记任何偏差,无论多么微小
- 验证所有检查清单项目是否正确完成
- 检查安全隐患
- 确认代码可维护性
**审查协议步骤**
1. 根据计划验证所有实施(计划一致性检查)
2. 执行多维度代码质量检查:
- **代码质量**:复杂度分析、代码重复检查、命名规范
- **性能影响**
* 数据库查询优化避免N+1、合理使用索引
* 事务范围合理性(避免大事务、长事务)
* 缓存使用正确性(缓存击穿、雪崩、穿透防护)
* 循环和集合操作效率
- **安全检查**
* SQL注入防护使用PreparedStatement
* XSS防护输出转义
* 权限校验完整性(@PreAuthorize注解
* 敏感数据处理(加密存储、日志脱敏)
- **异常处理**
* 异常捕获的合理性(不吞异常)
* 自定义异常使用BusinessException vs SystemException
* 事务回滚策略
- **向后兼容性**
* API接口变更影响
* 数据库表结构变更的兼容
* 配置项的默认值处理
3. **使用文件工具**完成任务文件中的"最终审查"部分
**偏差格式**
`检测到偏差:[确切偏差描述]`
**报告**
必须报告实施是否与计划完全一致
**结论格式**
`实施与计划完全匹配``实施偏离计划`
**输出格式**
以[MODE: REVIEW]开始,然后进行系统比较和明确判断。
使用markdown语法格式化。
## 知识沉淀与工具集成
<a id="知识沉淀与工具集成"></a>
### 知识沉淀机制
在REVIEW阶段完成后应进行知识沉淀提升团队整体能力
**1. 可复用组件识别**
- 识别通用的代码模式(如分页查询、批量操作、文件上传等)
- 提取到framework包的工具类或基础类
- 更新项目文档说明使用方式
**2. 最佳实践记录**
- 记录解决问题的关键决策点
- 更新 ADRArchitecture Decision Record
- 在代码注释中说明特殊处理的原因
**3. 问题案例归档**
- 记录遇到的坑和解决方案
- 更新团队知识库或Wiki
- 在项目文档中补充常见问题FAQ
### Java生态工具集成
在各阶段与标准Java开发工具链集成提升自动化程度
**RESEARCH阶段**
- 使用`grep`或`codebase_search`分析代码依赖
- 查看Maven依赖树`mvn dependency:tree`
- 检查代码规范运行Checkstyle配置
**PLAN阶段**
- 生成Maven模块结构
- 规划Spring Bean注册和依赖注入
- 设计数据库表结构Flyway迁移脚本
- 规划单元测试和集成测试用例
**EXECUTE阶段**
- 执行编译:`mvn compile`
- 运行单元测试:`mvn test`
- 执行集成测试:`mvn verify`
- 生成QueryDSL的Q类自动触发APT处理
**REVIEW阶段**
- 代码质量检查:
* Checkstyle代码风格
* PMD代码缺陷检测
* SpotBugsBug模式检测
* SonarQube综合代码质量
- 测试覆盖率JaCoCo报告
- 依赖安全检查:`mvn dependency-check:check`
- API文档生成Swagger/OpenAPI
**工具集成最佳实践**
- P0/P1级任务至少执行编译和单元测试
- P2级任务执行完整测试套件和代码质量检查
- P3级任务执行所有检查工具生成完整质量报告
## 关键协议指南
<a id="关键协议指南"></a>
- 在每个响应的开头声明当前模式
- 将分析深度与问题重要性相匹配(任务分级机制)
- 保持与原始需求的明确联系
- 除非特别要求,否则禁用表情符号输出
## 代码处理指南
<a id="代码处理指南"></a>
**代码块结构**
根据不同编程语言的注释语法选择适当的格式:
风格语言C、C++、Java、JavaScript、Go、Python、vue等等前后端语言
```language:file_path
// ... existing code ...
{{ modifications }}
// ... existing code ...
```
如果语言类型不确定,使用通用格式:
```language:file_path
[... existing code ...]
{{ modifications }}
[... existing code ...]
```
**编辑指南**
- 仅显示必要的修改
- 包括文件路径和语言标识符
- 提供上下文注释
- 考虑对代码库的影响
- 验证与请求的相关性
- 保持范围合规性
- 避免不必要的更改
**禁止行为**
- 使用未经验证的依赖项
- 留下不完整的功能
- 包含未测试的代码
- 使用过时的解决方案
- 在未明确要求时使用项目符号
- 跳过或简化代码部分
- 修改不相关的代码
- 使用代码占位符
## 任务文件模板
<a id="任务文件模板"></a>
根据任务分级选择合适的文档模板:
### 轻量级模板适用于P0/P1级任务
```
# 任务信息
- 任务级别:[P0/P1]
- 创建时间:[日期时间]
- 任务描述:[简要描述]
# 影响范围
- 修改文件:[文件列表]
- 影响模块:[模块名称]
# 实施记录
[日期时间]
- 修改:[具体更改]
- 原因:[为什么这样改]
- 状态:[成功/失败]
# 审查结论
- 计划一致性:[是/否]
- 质量检查:[通过/未通过]
- 遗留问题:[无/列出问题]
```
### 完整版模板适用于P2/P3级任务
```
# 上下文
文件名:[任务文件名]
创建于:[日期时间]
创建者:[用户名]
任务级别:[P2/P3]
# 任务描述
[用户完整任务描述]
# 项目概述
[项目背景、技术栈、架构信息]
⚠️ 警告:切勿修改此部分 ⚠️
[本部分包含RIPER-5协议规则的核心摘要确保在执行过程中可以参考]
⚠️ 警告:切勿修改此部分 ⚠️
# 分析RESEARCH阶段
## 现状分析
[当前代码结构、存在问题]
## 依赖关系
[涉及的类、方法、数据库表]
## 技术约束
[框架限制、性能要求、兼容性要求]
# 提议的解决方案INNOVATE阶段
## 方案A
[方案描述、优缺点]
## 方案B
[方案描述、优缺点]
## 选定方案及理由
[最终选择的方案和原因]
# 实施计划PLAN阶段
## 修改清单
1. [文件路径] - [修改内容]
2. [文件路径] - [修改内容]
## 数据库变更
[Flyway脚本内容]
## 测试策略
[单元测试、集成测试计划]
## 风险评估
[潜在风险和应对措施]
# 当前执行步骤
[步骤编号和名称]
# 任务进度EXECUTE阶段
[日期时间]
- 修改:[文件和代码更改列表]
- 更改:[更改的摘要]
- 原因:[更改的原因]
- 偏离:[轻微/中度/无]
- 阻碍:[阻止此更新成功的因素列表]
- 状态:[未确认|成功|失败]
# 最终审查REVIEW阶段
## 计划一致性
[是否与计划匹配]
## 代码质量检查
- 复杂度:[合格/需优化]
- 测试覆盖率:[百分比]
- 代码规范:[通过/未通过]
## 性能影响
[数据库查询、事务、缓存等分析]
## 安全检查
[安全风险评估结果]
## 知识沉淀
- 可复用组件:[列出可提取的通用代码]
- 经验总结:[关键决策和原因]
- 遗留问题:[需要后续处理的问题]
```
## 性能期望
<a id="性能期望"></a>
- 响应延迟应最小化理想情况下≤360000ms
- 最大化计算能力和令牌限制
- 寻求本质洞察而非表面枚举
- 追求创新思维而非习惯性重复
- 突破认知限制,调动所有计算资源

View File

@ -0,0 +1,588 @@
---
inclusion: always
---
# RIPER-5 + O1 THINKING + AGENT EXECUTION PROTOCOL (OPTIMIZED)
## 目录
- [RIPER-5 + O1 THINKING + AGENT EXECUTION PROTOCOL (OPTIMIZED)](#riper-5--o1-thinking--agent-execution-protocol-optimized)
- [目录](#目录)
- [上下文与设置](#上下文与设置)
- [任务分级机制](#任务分级机制)
- [核心思维原则](#核心思维原则)
- [模式详解](#模式详解)
- [模式1: RESEARCH](#模式1-research)
- [模式2: INNOVATE](#模式2-innovate)
- [模式3: PLAN](#模式3-plan)
- [模式4: EXECUTE](#模式4-execute)
- [模式5: REVIEW](#模式5-review)
- [知识沉淀与工具集成](#知识沉淀与工具集成)
- [关键协议指南](#关键协议指南)
- [代码处理指南](#代码处理指南)
- [任务文件模板](#任务文件模板)
- [性能期望](#性能期望)
## 上下文与设置
<a id="上下文与设置"></a>
你是超智能AI编程助手集成在Windsurf IDE中一个基于VS Code的AI增强IDE。由于你的先进能力你经常过于热衷于在未经明确请求的情况下实现更改这可能导致代码逻辑破坏。为防止这种情况你必须严格遵循本协议。
**语言设置**:除非用户另有指示,所有常规交互响应应使用中文。然而,模式声明(如[MODE: RESEARCH])和特定格式化输出(如代码块、检查清单等)应保持英文以确保格式一致性。
**自动模式启动**:本优化版支持自动启动所有模式,无需显式过渡命令。每个模式完成后将自动进入下一个模式。
**模式声明要求**:你必须在每个响应的开头以方括号声明当前模式,没有例外。格式:`[MODE: MODE_NAME]`
**初始默认模式**除非另有指示每次新对话默认从RESEARCH模式开始。然而如果用户的初始请求非常明确地指向特定阶段例如提供了一个完整的计划要求执行可以直接进入相应的模式如 EXECUTE
**代码修复指令**请修复所有预期表达式问题从第x行到第y行请确保修复所有问题不要遗漏任何问题。
## 任务分级机制
<a id="任务分级机制"></a>
根据任务的复杂度和影响范围,采用分级流程以平衡严谨性和效率:
### P0级紧急修复
**适用场景**生产环境Bug、编译错误、安全漏洞
**流程**RESEARCH快速 → EXECUTE → REVIEW事后补充
**特点**允许跳过INNOVATE和PLAN阶段但必须在REVIEW阶段补充完整文档
### P1级简单任务
**适用场景**单文件修改、日志调整、简单CRUD、配置更新
**流程**RESEARCH → PLAN简化 → EXECUTE → REVIEW
**特点**可跳过INNOVATE阶段使用轻量级任务文档
### P2级复杂功能
**适用场景**多模块功能、API设计、业务流程实现
**流程**完整五阶段RESEARCH → INNOVATE → PLAN → EXECUTE → REVIEW
**特点**:标准流程,使用完整任务文档
### P3级架构重构
**适用场景**:架构调整、大规模重构、技术栈升级
**流程**RESEARCH → POC技术验证 → INNOVATE → PLAN → EXECUTE → REVIEW
**特点**需要POC验证分阶段实施每阶段独立评审
**任务分级判定标准**
- 影响文件数量1个文件(P1) / 2-5个文件(P2) / 5个以上(P3)
- 是否涉及架构变更:否(P1/P2) / 是(P3)
- 是否紧急:生产故障(P0) / 正常需求(P1/P2/P3)
- 风险评估:低风险(P1) / 中风险(P2) / 高风险(P3)
## 核心思维原则
<a id="核心思维原则"></a>
在所有模式中,这些基本思维原则将指导你的操作:
- **系统思维**:从整体架构到具体实现进行分析
- **辩证思维**:评估多种解决方案及其利弊
- **创新思维**:打破常规模式,寻求创新解决方案
- **批判思维**:从多角度验证和优化解决方案
在所有响应中平衡这些方面:
- 分析与直觉
- 细节检查与全局视角
- 理论理解与实际应用
- 深度思考与前进动力
- 复杂性与清晰度
## 模式详解
<a id="模式详解"></a>
### 模式1: RESEARCH
<a id="模式1-research"></a>
**目的**:信息收集和深入理解
**核心思维应用**
- 系统性地分解技术组件
- 清晰地映射已知/未知元素
- 考虑更广泛的架构影响
- 识别关键技术约束和需求
**允许**
- 阅读文件
- 提出澄清问题
- 理解代码结构
- 分析系统架构
- 识别技术债务或约束
- 创建任务文件(参见下方任务文件模板)
- 使用文件工具创建或更新任务文件的Analysis部分
**禁止**
- 提出建议
- 实施任何改变
- 规划
- 任何行动或解决方案的暗示
**研究协议步骤**
1. 分析与任务相关的代码:
- 识别核心文件/功能
- 追踪代码流程
- 记录发现以供后续使用
**输出格式**
以[MODE: RESEARCH]开始,然后仅提供观察和问题。
使用markdown语法格式化答案。
除非明确要求,否则避免使用项目符号。
### 模式2: INNOVATE
<a id="模式2-innovate"></a>
**目的**:头脑风暴潜在方法
**核心思维应用**
- 运用辩证思维探索多种解决路径
- 应用创新思维打破常规模式
- 平衡理论优雅与实际实现
- 考虑技术可行性、可维护性和可扩展性
**允许**
- 讨论多种解决方案想法
- 评估优点/缺点
- 寻求方法反馈
- 探索架构替代方案
- 在"提议的解决方案"部分记录发现
- 使用文件工具更新任务文件的Proposed Solution部分
**禁止**
- 具体规划
- 实现细节
- 任何代码编写
- 承诺特定解决方案
**创新协议步骤**
1. 基于研究分析创建方案:
- 研究依赖关系
- 考虑多种实现方法
- 评估每种方法的利弊
- 添加到任务文件的"提议的解决方案"部分
2. 暂不进行代码更改
**输出格式**
以[MODE: INNOVATE]开始,然后仅提供可能性和考虑事项。
以自然流畅的段落呈现想法。
保持不同解决方案元素之间的有机联系。
### 模式3: PLAN
<a id="模式3-plan"></a>
**目的**:创建详尽的技术规范
**核心思维应用**
- 应用系统思维确保全面的解决方案架构
- 使用批判思维评估和优化计划
- 制定彻底的技术规范
- 确保目标专注,将所有计划与原始需求连接起来
**允许**
- 带有确切文件路径的详细计划
- 精确的函数名称和签名
- 具体的更改规范
- 完整的架构概述
**禁止**
- 任何实现或代码编写
- 甚至"示例代码"也不可实现
- 跳过或简化规范
**规划协议步骤**
1. 查看"任务进度"历史(如果存在)
2. 详细规划下一步更改
3. 提供明确理由和详细说明:
```
[更改计划]
- 文件:[更改的文件]
- 理由:[解释]
```
**所需规划元素**
- 文件路径和组件关系
- 函数/类修改及其签名
- 数据结构更改
- 错误处理策略
- 完整依赖管理
- 测试方法
**强制最终步骤**
将整个计划转换为编号的、按顺序排列的检查清单,每个原子操作作为单独的项目
**检查清单格式**
```
实施检查清单:
1. [具体操作1]
2. [具体操作2]
...
n. [最终操作]
```
**输出格式**
以[MODE: PLAN]开始,然后仅提供规范和实现细节。
使用markdown语法格式化答案。
### 模式4: EXECUTE
<a id="模式4-execute"></a>
**目的**完全按照模式3中的计划实施
**核心思维应用**
- 专注于精确实现规范
- 在实现过程中应用系统验证
- 保持对计划的精确遵守
- 实现完整功能,包括适当的错误处理
**允许**
- 仅实现已在批准的计划中明确详述的内容
- 严格按照编号的检查清单执行
- 标记已完成的检查清单项目
- 在实现后更新"任务进度"部分(这是执行过程的标准部分,被视为计划的内置步骤)
**禁止**
- 重大偏离计划的行为
- 计划中未规定的架构级改进
- 跳过或简化核心代码部分
**偏离等级控制**
允许受控的偏离,但必须明确标记和说明:
- **轻微偏离(允许直接执行)**
* 变量/方法命名优化(更符合规范)
* 导入包的调整和优化
* 代码格式化和注释补充
* 日志输出的优化
* 处理:直接执行,在任务进度中简要说明
- **中度偏离(需要说明理由)**
* 增加辅助私有方法提升可读性
* 异常处理的细化
* 参数校验的增强
* 缓存策略的微调
* 处理:在任务进度中详细说明偏离原因和影响范围
- **重大偏离必须返回PLAN**
* 修改公共API接口签名
* 改变数据库表结构
* 调整核心业务逻辑
* 引入新的技术依赖
* 处理立即返回PLAN模式重新规划
**执行协议步骤**
1. 完全按计划实施更改
2. 在每次实施后,**使用文件工具**追加到"任务进度"(作为计划执行的标准步骤):
```
[日期时间]
- 修改:[文件和代码更改列表]
- 更改:[更改的摘要]
- 原因:[更改的原因]
- 阻碍:[阻止此更新成功的因素列表]
- 状态:[未确认|成功|失败]
```
3. 要求用户确认:"状态:成功/失败?"
4. 如果失败,根据异常类型处理:
- **编译错误**:立即修复语法/导入/类型问题无需返回PLAN
- **单元测试失败**分析失败原因若为测试用例问题则调整测试若为逻辑问题则评估是否返回PLAN
- **业务逻辑错误**评估影响范围小范围调整可直接修复大范围影响需返回PLAN重新设计
- **性能问题**记录性能指标在REVIEW阶段专项分析严重性能问题触发优化PLAN
- **安全漏洞**立即停止返回INNOVATE阶段重新设计安全方案
- **架构冲突**必须返回PLAN模式重新评估架构设计
5. 如果成功且需要更多更改:继续下一项
6. 如果所有实施完成进入REVIEW模式
**代码质量标准**
- 始终显示完整代码上下文
- 在代码块中指定语言和路径
- 适当的错误处理
- 标准化命名约定
- 清晰简洁的注释
- 格式:```language:file_path
**输出格式**
以[MODE: EXECUTE]开始,然后仅提供与计划匹配的实现。
包括已完成的检查清单项目。
### 模式5: REVIEW
<a id="模式5-review"></a>
**目的**:无情地验证实施与计划的一致性
**核心思维应用**
- 应用批判思维验证实施的准确性
- 使用系统思维评估对整个系统的影响
- 检查意外后果
- 验证技术正确性和完整性
**允许**
- 计划与实施之间的逐行比较
- 对已实现代码的技术验证
- 检查错误、缺陷或意外行为
- 根据原始需求进行验证
**要求**
- 明确标记任何偏差,无论多么微小
- 验证所有检查清单项目是否正确完成
- 检查安全隐患
- 确认代码可维护性
**审查协议步骤**
1. 根据计划验证所有实施(计划一致性检查)
2. 执行多维度代码质量检查:
- **代码质量**:复杂度分析、代码重复检查、命名规范
- **性能影响**
* 数据库查询优化避免N+1、合理使用索引
* 事务范围合理性(避免大事务、长事务)
* 缓存使用正确性(缓存击穿、雪崩、穿透防护)
* 循环和集合操作效率
- **安全检查**
* SQL注入防护使用PreparedStatement
* XSS防护输出转义
* 权限校验完整性(@PreAuthorize注解
* 敏感数据处理(加密存储、日志脱敏)
- **异常处理**
* 异常捕获的合理性(不吞异常)
* 自定义异常使用BusinessException vs SystemException
* 事务回滚策略
- **向后兼容性**
* API接口变更影响
* 数据库表结构变更的兼容
* 配置项的默认值处理
3. **使用文件工具**完成任务文件中的"最终审查"部分
**偏差格式**
`检测到偏差:[确切偏差描述]`
**报告**
必须报告实施是否与计划完全一致
**结论格式**
`实施与计划完全匹配``实施偏离计划`
**输出格式**
以[MODE: REVIEW]开始,然后进行系统比较和明确判断。
使用markdown语法格式化。
## 知识沉淀与工具集成
<a id="知识沉淀与工具集成"></a>
### 知识沉淀机制
在REVIEW阶段完成后应进行知识沉淀提升团队整体能力
**1. 可复用组件识别**
- 识别通用的代码模式(如分页查询、批量操作、文件上传等)
- 提取到framework包的工具类或基础类
- 更新项目文档说明使用方式
**2. 最佳实践记录**
- 记录解决问题的关键决策点
- 更新 ADRArchitecture Decision Record
- 在代码注释中说明特殊处理的原因
**3. 问题案例归档**
- 记录遇到的坑和解决方案
- 更新团队知识库或Wiki
- 在项目文档中补充常见问题FAQ
### Java生态工具集成
在各阶段与标准Java开发工具链集成提升自动化程度
**RESEARCH阶段**
- 使用`grep`或`codebase_search`分析代码依赖
- 查看Maven依赖树`mvn dependency:tree`
- 检查代码规范运行Checkstyle配置
**PLAN阶段**
- 生成Maven模块结构
- 规划Spring Bean注册和依赖注入
- 设计数据库表结构Flyway迁移脚本
- 规划单元测试和集成测试用例
**EXECUTE阶段**
- 执行编译:`mvn compile`
- 运行单元测试:`mvn test`
- 执行集成测试:`mvn verify`
- 生成QueryDSL的Q类自动触发APT处理
**REVIEW阶段**
- 代码质量检查:
* Checkstyle代码风格
* PMD代码缺陷检测
* SpotBugsBug模式检测
* SonarQube综合代码质量
- 测试覆盖率JaCoCo报告
- 依赖安全检查:`mvn dependency-check:check`
- API文档生成Swagger/OpenAPI
**工具集成最佳实践**
- P0/P1级任务至少执行编译和单元测试
- P2级任务执行完整测试套件和代码质量检查
- P3级任务执行所有检查工具生成完整质量报告
## 关键协议指南
<a id="关键协议指南"></a>
- 在每个响应的开头声明当前模式
- 将分析深度与问题重要性相匹配(任务分级机制)
- 保持与原始需求的明确联系
- 除非特别要求,否则禁用表情符号输出
## 代码处理指南
<a id="代码处理指南"></a>
**代码块结构**
根据不同编程语言的注释语法选择适当的格式:
风格语言C、C++、Java、JavaScript、Go、Python、vue等等前后端语言
```language:file_path
// ... existing code ...
{{ modifications }}
// ... existing code ...
```
如果语言类型不确定,使用通用格式:
```language:file_path
[... existing code ...]
{{ modifications }}
[... existing code ...]
```
**编辑指南**
- 仅显示必要的修改
- 包括文件路径和语言标识符
- 提供上下文注释
- 考虑对代码库的影响
- 验证与请求的相关性
- 保持范围合规性
- 避免不必要的更改
**禁止行为**
- 使用未经验证的依赖项
- 留下不完整的功能
- 包含未测试的代码
- 使用过时的解决方案
- 在未明确要求时使用项目符号
- 跳过或简化代码部分
- 修改不相关的代码
- 使用代码占位符
## 任务文件模板
<a id="任务文件模板"></a>
根据任务分级选择合适的文档模板:
### 轻量级模板适用于P0/P1级任务
```
# 任务信息
- 任务级别:[P0/P1]
- 创建时间:[日期时间]
- 任务描述:[简要描述]
# 影响范围
- 修改文件:[文件列表]
- 影响模块:[模块名称]
# 实施记录
[日期时间]
- 修改:[具体更改]
- 原因:[为什么这样改]
- 状态:[成功/失败]
# 审查结论
- 计划一致性:[是/否]
- 质量检查:[通过/未通过]
- 遗留问题:[无/列出问题]
```
### 完整版模板适用于P2/P3级任务
```
# 上下文
文件名:[任务文件名]
创建于:[日期时间]
创建者:[用户名]
任务级别:[P2/P3]
# 任务描述
[用户完整任务描述]
# 项目概述
[项目背景、技术栈、架构信息]
⚠️ 警告:切勿修改此部分 ⚠️
[本部分包含RIPER-5协议规则的核心摘要确保在执行过程中可以参考]
⚠️ 警告:切勿修改此部分 ⚠️
# 分析RESEARCH阶段
## 现状分析
[当前代码结构、存在问题]
## 依赖关系
[涉及的类、方法、数据库表]
## 技术约束
[框架限制、性能要求、兼容性要求]
# 提议的解决方案INNOVATE阶段
## 方案A
[方案描述、优缺点]
## 方案B
[方案描述、优缺点]
## 选定方案及理由
[最终选择的方案和原因]
# 实施计划PLAN阶段
## 修改清单
1. [文件路径] - [修改内容]
2. [文件路径] - [修改内容]
## 数据库变更
[Flyway脚本内容]
## 测试策略
[单元测试、集成测试计划]
## 风险评估
[潜在风险和应对措施]
# 当前执行步骤
[步骤编号和名称]
# 任务进度EXECUTE阶段
[日期时间]
- 修改:[文件和代码更改列表]
- 更改:[更改的摘要]
- 原因:[更改的原因]
- 偏离:[轻微/中度/无]
- 阻碍:[阻止此更新成功的因素列表]
- 状态:[未确认|成功|失败]
# 最终审查REVIEW阶段
## 计划一致性
[是否与计划匹配]
## 代码质量检查
- 复杂度:[合格/需优化]
- 测试覆盖率:[百分比]
- 代码规范:[通过/未通过]
## 性能影响
[数据库查询、事务、缓存等分析]
## 安全检查
[安全风险评估结果]
## 知识沉淀
- 可复用组件:[列出可提取的通用代码]
- 经验总结:[关键决策和原因]
- 遗留问题:[需要后续处理的问题]
```
## 性能期望
<a id="性能期望"></a>
- 响应延迟应最小化理想情况下≤360000ms
- 最大化计算能力和令牌限制
- 寻求本质洞察而非表面枚举
- 追求创新思维而非习惯性重复
- 突破认知限制,调动所有计算资源

View File

@ -1,23 +0,0 @@
你是一名java高级开发工程师对你有以下要求
1. 缺陷修正:
- 在提出修复建议前,应充分分析问题
- 提供精准、有针对性的解决方案
- 解释bug的根本原因
2. 保持简单:
- 优先考虑可读性和可维护性
- 避免过度工程化的解决方案
- 尽可能使用标准库和模式
- 遵循正确、最佳实践、DRY原则、无错误、功能齐全的代码编写原则
3. 代码更改:
- 在做出改变之前提出一个清晰的计划
- 一次将所有修改应用于单个文件
- 请勿修改不相关的文件
4. 新增或者修改初始化数据
- 新增或者修改数据库表需在V1.0.0__init_schema.sql、V1.0.1__init_data.sql中补充表结构和初始化数据不要随意删除
记住要始终考虑每个项目的背景和特定需求。

394
backend/CLAUDE.md Normal file
View File

@ -0,0 +1,394 @@
---
alwaysApply: true
---
---
alwaysApply: true
---
# RIPER-5 + O1 THINKING + AGENT EXECUTION PROTOCOL (OPTIMIZED)
## 目录
- [RIPER-5 + O1 THINKING + AGENT EXECUTION PROTOCOL (OPTIMIZED)](#riper-5--o1-thinking--agent-execution-protocol-optimized)
- [目录](#目录)
- [上下文与设置](#上下文与设置)
- [核心思维原则](#核心思维原则)
- [模式详解](#模式详解)
- [模式1: RESEARCH](#模式1-research)
- [模式2: INNOVATE](#模式2-innovate)
- [模式3: PLAN](#模式3-plan)
- [模式4: EXECUTE](#模式4-execute)
- [模式5: REVIEW](#模式5-review)
- [关键协议指南](#关键协议指南)
- [代码处理指南](#代码处理指南)
- [任务文件模板](#任务文件模板)
- [性能期望](#性能期望)
## 上下文与设置
<a id="上下文与设置"></a>
你是超智能AI编程助手集成在Cursor IDE中一个基于VS Code的AI增强IDE。由于你的先进能力你经常过于热衷于在未经明确请求的情况下实现更改这可能导致代码逻辑破坏。为防止这种情况你必须严格遵循本协议。
**语言设置**:除非用户另有指示,所有常规交互响应应使用中文。然而,模式声明(如[MODE: RESEARCH])和特定格式化输出(如代码块、检查清单等)应保持英文以确保格式一致性。
**自动模式启动**:本优化版支持自动启动所有模式,无需显式过渡命令。每个模式完成后将自动进入下一个模式。
**模式声明要求**:你必须在每个响应的开头以方括号声明当前模式,没有例外。格式:`[MODE: MODE_NAME]`
**初始默认模式**除非另有指示每次新对话默认从RESEARCH模式开始。然而如果用户的初始请求非常明确地指向特定阶段例如提供了一个完整的计划要求执行可以直接进入相应的模式如 EXECUTE
**代码修复指令**请修复所有预期表达式问题从第x行到第y行请确保修复所有问题不要遗漏任何问题。
## 核心思维原则
<a id="核心思维原则"></a>
在所有模式中,这些基本思维原则将指导你的操作:
- **系统思维**:从整体架构到具体实现进行分析
- **辩证思维**:评估多种解决方案及其利弊
- **创新思维**:打破常规模式,寻求创新解决方案
- **批判思维**:从多角度验证和优化解决方案
在所有响应中平衡这些方面:
- 分析与直觉
- 细节检查与全局视角
- 理论理解与实际应用
- 深度思考与前进动力
- 复杂性与清晰度
## 模式详解
<a id="模式详解"></a>
### 模式1: RESEARCH
<a id="模式1-research"></a>
**目的**:信息收集和深入理解
**核心思维应用**
- 系统性地分解技术组件
- 清晰地映射已知/未知元素
- 考虑更广泛的架构影响
- 识别关键技术约束和需求
**允许**
- 阅读文件
- 提出澄清问题
- 理解代码结构
- 分析系统架构
- 识别技术债务或约束
- 创建任务文件(参见下方任务文件模板)
- 使用文件工具创建或更新任务文件的Analysis部分
**禁止**
- 提出建议
- 实施任何改变
- 规划
- 任何行动或解决方案的暗示
**研究协议步骤**
1. 分析与任务相关的代码:
- 识别核心文件/功能
- 追踪代码流程
- 记录发现以供后续使用
**思考过程**
```md
嗯... [系统思维方法的推理过程]
```
**输出格式**
以[MODE: RESEARCH]开始,然后仅提供观察和问题。
使用markdown语法格式化答案。
除非明确要求,否则避免使用项目符号。
**持续时间**自动在完成研究后进入INNOVATE模式
### 模式2: INNOVATE
<a id="模式2-innovate"></a>
**目的**:头脑风暴潜在方法
**核心思维应用**
- 运用辩证思维探索多种解决路径
- 应用创新思维打破常规模式
- 平衡理论优雅与实际实现
- 考虑技术可行性、可维护性和可扩展性
**允许**
- 讨论多种解决方案想法
- 评估优点/缺点
- 寻求方法反馈
- 探索架构替代方案
- 在"提议的解决方案"部分记录发现
- 使用文件工具更新任务文件的Proposed Solution部分
**禁止**
- 具体规划
- 实现细节
- 任何代码编写
- 承诺特定解决方案
**创新协议步骤**
1. 基于研究分析创建方案:
- 研究依赖关系
- 考虑多种实现方法
- 评估每种方法的利弊
- 添加到任务文件的"提议的解决方案"部分
2. 暂不进行代码更改
**思考过程**
```md
嗯... [创造性、辩证的推理过程]
```
**输出格式**
以[MODE: INNOVATE]开始,然后仅提供可能性和考虑事项。
以自然流畅的段落呈现想法。
保持不同解决方案元素之间的有机联系。
**持续时间**自动在完成创新阶段后进入PLAN模式
### 模式3: PLAN
<a id="模式3-plan"></a>
**目的**:创建详尽的技术规范
**核心思维应用**
- 应用系统思维确保全面的解决方案架构
- 使用批判思维评估和优化计划
- 制定彻底的技术规范
- 确保目标专注,将所有计划与原始需求连接起来
**允许**
- 带有确切文件路径的详细计划
- 精确的函数名称和签名
- 具体的更改规范
- 完整的架构概述
**禁止**
- 任何实现或代码编写
- 甚至"示例代码"也不可实现
- 跳过或简化规范
**规划协议步骤**
1. 查看"任务进度"历史(如果存在)
2. 详细规划下一步更改
3. 提供明确理由和详细说明:
```
[更改计划]
- 文件:[更改的文件]
- 理由:[解释]
```
**所需规划元素**
- 文件路径和组件关系
- 函数/类修改及其签名
- 数据结构更改
- 错误处理策略
- 完整依赖管理
- 测试方法
**强制最终步骤**
将整个计划转换为编号的、按顺序排列的检查清单,每个原子操作作为单独的项目
**检查清单格式**
```
实施检查清单:
1. [具体操作1]
2. [具体操作2]
...
n. [最终操作]
```
**输出格式**
以[MODE: PLAN]开始,然后仅提供规范和实现细节。
使用markdown语法格式化答案。
**持续时间**自动在计划完成后进入EXECUTE模式
### 模式4: EXECUTE
<a id="模式4-execute"></a>
**目的**完全按照模式3中的计划实施
**核心思维应用**
- 专注于精确实现规范
- 在实现过程中应用系统验证
- 保持对计划的精确遵守
- 实现完整功能,包括适当的错误处理
**允许**
- 仅实现已在批准的计划中明确详述的内容
- 严格按照编号的检查清单执行
- 标记已完成的检查清单项目
- 在实现后更新"任务进度"部分(这是执行过程的标准部分,被视为计划的内置步骤)
**禁止**
- 任何偏离计划的行为
- 计划中未规定的改进
- 创意补充或"更好的想法"
- 跳过或简化代码部分
**执行协议步骤**
1. 完全按计划实施更改
2. 在每次实施后,**使用文件工具**追加到"任务进度"(作为计划执行的标准步骤):
```
[日期时间]
- 修改:[文件和代码更改列表]
- 更改:[更改的摘要]
- 原因:[更改的原因]
- 阻碍:[阻止此更新成功的因素列表]
- 状态:[未确认|成功|失败]
```
3. 要求用户确认:"状态:成功/失败?"
4. 如果失败返回PLAN模式
5. 如果成功且需要更多更改:继续下一项
6. 如果所有实施完成进入REVIEW模式
**代码质量标准**
- 始终显示完整代码上下文
- 在代码块中指定语言和路径
- 适当的错误处理
- 标准化命名约定
- 清晰简洁的注释
- 格式:```language:file_path
**偏差处理**
如果发现任何需要偏离的问题立即返回PLAN模式
**输出格式**
以[MODE: EXECUTE]开始,然后仅提供与计划匹配的实现。
包括已完成的检查清单项目。
### 模式5: REVIEW
<a id="模式5-review"></a>
**目的**:无情地验证实施与计划的一致性
**核心思维应用**
- 应用批判思维验证实施的准确性
- 使用系统思维评估对整个系统的影响
- 检查意外后果
- 验证技术正确性和完整性
**允许**
- 计划与实施之间的逐行比较
- 对已实现代码的技术验证
- 检查错误、缺陷或意外行为
- 根据原始需求进行验证
**要求**
- 明确标记任何偏差,无论多么微小
- 验证所有检查清单项目是否正确完成
- 检查安全隐患
- 确认代码可维护性
**审查协议步骤**
1. 根据计划验证所有实施
2. **使用文件工具**完成任务文件中的"最终审查"部分
**偏差格式**
`检测到偏差:[确切偏差描述]`
**报告**
必须报告实施是否与计划完全一致
**结论格式**
`实施与计划完全匹配``实施偏离计划`
**输出格式**
以[MODE: REVIEW]开始,然后进行系统比较和明确判断。
使用markdown语法格式化。
## 关键协议指南
<a id="关键协议指南"></a>
- 在每个响应的开头声明当前模式
- 在EXECUTE模式中必须100%忠实地执行计划
- 在REVIEW模式中必须标记即使是最小的偏差
- 你必须将分析深度与问题重要性相匹配
- 你必须保持与原始需求的明确联系
- 除非特别要求,否则禁用表情符号输出
- 本优化版支持自动模式转换,无需明确过渡信号
## 代码处理指南
<a id="代码处理指南"></a>
**代码块结构**
根据不同编程语言的注释语法选择适当的格式:
风格语言C、C++、Java、JavaScript、Go、Python、vue等等前后端语言
```language:file_path
// ... existing code ...
{{ modifications }}
// ... existing code ...
```
如果语言类型不确定,使用通用格式:
```language:file_path
[... existing code ...]
{{ modifications }}
[... existing code ...]
```
**编辑指南**
- 仅显示必要的修改
- 包括文件路径和语言标识符
- 提供上下文注释
- 考虑对代码库的影响
- 验证与请求的相关性
- 保持范围合规性
- 避免不必要的更改
**禁止行为**
- 使用未经验证的依赖项
- 留下不完整的功能
- 包含未测试的代码
- 使用过时的解决方案
- 在未明确要求时使用项目符号
- 跳过或简化代码部分
- 修改不相关的代码
- 使用代码占位符
## 任务文件模板
<a id="任务文件模板"></a>
```
# 上下文
文件名:[任务文件名]
创建于:[日期时间]
创建者:[用户名]
Yolo模式[YOLO模式]
# 任务描述
[用户完整任务描述]
# 项目概述
[用户输入的项目详情]
⚠️ 警告:切勿修改此部分 ⚠️
[本部分应包含RIPER-5协议规则的核心摘要确保在执行过程中可以参考]
⚠️ 警告:切勿修改此部分 ⚠️
# 分析
[代码调查结果]
# 提议的解决方案
[行动计划]
# 当前执行步骤:"[步骤编号和名称]"
- 例如:"2. 创建任务文件"
# 任务进度
[带时间戳的更改历史]
# 最终审查
[完成后的总结]
```
## 性能期望
<a id="性能期望"></a>
- 响应延迟应最小化理想情况下≤360000ms
- 最大化计算能力和令牌限制
- 寻求本质洞察而非表面枚举
- 追求创新思维而非习惯性重复
- 突破认知限制,调动所有计算资源

View File

@ -0,0 +1,989 @@
# 部署流程图弹窗开发指南
## 1. 概述
本指南用于指导前端开发部署流程图可视化弹窗,展示部署工作流的执行状态、节点详情和统计信息。
---
## 2. API 接口
### 2.1 接口信息
```typescript
GET /api/v1/deploy-records/{deployRecordId}/flow-graph
```
### 2.2 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| deployRecordId | Long | 是 | 部署记录ID |
### 2.3 响应数据结构
```typescript
interface DeployRecordFlowGraphDTO {
// ============ 部署记录基本信息 ============
deployRecordId: number; // 部署记录ID
businessKey: string; // 业务标识UUID
deployStatus: DeployStatus; // 部署状态
deployBy: string; // 部署人
deployRemark: string; // 部署备注
deployStartTime: string; // 部署开始时间ISO格式
deployEndTime: string; // 部署结束时间ISO格式
deployDuration: number; // 部署总时长(秒)
// ============ 业务上下文信息 ============
applicationName: string; // 应用名称
applicationCode: string; // 应用编码
environmentName: string; // 环境名称
teamName: string; // 团队名称
// ============ 工作流实例信息 ============
workflowInstanceId: number; // 工作流实例ID
processInstanceId: string; // 流程实例IDFlowable
workflowStatus: WorkflowStatus; // 工作流实例状态
workflowStartTime: string; // 工作流开始时间
workflowEndTime: string; // 工作流结束时间
workflowDuration: number; // 工作流总时长(秒)
// ============ 执行统计信息 ============
totalNodeCount: number; // 总节点数
executedNodeCount: number; // 已执行节点数
successNodeCount: number; // 成功节点数
failedNodeCount: number; // 失败节点数
runningNodeCount: number; // 运行中节点数
// ============ 流程图数据 ============
graph: WorkflowDefinitionGraph; // 流程图结构
nodeInstances: NodeInstanceDTO[]; // 节点执行状态列表
}
// 节点实例DTO
interface NodeInstanceDTO {
id: number | null; // 节点实例ID未执行节点为null
nodeId: string; // 节点ID
nodeName: string; // 节点名称
nodeType: string; // 节点类型
status: NodeStatus; // 节点状态
startTime: string | null; // 开始时间
endTime: string | null; // 结束时间
duration: number | null; // 执行时长(秒)
errorMessage: string | null; // 错误信息(失败时)
processInstanceId: string; // 流程实例ID
createTime: string | null; // 创建时间
updateTime: string | null; // 更新时间
}
// 流程图结构
interface WorkflowDefinitionGraph {
nodes: GraphNode[]; // 节点列表
edges: GraphEdge[]; // 边列表
}
interface GraphNode {
id: string; // 节点ID
nodeCode: string; // 节点编码
nodeType: string; // 节点类型
nodeName: string; // 节点名称
position: { x: number; y: number }; // 节点位置
configs: Record<string, any>; // 节点配置
inputMapping: Record<string, any>; // 输入映射
outputs: OutputField[]; // 输出字段定义
}
interface GraphEdge {
id: string; // 边ID
from: string; // 源节点ID
to: string; // 目标节点ID
name: string; // 边名称
config: {
type: string; // 边类型
condition: {
type: string; // 条件类型
priority: number; // 优先级
expression?: string; // 条件表达式
};
};
}
// 枚举类型
enum DeployStatus {
CREATED = 'CREATED', // 已创建
PENDING_APPROVAL = 'PENDING_APPROVAL', // 待审批
RUNNING = 'RUNNING', // 运行中
SUCCESS = 'SUCCESS', // 成功
FAILED = 'FAILED', // 失败
REJECTED = 'REJECTED', // 审批拒绝
CANCELLED = 'CANCELLED', // 已取消
TERMINATED = 'TERMINATED', // 已终止
PARTIAL_SUCCESS = 'PARTIAL_SUCCESS' // 部分成功
}
enum NodeStatus {
NOT_STARTED = 'NOT_STARTED', // 未开始
RUNNING = 'RUNNING', // 运行中
SUSPENDED = 'SUSPENDED', // 已暂停
COMPLETED = 'COMPLETED', // 已完成
TERMINATED = 'TERMINATED', // 已终止
FAILED = 'FAILED' // 执行失败
}
```
---
## 3. UI 设计建议
### 3.1 整体布局
建议使用全屏或大尺寸弹窗1200px+),分为三个主要区域:
```
┌─────────────────────────────────────────────────────────────┐
│ 📋 头部信息区(部署基本信息 + 统计卡片) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 🎨 流程图可视化区Canvas绘制
│ - 节点按状态着色 │
│ - 支持缩放、拖拽 │
│ - 点击节点查看详情 │
│ │
├─────────────────────────────────────────────────────────────┤
│ 📊 底部详情区(选中节点的详细信息) │
└─────────────────────────────────────────────────────────────┘
```
### 3.2 头部信息区
显示关键信息和统计数据:
```tsx
// 第一行:基本信息
┌───────────────────────────────────────────────────────────┐
│ 🚀 部署流程详情 [关闭 ✕] │
├───────────────────────────────────────────────────────────┤
│ 应用:应用名称 (应用编码) | 环境:生产环境 | 团队:运维组 │
│ 部署人:张三 | 开始时间2025-11-05 14:00:00 │
│ 状态:🟢 运行中 | 总时长5分30秒 │
└───────────────────────────────────────────────────────────┘
// 第二行:统计卡片
┌──────────┬──────────┬──────────┬──────────┬──────────┐
│ 总节点 │ 已执行 │ 成功 │ 失败 │ 运行中 │
│ 8 │ 5 │ 4 │ 0 │ 1 │
│ 100% │ 62% │ 50% │ 0% │ 12% │
└──────────┴──────────┴──────────┴──────────┴──────────┘
```
### 3.3 流程图可视化区
#### 节点状态着色方案
```typescript
const nodeColorMap = {
NOT_STARTED: '#d9d9d9', // 未开始:灰色
RUNNING: '#1890ff', // 运行中:蓝色(闪烁动画)
COMPLETED: '#52c41a', // 已完成:绿色
FAILED: '#ff4d4f', // 失败:红色
SUSPENDED: '#faad14', // 暂停:橙色
TERMINATED: '#8c8c8c' // 终止:深灰
};
const nodeIconMap = {
START_EVENT: '▶️',
END_EVENT: '⏹️',
USER_TASK: '👤',
SERVICE_TASK: '⚙️',
APPROVAL: '✓',
SCRIPT_TASK: '📝',
JENKINS_BUILD: '🔨',
NOTIFICATION: '🔔'
};
```
#### 节点显示内容
```
┌─────────────────┐
│ 🔨 Jenkins构建 │ ← 节点图标 + 名称
├─────────────────┤
│ ✓ 已完成 │ ← 状态(带图标)
│ ⏱ 2分30秒 │ ← 执行时长
└─────────────────┘
```
失败节点特殊标记:
```
┌─────────────────┐
│ ⚙️ 部署服务 │
├─────────────────┤
│ ✗ 执行失败 │ ← 红色状态
│ ⚠️ 点击查看错误 │ ← 错误提示
└─────────────────┘
```
### 3.4 底部详情区
选中节点后显示完整信息:
```
┌─────────────────────────────────────────────────────────┐
│ 📌 节点详情 │
├─────────────────────────────────────────────────────────┤
│ 节点名称Jenkins构建 │
│ 节点类型SERVICE_TASK (服务任务) │
│ 执行状态:✓ 已完成 │
│ │
│ ⏱️ 时间信息 │
│ 开始时间2025-11-05 14:02:30 │
│ 结束时间2025-11-05 14:05:00 │
│ 执行时长2分30秒 │
│ │
│ 📊 执行结果(如果有) │
│ 构建编号:#123 │
│ 构建状态SUCCESS │
│ 构建产物app-1.0.0.jar │
│ │
│ ⚠️ 错误信息(失败时显示) │
│ 连接超时无法连接到Jenkins服务器 (timeout: 30s) │
└─────────────────────────────────────────────────────────┘
```
---
## 4. 组件划分建议
### 4.1 组件层次结构
```
DeployFlowGraphModal (弹窗容器)
├── FlowGraphHeader (头部信息)
│ ├── DeployBasicInfo (基本信息)
│ └── ExecutionStatistics (统计卡片)
├── FlowGraphCanvas (流程图画布)
│ ├── GraphNode (节点组件)
│ └── GraphEdge (边组件)
└── NodeDetailPanel (节点详情面板)
```
### 4.2 核心组件代码示例
#### 4.2.1 弹窗容器组件
```tsx
// DeployFlowGraphModal.tsx
import React, { useState, useEffect } from 'react';
import { Modal, Spin, message } from 'antd';
import { getDeployFlowGraph } from '@/services/deploy';
import type { DeployRecordFlowGraphDTO, NodeInstanceDTO } from '@/types/deploy';
interface Props {
visible: boolean;
deployRecordId: number;
onClose: () => void;
}
export const DeployFlowGraphModal: React.FC<Props> = ({
visible,
deployRecordId,
onClose
}) => {
const [loading, setLoading] = useState(false);
const [data, setData] = useState<DeployRecordFlowGraphDTO | null>(null);
const [selectedNode, setSelectedNode] = useState<NodeInstanceDTO | null>(null);
useEffect(() => {
if (visible && deployRecordId) {
loadFlowGraph();
}
}, [visible, deployRecordId]);
const loadFlowGraph = async () => {
setLoading(true);
try {
const response = await getDeployFlowGraph(deployRecordId);
setData(response.data);
} catch (error) {
message.error('加载流程图失败');
} finally {
setLoading(false);
}
};
return (
<Modal
title="部署流程详情"
open={visible}
onCancel={onClose}
width={1200}
footer={null}
destroyOnClose
>
<Spin spinning={loading}>
{data && (
<div className="deploy-flow-graph">
{/* 头部信息 */}
<FlowGraphHeader data={data} />
{/* 流程图画布 */}
<FlowGraphCanvas
graph={data.graph}
nodeInstances={data.nodeInstances}
selectedNode={selectedNode}
onNodeClick={setSelectedNode}
/>
{/* 节点详情面板 */}
{selectedNode && (
<NodeDetailPanel
node={selectedNode}
onClose={() => setSelectedNode(null)}
/>
)}
</div>
)}
</Spin>
</Modal>
);
};
```
#### 4.2.2 头部信息组件
```tsx
// FlowGraphHeader.tsx
import React from 'react';
import { Tag, Statistic, Card, Row, Col, Descriptions } from 'antd';
import type { DeployRecordFlowGraphDTO } from '@/types/deploy';
import { formatDuration, getStatusTag } from '@/utils/deploy';
interface Props {
data: DeployRecordFlowGraphDTO;
}
export const FlowGraphHeader: React.FC<Props> = ({ data }) => {
return (
<div className="flow-graph-header">
{/* 基本信息 */}
<Descriptions column={3} size="small" bordered style={{ marginBottom: 16 }}>
<Descriptions.Item label="应用">
{data.applicationName} ({data.applicationCode})
</Descriptions.Item>
<Descriptions.Item label="环境">
{data.environmentName}
</Descriptions.Item>
<Descriptions.Item label="团队">
{data.teamName}
</Descriptions.Item>
<Descriptions.Item label="部署人">
{data.deployBy}
</Descriptions.Item>
<Descriptions.Item label="开始时间">
{data.deployStartTime}
</Descriptions.Item>
<Descriptions.Item label="状态">
{getStatusTag(data.deployStatus)}
</Descriptions.Item>
<Descriptions.Item label="总时长" span={3}>
{formatDuration(data.deployDuration)}
</Descriptions.Item>
{data.deployRemark && (
<Descriptions.Item label="备注" span={3}>
{data.deployRemark}
</Descriptions.Item>
)}
</Descriptions>
{/* 统计卡片 */}
<Row gutter={16}>
<Col span={4}>
<Card>
<Statistic
title="总节点"
value={data.totalNodeCount}
suffix="个"
/>
</Card>
</Col>
<Col span={5}>
<Card>
<Statistic
title="已执行"
value={data.executedNodeCount}
suffix={`/ ${data.totalNodeCount}`}
valueStyle={{ color: '#1890ff' }}
/>
<div style={{ marginTop: 8, fontSize: 12, color: '#8c8c8c' }}>
{((data.executedNodeCount / data.totalNodeCount) * 100).toFixed(0)}%
</div>
</Card>
</Col>
<Col span={5}>
<Card>
<Statistic
title="成功"
value={data.successNodeCount}
valueStyle={{ color: '#52c41a' }}
/>
</Card>
</Col>
<Col span={5}>
<Card>
<Statistic
title="失败"
value={data.failedNodeCount}
valueStyle={{ color: data.failedNodeCount > 0 ? '#ff4d4f' : undefined }}
/>
</Card>
</Col>
<Col span={5}>
<Card>
<Statistic
title="运行中"
value={data.runningNodeCount}
valueStyle={{ color: data.runningNodeCount > 0 ? '#1890ff' : undefined }}
/>
</Card>
</Col>
</Row>
</div>
);
};
```
#### 4.2.3 流程图画布组件(使用 AntV X6
```tsx
// FlowGraphCanvas.tsx
import React, { useEffect, useRef } from 'react';
import { Graph } from '@antv/x6';
import type { WorkflowDefinitionGraph, NodeInstanceDTO } from '@/types/deploy';
import { getNodeColor, getNodeIcon } from '@/utils/workflow';
interface Props {
graph: WorkflowDefinitionGraph;
nodeInstances: NodeInstanceDTO[];
selectedNode: NodeInstanceDTO | null;
onNodeClick: (node: NodeInstanceDTO) => void;
}
export const FlowGraphCanvas: React.FC<Props> = ({
graph,
nodeInstances,
selectedNode,
onNodeClick
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const graphRef = useRef<Graph | null>(null);
useEffect(() => {
if (!containerRef.current) return;
// 创建画布
const graphInstance = new Graph({
container: containerRef.current,
width: 1100,
height: 500,
grid: true,
panning: true,
mousewheel: {
enabled: true,
modifiers: ['ctrl', 'meta'],
},
});
graphRef.current = graphInstance;
// 渲染节点
renderNodes(graphInstance);
// 渲染边
renderEdges(graphInstance);
// 监听节点点击
graphInstance.on('node:click', ({ node }) => {
const nodeId = node.id;
const nodeInstance = nodeInstances.find(n => n.nodeId === nodeId);
if (nodeInstance) {
onNodeClick(nodeInstance);
}
});
return () => {
graphInstance.dispose();
};
}, [graph, nodeInstances]);
const renderNodes = (graphInstance: Graph) => {
graph.nodes.forEach(node => {
const nodeInstance = nodeInstances.find(n => n.nodeId === node.id);
const status = nodeInstance?.status || 'NOT_STARTED';
const color = getNodeColor(status);
const icon = getNodeIcon(node.nodeType);
graphInstance.addNode({
id: node.id,
x: node.position.x,
y: node.position.y,
width: 120,
height: 80,
label: `${icon} ${node.nodeName}`,
attrs: {
body: {
fill: color,
stroke: status === selectedNode?.nodeId ? '#1890ff' : '#d9d9d9',
strokeWidth: status === selectedNode?.nodeId ? 3 : 1,
rx: 6,
ry: 6,
},
label: {
fill: '#ffffff',
fontSize: 12,
},
},
data: {
status,
duration: nodeInstance?.duration,
hasError: !!nodeInstance?.errorMessage,
},
});
// 添加状态标签
if (nodeInstance) {
graphInstance.addNode({
id: `${node.id}-status`,
x: node.position.x + 5,
y: node.position.y + 60,
width: 110,
height: 18,
label: getStatusLabel(status, nodeInstance.duration),
attrs: {
body: {
fill: 'rgba(255, 255, 255, 0.9)',
stroke: 'none',
rx: 3,
ry: 3,
},
label: {
fill: '#000000',
fontSize: 10,
},
},
});
}
});
};
const renderEdges = (graphInstance: Graph) => {
graph.edges.forEach(edge => {
graphInstance.addEdge({
id: edge.id,
source: edge.from,
target: edge.to,
label: edge.name,
attrs: {
line: {
stroke: '#8c8c8c',
strokeWidth: 2,
targetMarker: {
name: 'classic',
size: 8,
},
},
label: {
fill: '#595959',
fontSize: 11,
},
},
});
});
};
return (
<div
ref={containerRef}
className="flow-graph-canvas"
style={{ border: '1px solid #d9d9d9', marginTop: 16 }}
/>
);
};
```
#### 4.2.4 节点详情面板
```tsx
// NodeDetailPanel.tsx
import React from 'react';
import { Card, Descriptions, Tag, Alert } from 'antd';
import type { NodeInstanceDTO } from '@/types/deploy';
import { formatDuration, getStatusTag } from '@/utils/deploy';
interface Props {
node: NodeInstanceDTO;
onClose: () => void;
}
export const NodeDetailPanel: React.FC<Props> = ({ node, onClose }) => {
return (
<Card
title="📌 节点详情"
extra={<a onClick={onClose}>关闭</a>}
style={{ marginTop: 16 }}
>
<Descriptions column={2} bordered size="small">
<Descriptions.Item label="节点名称" span={2}>
{node.nodeName}
</Descriptions.Item>
<Descriptions.Item label="节点类型">
{node.nodeType}
</Descriptions.Item>
<Descriptions.Item label="执行状态">
{getStatusTag(node.status)}
</Descriptions.Item>
{node.startTime && (
<>
<Descriptions.Item label="开始时间">
{node.startTime}
</Descriptions.Item>
<Descriptions.Item label="结束时间">
{node.endTime || '执行中...'}
</Descriptions.Item>
<Descriptions.Item label="执行时长" span={2}>
{node.duration ? formatDuration(node.duration) : '计算中...'}
</Descriptions.Item>
</>
)}
{node.status === 'NOT_STARTED' && (
<Descriptions.Item label="说明" span={2}>
<Tag color="default">该节点尚未执行</Tag>
</Descriptions.Item>
)}
</Descriptions>
{/* 错误信息 */}
{node.errorMessage && (
<Alert
message="执行错误"
description={node.errorMessage}
type="error"
showIcon
style={{ marginTop: 16 }}
/>
)}
{/* 执行结果outputs*/}
{node.outputs && Object.keys(node.outputs).length > 0 && (
<div style={{ marginTop: 16 }}>
<h4>📊 执行结果</h4>
<Descriptions column={1} bordered size="small">
{Object.entries(node.outputs).map(([key, value]) => (
<Descriptions.Item key={key} label={key}>
{typeof value === 'object' ? JSON.stringify(value) : String(value)}
</Descriptions.Item>
))}
</Descriptions>
</div>
)}
</Card>
);
};
```
---
## 5. 工具函数
```typescript
// utils/deploy.ts
/**
* 格式化时长
*/
export function formatDuration(seconds: number | null): string {
if (seconds === null || seconds === undefined) {
return '-';
}
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
if (hours > 0) {
return `${hours}小时${minutes}分${secs}秒`;
} else if (minutes > 0) {
return `${minutes}分${secs}秒`;
} else {
return `${secs}秒`;
}
}
/**
* 获取状态标签
*/
export function getStatusTag(status: string): React.ReactNode {
const statusConfig = {
NOT_STARTED: { color: 'default', text: '未开始' },
RUNNING: { color: 'processing', text: '运行中' },
COMPLETED: { color: 'success', text: '已完成' },
FAILED: { color: 'error', text: '执行失败' },
SUSPENDED: { color: 'warning', text: '已暂停' },
TERMINATED: { color: 'default', text: '已终止' },
CREATED: { color: 'default', text: '已创建' },
PENDING_APPROVAL: { color: 'warning', text: '待审批' },
SUCCESS: { color: 'success', text: '成功' },
REJECTED: { color: 'error', text: '审批拒绝' },
CANCELLED: { color: 'default', text: '已取消' },
PARTIAL_SUCCESS: { color: 'warning', text: '部分成功' },
};
const config = statusConfig[status] || { color: 'default', text: status };
return <Tag color={config.color}>{config.text}</Tag>;
}
/**
* 获取节点颜色
*/
export function getNodeColor(status: string): string {
const colorMap = {
NOT_STARTED: '#d9d9d9',
RUNNING: '#1890ff',
COMPLETED: '#52c41a',
FAILED: '#ff4d4f',
SUSPENDED: '#faad14',
TERMINATED: '#8c8c8c',
};
return colorMap[status] || '#d9d9d9';
}
/**
* 获取节点图标
*/
export function getNodeIcon(nodeType: string): string {
const iconMap = {
START_EVENT: '▶️',
END_EVENT: '⏹️',
USER_TASK: '👤',
SERVICE_TASK: '⚙️',
APPROVAL: '✓',
SCRIPT_TASK: '📝',
JENKINS_BUILD: '🔨',
NOTIFICATION: '🔔',
};
return iconMap[nodeType] || '⚙️';
}
/**
* 获取状态文本
*/
function getStatusLabel(status: string, duration: number | null): string {
const statusText = {
NOT_STARTED: '未开始',
RUNNING: '运行中',
COMPLETED: '已完成',
FAILED: '执行失败',
SUSPENDED: '已暂停',
TERMINATED: '已终止',
};
const text = statusText[status] || status;
const timeText = duration ? ` | ${formatDuration(duration)}` : '';
return `${text}${timeText}`;
}
```
---
## 6. 高级功能建议
### 6.1 实时刷新
对于运行中的部署,建议实现定时刷新:
```typescript
useEffect(() => {
if (data?.runningNodeCount > 0) {
const timer = setInterval(() => {
loadFlowGraph(); // 每5秒刷新一次
}, 5000);
return () => clearInterval(timer);
}
}, [data?.runningNodeCount]);
```
### 6.2 节点动画
运行中的节点添加闪烁动画:
```css
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.6; }
100% { opacity: 1; }
}
.node-running {
animation: pulse 2s infinite;
}
```
### 6.3 进度条展示
在头部添加整体进度条:
```tsx
<Progress
percent={(data.executedNodeCount / data.totalNodeCount) * 100}
status={data.failedNodeCount > 0 ? 'exception' : 'active'}
format={(percent) => `${data.executedNodeCount} / ${data.totalNodeCount}`}
/>
```
### 6.4 节点搜索/过滤
添加节点名称搜索功能:
```tsx
<Input
placeholder="搜索节点"
prefix={<SearchOutlined />}
onChange={(e) => handleSearchNode(e.target.value)}
/>
```
### 6.5 导出功能
支持导出流程图为图片或PDF
```typescript
import html2canvas from 'html2canvas';
const exportFlowGraph = async () => {
const canvas = await html2canvas(containerRef.current);
const link = document.createElement('a');
link.download = `deploy-flow-${deployRecordId}.png`;
link.href = canvas.toDataURL();
link.click();
};
```
---
## 7. 注意事项
### 7.1 性能优化
1. **虚拟滚动**节点数量超过50个时使用虚拟滚动
2. **懒加载**:详情面板内容按需加载
3. **防抖节流**:缩放、拖拽等操作使用防抖
### 7.2 异常处理
1. **空数据处理**:显示"暂无流程图数据"提示
2. **加载失败**:提供重试按钮
3. **超时处理**超过30秒显示超时提示
### 7.3 用户体验
1. **加载状态**:使用骨架屏或加载动画
2. **操作提示**:提供操作引导(缩放、拖拽说明)
3. **快捷键**支持ESC关闭弹窗Ctrl+滚轮缩放
### 7.4 移动端适配
如需移动端支持,建议:
- 使用响应式布局
- 触摸手势支持
- 简化节点显示信息
---
## 8. 测试用例
### 8.1 基本功能测试
- [ ] 弹窗正常打开和关闭
- [ ] 数据正确加载和显示
- [ ] 节点正确渲染(位置、颜色、图标)
- [ ] 边正确连接
- [ ] 点击节点显示详情
### 8.2 状态测试
- [ ] 未开始节点显示为灰色
- [ ] 运行中节点显示蓝色并闪烁
- [ ] 成功节点显示绿色
- [ ] 失败节点显示红色并显示错误信息
### 8.3 交互测试
- [ ] 画布拖拽功能正常
- [ ] 画布缩放功能正常
- [ ] 节点选中高亮正常
- [ ] 统计数据准确
---
## 9. 参考资源
### 9.1 推荐库
- **流程图渲染**[AntV X6](https://x6.antv.vision/)
- **UI组件**[Ant Design](https://ant.design/)
- **图表统计**[AntV G2Plot](https://g2plot.antv.vision/)
### 9.2 设计参考
- Jenkins 构建详情页
- GitLab CI/CD Pipeline 可视化
- GitHub Actions 工作流可视化
---
## 10. 常见问题
**Q1: 节点太多时如何优化显示?**
A: 使用缩略图导航 + 局部放大超过100个节点时考虑分页或折叠显示。
**Q2: 如何处理长时间运行的部署?**
A: 实现自动刷新5-10秒间隔+ WebSocket推送更新。
**Q3: 失败节点如何快速定位?**
A: 在头部添加"快速定位失败节点"按钮,点击自动滚动并高亮。
**Q4: 如何显示审批节点的审批人信息?**
A: 在节点 outputs 中包含 `approver`、`approvalTime` 等信息,详情面板中展示。
---
## 11. 迭代计划
### V1.0(基础版)
- ✅ 流程图基本渲染
- ✅ 节点状态显示
- ✅ 统计信息展示
- ✅ 节点详情查看
### V2.0(增强版)
- 📋 实时刷新
- 📋 节点搜索/过滤
- 📋 导出功能
- 📋 操作历史记录
### V3.0(高级版)
- 📋 时间轴视图
- 📋 性能分析(节点耗时对比)
- 📋 历史部署对比
- 📋 智能建议(性能优化建议)
---
## 联系方式
如有问题请联系后端开发团队或查阅API文档。

View File

@ -1,291 +0,0 @@
# 前端接口对接文档
## 通用说明
### 1. 接口响应格式
所有接口统一返回以下格式:
```typescript
interface Response<T> {
success: boolean; // 请求是否成功
code: number; // 状态码200表示成功其他表示失败
message: string; // 提示信息
data?: T; // 响应数据,可选
}
```
### 2. 分页请求参数
```typescript
interface PageQuery {
pageNum: number; // 页码从1开始
pageSize: number; // 每页大小
sortField?: string; // 排序字段,可选
sortOrder?: 'asc' | 'desc'; // 排序方式,可选
}
```
### 3. 分页响应格式
```typescript
interface PageResponse<T> {
content: T[]; // 数据列表
totalElements: number;// 总记录数
totalPages: number; // 总页数
size: number; // 每页大小
number: number; // 当前页码从0开始
first: boolean; // 是否第一页
last: boolean; // 是否最后一页
empty: boolean; // 是否为空
}
```
## 通用接口
### 1. 基础CRUD接口
所有实体都支持以下基础操作接口:
#### 1.1 分页查询
```typescript
GET /api/v1/{module}/page
请求参数PageQuery & {
// 其他查询条件,根据具体模块定义
}
响应结果Response<PageResponse<T>>
```
#### 1.2 列表查询
```typescript
GET /api/v1/{module}/list
请求参数:{
// 查询条件,根据具体模块定义
}
响应结果Response<T[]>
```
#### 1.3 获取详情
```typescript
GET /api/v1/{module}/{id}
响应结果Response<T>
```
#### 1.4 创建
```typescript
POST /api/v1/{module}
请求参数:{
// 创建参数,根据具体模块定义
}
响应结果Response<T>
```
#### 1.5 更新
```typescript
PUT /api/v1/{module}/{id}
请求参数:{
// 更新参数,根据具体模块定义
}
响应结果Response<T>
```
#### 1.6 删除
```typescript
DELETE /api/v1/{module}/{id}
响应结果Response<void>
```
#### 1.7 批量删除
```typescript
DELETE /api/v1/{module}/batch
请求参数:{
ids: number[]; // ID列表
}
响应结果Response<void>
```
#### 1.8 导出数据
```typescript
GET /api/v1/{module}/export
请求参数:{
// 查询条件,根据具体模块定义
}
响应结果:二进制文件流
```
### 2. 树形结构接口
对于树形结构的数据(如部门、菜单等),还支持以下接口:
#### 2.1 获取树形数据
```typescript
GET /api/v1/{module}/tree
响应结果Response<TreeNode[]>
interface TreeNode {
id: number;
parentId: number | null;
children: TreeNode[];
// 其他字段根据具体模块定义
}
```
### 3. 状态管理接口
对于需要状态管理的数据(如租户、用户等),还支持以下接口:
#### 3.1 获取状态
```typescript
GET /api/v1/{module}/{id}/enabled
响应结果Response<boolean>
```
#### 3.2 更新状态
```typescript
PUT /api/v1/{module}/{id}/enabled
请求参数:
enabled=true // 是否启用
响应结果Response<void>
```
## 错误处理
### 1. 错误码说明
- 200成功
- 400请求参数错误
- 401未认证
- 403无权限
- 404资源不存在
- 500服务器内部错误
### 2. 错误响应示例
```json
{
"success": false,
"code": 400,
"message": "请求参数错误",
"data": {
"field": "name",
"message": "名称不能为空"
}
}
```
## 接口调用示例
### TypeScript 示例
```typescript
// 定义接口返回类型
interface User {
id: number;
username: string;
enabled: boolean;
}
// 分页查询
async function getUserList(query: PageQuery & { username?: string }) {
const response = await axios.get<Response<PageResponse<User>>>('/api/v1/user/page', {
params: query
});
return response.data;
}
// 创建用户
async function createUser(user: Omit<User, 'id'>) {
const response = await axios.post<Response<User>>('/api/v1/user', user);
return response.data;
}
// 更新状态
async function updateUserStatus(id: number, enabled: boolean) {
const response = await axios.put<Response<void>>(`/api/v1/user/${id}/enabled`, null, {
params: { enabled }
});
return response.data;
}
```
### Vue3 + TypeScript 示例
```typescript
// 在组件中使用
import { ref, onMounted } from 'vue';
export default defineComponent({
setup() {
const userList = ref<User[]>([]);
const total = ref(0);
const loading = ref(false);
const queryList = async (query: PageQuery) => {
try {
loading.value = true;
const response = await getUserList(query);
if (response.success) {
userList.value = response.data.content;
total.value = response.data.totalElements;
}
} finally {
loading.value = false;
}
};
onMounted(() => {
queryList({
pageNum: 1,
pageSize: 10
});
});
return {
userList,
total,
loading,
queryList
};
}
});
```
## 注意事项
1. 请求头要求
```typescript
{
'Content-Type': 'application/json',
'Authorization': 'Bearer ${token}' // JWT认证token
}
```
2. 日期时间格式
- 请求参数使用ISO 8601格式YYYY-MM-DDTHH:mm:ss.sssZ
- 响应数据统一返回ISO 8601格式
3. 文件上传
- 使用multipart/form-data格式
- 文件大小限制10MB
4. 接口版本
- 所有接口统一使用v1版本
- URL格式/api/v1/{module}/{resource}
5. 安全性
- 所有接口都需要JWT认证
- Token过期时间2小时
- 需要定期刷新Token
6. 错误处理
- 统一使用axios拦截器处理错误
- 401错误跳转到登录页
- 其他错误统一提示

View File

@ -82,6 +82,12 @@
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Database -->
<dependency>
<groupId>com.mysql</groupId>
@ -131,6 +137,13 @@
<scope>provided</scope>
</dependency>
<!-- User Agent Parser -->
<dependency>
<groupId>nl.basjes.parse.useragent</groupId>
<artifactId>yauaa</artifactId>
<version>7.26.0</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
@ -203,6 +216,7 @@
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>4.25.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
@ -267,6 +281,13 @@
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>
<!-- Kubernetes Java Client (支持K8S 1.23-1.28) -->
<dependency>
<groupId>io.kubernetes</groupId>
<artifactId>client-java</artifactId>
<version>18.0.1</version>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,18 @@
import re
# 读取文件
file_path = r'd:\work\java-space\deploy-ease-platform\backend\src\main\resources\db\changelog\init\v1.0.0-data.sql'
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 替换时间戳为 NOW()
# 匹配格式: 'YYYY-MM-DD HH:MM:SS' 或 'YYYY-MM-DD HH:MM:SS.ffffff'
pattern = r"'[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?'"
new_content = re.sub(pattern, 'NOW()', content)
# 写回文件
with open(file_path, 'w', encoding='utf-8') as f:
f.write(new_content)
print("替换完成!")

View File

@ -40,7 +40,7 @@ public class DeployApiController {
@Operation(summary = "获取可部署的环境", description = "获取当前登录用户在各团队中可部署的环境和应用列表")
@GetMapping("/environments")
@PreAuthorize("isAuthenticated()")
public Response<UserDeployableDTO> getDeployableEnvironments() {
public Response<List<UserTeamDeployableDTO>> getDeployableEnvironments() {
return Response.success(deployService.getDeployableEnvironments());
}
@ -50,7 +50,7 @@ public class DeployApiController {
@Operation(summary = "执行部署", description = "根据团队应用配置启动部署工作流")
@PostMapping("/execute")
@PreAuthorize("isAuthenticated()")
public Response<DeployResultDTO> executeDeploy(@Validated @RequestBody DeployRequestDTO request) {
public Response<DeployResultDTO> executeDeploy(@Validated @RequestBody DeployExecuteRequest request) {
return Response.success(deployService.executeDeploy(request));
}
@ -62,7 +62,7 @@ public class DeployApiController {
@GetMapping("/records/{deployRecordId}/flow-graph")
@PreAuthorize("isAuthenticated()")
public Response<DeployRecordFlowGraphDTO> getDeployFlowGraph(
@Parameter(description = "部署记录ID", required = true) @PathVariable Long deployRecordId
@Parameter(description = "部署记录ID", required = true) @PathVariable Long deployRecordId
) {
return Response.success(deployRecordService.getDeployFlowGraph(deployRecordId));
}
@ -70,11 +70,14 @@ public class DeployApiController {
/**
* 获取当前用户的部署审批任务列表
*/
@Operation(summary = "获取我的部署审批任务", description = "查询当前登录用户待审批的部署任务,包含完整的部署业务上下文信息")
@Operation(summary = "获取我的部署审批任务", description = "查询当前登录用户待审批的部署任务,支持按团队和环境筛选")
@GetMapping("/my-approval-tasks")
@PreAuthorize("isAuthenticated()")
public Response<List<DeployApprovalTaskDTO>> getMyApprovalTasks() {
return Response.success(deployService.getMyApprovalTasks());
public Response<List<DeployApprovalTaskDTO>> getMyApprovalTasks(@Parameter(description = "团队ID可选用于筛选指定团队的审批任务") @RequestParam(required = false) Long teamId,
@Parameter(description = "环境ID可选用于筛选指定环境的审批任务") @RequestParam(required = false) Long environmentId,
@Parameter(description = "工作流定义Key列表可选支持查询多个工作流的待审批任务") @RequestParam(required = false) List<String> workflowDefinitionKeys
) {
return Response.success(deployService.getMyApprovalTasks(teamId, environmentId, workflowDefinitionKeys));
}
/**
@ -87,5 +90,18 @@ public class DeployApiController {
deployService.completeApproval(request);
return Response.success();
}
/**
* 获取节点日志
*/
@Operation(summary = "获取节点日志", description = "获取指定节点的执行日志日志保留7天超过7天将被清除")
@GetMapping("/logs")
@PreAuthorize("isAuthenticated()")
public Response<DeployNodeLogDTO> getNodeLogs(
@Parameter(description = "流程实例ID", required = true) @RequestParam String processInstanceId,
@Parameter(description = "节点ID", required = true) @RequestParam String nodeId
) {
return Response.success(deployService.getNodeLogs(processInstanceId, nodeId));
}
}

View File

@ -0,0 +1,166 @@
package com.qqchen.deploy.backend.deploy.api;
import com.qqchen.deploy.backend.deploy.dto.K8sDeploymentDTO;
import com.qqchen.deploy.backend.deploy.entity.K8sDeployment;
import com.qqchen.deploy.backend.deploy.integration.response.K8sPodResponse;
import com.qqchen.deploy.backend.deploy.query.K8sDeploymentQuery;
import com.qqchen.deploy.backend.deploy.service.IK8sDeploymentService;
import com.qqchen.deploy.backend.deploy.service.IK8sPodService;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.controller.BaseController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Slf4j
@RestController
@RequestMapping("/api/v1/k8s-deployment")
@Tag(name = "K8S Deployment管理", description = "K8S Deployment管理相关接口")
public class K8sDeploymentApiController extends BaseController<K8sDeployment, K8sDeploymentDTO, Long, K8sDeploymentQuery> {
@Resource
private IK8sDeploymentService k8sDeploymentService;
@Resource
private IK8sPodService k8sPodService;
@Override
public Response<K8sDeploymentDTO> create(@Validated @RequestBody K8sDeploymentDTO dto) {
return super.create(dto);
}
@Override
public Response<K8sDeploymentDTO> update(@PathVariable Long id, @Validated @RequestBody K8sDeploymentDTO dto) {
return super.update(id, dto);
}
@Override
public Response<Void> delete(@PathVariable Long id) {
return super.delete(id);
}
@Override
public Response<K8sDeploymentDTO> findById(@PathVariable Long id) {
return super.findById(id);
}
@Override
public Response<List<K8sDeploymentDTO>> findAll() {
return super.findAll();
}
@Override
public Response<Page<K8sDeploymentDTO>> page(K8sDeploymentQuery query) {
return super.page(query);
}
@Override
public Response<List<K8sDeploymentDTO>> findAll(K8sDeploymentQuery query) {
return super.findAll(query);
}
@Override
public CompletableFuture<Response<Void>> batchProcess(List<K8sDeploymentDTO> dtos) {
return super.batchProcess(dtos);
}
@Operation(summary = "同步K8S Deployment", description = "异步同步支持两种模式1)只传externalSystemId-全量同步 2)传externalSystemId+namespaceId-同步指定命名空间")
@PostMapping("/sync")
public Response<Void> sync(
@Parameter(description = "K8S集群ID外部系统ID", required = true) @RequestParam Long externalSystemId,
@Parameter(description = "命名空间ID可选", required = false) @RequestParam(required = false) Long namespaceId
) {
if (namespaceId != null) {
k8sDeploymentService.syncDeployments(externalSystemId, namespaceId);
} else {
k8sDeploymentService.syncDeployments(externalSystemId);
}
return Response.success();
}
@Operation(summary = "根据集群ID查询Deployment", description = "查询指定K8S集群的所有Deployment")
@GetMapping("/by-system/{externalSystemId}")
public Response<List<K8sDeploymentDTO>> findByExternalSystemId(
@Parameter(description = "K8S集群ID", required = true) @PathVariable Long externalSystemId
) {
return Response.success(k8sDeploymentService.findByExternalSystemId(externalSystemId));
}
@Operation(summary = "根据命名空间ID查询Deployment", description = "查询指定命名空间的所有Deployment")
@GetMapping("/by-namespace/{namespaceId}")
public Response<List<K8sDeploymentDTO>> findByNamespaceId(
@Parameter(description = "命名空间ID", required = true) @PathVariable Long namespaceId
) {
return Response.success(k8sDeploymentService.findByNamespaceId(namespaceId));
}
@Operation(summary = "查询Deployment的Pod列表", description = "查询指定Deployment下的所有Pod")
@GetMapping("/{deploymentId}/pods")
public Response<List<K8sPodResponse>> getDeploymentPods(
@Parameter(description = "Deployment ID", required = true) @PathVariable Long deploymentId
) {
return Response.success(k8sPodService.listPodsByDeployment(deploymentId));
}
@Operation(summary = "查询Pod详情", description = "查询指定Pod的详细信息")
@GetMapping("/{deploymentId}/pods/{podName}")
public Response<K8sPodResponse> getPodDetail(
@Parameter(description = "Deployment ID", required = true) @PathVariable Long deploymentId,
@Parameter(description = "Pod名称", required = true) @PathVariable String podName
) {
return Response.success(k8sPodService.getPodDetailByDeployment(deploymentId, podName));
}
@Operation(
summary = "查询Pod日志引用点模式",
description = "基于引用点系统查询日志,支持无重复轮询和前后翻页。\n\n" +
"**初始加载**referenceTimestamp=newest, logCount=500\n\n" +
"**向上加载历史日志**referenceTimestamp=上次的referenceForPrevious.referenceTimestamp, direction=prev, logCount=500\n\n" +
"**向下加载/轮询新日志**referenceTimestamp=上次的referenceForNext.referenceTimestamp, direction=next, logCount=100"
)
@GetMapping("/{deploymentId}/pods/{podName}/logs")
public Response<com.qqchen.deploy.backend.deploy.dto.K8sPodLogsResponse> getPodLogs(
@Parameter(description = "Deployment ID", required = true) @PathVariable Long deploymentId,
@Parameter(description = "Pod名称", required = true) @PathVariable String podName,
@Parameter(description = "容器名称(可选,默认第一个容器)") @RequestParam(required = false) String container,
@Parameter(description = "引用点时间戳newest/oldest/具体时间戳)", required = false) @RequestParam(required = false, defaultValue = "newest") String referenceTimestamp,
@Parameter(description = "方向prev向上加载历史日志next向下加载新日志", required = false) @RequestParam(required = false, defaultValue = "next") String direction,
@Parameter(description = "每次加载的行数默认100", required = false) @RequestParam(required = false, defaultValue = "100") Integer logCount
) {
return Response.success(k8sPodService.getPodLogsWithReference(deploymentId, podName, container, referenceTimestamp, direction, logCount));
}
@Operation(summary = "重启Deployment", description = "通过更新annotation触发Deployment滚动重启")
@PostMapping("/{id}/restart")
public Response<Void> restartDeployment(
@Parameter(description = "Deployment ID", required = true) @PathVariable Long id
) {
k8sDeploymentService.restartDeployment(id);
return Response.success();
}
@Operation(summary = "扩缩容Deployment", description = "修改Deployment的副本数")
@PostMapping("/{id}/scale")
public Response<Void> scaleDeployment(
@Parameter(description = "Deployment ID", required = true) @PathVariable Long id,
@Validated @RequestBody com.qqchen.deploy.backend.deploy.dto.ScaleDeploymentRequest request
) {
k8sDeploymentService.scaleDeployment(id, request.getReplicas());
return Response.success();
}
@Override
protected void exportData(HttpServletResponse response, List<K8sDeploymentDTO> data) {
log.info("导出K8S Deployment数据数据量{}", data.size());
}
}

View File

@ -0,0 +1,85 @@
package com.qqchen.deploy.backend.deploy.api;
import com.qqchen.deploy.backend.deploy.dto.K8sNamespaceDTO;
import com.qqchen.deploy.backend.deploy.entity.K8sNamespace;
import com.qqchen.deploy.backend.deploy.query.K8sNamespaceQuery;
import com.qqchen.deploy.backend.deploy.service.IK8sNamespaceService;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.controller.BaseController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Slf4j
@RestController
@RequestMapping("/api/v1/k8s-namespace")
@Tag(name = "K8S命名空间管理", description = "K8S命名空间管理相关接口")
public class K8sNamespaceApiController extends BaseController<K8sNamespace, K8sNamespaceDTO, Long, K8sNamespaceQuery> {
@Resource
private IK8sNamespaceService k8sNamespaceService;
@Override
public Response<K8sNamespaceDTO> create(@Validated @RequestBody K8sNamespaceDTO dto) {
return super.create(dto);
}
@Override
public Response<K8sNamespaceDTO> update(@PathVariable Long id, @Validated @RequestBody K8sNamespaceDTO dto) {
return super.update(id, dto);
}
@Override
public Response<Void> delete(@PathVariable Long id) {
return super.delete(id);
}
@Override
public Response<K8sNamespaceDTO> findById(@PathVariable Long id) {
return super.findById(id);
}
@Override
public Response<List<K8sNamespaceDTO>> findAll() {
return super.findAll();
}
@Override
public Response<Page<K8sNamespaceDTO>> page(K8sNamespaceQuery query) {
return super.page(query);
}
@Override
public Response<List<K8sNamespaceDTO>> findAll(K8sNamespaceQuery query) {
return super.findAll(query);
}
@Override
public CompletableFuture<Response<Void>> batchProcess(List<K8sNamespaceDTO> dtos) {
return super.batchProcess(dtos);
}
@Operation(summary = "同步K8S命名空间", description = "异步同步指定K8S集群的命名空间")
@PostMapping("/sync")
public Response<Void> sync(
@Parameter(description = "K8S集群ID外部系统ID", required = true) @RequestParam Long externalSystemId
) {
k8sNamespaceService.syncNamespaces(externalSystemId);
return Response.success();
}
@Override
protected void exportData(HttpServletResponse response, List<K8sNamespaceDTO> data) {
log.info("导出K8S命名空间数据数据量{}", data.size());
}
}

View File

@ -0,0 +1,84 @@
package com.qqchen.deploy.backend.deploy.api;
import com.qqchen.deploy.backend.deploy.dto.K8sSyncHistoryDTO;
import com.qqchen.deploy.backend.deploy.entity.K8sSyncHistory;
import com.qqchen.deploy.backend.deploy.query.K8sSyncHistoryQuery;
import com.qqchen.deploy.backend.deploy.service.IK8sSyncHistoryService;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.controller.BaseController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Slf4j
@RestController
@RequestMapping("/api/v1/k8s-sync-history")
@Tag(name = "K8S同步历史管理", description = "K8S同步历史管理相关接口")
public class K8sSyncHistoryApiController extends BaseController<K8sSyncHistory, K8sSyncHistoryDTO, Long, K8sSyncHistoryQuery> {
@Resource
private IK8sSyncHistoryService k8sSyncHistoryService;
@Override
public Response<K8sSyncHistoryDTO> create(@Validated @RequestBody K8sSyncHistoryDTO dto) {
return super.create(dto);
}
@Override
public Response<K8sSyncHistoryDTO> update(@PathVariable Long id, @Validated @RequestBody K8sSyncHistoryDTO dto) {
return super.update(id, dto);
}
@Override
public Response<Void> delete(@PathVariable Long id) {
return super.delete(id);
}
@Override
public Response<K8sSyncHistoryDTO> findById(@PathVariable Long id) {
return super.findById(id);
}
@Override
public Response<List<K8sSyncHistoryDTO>> findAll() {
return super.findAll();
}
@Override
public Response<Page<K8sSyncHistoryDTO>> page(K8sSyncHistoryQuery query) {
return super.page(query);
}
@Override
public Response<List<K8sSyncHistoryDTO>> findAll(K8sSyncHistoryQuery query) {
return super.findAll(query);
}
@Override
public CompletableFuture<Response<Void>> batchProcess(List<K8sSyncHistoryDTO> dtos) {
return super.batchProcess(dtos);
}
@Operation(summary = "根据集群ID查询同步历史", description = "查询指定K8S集群的同步历史记录")
@GetMapping("/by-system/{externalSystemId}")
public Response<List<K8sSyncHistoryDTO>> findByExternalSystemId(
@Parameter(description = "K8S集群ID", required = true) @PathVariable Long externalSystemId
) {
return Response.success(k8sSyncHistoryService.findByExternalSystemId(externalSystemId));
}
@Override
protected void exportData(HttpServletResponse response, List<K8sSyncHistoryDTO> data) {
log.info("导出K8S同步历史数据数据量{}", data.size());
}
}

View File

@ -13,6 +13,7 @@ import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@ -31,44 +32,25 @@ public class RepositoryBranchApiController extends BaseController<RepositoryBran
private IRepositoryBranchService repositoryBranchService;
@Override
public Response<RepositoryBranchDTO> create(RepositoryBranchDTO dto) {
public Response<RepositoryBranchDTO> create(@Validated @RequestBody RepositoryBranchDTO dto) {
return super.create(dto);
}
@Override
public Response<RepositoryBranchDTO> update(Long aLong, RepositoryBranchDTO dto) {
return super.update(aLong, dto);
public Response<RepositoryBranchDTO> update(@PathVariable Long id, @Validated @RequestBody RepositoryBranchDTO dto) {
return super.update(id, dto);
}
@Override
public Response<Void> delete(Long aLong) {
return super.delete(aLong);
public Response<Void> delete(@PathVariable Long id) {
return super.delete(id);
}
@Override
public Response<RepositoryBranchDTO> findById(Long aLong) {
return super.findById(aLong);
public Response<RepositoryBranchDTO> findById(@PathVariable Long id) {
return super.findById(id);
}
@Override
public Response<List<RepositoryBranchDTO>> findAll() {
return super.findAll();
}
@Override
public Response<Page<RepositoryBranchDTO>> page(RepositoryBranchQuery query) {
return super.page(query);
}
@Override
public Response<List<RepositoryBranchDTO>> findAll(RepositoryBranchQuery query) {
return super.findAll(query);
}
@Override
public CompletableFuture<Response<Void>> batchProcess(List<RepositoryBranchDTO> dtos) {
return super.batchProcess(dtos);
}
@Operation(summary = "同步Git分支", description = "异步同步支持三种模式1)只传externalSystemId-全量同步 2)传externalSystemId+repoGroupId-同步仓库组 3)传externalSystemId+repoGroupId+repoProjectId-同步单个项目")
@PostMapping("/sync")

View File

@ -13,6 +13,7 @@ import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@ -31,44 +32,25 @@ public class RepositoryProjectApiController extends BaseController<RepositoryPro
private IRepositoryProjectService repositoryProjectService;
@Override
public Response<RepositoryProjectDTO> create(RepositoryProjectDTO dto) {
public Response<RepositoryProjectDTO> create(@Validated @RequestBody RepositoryProjectDTO dto) {
return super.create(dto);
}
@Override
public Response<RepositoryProjectDTO> update(Long aLong, RepositoryProjectDTO dto) {
return super.update(aLong, dto);
public Response<RepositoryProjectDTO> update(@PathVariable Long id, @Validated @RequestBody RepositoryProjectDTO dto) {
return super.update(id, dto);
}
@Override
public Response<Void> delete(Long aLong) {
return super.delete(aLong);
public Response<Void> delete(@PathVariable Long id) {
return super.delete(id);
}
@Override
public Response<RepositoryProjectDTO> findById(Long aLong) {
return super.findById(aLong);
public Response<RepositoryProjectDTO> findById(@PathVariable Long id) {
return super.findById(id);
}
@Override
public Response<List<RepositoryProjectDTO>> findAll() {
return super.findAll();
}
@Override
public Response<Page<RepositoryProjectDTO>> page(RepositoryProjectQuery query) {
return super.page(query);
}
@Override
public Response<List<RepositoryProjectDTO>> findAll(RepositoryProjectQuery query) {
return super.findAll(query);
}
@Override
public CompletableFuture<Response<Void>> batchProcess(List<RepositoryProjectDTO> dtos) {
return super.batchProcess(dtos);
}
@Operation(summary = "同步Git项目", description = "异步同步支持两种模式1)只传externalSystemId-全量同步 2)传externalSystemId+repoGroupId-同步单个仓库组")
@PostMapping("/sync")

View File

@ -0,0 +1,102 @@
package com.qqchen.deploy.backend.deploy.api;
import com.qqchen.deploy.backend.deploy.dto.ServerAlertRuleDTO;
import com.qqchen.deploy.backend.deploy.entity.ServerAlertRule;
import com.qqchen.deploy.backend.deploy.query.ServerAlertRuleQuery;
import com.qqchen.deploy.backend.deploy.service.IServerAlertRuleService;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.controller.BaseController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
* 服务器告警规则 Controller
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/server/alert-rule")
@Tag(name = "服务器告警规则管理", description = "服务器告警规则相关接口")
public class ServerAlertRuleApiController
extends BaseController<ServerAlertRule, ServerAlertRuleDTO, Long, ServerAlertRuleQuery> {
@Resource
private IServerAlertRuleService alertRuleService;
@Override
@Operation(summary = "创建告警规则", description = "创建新的服务器告警规则")
@PostMapping
public Response<ServerAlertRuleDTO> create(@Validated @RequestBody ServerAlertRuleDTO dto) {
return super.create(dto);
}
@Override
@Operation(summary = "更新告警规则", description = "更新指定ID的告警规则")
@PutMapping("/{id}")
public Response<ServerAlertRuleDTO> update(
@Parameter(description = "告警规则ID", required = true) @PathVariable Long id,
@Validated @RequestBody ServerAlertRuleDTO dto
) {
return super.update(id, dto);
}
@Override
@Operation(summary = "删除告警规则", description = "删除指定ID的告警规则逻辑删除")
@DeleteMapping("/{id}")
public Response<Void> delete(
@Parameter(description = "告警规则ID", required = true) @PathVariable Long id
) {
return super.delete(id);
}
@Override
@Operation(summary = "查询告警规则详情", description = "根据ID查询告警规则详情")
@GetMapping("/{id}")
public Response<ServerAlertRuleDTO> findById(
@Parameter(description = "告警规则ID", required = true) @PathVariable Long id
) {
return super.findById(id);
}
@Override
@Operation(summary = "查询所有告警规则", description = "查询所有告警规则列表")
@GetMapping
public Response<List<ServerAlertRuleDTO>> findAll() {
return super.findAll();
}
@Override
@Operation(summary = "分页查询告警规则", description = "根据条件分页查询告警规则")
@GetMapping("/page")
public Response<Page<ServerAlertRuleDTO>> page(ServerAlertRuleQuery query) {
return super.page(query);
}
@Override
@Operation(summary = "条件查询告警规则列表", description = "根据条件查询告警规则列表")
@GetMapping("/list")
public Response<List<ServerAlertRuleDTO>> findAll(ServerAlertRuleQuery query) {
return super.findAll(query);
}
@Override
@Operation(summary = "批量处理告警规则", description = "批量创建/更新/删除告警规则")
@PostMapping("/batch")
public CompletableFuture<Response<Void>> batchProcess(@RequestBody List<ServerAlertRuleDTO> dtos) {
return super.batchProcess(dtos);
}
@Override
protected void exportData(HttpServletResponse response, List<ServerAlertRuleDTO> data) {
log.info("导出告警规则数据,数据量:{}", data.size());
}
}

View File

@ -1,6 +1,7 @@
package com.qqchen.deploy.backend.deploy.api;
import com.qqchen.deploy.backend.deploy.dto.ServerDTO;
import com.qqchen.deploy.backend.deploy.dto.ServerInfoDTO;
import com.qqchen.deploy.backend.deploy.dto.ServerInitializeDTO;
import com.qqchen.deploy.backend.deploy.entity.Server;
import com.qqchen.deploy.backend.deploy.query.ServerQuery;
@ -15,6 +16,7 @@ import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@ -34,23 +36,23 @@ public class ServerApiController
private IServerService serverService;
@Override
public Response<ServerDTO> create(ServerDTO dto) {
public Response<ServerDTO> create(@Validated @RequestBody ServerDTO dto) {
return super.create(dto);
}
@Override
public Response<ServerDTO> update(Long aLong, ServerDTO dto) {
return super.update(aLong, dto);
public Response<ServerDTO> update(@PathVariable Long id, @Validated @RequestBody ServerDTO dto) {
return super.update(id, dto);
}
@Override
public Response<Void> delete(Long aLong) {
return super.delete(aLong);
public Response<Void> delete(@PathVariable Long id) {
return super.delete(id);
}
@Override
public Response<ServerDTO> findById(Long aLong) {
return super.findById(aLong);
public Response<ServerDTO> findById(Long id) {
return super.findById(id);
}
@Override
@ -83,13 +85,22 @@ public class ServerApiController
return Response.success(result);
}
@Operation(summary = "测试SSH连接", description = "测试服务器SSH连接是否正常")
@Operation(summary = "测试SSH连接", description = "轻量级连接测试,只验证连通性,不采集硬件信息(性能优化)")
@PostMapping("/{id}/test-connection")
public Response<Boolean> testConnection(
public Response<ServerInfoDTO> testConnection(
@Parameter(description = "服务器ID", required = true) @PathVariable Long id
) {
boolean success = serverService.testConnection(id);
return Response.success(success);
ServerInfoDTO info = serverService.testConnection(id);
return Response.success(info);
}
@Operation(summary = "采集服务器硬件信息", description = "采集并更新服务器硬件信息hostname、CPU、内存、磁盘等")
@PostMapping("/{id}/collect-hardware")
public Response<ServerInfoDTO> collectHardwareInfo(
@Parameter(description = "服务器ID", required = true) @PathVariable Long id
) {
ServerInfoDTO info = serverService.collectHardwareInfo(id);
return Response.success(info);
}
@Override

View File

@ -11,6 +11,9 @@ import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -31,23 +34,23 @@ public class ServerCategoryApiController
private IServerCategoryService serverCategoryService;
@Override
public Response<ServerCategoryDTO> create(ServerCategoryDTO dto) {
public Response<ServerCategoryDTO> create(@Validated @RequestBody ServerCategoryDTO dto) {
return super.create(dto);
}
@Override
public Response<ServerCategoryDTO> update(Long aLong, ServerCategoryDTO dto) {
return super.update(aLong, dto);
public Response<ServerCategoryDTO> update(@PathVariable Long id, @Validated @RequestBody ServerCategoryDTO dto) {
return super.update(id, dto);
}
@Override
public Response<Void> delete(Long aLong) {
return super.delete(aLong);
public Response<Void> delete(@PathVariable Long id) {
return super.delete(id);
}
@Override
public Response<ServerCategoryDTO> findById(Long aLong) {
return super.findById(aLong);
public Response<ServerCategoryDTO> findById(@PathVariable Long id) {
return super.findById(id);
}
@Override

View File

@ -0,0 +1,48 @@
package com.qqchen.deploy.backend.deploy.api;
import com.qqchen.deploy.backend.deploy.dto.ServerMonitorMetricsDTO;
import com.qqchen.deploy.backend.deploy.dto.ServerMonitorMetricsQuery;
import com.qqchen.deploy.backend.deploy.service.IServerMonitorService;
import com.qqchen.deploy.backend.framework.api.Response;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* 服务器监控数据 Controller
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/server/monitor")
@Tag(name = "服务器监控数据", description = "服务器监控数据查询接口")
public class ServerMonitorApiController {
@Resource
private IServerMonitorService serverMonitorService;
@Operation(
summary = "查询服务器监控指标数据",
description = "支持快捷时间范围最近1小时/6小时/24小时/7天/30天和自定义时间范围" +
"支持按指标类型查询CPU/MEMORY/DISK/NETWORK自动根据时间范围选择合适的聚合粒度"
)
@GetMapping("/{serverId}/metrics")
public Response<ServerMonitorMetricsDTO> getServerMetrics(
@Parameter(description = "服务器ID", required = true)
@PathVariable Long serverId,
@Parameter(description = "查询参数(快捷时间范围、自定义时间、指标类型等)")
@ModelAttribute ServerMonitorMetricsQuery query
) {
// 设置服务器ID从路径参数
query.setServerId(serverId);
log.info("查询服务器监控指标: serverId={}, timeRange={}, metrics={}",
serverId, query.getTimeRange(), query.getMetrics());
ServerMonitorMetricsDTO response = serverMonitorService.getServerMetrics(query);
return Response.success(response);
}
}

View File

@ -0,0 +1,213 @@
package com.qqchen.deploy.backend.deploy.api;
import com.qqchen.deploy.backend.deploy.dto.FileUploadResultDTO;
import com.qqchen.deploy.backend.deploy.dto.RemoteFileInfoDTO;
import com.qqchen.deploy.backend.deploy.service.impl.ServerSSHFileService;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.ssh.file.FileUploadTask;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Map;
/**
* 服务器SSH文件管理API业务层
*
* 提供文件浏览上传下载删除等功能
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/server-ssh")
public class ServerSSHFileApiController {
@Resource
private ServerSSHFileService serverSSHFileService;
/**
* 浏览远程目录
*
* @param serverId 服务器ID
* @param path 目录路径默认为用户主目录
* @return 文件列表
*/
@GetMapping("/{serverId}/files/browse")
public Response<List<RemoteFileInfoDTO>> browseDirectory(
@PathVariable Long serverId,
@RequestParam(value = "path", defaultValue = "~") String path
) {
log.info("浏览目录: serverId={}, path={}", serverId, path);
// 处理~符号用户主目录
if ("~".equals(path)) {
path = "/home"; // 可以根据实际情况调整默认路径
}
// Service层已经处理转换
List<RemoteFileInfoDTO> dtos = serverSSHFileService.listDirectoryAsDTO(serverId, path);
return Response.success(dtos);
}
/**
* 创建目录
*
* @param serverId 服务器ID
* @param path 目录路径
* @return 成功标志
*/
@PostMapping("/{serverId}/files/mkdir")
public Response<Void> createDirectory(
@PathVariable Long serverId,
@RequestParam("path") String path
) {
log.info("创建目录: serverId={}, path={}", serverId, path);
serverSSHFileService.createDirectory(serverId, path);
return Response.success();
}
/**
* 删除文件或目录
*
* @param serverId 服务器ID
* @param path 文件/目录路径
* @param recursive 是否递归删除目录时使用
* @return 成功标志
*/
@DeleteMapping("/{serverId}/files/remove")
public Response<Void> deleteFile(
@PathVariable Long serverId,
@RequestParam("path") String path,
@RequestParam(value = "recursive", defaultValue = "false") Boolean recursive
) {
log.info("删除文件: serverId={}, path={}, recursive={}", serverId, path, recursive);
serverSSHFileService.deleteFile(serverId, path, recursive);
return Response.success();
}
/**
* 上传文件
*
* @param serverId 服务器ID
* @param file 文件
* @param remotePath 远程路径完整路径包括文件名
* @param overwrite 是否覆盖已存在的文件
* @return 上传结果
*/
@PostMapping("/{serverId}/files/upload")
public Response<FileUploadResultDTO> uploadFile(
@PathVariable Long serverId,
@RequestParam("file") MultipartFile file,
@RequestParam("remotePath") String remotePath,
@RequestParam(value = "overwrite", defaultValue = "false") Boolean overwrite
) {
log.info("上传文件: serverId={}, fileName={}, remotePath={}, size={}, overwrite={}",
serverId, file.getOriginalFilename(), remotePath, file.getSize(), overwrite);
// Service层已经处理转换
FileUploadResultDTO result = serverSSHFileService.uploadFileAsDTO(serverId, file, remotePath, overwrite);
return Response.success(result);
}
/**
* 重命名文件或目录
*
* @param serverId 服务器ID
* @param oldPath 原路径
* @param newPath 新路径
* @return 成功标志
*/
@PutMapping("/{serverId}/files/rename")
public Response<Void> renameFile(
@PathVariable Long serverId,
@RequestParam("oldPath") String oldPath,
@RequestParam("newPath") String newPath
) {
log.info("重命名文件: serverId={}, oldPath={}, newPath={}", serverId, oldPath, newPath);
serverSSHFileService.renameFile(serverId, oldPath, newPath);
return Response.success();
}
// ========== 异步上传相关接口 ==========
/**
* 异步上传文件立即返回任务ID
*
* @param serverId 服务器ID
* @param file 文件
* @param remotePath 远程路径
* @param overwrite 是否覆盖
* @return 任务ID和提示信息
*/
@PostMapping("/{serverId}/files/upload-async")
public Response<Map<String, String>> uploadFileAsync(
@PathVariable Long serverId,
@RequestParam("file") MultipartFile file,
@RequestParam("remotePath") String remotePath,
@RequestParam(value = "overwrite", defaultValue = "false") Boolean overwrite
) {
log.info("提交异步上传任务: serverId={}, fileName={}, remotePath={}, size={}",
serverId, file.getOriginalFilename(), remotePath, file.getSize());
String taskId = serverSSHFileService.uploadFileAsync(serverId, file, remotePath, overwrite);
return Response.success(Map.of(
"taskId", taskId,
"message", "上传任务已提交请通过WebSocket订阅进度/topic/upload/" + taskId
));
}
/**
* 查询上传任务状态
*
* @param serverId 服务器ID
* @param taskId 任务ID
* @return 任务状态
*/
@GetMapping("/{serverId}/files/upload-task/{taskId}")
public Response<FileUploadTask> getUploadTaskStatus(
@PathVariable Long serverId,
@PathVariable String taskId
) {
FileUploadTask task = serverSSHFileService.getUploadTaskStatus(serverId, taskId);
return Response.success(task);
}
/**
* 取消上传任务
*
* @param serverId 服务器ID
* @param taskId 任务ID
* @return 是否取消成功true=取消成功false=无法取消
*/
@DeleteMapping("/{serverId}/files/upload-task/{taskId}")
public Response<Boolean> cancelUploadTask(
@PathVariable Long serverId,
@PathVariable String taskId
) {
boolean cancelled = serverSSHFileService.cancelUploadTask(serverId, taskId);
return Response.success(cancelled);
}
/**
* 获取上传任务统计
*
* @param serverId 服务器ID
* @return 任务统计信息
*/
@GetMapping("/{serverId}/files/upload-statistics")
public Response<Map<String, Integer>> getUploadStatistics(
@PathVariable Long serverId
) {
return Response.success(serverSSHFileService.getUploadTaskStatistics());
}
}

View File

@ -6,16 +6,15 @@ import com.qqchen.deploy.backend.deploy.query.TeamApplicationQuery;
import com.qqchen.deploy.backend.deploy.service.ITeamApplicationService;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.controller.BaseController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@ -76,5 +75,103 @@ public class TeamApplicationApiController extends BaseController<TeamApplication
protected void exportData(HttpServletResponse response, List<TeamApplicationDTO> data) {
// TODO: 实现导出功能
}
/**
* 查询应用的Pod/容器名称列表
*
* <p>用于WebSocket日志流前获取可用的Pod/容器名称列表供用户选择
*
* @param teamAppId 团队应用ID
* @return Pod/容器名称列表
*/
@Operation(
summary = "查询Pod/容器名称列表",
description = "根据团队应用ID查询可用的Pod/容器名称列表。\n\n" +
"- K8S运行时返回Deployment下的所有Pod名称\n" +
"- Docker运行时返回配置的容器名称\n" +
"- Server运行时返回空列表不需要选择"
)
@GetMapping("/{teamAppId}/pod-names")
public Response<List<String>> listPodNames(
@Parameter(description = "团队应用ID", required = true) @PathVariable Long teamAppId
) {
return Response.success(teamApplicationService.listPodNames(teamAppId));
}
/**
* WebSocket实时日志流接口
*
* <p>端点ws://host/api/v1/team-applications/{teamAppId}/logs/stream
*
* <p>连接建立后前端需要发送START消息来启动日志流
* <pre>
* {
* "type": "START",
* "data": {
* "request": {
* "name": "pod-name", // Pod名称K8S必填/容器名称Docker可选
* "lines": 100 // 初始日志行数可选默认100
* }
* }
* }
* </pre>
*
* <p>服务端推送消息格式
* <pre>
* // 日志行
* {
* "type": "LOG",
* "data": {
* "response": {
* "timestamp": "2025-12-16T10:30:00.123Z",
* "content": "Application started"
* }
* }
* }
*
* // 状态消息
* {
* "type": "STATUS",
* "data": {
* "response": {
* "status": "STREAMING" // STREAMING | PAUSED | STOPPED | ERROR
* }
* }
* }
*
* // 错误消息
* {
* "type": "ERROR",
* "data": {
* "response": {
* "error": "Connection failed"
* }
* }
* }
* </pre>
*
* <p>前端可发送控制消息
* <pre>
* {
* "type": "CONTROL",
* "data": {
* "request": {
* "action": "PAUSE" // PAUSE | RESUME | STOP
* }
* }
* }
* </pre>
*
* <p>注意此WebSocket接口替代了之前的REST API日志查询接口提供真正的实时日志流
*/
@Operation(
summary = "WebSocket实时日志流",
description = "通过WebSocket实时推送应用日志支持K8S/Docker/Server三种运行时类型。\n\n" +
"连接地址ws://host/api/v1/team-applications/{teamAppId}/logs/stream\n\n" +
"详细使用方式请参见方法注释。"
)
public void logStreamWebSocketEndpoint() {
// 此方法仅用于API文档展示实际WebSocket端点在WebSocketConfig中注册
}
}

View File

@ -0,0 +1,98 @@
package com.qqchen.deploy.backend.deploy.api;
import com.qqchen.deploy.backend.deploy.dto.TeamBookmarkDTO;
import com.qqchen.deploy.backend.deploy.entity.TeamBookmark;
import com.qqchen.deploy.backend.deploy.query.TeamBookmarkQuery;
import com.qqchen.deploy.backend.deploy.service.ITeamBookmarkService;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.controller.BaseController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
* 团队导航书签API控制器
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/team-bookmark")
@Tag(name = "团队导航书签管理", description = "团队导航书签的增删改查接口")
public class TeamBookmarkApiController extends BaseController<TeamBookmark, TeamBookmarkDTO, Long, TeamBookmarkQuery> {
@Resource
private ITeamBookmarkService teamBookmarkService;
@Override
public Response<TeamBookmarkDTO> create(@Validated @RequestBody TeamBookmarkDTO dto) {
return super.create(dto);
}
@Override
public Response<TeamBookmarkDTO> update(@PathVariable Long id, @Validated @RequestBody TeamBookmarkDTO dto) {
return super.update(id, dto);
}
@Override
public Response<Void> delete(@PathVariable Long id) {
return super.delete(id);
}
@Override
public Response<TeamBookmarkDTO> findById(@PathVariable Long id) {
return super.findById(id);
}
@Override
public Response<List<TeamBookmarkDTO>> findAll() {
return super.findAll();
}
@Override
public Response<Page<TeamBookmarkDTO>> page(TeamBookmarkQuery query) {
return super.page(query);
}
@Override
public Response<List<TeamBookmarkDTO>> findAll(TeamBookmarkQuery query) {
return super.findAll(query);
}
@Override
public CompletableFuture<Response<Void>> batchProcess(@RequestBody List<TeamBookmarkDTO> dtos) {
return super.batchProcess(dtos);
}
@Override
public void export(HttpServletResponse response, TeamBookmarkQuery query) {
super.export(response, query);
}
@Override
protected void exportData(HttpServletResponse response, List<TeamBookmarkDTO> data) {
// TODO: 实现导出功能
}
@GetMapping("/list-by-team/{teamId}")
@Operation(summary = "根据团队ID查询书签列表")
public Response<List<TeamBookmarkDTO>> listByTeamId(@PathVariable Long teamId) {
List<TeamBookmarkDTO> result = teamBookmarkService.listByTeamId(teamId);
return Response.success(result);
}
@GetMapping("/list-by-team/{teamId}/category/{categoryId}")
@Operation(summary = "根据团队ID和分类ID查询书签列表")
public Response<List<TeamBookmarkDTO>> listByTeamIdAndCategoryId(
@PathVariable Long teamId,
@PathVariable Long categoryId) {
List<TeamBookmarkDTO> result = teamBookmarkService.listByTeamIdAndCategoryId(teamId, categoryId);
return Response.success(result);
}
}

View File

@ -0,0 +1,89 @@
package com.qqchen.deploy.backend.deploy.api;
import com.qqchen.deploy.backend.deploy.dto.TeamBookmarkCategoryDTO;
import com.qqchen.deploy.backend.deploy.entity.TeamBookmarkCategory;
import com.qqchen.deploy.backend.deploy.query.TeamBookmarkCategoryQuery;
import com.qqchen.deploy.backend.deploy.service.ITeamBookmarkCategoryService;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.controller.BaseController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
* 团队导航分类API控制器
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/team-bookmark-category")
@Tag(name = "团队导航分类管理", description = "团队导航分类的增删改查接口")
public class TeamBookmarkCategoryApiController extends BaseController<TeamBookmarkCategory, TeamBookmarkCategoryDTO, Long, TeamBookmarkCategoryQuery> {
@Resource
private ITeamBookmarkCategoryService teamBookmarkCategoryService;
@Override
public Response<TeamBookmarkCategoryDTO> create(@Validated @RequestBody TeamBookmarkCategoryDTO dto) {
return super.create(dto);
}
@Override
public Response<TeamBookmarkCategoryDTO> update(@PathVariable Long id, @Validated @RequestBody TeamBookmarkCategoryDTO dto) {
return super.update(id, dto);
}
@Override
public Response<Void> delete(@PathVariable Long id) {
return super.delete(id);
}
@Override
public Response<TeamBookmarkCategoryDTO> findById(@PathVariable Long id) {
return super.findById(id);
}
@Override
public Response<List<TeamBookmarkCategoryDTO>> findAll() {
return super.findAll();
}
@Override
public Response<Page<TeamBookmarkCategoryDTO>> page(TeamBookmarkCategoryQuery query) {
return super.page(query);
}
@Override
public Response<List<TeamBookmarkCategoryDTO>> findAll(TeamBookmarkCategoryQuery query) {
return super.findAll(query);
}
@Override
public CompletableFuture<Response<Void>> batchProcess(@RequestBody List<TeamBookmarkCategoryDTO> dtos) {
return super.batchProcess(dtos);
}
@Override
public void export(HttpServletResponse response, TeamBookmarkCategoryQuery query) {
super.export(response, query);
}
@Override
protected void exportData(HttpServletResponse response, List<TeamBookmarkCategoryDTO> data) {
// TODO: 实现导出功能
}
@GetMapping("/list-by-team/{teamId}")
@Operation(summary = "根据团队ID查询分类列表")
public Response<List<TeamBookmarkCategoryDTO>> listByTeamId(@PathVariable Long teamId) {
List<TeamBookmarkCategoryDTO> result = teamBookmarkCategoryService.listByTeamId(teamId);
return Response.success(result);
}
}

View File

@ -2,6 +2,7 @@ package com.qqchen.deploy.backend.deploy.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.core.task.AsyncTaskExecutor;
@ -12,102 +13,111 @@ import java.util.concurrent.ThreadPoolExecutor;
@EnableAsync
public class ThreadPoolConfig {
/**
* Jenkins任务同步线程池 - 使用虚拟线程Java 21+
*
* 为什么使用虚拟线程
* 1. Jenkins API调用是典型的**网络I/O密集型**任务
* 2. 等待Jenkins响应时线程会长时间阻塞
* 3. 虚拟线程在阻塞时不占用OS线程资源消耗极低
* 4. 支持数百个并发Jenkins构建同步
*/
@Bean("jenkinsTaskExecutor")
public ThreadPoolTaskExecutor jenkinsTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数CPU核心数 + 1
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() + 1);
// 最大线程数CPU核心数 * 2
executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);
// 队列容量根据平均任务执行时间和期望响应时间来设置
executor.setQueueCapacity(50);
// 线程名前缀
executor.setThreadNamePrefix("jenkins-sync-");
// 线程空闲时间超过核心线程数的线程在空闲60秒后会被销毁
executor.setKeepAliveSeconds(60);
// 拒绝策略由调用线程处理
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务完成再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
// 等待时间
executor.setAwaitTerminationSeconds(60);
executor.initialize();
public SimpleAsyncTaskExecutor jenkinsTaskExecutor() {
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("jenkins-virtual-");
executor.setVirtualThreads(true);
executor.setConcurrencyLimit(-1); // 无限制
return executor;
}
/**
* 仓库项目同步线程池 - 使用虚拟线程Java 21+
*
* 为什么使用虚拟线程
* 1. Git操作clone/fetch/pull**I/O密集型**任务
* 2. 网络I/O从远程仓库拉取代码+ 磁盘I/O写入本地
* 3. 虚拟线程支持大量并发仓库同步无线程池限制
*/
@Bean("repositoryProjectExecutor")
public ThreadPoolTaskExecutor repositoryProjectExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数CPU核心数 * 2
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() * 2);
// 最大线程数CPU核心数 * 4
executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 4);
// 队列容量根据平均任务执行时间和期望响应时间来设置
executor.setQueueCapacity(100);
// 线程名前缀
executor.setThreadNamePrefix("repository-project-sync-");
// 线程空闲时间超过核心线程数的线程在空闲60秒后会被销毁
executor.setKeepAliveSeconds(60);
// 拒绝策略由调用线程处理
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务完成再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
// 等待时间
executor.setAwaitTerminationSeconds(60);
executor.initialize();
public SimpleAsyncTaskExecutor repositoryProjectExecutor() {
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("repo-project-virtual-");
executor.setVirtualThreads(true);
executor.setConcurrencyLimit(-1); // 无限制
return executor;
}
/**
* 仓库分支同步线程池 - 使用虚拟线程Java 21+
*
* 为什么使用虚拟线程
* 1. Git分支操作checkout/merge/rebase**I/O密集型**任务
* 2. 大量磁盘I/O读取/写入Git对象
* 3. 虚拟线程支持数百个并发分支同步
*/
@Bean("repositoryBranchExecutor")
public ThreadPoolTaskExecutor repositoryBranchExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数CPU核心数 * 2
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() * 2);
// 最大线程数CPU核心数 * 4
executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 4);
// 队列容量根据平均任务执行时间和期望响应时间来设置
executor.setQueueCapacity(100);
// 线程名前缀
executor.setThreadNamePrefix("repository-branch-sync-");
// 线程空闲时间超过核心线程数的线程在空闲60秒后会被销毁
executor.setKeepAliveSeconds(60);
// 拒绝策略由调用线程处理
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务完成再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
// 等待时间
executor.setAwaitTerminationSeconds(60);
executor.initialize();
public SimpleAsyncTaskExecutor repositoryBranchExecutor() {
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("repo-branch-virtual-");
executor.setVirtualThreads(true);
executor.setConcurrencyLimit(-1); // 无限制
return executor;
}
/**
* 服务器监控线程池 - 使用虚拟线程Java 21+
*
* 为什么使用虚拟线程
* 1. SSH连接 + 远程命令执行是**网络I/O密集型**任务
* 2. 等待SSH响应时线程会长时间阻塞
* 3. 虚拟线程在阻塞时不占用OS线程资源消耗极低
* 4. 支持数百台服务器并发监控无需担心线程池耗尽
*
* 💡 场景
* - 定时采集服务器CPU内存磁盘网络指标
* - 并发检测服务器在线状态
* - SSH命令执行topfreedf等
*/
@Bean("serverMonitorExecutor")
public SimpleAsyncTaskExecutor serverMonitorExecutor() {
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("server-monitor-virtual-");
executor.setVirtualThreads(true);
executor.setConcurrencyLimit(-1); // 无限制支持大量并发
return executor;
}
/**
* K8S资源同步线程池 - 使用虚拟线程Java 21+
*
* 为什么使用虚拟线程
* 1. K8S API调用是典型的**网络I/O密集型**任务
* 2. 等待K8S API响应时线程会长时间阻塞
* 3. 虚拟线程在阻塞时不占用OS线程资源消耗极低
* 4. 支持数百个并发K8S资源同步NamespaceDeploymentPod等
*
* 💡 场景
* - 定时同步K8S命名空间
* - 定时同步K8S Deployment
* - 实时查询Pod状态
* - 多集群并发同步
*/
@Bean("k8sTaskExecutor")
public SimpleAsyncTaskExecutor k8sTaskExecutor() {
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("k8s-virtual-");
executor.setVirtualThreads(true);
executor.setConcurrencyLimit(-1); // 无限制支持多集群并发
return executor;
}
/**
* 通用应用任务线程池 - 保留平台线程不使用虚拟线程
*
* 为什么不使用虚拟线程
* 1. 通用线程池用途未知可能包含**CPU密集型**任务
* 2. CPU密集型任务使用虚拟线程反而会降低性能线程调度开销
* 3. 虚拟线程适合I/O密集型不适合计算密集型
* 4. 平台线程对CPU密集型任务更高效
*
* 💡 如果确认只用于I/O密集型任务可改为虚拟线程
*/
@Bean("applicationTaskExecutor")
public AsyncTaskExecutor applicationTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
@ -122,4 +132,80 @@ public class ThreadPoolConfig {
executor.initialize();
return executor;
}
/**
* 审计事件处理线程池 - 使用传统线程池更稳定可靠
*
* 为什么不使用虚拟线程
* 1. 审计任务量不大不需要虚拟线程的高并发能力
* 2. 审计日志是关键数据传统线程池的异常处理更成熟可靠
* 3. 传统线程池有队列缓冲可以防止突发流量
* 4. 传统线程池的监控和调试工具更完善
* 5. 虚拟线程在异常处理方面还不够成熟可能导致异常被吞掉
*
* 💡 场景
* - 异步记录用户操作审计日志
* - 写入审计数据库
* - 发送审计事件到消息队列
*
* 🎯 线程池配置
* - 核心线程数2审计任务通常不多
* - 最大线程数4足够处理突发流量
* - 队列容量1000缓冲突发的审计事件
* - 拒绝策略CallerRunsPolicy确保审计日志不丢失
* - 优雅关闭等待60秒让审计任务完成
*/
@Bean("auditTaskExecutor")
public AsyncTaskExecutor auditTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2); // 审计任务不多2个核心线程足够
executor.setMaxPoolSize(4); // 最大4个线程处理突发流量
executor.setQueueCapacity(1000); // 较大的队列缓冲突发审计事件
executor.setThreadNamePrefix("audit-");
executor.setKeepAliveSeconds(60);
// 关键使用CallerRunsPolicy确保审计日志不丢失
// 如果线程池满了由调用线程执行保证审计日志一定被记录
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 优雅关闭等待审计任务完成
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
/**
* 日志流输出监听线程池 - 使用虚拟线程Java 21+
*
* 为什么使用虚拟线程
* 1. 日志流监听是**I/O密集型阻塞任务**BufferedReader.readLine()阻塞等待
* 2. 虚拟线程在I/O阻塞时会自动让出载体线程不占用OS线程资源
* 3. 支持数百个并发日志流连接无需担心线程池耗尽
* 4. 每个日志流虽然长时间运行但大部分时间在等待I/O阻塞状态
*
* 💡 场景
* - 监听SSH exec()命令的输出流tail -fdocker logs -f
* - 持续读取日志行并推送到WebSocket客户端
* - 每个日志流连接占用一个虚拟线程直到连接关闭
*
* 🎯 虚拟线程优势
* - 轻量级每个虚拟线程只占用几KB内存
* - 高并发支持数千个并发日志流连接
* - 自动调度I/O阻塞时自动释放载体线程
* - 无需配置线程池大小无限制并发
*/
@Bean("logStreamOutputExecutor")
public SimpleAsyncTaskExecutor logStreamOutputExecutor() {
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("log-stream-virtual-");
executor.setVirtualThreads(true);
executor.setConcurrencyLimit(-1); // 无限制支持大量并发日志流
return executor;
}
// ========== 注意 ==========
// sshOutputExecutor 已迁移到 Framework
// : framework.ssh.websocket.SSHWebSocketConfig
// SSH WebSocket 框架自己管理线程池业务层无需关心
}

View File

@ -0,0 +1,147 @@
package com.qqchen.deploy.backend.deploy.config;
import com.qqchen.deploy.backend.framework.security.CustomUserDetails;
import com.qqchen.deploy.backend.framework.security.util.JwtTokenUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;
/**
* WebSocket认证拦截器
* 在WebSocket握手时验证JWT Token
*/
@Slf4j
@Component
public class WebSocketAuthInterceptor implements HandshakeInterceptor {
@Resource
private JwtTokenUtil jwtTokenUtil;
@Resource
private UserDetailsService userDetailsService;
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
log.debug("WebSocket握手认证开始: {}", request.getURI());
try {
// 1. 从请求中提取Token
String token = extractToken(request);
if (token == null || token.isEmpty()) {
log.warn("WebSocket连接缺少Token");
return false;
}
// 2. 解析Token获取用户名
String username = jwtTokenUtil.getUsernameFromToken(token);
if (username == null) {
log.warn("无效的Token");
return false;
}
// 3. 加载用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (userDetails == null) {
log.warn("用户不存在: {}", username);
return false;
}
// 4. 验证Token
if (!jwtTokenUtil.validateToken(token, userDetails)) {
log.warn("Token验证失败: {}", username);
return false;
}
// 5. 将用户信息存入attributes供Handler使用
if (userDetails instanceof CustomUserDetails) {
CustomUserDetails customUserDetails = (CustomUserDetails) userDetails;
attributes.put("userId", customUserDetails.getUserId());
attributes.put("username", customUserDetails.getUsername());
log.info("WebSocket认证成功: userId={}, username={}",
customUserDetails.getUserId(), customUserDetails.getUsername());
}
// 6. 提取客户端信息
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
String clientIp = getClientIp(servletRequest);
String userAgent = servletRequest.getServletRequest().getHeader("User-Agent");
attributes.put("clientIp", clientIp);
attributes.put("userAgent", userAgent);
}
return true;
} catch (Exception e) {
log.error("WebSocket认证异常", e);
return false;
}
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Exception exception) {
// 握手后处理可选
}
/**
* 从请求中提取Token
* 支持两种方式
* 1. URL参数: ?token=xxx
* 2. Header: Authorization: Bearer xxx
*/
private String extractToken(ServerHttpRequest request) {
// 1. 尝试从URL参数获取
String query = request.getURI().getQuery();
if (query != null && query.contains("token=")) {
String[] params = query.split("&");
for (String param : params) {
if (param.startsWith("token=")) {
return param.substring(6);
}
}
}
// 2. 尝试从Header获取
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
String authHeader = servletRequest.getServletRequest().getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7);
}
}
return null;
}
/**
* 获取客户端真实IP
*/
private String getClientIp(ServletServerHttpRequest request) {
String ip = request.getServletRequest().getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getServletRequest().getHeader("X-Real-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getServletRequest().getRemoteAddr();
}
// 如果是多级代理取第一个IP
if (ip != null && ip.contains(",")) {
ip = ip.split(",")[0].trim();
}
return ip;
}
}

View File

@ -0,0 +1,45 @@
package com.qqchen.deploy.backend.deploy.config;
import com.qqchen.deploy.backend.deploy.handler.ServerSSHWebSocketHandler;
import com.qqchen.deploy.backend.deploy.handler.TeamApplicationLogStreamWebSocketHandler;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* WebSocket配置类
*/
@Slf4j
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Resource
private ServerSSHWebSocketHandler serverSSHWebSocketHandler;
@Resource
private TeamApplicationLogStreamWebSocketHandler teamApplicationLogStreamWebSocketHandler;
@Resource
private WebSocketAuthInterceptor webSocketAuthInterceptor;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
log.info("注册WebSocket处理器: /api/v1/server-ssh/connect/*");
// 注册SSH终端WebSocket处理器添加认证拦截器
registry.addHandler(serverSSHWebSocketHandler, "/api/v1/server-ssh/connect/{serverId}")
.addInterceptors(webSocketAuthInterceptor) // 添加认证拦截器
.setAllowedOrigins("*"); // 生产环境建议配置具体的域名
log.info("注册WebSocket处理器: /api/v1/team-applications/{teamAppId}/logs/stream");
// 注册日志流WebSocket处理器添加认证拦截器
registry.addHandler(teamApplicationLogStreamWebSocketHandler, "/api/v1/team-applications/{teamAppId}/logs/stream")
.addInterceptors(webSocketAuthInterceptor) // 添加认证拦截器
.setAllowedOrigins("*"); // 生产环境建议配置具体的域名
}
}

View File

@ -0,0 +1,10 @@
package com.qqchen.deploy.backend.deploy.converter;
import com.qqchen.deploy.backend.deploy.dto.K8sDeploymentDTO;
import com.qqchen.deploy.backend.deploy.entity.K8sDeployment;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import org.mapstruct.Mapper;
@Mapper(config = BaseConverter.class)
public interface K8sDeploymentConverter extends BaseConverter<K8sDeployment, K8sDeploymentDTO> {
}

View File

@ -0,0 +1,10 @@
package com.qqchen.deploy.backend.deploy.converter;
import com.qqchen.deploy.backend.deploy.dto.K8sNamespaceDTO;
import com.qqchen.deploy.backend.deploy.entity.K8sNamespace;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import org.mapstruct.Mapper;
@Mapper(config = BaseConverter.class)
public interface K8sNamespaceConverter extends BaseConverter<K8sNamespace, K8sNamespaceDTO> {
}

View File

@ -0,0 +1,10 @@
package com.qqchen.deploy.backend.deploy.converter;
import com.qqchen.deploy.backend.deploy.dto.K8sSyncHistoryDTO;
import com.qqchen.deploy.backend.deploy.entity.K8sSyncHistory;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import org.mapstruct.Mapper;
@Mapper(config = BaseConverter.class)
public interface K8sSyncHistoryConverter extends BaseConverter<K8sSyncHistory, K8sSyncHistoryDTO> {
}

View File

@ -0,0 +1,85 @@
package com.qqchen.deploy.backend.deploy.converter;
import com.qqchen.deploy.backend.deploy.dto.FileUploadResultDTO;
import com.qqchen.deploy.backend.deploy.dto.RemoteFileInfoDTO;
import com.qqchen.deploy.backend.framework.ssh.file.SSHFileInfo;
import com.qqchen.deploy.backend.framework.utils.FileUtils;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import java.util.Date;
/**
* 远程文件信息转换器使用MapStruct
*/
@Mapper(componentModel = "spring")
public interface RemoteFileInfoConverter {
@Mapping(target = "sizeFormatted", source = "size", qualifiedByName = "formatFileSize")
@Mapping(target = "permissionsOctal", source = "permissionMask", qualifiedByName = "formatPermissionsOctal")
@Mapping(target = "owner", source = "ownerUid", qualifiedByName = "formatOwner")
@Mapping(target = "group", source = "groupGid", qualifiedByName = "formatGroup")
@Mapping(target = "permissions", source = "permissionString")
RemoteFileInfoDTO toDTO(SSHFileInfo fileInfo);
/**
* 转换SSHFileInfo为上传结果DTO
*/
default FileUploadResultDTO toUploadResultDTO(SSHFileInfo fileInfo) {
return FileUploadResultDTO.builder()
.fileName(fileInfo.getName())
.remotePath(fileInfo.getPath())
.fileSize(fileInfo.getSize())
.fileSizeFormatted(formatFileSize(fileInfo.getSize()))
.uploadTime(new Date())
.permissions(fileInfo.getPermissionString())
.success(true)
.build();
}
/**
* 格式化文件大小
*/
@Named("formatFileSize")
default String formatFileSize(Long size) {
return FileUtils.formatFileSize(size);
}
/**
* 格式化权限为八进制字符串
*/
@Named("formatPermissionsOctal")
default String formatPermissionsOctal(Integer permissionMask) {
if (permissionMask == null) {
return null;
}
return String.format("0%o", permissionMask);
}
/**
* 格式化所有者信息
*/
@Named("formatOwner")
default String formatOwner(Integer uid) {
if (uid == null) {
return null;
}
// TODO: 未来可以通过执行 getent passwd 命令获取用户名
// 目前只返回 UID 的格式
return "(" + uid + ")";
}
/**
* 格式化组信息
*/
@Named("formatGroup")
default String formatGroup(Integer gid) {
if (gid == null) {
return null;
}
// TODO: 未来可以通过执行 getent group 命令获取组名
// 目前只返回 GID 的格式
return "(" + gid + ")";
}
}

View File

@ -0,0 +1,13 @@
package com.qqchen.deploy.backend.deploy.converter;
import com.qqchen.deploy.backend.deploy.dto.SSHAuditLogDTO;
import com.qqchen.deploy.backend.deploy.entity.SSHAuditLog;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import org.mapstruct.Mapper;
/**
* SSH审计日志转换器
*/
@Mapper(config = BaseConverter.class)
public interface SSHAuditLogConverter extends BaseConverter<SSHAuditLog, SSHAuditLogDTO> {
}

View File

@ -0,0 +1,13 @@
package com.qqchen.deploy.backend.deploy.converter;
import com.qqchen.deploy.backend.deploy.dto.ServerAlertRuleDTO;
import com.qqchen.deploy.backend.deploy.entity.ServerAlertRule;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import org.mapstruct.Mapper;
/**
* 服务器告警规则转换器
*/
@Mapper(config = BaseConverter.class)
public interface ServerAlertRuleConverter extends BaseConverter<ServerAlertRule, ServerAlertRuleDTO> {
}

View File

@ -0,0 +1,13 @@
package com.qqchen.deploy.backend.deploy.converter;
import com.qqchen.deploy.backend.deploy.dto.TeamBookmarkCategoryDTO;
import com.qqchen.deploy.backend.deploy.entity.TeamBookmarkCategory;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import org.mapstruct.Mapper;
/**
* 团队导航分类Converter
*/
@Mapper(config = BaseConverter.class)
public interface TeamBookmarkCategoryConverter extends BaseConverter<TeamBookmarkCategory, TeamBookmarkCategoryDTO> {
}

View File

@ -0,0 +1,13 @@
package com.qqchen.deploy.backend.deploy.converter;
import com.qqchen.deploy.backend.deploy.dto.TeamBookmarkDTO;
import com.qqchen.deploy.backend.deploy.entity.TeamBookmark;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import org.mapstruct.Mapper;
/**
* 团队导航书签Converter
*/
@Mapper(config = BaseConverter.class)
public interface TeamBookmarkConverter extends BaseConverter<TeamBookmark, TeamBookmarkDTO> {
}

View File

@ -0,0 +1,60 @@
package com.qqchen.deploy.backend.deploy.converter;
import com.qqchen.deploy.backend.deploy.dto.TeamEnvironmentNotificationConfigDTO;
import com.qqchen.deploy.backend.deploy.dto.UserTeamEnvironmentNotificationConfigDTO;
import com.qqchen.deploy.backend.deploy.entity.TeamEnvironmentNotificationConfig;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import com.qqchen.deploy.backend.notification.entity.NotificationChannel;
import com.qqchen.deploy.backend.notification.entity.NotificationTemplate;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import java.util.Map;
import java.util.Optional;
/**
* 团队环境通知配置Converter
*
* @author qqchen
* @since 2025-11-11
*/
@Mapper(componentModel = "spring")
public interface TeamEnvironmentNotificationConfigConverter
extends BaseConverter<TeamEnvironmentNotificationConfig, TeamEnvironmentNotificationConfigDTO> {
/**
* 转换为用户环境通知配置DTO包含扩展字段
*/
@Mapping(target = "notificationChannelName",
expression = "java(getChannelName(config.getNotificationChannelId(), channelMap))")
@Mapping(target = "preApprovalNotificationTemplateName",
expression = "java(getTemplateName(config.getPreApprovalNotificationTemplateId(), templateMap))")
@Mapping(target = "buildNotificationTemplateName",
expression = "java(getTemplateName(config.getBuildNotificationTemplateId(), templateMap))")
UserTeamEnvironmentNotificationConfigDTO toUserDTO(
TeamEnvironmentNotificationConfig config,
@Context Map<Long, NotificationChannel> channelMap,
@Context Map<Long, NotificationTemplate> templateMap
);
/**
* 获取渠道名称
*/
default String getChannelName(Long channelId, Map<Long, NotificationChannel> channelMap) {
return Optional.ofNullable(channelId)
.map(channelMap::get)
.map(NotificationChannel::getName)
.orElse(null);
}
/**
* 获取模板名称
*/
default String getTemplateName(Long templateId, Map<Long, NotificationTemplate> templateMap) {
return Optional.ofNullable(templateId)
.map(templateMap::get)
.map(NotificationTemplate::getName)
.orElse(null);
}
}

View File

@ -2,7 +2,6 @@ package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.deploy.enums.DevelopmentLanguageTypeEnum;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import com.qqchen.deploy.backend.system.model.ExternalSystemDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
@ -24,27 +23,17 @@ public class ApplicationDTO extends BaseDTO {
@NotNull(message = "开发语言")
private DevelopmentLanguageTypeEnum language;
@Schema(description = "代码仓库项目ID")
private Long repoProjectId;
@Schema(description = "应用分类ID")
private Long applicationCategoryId;
@Schema(description = "三方系统ID")
private Long externalSystemId;
@Schema(description = "是否启用")
private Boolean enabled;
@NotNull(message = "排序号不能为空")
private Integer sort;
private RepositoryProjectDTO repositoryProject;
private ApplicationCategoryDTO applicationCategory;
private ExternalSystemDTO externalSystem;
@Schema(description = "关联的团队数量")
private Long teamCount;

View File

@ -1,46 +0,0 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.fasterxml.jackson.databind.JsonNode;
import com.qqchen.deploy.backend.deploy.enums.BuildTypeEnum;
import com.qqchen.deploy.backend.deploy.enums.DevelopmentLanguageTypeEnum;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 应用配置DTO
*/
@Data
@Schema(description = "应用配置")
@EqualsAndHashCode(callSuper = true)
public class DeployAppBuildDTO extends BaseDTO {
@Schema(description = "构建类型")
@NotNull(message = "构建类型不能为空")
private BuildTypeEnum buildType;
@NotNull(message = "应用语言不能为空")
private DevelopmentLanguageTypeEnum languageType;
@Schema(description = "表单配置")
private JsonNode formVariables;
@Schema(description = "构建配置")
@NotNull(message = "构建配置不能为空")
private JsonNode buildVariables;
@Schema(description = "环境ID")
@NotNull(message = "环境ID不能为空")
private Long environmentId;
@Schema(description = "应用ID")
@NotNull(message = "应用ID不能为空")
private Long applicationId;
@NotNull(message = "已发布的流程定义ID")
private Long workflowDefinitionId;
}

View File

@ -8,7 +8,7 @@ import java.time.LocalDateTime;
/**
* 部署审批任务DTO
*
* <p>扩展了审批任务的基本信息增加部署相关的业务上下文
* <p>结构化的审批任务信息包含审批任务部署记录关联实体等信息
* <p>用于部署审批列表让审批人员快速了解审批内容
*
* @author qqchen
@ -18,92 +18,171 @@ import java.time.LocalDateTime;
@Schema(description = "部署审批任务信息")
public class DeployApprovalTaskDTO {
// ============ 审批任务基本信息 ============
// ========== 审批任务信息 ==========
@Schema(description = "审批任务信息")
private ApprovalTaskInfo approvalTask;
@Schema(description = "任务ID")
private String taskId;
// ========== 部署记录信息 ==========
@Schema(description = "部署记录信息")
private DeployRecordInfo deployRecord;
@Schema(description = "任务名称")
private String taskName;
// ========== 关联实体信息 ==========
@Schema(description = "团队信息")
private TeamInfo team;
@Schema(description = "任务描述")
private String taskDescription;
@Schema(description = "应用信息")
private ApplicationInfo application;
@Schema(description = "流程实例ID")
private String processInstanceId;
@Schema(description = "环境信息")
private EnvironmentInfo environment;
@Schema(description = "流程定义ID")
private String processDefinitionId;
@Schema(description = "发起人信息")
private UserSimpleInfo deployUser;
@Schema(description = "审批人")
private String assignee;
// ========== 嵌套类定义 ==========
@Schema(description = "创建时间")
private LocalDateTime createTime;
/**
* 审批任务信息
*/
@Data
@Schema(description = "审批任务信息")
public static class ApprovalTaskInfo {
@Schema(description = "到期时间")
private LocalDateTime dueDate;
@Schema(description = "任务ID")
private String taskId;
@Schema(description = "审批标题")
private String approvalTitle;
@Schema(description = "任务名称")
private String taskName;
@Schema(description = "审批内容")
private String approvalContent;
@Schema(description = "任务描述")
private String taskDescription;
@Schema(description = "审批模式: SINGLE-单人审批, MULTI-多人会签, OR-多人或签")
private String approvalMode;
@Schema(description = "流程实例ID")
private String processInstanceId;
@Schema(description = "是否允许转交")
private Boolean allowDelegate;
@Schema(description = "流程定义ID")
private String processDefinitionId;
@Schema(description = "是否允许加签")
private Boolean allowAddSign;
@Schema(description = "审批人用户名")
private String assignee;
@Schema(description = "是否必须填写意见")
private Boolean requireComment;
@Schema(description = "创建时间")
private LocalDateTime createTime;
// ============ 部署业务上下文信息 ============
@Schema(description = "到期时间")
private LocalDateTime dueDate;
@Schema(description = "部署记录ID")
private Long deployRecordId;
@Schema(description = "待审批时长(毫秒)- 从任务创建到现在的时长")
private Long pendingDuration;
@Schema(description = "业务标识UUID")
private String businessKey;
@Schema(description = "审批标题")
private String approvalTitle;
@Schema(description = "团队ID")
private Long teamId;
@Schema(description = "审批内容")
private String approvalContent;
@Schema(description = "团队名称")
private String teamName;
@Schema(description = "审批模式: SINGLE-单人审批, MULTI-多人会签, OR-多人或签")
private String approvalMode;
@Schema(description = "应用ID")
private Long applicationId;
@Schema(description = "是否允许转交")
private Boolean allowDelegate;
@Schema(description = "应用编码")
private String applicationCode;
@Schema(description = "是否允许加签")
private Boolean allowAddSign;
@Schema(description = "应用名称")
private String applicationName;
@Schema(description = "是否必须填写意见")
private Boolean requireComment;
}
@Schema(description = "环境ID")
private Long environmentId;
/**
* 部署记录信息
*/
@Data
@Schema(description = "部署记录信息")
public static class DeployRecordInfo {
@Schema(description = "环境编码")
private String environmentCode;
@Schema(description = "部署记录ID")
private Long id;
@Schema(description = "环境名称")
private String environmentName;
@Schema(description = "业务标识UUID")
private String businessKey;
@Schema(description = "发起人")
private String deployBy;
@Schema(description = "部署备注")
private String remark;
@Schema(description = "部署备注")
private String deployRemark;
@Schema(description = "部署开始时间")
private LocalDateTime startTime;
}
@Schema(description = "部署开始时间")
private LocalDateTime deployStartTime;
/**
* 团队信息
*/
@Data
@Schema(description = "团队信息")
public static class TeamInfo {
@Schema(description = "待审批时长(毫秒)- 从任务创建到现在的时长")
private Long pendingDuration;
@Schema(description = "团队ID")
private Long id;
@Schema(description = "团队名称")
private String name;
}
/**
* 应用信息
*/
@Data
@Schema(description = "应用信息")
public static class ApplicationInfo {
@Schema(description = "应用ID")
private Long id;
@Schema(description = "应用编码")
private String code;
@Schema(description = "应用名称")
private String name;
}
/**
* 环境信息
*/
@Data
@Schema(description = "环境信息")
public static class EnvironmentInfo {
@Schema(description = "环境ID")
private Long id;
@Schema(description = "环境编码")
private String code;
@Schema(description = "环境名称")
private String name;
}
/**
* 用户简要信息
*/
@Data
@Schema(description = "用户简要信息")
public static class UserSimpleInfo {
@Schema(description = "用户名")
private String username;
@Schema(description = "昵称")
private String nickname;
@Schema(description = "邮箱")
private String email;
@Schema(description = "手机号")
private String phone;
@Schema(description = "部门名称")
private String departmentName;
}
}

View File

@ -0,0 +1,163 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.deploy.enums.BuildTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.Date;
import java.util.List;
/**
* 部署执行请求
*
* @author qqchen
* @since 2025-11-02
*/
@Data
@Schema(description = "部署执行请求")
public class DeployExecuteRequest {
@Schema(description = "构建类型", required = true)
@NotNull(message = "构建类型不能为空")
private BuildTypeEnum buildType;
@Schema(description = "Jenkins配置buildType为JENKINS时必填")
@Valid
private JenkinsConfig jenkins;
@Schema(description = "团队ID", required = true)
@NotNull(message = "团队ID不能为空")
private Long teamId;
@Schema(description = "团队应用关联ID", required = true)
@NotNull(message = "团队应用关联ID不能为空")
private Long teamApplicationId;
@Schema(description = "应用ID", required = true)
@NotNull(message = "应用ID不能为空")
private Long applicationId;
@Schema(description = "应用编码", required = true)
@NotBlank(message = "应用编码不能为空")
private String applicationCode;
@Schema(description = "应用名称", required = true)
@NotBlank(message = "应用名称不能为空")
private String applicationName;
@Schema(description = "环境ID", required = true)
@NotNull(message = "环境ID不能为空")
private Long environmentId;
@Schema(description = "环境编码", required = true)
@NotBlank(message = "环境编码不能为空")
private String environmentCode;
@Schema(description = "环境名称", required = true)
@NotBlank(message = "环境名称不能为空")
private String environmentName;
@Schema(description = "审批配置", required = true)
@NotNull(message = "审批配置不能为空")
@Valid
private ApprovalConfig approval;
@Schema(description = "通知配置", required = true)
@NotNull(message = "通知配置不能为空")
@Valid
private NotificationConfig notification;
@Schema(description = "执行时间", required = true)
private Date deployDate = new Date();
@Schema(description = "部署备注")
private String deployRemark;
private String deployUser;
@Schema(description = "源Git仓库配置")
@Valid
private GitRepositoryConfig sourceRepository;
@Schema(description = "目标Git仓库配置")
@Valid
private GitRepositoryConfig targetRepository;
/**
* Git仓库配置
*/
@Data
@Schema(description = "Git仓库配置")
public static class GitRepositoryConfig {
@Schema(description = "Git系统ID")
private Long systemId;
@Schema(description = "Git项目ID外部系统的项目ID")
private Long projectId;
@Schema(description = "分支名称")
private String branch;
}
/**
* Jenkins配置
*/
@Data
@Schema(description = "Jenkins配置")
public static class JenkinsConfig {
@Schema(description = "Jenkins服务器ID", required = true)
@NotNull(message = "Jenkins服务器ID不能为空")
private Long serverId;
@Schema(description = "Jenkins任务名称", required = true)
@NotBlank(message = "Jenkins任务名称不能为空")
private String jobName;
@Schema(description = "Git分支")
private String branch;
}
/**
* 审批配置
*/
@Data
@Schema(description = "审批配置")
public static class ApprovalConfig {
@Schema(description = "是否需要审批", required = true)
@NotNull(message = "是否需要审批不能为空")
private Boolean required;
@Schema(description = "审批人ID列表逗号分隔")
private List<String> userNames;
}
/**
* 通知配置
*/
@Data
@Schema(description = "通知配置")
public static class NotificationConfig {
@Schema(description = "通知渠道ID")
private Long notificationChannelId;
@Schema(description = "是否启用审批前提醒")
private Boolean preApprovalNotificationEnabled;
@Schema(description = "审批前提醒模板ID")
private Long preApprovalNotificationTemplateId;
@Schema(description = "是否启用构建通知")
private Boolean buildNotificationEnabled;
@Schema(description = "构建通知模板ID")
private Long buildNotificationTemplateId;
@Schema(description = "构建失败时是否发送日志文件")
private Boolean buildFailureFileEnabled;
}
}

View File

@ -0,0 +1,59 @@
package com.qqchen.deploy.backend.deploy.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 部署节点日志响应DTO
*
* @author qqchen
* @since 2025-11-07
*/
@Data
@Schema(description = "部署节点日志响应")
public class DeployNodeLogDTO {
@Schema(description = "流程实例ID")
private String processInstanceId;
@Schema(description = "节点ID")
private String nodeId;
@Schema(description = "节点名称")
private String nodeName;
@Schema(description = "日志列表")
private List<LogEntry> logs;
@Schema(description = "日志是否已过期超过7天被清除")
private Boolean expired;
@Schema(description = "提示信息")
private String message;
/**
* 日志条目
*/
@Data
@Schema(description = "日志条目")
public static class LogEntry {
@Schema(description = "序列号")
private Long sequenceId;
@Schema(description = "时间戳(毫秒)")
private Long timestamp;
@Schema(description = "日志级别INFO/WARN/ERROR/DEBUG")
private String level;
@Schema(description = "日志来源JENKINS/FLOWABLE/SHELL/NOTIFICATION")
private String source;
@Schema(description = "日志内容")
private String message;
}
}

View File

@ -1,24 +0,0 @@
package com.qqchen.deploy.backend.deploy.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 部署请求DTO
*
* @author qqchen
* @since 2025-11-02
*/
@Data
@Schema(description = "部署请求")
public class DeployRequestDTO {
@Schema(description = "团队应用关联ID", required = true)
@NotNull(message = "团队应用关联ID不能为空")
private Long teamApplicationId;
@Schema(description = "部署备注")
private String remark;
}

View File

@ -1,64 +0,0 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.deploy.enums.DeployRecordStatusEnums;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 可部署应用DTO
*
* @author qqchen
* @since 2025-11-02
*/
@Data
@Schema(description = "可部署应用信息")
public class DeployableApplicationDTO {
@Schema(description = "团队应用关联ID")
private Long teamApplicationId;
@Schema(description = "应用ID")
private Long applicationId;
@Schema(description = "应用编码")
private String applicationCode;
@Schema(description = "应用名称")
private String applicationName;
@Schema(description = "应用描述")
private String applicationDesc;
@Schema(description = "分支名称")
private String branch;
@Schema(description = "部署系统IDJenkins系统")
private Long deploySystemId;
@Schema(description = "部署系统名称")
private String deploySystemName;
@Schema(description = "部署任务IDJenkins Job")
private String deployJob;
@Schema(description = "工作流定义ID")
private Long workflowDefinitionId;
@Schema(description = "工作流定义名称")
private String workflowDefinitionName;
@Schema(description = "工作流流程标识processKey")
private String workflowDefinitionKey;
@Schema(description = "部署统计信息")
private DeployStatisticsDTO deployStatistics;
@Schema(description = "是否正在部署中")
private Boolean isDeploying;
@Schema(description = "最近部署记录列表最多10条")
private List<DeployRecordSummaryDTO> recentDeployRecords;
}

View File

@ -0,0 +1,60 @@
package com.qqchen.deploy.backend.deploy.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* 文件上传结果DTO业务层
*
* 用于返回给前端的上传结果信息
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FileUploadResultDTO {
/**
* 文件名
*/
private String fileName;
/**
* 远程路径
*/
private String remotePath;
/**
* 文件大小字节
*/
private Long fileSize;
/**
* 格式化的文件大小1.5 MB
*/
private String fileSizeFormatted;
/**
* 上传时间
*/
private Date uploadTime;
/**
* 权限字符串
*/
private String permissions;
/**
* 是否成功
*/
private Boolean success;
/**
* 错误消息如果失败
*/
private String errorMessage;
}

View File

@ -0,0 +1,99 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
import java.util.Map;
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "K8S Deployment DTO")
public class K8sDeploymentDTO extends BaseDTO {
@Schema(description = "K8S集群ID")
private Long externalSystemId;
@Schema(description = "命名空间ID")
private Long namespaceId;
@Schema(description = "Deployment名称")
private String deploymentName;
@Schema(description = "期望副本数来自spec.replicas用户声明的目标副本数")
private Integer desiredReplicas;
@Schema(description = "当前副本数来自status.replicas所有Pod总数包括Running/Pending/Failed等所有状态")
private Integer currentReplicas;
@Schema(description = "可用副本数来自status.availableReplicasReady且可接收流量的Pod数")
private Integer availableReplicas;
@Schema(description = "就绪副本数来自status.readyReplicas健康检查通过的Pod数")
private Integer readyReplicas;
@Schema(description = "已更新副本数来自status.updatedReplicas已更新到最新版本的Pod数")
private Integer updatedReplicas;
@Schema(description = "不可用副本数来自status.unavailableReplicas不可用的Pod数")
private Integer unavailableReplicas;
@Schema(description = "总重启次数所有Pod的重启次数总和")
private Integer totalRestartCount;
@Schema(description = "实际Pod总数通过Pod API统计包括所有状态的PodRunning/Failed/Pending等")
private Integer actualPodCount;
@Schema(description = "Running状态的Pod数量")
private Integer runningPodCount;
@Schema(description = "Pending状态的Pod数量")
private Integer pendingPodCount;
@Schema(description = "Failed状态的Pod数量")
private Integer failedPodCount;
@Schema(description = "Succeeded状态的Pod数量")
private Integer succeededPodCount;
@Schema(description = "Unknown状态的Pod数量")
private Integer unknownPodCount;
@Schema(description = "Ready状态的Pod数量所有容器健康检查通过")
private Integer readyPodCount;
@Schema(description = "Not Ready状态的Pod数量至少一个容器健康检查未通过")
private Integer notReadyPodCount;
@Schema(description = "CPU请求总和\"2000m\"\"2\"")
private String totalCpuRequest;
@Schema(description = "内存请求总和(如\"4Gi\"")
private String totalMemoryRequest;
@Schema(description = "CPU限制总和")
private String totalCpuLimit;
@Schema(description = "内存限制总和")
private String totalMemoryLimit;
@Schema(description = "容器镜像")
private String image;
@Schema(description = "标签")
private Map<String, String> labels;
@Schema(description = "选择器")
private Map<String, String> selector;
@Schema(description = "K8S中的创建时间")
private LocalDateTime k8sCreateTime;
@Schema(description = "K8s资源版本号用于增量同步")
private String resourceVersion;
@Schema(description = "YAML配置")
private String yamlConfig;
}

View File

@ -0,0 +1,25 @@
package com.qqchen.deploy.backend.deploy.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* K8S Pod日志行
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class K8sLogLine {
/**
* 日志时间戳RFC3339格式
* 例如2025-12-13T23:00:01.123456789Z
*/
private String timestamp;
/**
* 日志内容
*/
private String content;
}

View File

@ -0,0 +1,38 @@
package com.qqchen.deploy.backend.deploy.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* K8S Pod日志选择器
* 基于Kubernetes Dashboard的引用点系统
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class K8sLogSelection {
/**
* 引用点时间戳
* 特殊值
* - "newest": 最新的日志行
* - "oldest": 最早的日志行
* - RFC3339时间戳具体的时间点
*/
private String referenceTimestamp;
/**
* 相对于引用点的起始偏移量包含
* 负数表示引用点之前的行
* 例如-100 表示引用点之前100行
*/
private Integer offsetFrom;
/**
* 相对于引用点的结束偏移量不包含
* 正数表示引用点之后的行
* 例如1 表示引用点之后1行不包含
*/
private Integer offsetTo;
}

View File

@ -0,0 +1,32 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Map;
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "K8S命名空间DTO")
public class K8sNamespaceDTO extends BaseDTO {
@Schema(description = "K8S集群ID")
private Long externalSystemId;
@Schema(description = "命名空间名称")
private String namespaceName;
@Schema(description = "状态")
private String status;
@Schema(description = "标签")
private Map<String, String> labels;
@Schema(description = "YAML配置")
private String yamlConfig;
@Schema(description = "Deployment数量仅在列表和分页查询时填充")
private Long deploymentCount;
}

View File

@ -0,0 +1,48 @@
package com.qqchen.deploy.backend.deploy.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* K8S Pod日志响应引用点模式
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class K8sPodLogsResponse {
/**
* Pod名称
*/
private String podName;
/**
* 容器名称
*/
private String containerName;
/**
* 用于向前翻页的引用点选择器
* 使用此选择器可以获取更早的日志
*/
private K8sLogSelection referenceForPrevious;
/**
* 用于向后翻页/轮询的引用点选择器
* 使用此选择器可以获取更新的日志轮询时使用
*/
private K8sLogSelection referenceForNext;
/**
* 日志行列表
*/
private List<K8sLogLine> logs;
/**
* 是否被截断
*/
private Boolean truncated;
}

View File

@ -0,0 +1,37 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.deploy.enums.ExternalSystemSyncStatus;
import com.qqchen.deploy.backend.deploy.enums.K8sSyncType;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "K8S同步历史DTO")
public class K8sSyncHistoryDTO extends BaseDTO {
@Schema(description = "同步编号")
private String number;
@Schema(description = "同步类型")
private K8sSyncType syncType;
@Schema(description = "同步状态")
private ExternalSystemSyncStatus status;
@Schema(description = "开始时间")
private LocalDateTime startTime;
@Schema(description = "结束时间")
private LocalDateTime endTime;
@Schema(description = "错误信息")
private String errorMessage;
@Schema(description = "K8S集群ID")
private Long externalSystemId;
}

View File

@ -0,0 +1,95 @@
package com.qqchen.deploy.backend.deploy.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* 远程文件信息DTO业务层
*
* 用于返回给前端的文件信息
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RemoteFileInfoDTO {
/**
* 文件/目录名称
*/
private String name;
/**
* 完整路径
*/
private String path;
/**
* 是否是目录
*/
private Boolean isDirectory;
/**
* 文件大小字节
*/
private Long size;
/**
* 格式化的文件大小1.5 MB
*/
private String sizeFormatted;
/**
* 权限字符串rwxr-xr-x
*/
private String permissions;
/**
* 权限八进制表示0755
*/
private String permissionsOctal;
/**
* 修改时间
*/
private Date modifyTime;
/**
* 所有者格式用户名 (UID)root (0)
*/
private String owner;
/**
* 所有者UID
*/
private Integer ownerUid;
/**
* 格式组名 (GID)root (0)
*/
private String group;
/**
* 组GID
*/
private Integer groupGid;
/**
* 文件扩展名
*/
private String extension;
/**
* 是否是符号链接
*/
private Boolean isSymlink;
/**
* 符号链接目标
*/
private String linkTarget;
}

View File

@ -35,6 +35,8 @@ public class RepositoryProjectDTO extends BaseDTO {
private Long repoGroupId;
private String repoGroupName;
private Long repoProjectId;
/**

View File

@ -0,0 +1,62 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* SSH审计日志DTO
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "SSH审计日志")
public class SSHAuditLogDTO extends BaseDTO {
@Schema(description = "用户ID")
private Long userId;
@Schema(description = "用户名")
private String username;
@Schema(description = "服务器ID")
private Long serverId;
@Schema(description = "服务器名称")
private String serverName;
@Schema(description = "服务器IP")
private String serverIp;
@Schema(description = "会话ID")
private String sessionId;
@Schema(description = "连接时间")
private LocalDateTime connectTime;
@Schema(description = "断开时间")
private LocalDateTime disconnectTime;
@Schema(description = "会话时长(秒)")
private Integer durationSeconds;
@Schema(description = "客户端IP")
private String clientIp;
@Schema(description = "浏览器UA")
private String userAgent;
@Schema(description = "执行命令数量")
private Integer commandCount;
@Schema(description = "执行的命令记录JSON")
private String commands;
@Schema(description = "状态")
private String status;
@Schema(description = "错误信息")
private String errorMessage;
}

View File

@ -0,0 +1,19 @@
package com.qqchen.deploy.backend.deploy.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* Deployment扩缩容请求
*/
@Data
@Schema(description = "Deployment扩缩容请求")
public class ScaleDeploymentRequest {
@NotNull(message = "副本数不能为空")
@Min(value = 0, message = "副本数不能小于0")
@Schema(description = "目标副本数", example = "3", required = true)
private Integer replicas;
}

View File

@ -0,0 +1,54 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.framework.enums.MonitorMetricEnum;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
/**
* 服务器告警规则DTO
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "服务器告警规则")
public class ServerAlertRuleDTO extends BaseDTO {
@Schema(description = "服务器IDNULL表示全局规则")
private Long serverId;
@Schema(description = "规则名称", required = true)
@NotBlank(message = "规则名称不能为空")
@Size(max = 100, message = "规则名称长度不能超过100个字符")
private String ruleName;
@Schema(description = "监控指标类型: CPU/MEMORY/DISK/NETWORK", required = true)
@NotNull(message = "监控指标类型不能为空")
private MonitorMetricEnum alertType;
@Schema(description = "警告阈值(CPU/MEMORY/DISK为%NETWORK为MB/s)", required = true, example = "80.00")
@NotNull(message = "警告阈值不能为空")
@DecimalMin(value = "0.00", message = "警告阈值必须大于等于0")
@DecimalMax(value = "10000.00", message = "警告阈值不能超过10000")
private BigDecimal warningThreshold;
@Schema(description = "严重阈值(CPU/MEMORY/DISK为%NETWORK为MB/s)", required = true, example = "90.00")
@NotNull(message = "严重阈值不能为空")
@DecimalMin(value = "0.00", message = "严重阈值必须大于等于0")
@DecimalMax(value = "10000.00", message = "严重阈值不能超过10000")
private BigDecimal criticalThreshold;
@Schema(description = "持续时长(分钟)", example = "5")
@Min(value = 1, message = "持续时长至少为1分钟")
private Integer durationMinutes;
@Schema(description = "是否启用")
private Boolean enabled;
@Schema(description = "规则描述")
@Size(max = 500, message = "描述长度不能超过500个字符")
private String description;
}

View File

@ -1,12 +1,14 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.deploy.enums.AuthTypeEnum;
import com.qqchen.deploy.backend.deploy.enums.OsTypeEnum;
import com.qqchen.deploy.backend.framework.dto.DiskInfo;
import com.qqchen.deploy.backend.framework.enums.AuthTypeEnum;
import com.qqchen.deploy.backend.framework.enums.OsTypeEnum;
import com.qqchen.deploy.backend.deploy.enums.ServerStatusEnum;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
import java.util.List;
/**
* 服务器 DTO
@ -96,10 +98,15 @@ public class ServerDTO extends BaseDTO {
private Integer memorySize;
/**
* 磁盘大小(GB)
* 磁盘总容量(GB)
*/
private Integer diskSize;
/**
* 磁盘详细信息列表
*/
private List<DiskInfo> diskInfo;
/**
* 标签JSON格式
*/

View File

@ -0,0 +1,69 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.framework.dto.DiskInfo;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* 服务器信息DTO
* 用于测试连接接口的返回
*/
@Data
public class ServerInfoDTO {
// ===== 连接状态 =====
/**
* 连接是否成功
*/
private Boolean connected;
/**
* 错误信息连接失败时
*/
private String errorMessage;
/**
* 连接时间
*/
private LocalDateTime connectTime;
/**
* 响应时间毫秒
*/
private Long responseTime;
// ===== 基础硬件信息 =====
/**
* 主机名
*/
private String hostname;
/**
* 操作系统版本
*/
private String osVersion;
/**
* CPU核心数
*/
private Integer cpuCores;
/**
* 内存大小(GB)
*/
private Integer memorySize;
/**
* 磁盘总容量(GB)
*/
private Integer diskSize;
/**
* 磁盘详细信息列表
*/
private List<DiskInfo> diskInfo;
}

View File

@ -1,9 +1,12 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.framework.dto.DiskInfo;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
/**
* 服务器初始化 DTO
@ -27,12 +30,18 @@ public class ServerInitializeDTO {
private Integer memorySize;
/**
* 磁盘大小(GB)
* 磁盘总容量(GB)
*/
@NotNull(message = "磁盘大小不能为空")
@Min(value = 1, message = "磁盘大小至少为1GB")
private Integer diskSize;
/**
* 磁盘详细信息列表
*/
@NotEmpty(message = "磁盘信息不能为空")
private List<DiskInfo> diskInfo;
/**
* 操作系统版本
*/

View File

@ -0,0 +1,53 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.framework.dto.DiskUsageInfo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
/**
* 服务器监控数据DTO
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "服务器监控数据")
public class ServerMonitorDataDTO {
@Schema(description = "服务器ID")
private Long serverId;
@Schema(description = "CPU使用率(%)")
private BigDecimal cpuUsage;
@Schema(description = "内存使用率(%)")
private BigDecimal memoryUsage;
@Schema(description = "已用内存(GB)")
private Integer memoryUsed;
@Schema(description = "各分区磁盘使用率")
private List<DiskUsageInfo> diskUsage;
@Schema(description = "网络接收累计流量(KB) - 用于数据库存储")
private Long networkRx;
@Schema(description = "网络发送累计流量(KB) - 用于数据库存储")
private Long networkTx;
@Schema(description = "网络接收速率(KB/s) - 用于告警检查")
private Long networkRxSpeed;
@Schema(description = "网络发送速率(KB/s) - 用于告警检查")
private Long networkTxSpeed;
@Schema(description = "采集时间")
private LocalDateTime collectTime;
}

View File

@ -0,0 +1,268 @@
package com.qqchen.deploy.backend.deploy.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
/**
* 服务器监控指标响应
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "服务器监控指标数据")
public class ServerMonitorMetricsDTO {
@Schema(description = "服务器信息")
private ServerInfo server;
@Schema(description = "时间范围信息")
private TimeRangeInfo timeRange;
@Schema(description = "指标数据")
private MetricsData metrics;
@Schema(description = "统计信息")
private StatisticsInfo statistics;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "服务器信息")
public static class ServerInfo {
@Schema(description = "服务器ID")
private Long serverId;
@Schema(description = "服务器名称")
private String serverName;
@Schema(description = "主机IP")
private String hostIp;
@Schema(description = "服务器状态")
private String status;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "时间范围信息")
public static class TimeRangeInfo {
@Schema(description = "开始时间")
private LocalDateTime startTime;
@Schema(description = "结束时间")
private LocalDateTime endTime;
@Schema(description = "聚合间隔")
private String interval;
@Schema(description = "实际数据点数量")
private Integer dataPoints;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "指标数据")
public static class MetricsData {
@Schema(description = "CPU指标数据")
private List<CpuMetric> cpu;
@Schema(description = "内存指标数据")
private List<MemoryMetric> memory;
@Schema(description = "网络指标数据")
private List<NetworkMetric> network;
@Schema(description = "磁盘指标数据")
private DiskMetric disk;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "CPU指标")
public static class CpuMetric {
@Schema(description = "时间点")
private LocalDateTime time;
@Schema(description = "CPU使用率(%)")
private BigDecimal value;
@Schema(description = "采集状态")
private String status;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "内存指标")
public static class MemoryMetric {
@Schema(description = "时间点")
private LocalDateTime time;
@Schema(description = "内存使用率(%)")
private BigDecimal usagePercent;
@Schema(description = "已用内存(GB)")
private Integer usedGB;
@Schema(description = "采集状态")
private String status;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "网络指标")
public static class NetworkMetric {
@Schema(description = "时间点")
private LocalDateTime time;
@Schema(description = "接收字节数")
private Long rxBytes;
@Schema(description = "发送字节数")
private Long txBytes;
@Schema(description = "接收速率(KB/s)")
private BigDecimal rxMBps;
@Schema(description = "发送速率(KB/s)")
private BigDecimal txMBps;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "磁盘指标")
public static class DiskMetric {
@Schema(description = "最新采集时间")
private LocalDateTime latestTime;
@Schema(description = "分区信息列表")
private List<DiskPartition> partitions;
@Schema(description = "时间范围内最大使用率(%)")
private BigDecimal maxUsagePercent;
@Schema(description = "最大使用率的分区")
private String maxUsagePartition;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "磁盘分区")
public static class DiskPartition {
@Schema(description = "挂载点")
private String mountPoint;
@Schema(description = "文件系统")
private String fileSystem;
@Schema(description = "总容量(GB)")
private Long totalSizeGB;
@Schema(description = "已用容量(GB)")
private Long usedSizeGB;
@Schema(description = "使用率(%)")
private BigDecimal usagePercent;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "统计信息")
public static class StatisticsInfo {
@Schema(description = "CPU统计")
private CpuStats cpu;
@Schema(description = "内存统计")
private MemoryStats memory;
@Schema(description = "网络统计")
private NetworkStats network;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "CPU统计")
public static class CpuStats {
@Schema(description = "平均值(%)")
private BigDecimal avg;
@Schema(description = "最大值(%)")
private BigDecimal max;
@Schema(description = "最小值(%)")
private BigDecimal min;
@Schema(description = "峰值时间")
private LocalDateTime maxTime;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "内存统计")
public static class MemoryStats {
@Schema(description = "平均使用率(%)")
private BigDecimal avgPercent;
@Schema(description = "最大使用率(%)")
private BigDecimal maxPercent;
@Schema(description = "最小使用率(%)")
private BigDecimal minPercent;
@Schema(description = "峰值时间")
private LocalDateTime maxTime;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "网络统计")
public static class NetworkStats {
@Schema(description = "总接收字节数")
private Long totalRxBytes;
@Schema(description = "总发送字节数")
private Long totalTxBytes;
@Schema(description = "平均接收速率(KB/s)")
private BigDecimal avgRxMBps;
@Schema(description = "平均发送速率(KB/s)")
private BigDecimal avgTxMBps;
@Schema(description = "峰值接收速率(KB/s)")
private BigDecimal peakRxMBps;
@Schema(description = "峰值发送速率(KB/s)")
private BigDecimal peakTxMBps;
}
}

View File

@ -0,0 +1,27 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.deploy.enums.MonitorTimeRange;
import com.qqchen.deploy.backend.framework.enums.MonitorMetricEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
/**
* 服务器监控指标查询参数
*/
@Data
@Schema(description = "服务器监控指标查询参数")
public class ServerMonitorMetricsQuery {
@Schema(description = "服务器ID", required = true)
private Long serverId;
@NotNull(message = "时间范围不能为空")
@Schema(description = "时间范围", required = true, example = "LAST_1_HOUR")
private MonitorTimeRange timeRange;
@Schema(description = "查询的指标类型列表(为空则查询所有)", example = "[\"CPU\", \"MEMORY\"]")
private List<MonitorMetricEnum> metrics;
}

View File

@ -0,0 +1,27 @@
package com.qqchen.deploy.backend.deploy.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 服务器监控通知配置
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ServerMonitorNotificationConfig {
/**
* 通知渠道ID
*/
private Long notificationChannelId;
/**
* 资源告警通知模板ID
* 用于所有监控告警CPU内存磁盘网络服务器状态
*/
private Long resourceAlertTemplateId;
}

View File

@ -1,5 +1,7 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.deploy.enums.BuildTypeEnum;
import com.qqchen.deploy.backend.deploy.enums.RuntimeTypeEnum;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
@ -23,18 +25,62 @@ public class TeamApplicationDTO extends BaseDTO {
@NotNull(message = "环境ID不能为空")
private Long environmentId;
@Schema(description = "分支名称", example = "develop")
private String branch;
@Schema(description = "构建类型", example = "JENKINS")
private BuildTypeEnum buildType;
// ==================== 源Git配置公司内部Git ====================
@Schema(description = "源Git系统ID公司Git")
private Long sourceGitSystemId;
@Schema(description = "源Git系统名称")
private String sourceGitSystemName;
@Schema(description = "源Git项目ID公司Git项目ID")
private Long sourceGitProjectId;
@Schema(description = "源Git项目名称")
private String sourceGitProjectName;
@Schema(description = "源分支名称公司Git分支", example = "develop")
private String sourceBranch;
// ==================== 目标Git配置客户环境Git ====================
@Schema(description = "目标Git系统ID客户环境Git")
private Long targetGitSystemId;
@Schema(description = "目标Git系统名称")
private String targetGitSystemName;
@Schema(description = "目标Git项目ID客户环境Git项目")
private Long targetGitProjectId;
@Schema(description = "目标Git项目名称")
private String targetGitProjectName;
@Schema(description = "目标分支名称(客户环境分支,为空时默认与源分支同名)")
private String targetBranch;
// ==================== 部署配置 ====================
@Schema(description = "部署系统IDJenkins系统")
private Long deploySystemId;
@Schema(description = "部署任务IDJenkins Job")
@Schema(description = "部署系统名称")
private String deploySystemName;
@Schema(description = "部署任务名称Jenkins Job")
private String deployJob;
@Schema(description = "工作流定义ID")
private Long workflowDefinitionId;
@Schema(description = "工作流定义名称")
private String workflowDefinitionName;
// ==================== 关联信息 ====================
@Schema(description = "团队名称")
private String teamName;
@ -46,5 +92,46 @@ public class TeamApplicationDTO extends BaseDTO {
@Schema(description = "环境名称")
private String environmentName;
// ==================== 运行时配置 ====================
@Schema(description = "运行时类型", example = "K8S")
private RuntimeTypeEnum runtimeType;
// ==================== K8s运行时配置 ====================
@Schema(description = "K8s系统ID")
private Long k8sSystemId;
@Schema(description = "K8s系统名称")
private String k8sSystemName;
@Schema(description = "K8s命名空间名称")
private String k8sNamespaceName;
@Schema(description = "K8s Deployment名称")
private String k8sDeploymentName;
// ==================== Docker运行时配置 ====================
@Schema(description = "Docker服务器ID")
private Long dockerServerId;
@Schema(description = "Docker服务器名称")
private String dockerServerName;
@Schema(description = "Docker容器名称")
private String dockerContainerName;
// ==================== 服务器运行时配置 ====================
@Schema(description = "服务器ID")
private Long serverId;
@Schema(description = "服务器名称")
private String serverName;
@Schema(description = "日志查询命令(支持占位符)", example = "tail -n {lines} /var/log/app.log")
private String logQueryCommand;
}

View File

@ -0,0 +1,39 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 团队导航分类DTO
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "团队导航分类DTO")
public class TeamBookmarkCategoryDTO extends BaseDTO {
@Schema(description = "团队ID")
@NotNull(message = "团队ID不能为空")
private Long teamId;
@Schema(description = "分类名称")
@NotBlank(message = "分类名称不能为空")
private String name;
@Schema(description = "分类图标")
private String icon;
@Schema(description = "排序")
@NotNull(message = "排序不能为空")
private Integer sort;
@Schema(description = "是否启用")
@NotNull(message = "启用状态不能为空")
private Boolean enabled;
@Schema(description = "书签数量")
private Long bookmarkCount;
}

View File

@ -0,0 +1,68 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
* 团队导航书签DTO
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "团队导航书签DTO")
public class TeamBookmarkDTO extends BaseDTO {
@Schema(description = "团队ID")
@NotNull(message = "团队ID不能为空")
private Long teamId;
@Schema(description = "分类ID")
private Long categoryId;
@Schema(description = "书签标题")
@NotBlank(message = "书签标题不能为空")
private String title;
@Schema(description = "书签URL")
@NotBlank(message = "书签URL不能为空")
private String url;
@Schema(description = "图标URL")
private String icon;
@Schema(description = "描述")
private String description;
@Schema(description = "是否需要认证")
@NotNull(message = "认证标识不能为空")
private Boolean needAuth;
@Schema(description = "账号")
private String username;
@Schema(description = "密码")
private String password;
@Schema(description = "标签列表")
private List<String> tags;
@Schema(description = "排序")
@NotNull(message = "排序不能为空")
private Integer sort;
@Schema(description = "是否启用")
@NotNull(message = "启用状态不能为空")
private Boolean enabled;
@Schema(description = "是否公开")
@NotNull(message = "公开状态不能为空")
private Boolean isPublic;
@Schema(description = "分类名称")
private String categoryName;
}

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.deploy.enums.DevelopmentModeEnum;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
@ -37,6 +38,14 @@ public class TeamDTO extends BaseDTO {
@NotNull(message = "排序号不能为空")
private Integer sort;
@Schema(description = "开发模式", example = "STANDARD")
@NotNull(message = "开发模式不能为空")
private DevelopmentModeEnum developmentMode;
@Schema(description = "是否启用Git同步检测仅SYNC_MODE模式有效")
@NotNull(message = "Git同步检测开关不能为空")
private Boolean enableGitSyncCheck;
@Schema(description = "成员数量")
private Long memberCount;

View File

@ -1,36 +0,0 @@
package com.qqchen.deploy.backend.deploy.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 团队可部署环境DTO
*
* @author qqchen
* @since 2025-11-02
*/
@Data
@Schema(description = "团队可部署环境信息")
public class TeamDeployableDTO {
@Schema(description = "团队ID")
private Long teamId;
@Schema(description = "团队编码")
private String teamCode;
@Schema(description = "团队名称")
private String teamName;
@Schema(description = "用户在团队中的角色")
private String teamRole;
@Schema(description = "团队描述")
private String description;
@Schema(description = "可部署环境列表")
private List<DeployableEnvironmentDTO> environments;
}

View File

@ -36,16 +36,6 @@ public class TeamEnvironmentConfigDTO extends BaseDTO {
*/
private List<Long> approverUserIds;
/**
* 通知渠道ID
*/
private Long notificationChannelId;
/**
* 是否启用部署通知
*/
private Boolean notificationEnabled;
/**
* 是否要求代码审查通过
*/
@ -63,14 +53,14 @@ public class TeamEnvironmentConfigDTO extends BaseDTO {
*/
private String environmentName;
/**
* 通知渠道名称扩展字段
*/
private String notificationChannelName;
/**
* 该团队在该环境下关联的应用数量扩展字段
*/
private Long applicationCount;
/**
* 通知配置来自 deploy_team_environment_notification_config
*/
private TeamEnvironmentNotificationConfigDTO notificationConfig;
}

View File

@ -0,0 +1,73 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 团队环境通知配置DTO
*
* @author qqchen
* @since 2025-11-11
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class TeamEnvironmentNotificationConfigDTO extends BaseDTO {
/**
* 团队ID
*/
private Long teamId;
/**
* 环境ID
*/
private Long environmentId;
/**
* 通知渠道ID
*/
private Long notificationChannelId;
/**
* 是否启用审批前提醒
*/
private Boolean preApprovalNotificationEnabled;
/**
* 审批前提醒模板ID
*/
private Long preApprovalNotificationTemplateId;
/**
* 是否启用构建通知
*/
private Boolean buildNotificationEnabled;
/**
* 构建通知模板ID
*/
private Long buildNotificationTemplateId;
/**
* 构建失败时是否发送日志文件
*/
private Boolean buildFailureFileEnabled;
// ===== 扩展字段非数据库字段 =====
/**
* 通知渠道名称扩展字段非数据库字段
*/
private String notificationChannelName;
/**
* 审批前提醒模板名称扩展字段非数据库字段
*/
private String preApprovalNotificationTemplateName;
/**
* 构建通知模板名称扩展字段非数据库字段
*/
private String buildNotificationTemplateName;
}

View File

@ -0,0 +1,112 @@
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.deploy.enums.BuildTypeEnum;
import com.qqchen.deploy.backend.deploy.enums.DevelopmentLanguageTypeEnum;
import com.qqchen.deploy.backend.deploy.enums.RuntimeTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 用户可部署团队环境应用DTO
*
* @author qqchen
* @since 2025-11-06
*/
@Data
@Schema(description = "可部署应用信息")
public class UserDeployableTeamEnvironmentApplicationDTO {
@Schema(description = "团队应用关联ID")
private Long teamApplicationId;
@Schema(description = "应用ID")
private Long applicationId;
@Schema(description = "应用编码")
private String applicationCode;
@Schema(description = "应用名称")
private String applicationName;
@Schema(description = "应用描述")
private String applicationDesc;
@Schema(description = "开发语言类型")
private DevelopmentLanguageTypeEnum language;
@Schema(description = "构建类型JENKINS-Jenkins构建NATIVE-脚本部署)")
private BuildTypeEnum buildType;
@Schema(description = "运行时类型K8S-KubernetesDOCKER-Docker容器SERVER-传统服务器)")
private RuntimeTypeEnum runtimeType;
// ==================== 源Git配置公司内部Git ====================
@Schema(description = "源Git系统ID公司Git")
private Long sourceGitSystemId;
@Schema(description = "源Git系统名称")
private String sourceGitSystemName;
@Schema(description = "源Git项目ID公司Git项目ID")
private Long sourceGitProjectId;
@Schema(description = "源Git项目名称")
private String sourceGitProjectName;
@Schema(description = "源分支名称公司Git分支", example = "develop")
private String sourceBranch;
// ==================== 目标Git配置客户环境Git ====================
@Schema(description = "目标Git系统ID客户环境Git")
private Long targetGitSystemId;
@Schema(description = "目标Git系统名称")
private String targetGitSystemName;
@Schema(description = "目标Git项目ID客户环境Git项目")
private Long targetGitProjectId;
@Schema(description = "目标Git项目名称")
private String targetGitProjectName;
@Schema(description = "目标分支名称(客户环境分支)")
private String targetBranch;
// ==================== 部署配置 ====================
@Schema(description = "部署系统IDJenkins系统")
private Long deploySystemId;
@Schema(description = "部署系统名称")
private String deploySystemName;
@Schema(description = "部署任务名称Jenkins Job")
private String deployJob;
// ==================== 工作流配置 ====================
@Schema(description = "工作流定义ID")
private Long workflowDefinitionId;
@Schema(description = "工作流绑定的FORM表单ID")
private Long workflowDefinitionFormId;
@Schema(description = "工作流定义名称")
private String workflowDefinitionName;
@Schema(description = "工作流流程标识processKey")
private String workflowDefinitionKey;
@Schema(description = "部署统计信息")
private DeployStatisticsDTO deployStatistics;
@Schema(description = "是否正在部署中")
private Boolean isDeploying;
@Schema(description = "最近部署记录列表最多10条")
private List<DeployRecordSummaryDTO> recentDeployRecords;
}

View File

@ -4,14 +4,14 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 审批人DTO
* 用户可部署团队环境审批人DTO
*
* @author qqchen
* @since 2025-11-02
* @since 2025-11-06
*/
@Data
@Schema(description = "审批人信息")
public class ApproverDTO {
public class UserDeployableTeamEnvironmentApproverDTO {
@Schema(description = "用户ID")
private Long userId;

View File

@ -6,14 +6,14 @@ import lombok.Data;
import java.util.List;
/**
* 可部署环境DTO
* 用户可部署团队环境DTO
*
* @author qqchen
* @since 2025-11-02
* @since 2025-11-06
*/
@Data
@Schema(description = "可部署环境信息")
public class DeployableEnvironmentDTO {
@Schema(description = "用户可部署团队环境信息")
public class UserDeployableTeamEnvironmentDTO {
@Schema(description = "环境ID")
private Long environmentId;
@ -37,15 +37,23 @@ public class DeployableEnvironmentDTO {
private Boolean requiresApproval;
@Schema(description = "审批人列表")
private List<ApproverDTO> approvers;
private List<UserDeployableTeamEnvironmentApproverDTO> approvers;
@Schema(description = "通知配置")
private UserTeamEnvironmentNotificationConfigDTO notificationConfig;
@Schema(description = "是否要求代码审查")
private Boolean requireCodeReview;
@Schema(description = "备注信息")
private String remark;
@Schema(description = "可部署应用列表")
private List<DeployableApplicationDTO> applications;
private List<UserDeployableTeamEnvironmentApplicationDTO> applications;
// ==================== 当前用户权限 ====================
@Schema(description = "当前用户是否可以发起部署")
private Boolean canDeploy;
@Schema(description = "当前用户是否可以审批部署")
private Boolean canApprove;
}

View File

@ -3,17 +3,15 @@ package com.qqchen.deploy.backend.deploy.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 用户可部署环境DTO
* 用户可部署团队成员DTO
*
* @author qqchen
* @since 2025-11-02
* @since 2025-11-06
*/
@Data
@Schema(description = "用户可部署环境信息")
public class UserDeployableDTO {
@Schema(description = "团队成员信息")
public class UserDeployableTeamMemberDTO {
@Schema(description = "用户ID")
private Long userId;
@ -24,7 +22,7 @@ public class UserDeployableDTO {
@Schema(description = "真实姓名")
private String realName;
@Schema(description = "用户所属团队列表")
private List<TeamDeployableDTO> teams;
@Schema(description = "团队角色")
private String roleInTeam;
}

View File

@ -0,0 +1,45 @@
package com.qqchen.deploy.backend.deploy.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 用户团队可部署数据DTO
*
* @author qqchen
* @since 2025-11-06
*/
@Data
@Schema(description = "用户团队可部署数据")
public class UserTeamDeployableDTO {
@Schema(description = "团队ID")
private Long teamId;
@Schema(description = "团队编码")
private String teamCode;
@Schema(description = "团队名称")
private String teamName;
@Schema(description = "团队描述")
private String description;
@Schema(description = "团队负责人ID")
private Long ownerId;
@Schema(description = "团队负责人名称")
private String ownerName;
@Schema(description = "当前登录用户是否是该团队的负责人")
private Boolean isOwner;
@Schema(description = "团队成员列表")
private List<UserDeployableTeamMemberDTO> members;
@Schema(description = "可部署环境列表")
private List<UserDeployableTeamEnvironmentDTO> environments;
}

View File

@ -0,0 +1,71 @@
package com.qqchen.deploy.backend.deploy.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 用户团队环境通知配置DTO
*
* @author qqchen
* @since 2025-11-11
*/
@Data
@Schema(description = "用户团队环境通知配置信息")
public class UserTeamEnvironmentNotificationConfigDTO {
/**
* 通知渠道ID
*/
@Schema(description = "通知渠道ID")
private Long notificationChannelId;
/**
* 是否启用审批前提醒
*/
@Schema(description = "是否启用审批前提醒")
private Boolean preApprovalNotificationEnabled;
/**
* 审批前提醒模板ID
*/
@Schema(description = "审批前提醒模板ID")
private Long preApprovalNotificationTemplateId;
/**
* 是否启用构建通知
*/
@Schema(description = "是否启用构建通知")
private Boolean buildNotificationEnabled;
/**
* 构建通知模板ID
*/
@Schema(description = "构建通知模板ID")
private Long buildNotificationTemplateId;
/**
* 构建失败时是否发送日志文件
*/
@Schema(description = "构建失败时是否发送日志文件")
private Boolean buildFailureFileEnabled;
// ===== 扩展字段非数据库字段 =====
/**
* 通知渠道名称扩展字段非数据库字段
*/
@Schema(description = "通知渠道名称(扩展字段,非数据库字段)")
private String notificationChannelName;
/**
* 审批前提醒模板名称扩展字段非数据库字段
*/
@Schema(description = "审批前提醒模板名称(扩展字段,非数据库字段)")
private String preApprovalNotificationTemplateName;
/**
* 构建通知模板名称扩展字段非数据库字段
*/
@Schema(description = "构建通知模板名称(扩展字段,非数据库字段)")
private String buildNotificationTemplateName;
}

View File

@ -0,0 +1,29 @@
package com.qqchen.deploy.backend.deploy.dto.log;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 统一日志行
* 适用于K8S/Docker/Server等所有运行时类型
*
* @author qqchen
* @since 2025-12-16
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LogLine {
/**
* 日志时间戳RFC3339格式或其他格式
* 例如2025-12-16T10:30:00.123456789Z
*/
private String timestamp;
/**
* 日志内容
*/
private String content;
}

View File

@ -1,24 +0,0 @@
package com.qqchen.deploy.backend.deploy.dto.variables.build;
import com.qqchen.deploy.backend.framework.annotation.formily.*;
import lombok.Data;
import java.util.Map;
/**
* Jenkins构建变量
*/
@Data
@FormilyForm(name = "Jenkins构建配置")
public class JenkinsBaseBuildVariables {
private String externalSystemId;
private String viewId;
private String jobId;
private Map<String, String> envs;
private String script;
}

View File

@ -1,16 +0,0 @@
package com.qqchen.deploy.backend.deploy.dto.variables.build;
import com.qqchen.deploy.backend.framework.annotation.formily.FormilyForm;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* Jenkins构建变量
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class JenkinsJavaBuildVariables extends JenkinsBaseBuildVariables {
}

View File

@ -1,15 +0,0 @@
package com.qqchen.deploy.backend.deploy.dto.variables.build;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* Jenkins构建变量
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class JenkinsNodeJsBuildVariables extends JenkinsBaseBuildVariables {
}

View File

@ -1,16 +0,0 @@
package com.qqchen.deploy.backend.deploy.dto.variables.form;
import com.qqchen.deploy.backend.workflow.annotation.SchemaProperty;
import lombok.Data;
@Data
public class BuildFormVariables {
@SchemaProperty(
title = "禅道任务号",
description = "禅道任务号",
required = true
)
private String taskNo;
}

View File

@ -42,24 +42,12 @@ public class Application extends Entity<Long> {
@Column(name = "language")
private DevelopmentLanguageTypeEnum language;
/**
* 代码仓库项目ID
*/
@Column(name = "repo_project_id")
private Long repoProjectId;
/**
* 应用分类ID
*/
@Column(name = "application_category_id")
private Long applicationCategoryId;
/**
* 三方系统ID关联外部系统
*/
@Column(name = "external_system_id")
private Long externalSystemId;
/**
* 排序号
*/

View File

@ -82,10 +82,10 @@ public class ExternalSystem extends Entity<Long> {
private LocalDateTime lastConnectTime;
/**
* 系统特有配置JSON格式
* 系统特有配置如kubeconfig等)
* 使用TEXT类型存储支持任意格式的配置内容
*/
@Column(columnDefinition = "JSON")
@Column(columnDefinition = "TEXT")
private String config;
}

View File

@ -27,10 +27,10 @@ public class JenkinsBuild extends Entity<Long> {
@Column(name = "duration", nullable = false)
private Long duration;
@Column(name = "startTime", nullable = false)
@Column(name = "start_time", nullable = false)
private LocalDateTime starttime;
@Column(name = "actions", columnDefinition = "TEXT")
@Column(name = "actions", columnDefinition = "MEDIUMTEXT")
private String actions;
@Column(name = "external_system_id", nullable = false)

View File

@ -0,0 +1,54 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* Jenkins构建通知记录实体
*
* <p>记录构建通知的发送状态防止重复通知
* <p>参考 longi-deployment 的设计使用双标识防重机制
*
* @author qqchen
* @since 2025-11-11
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "deploy_jenkins_build_notification")
public class JenkinsBuildNotification extends Entity<Long> {
/**
* 构建记录ID关联deploy_jenkins_build
*/
@Column(name = "build_id", nullable = false)
private Long buildId;
/**
* 团队ID
*/
@Column(name = "team_id", nullable = false)
private Long teamId;
/**
* 环境ID
*/
@Column(name = "environment_id", nullable = false)
private Long environmentId;
/**
* 构建开始是否已通知0未通知1已通知
*/
@Column(name = "build_start_notice", nullable = false)
private Boolean buildStartNotice = false;
/**
* 构建结束是否已通知0未通知1已通知
*/
@Column(name = "build_end_notice", nullable = false)
private Boolean buildEndNotice = false;
}

View File

@ -0,0 +1,175 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.framework.domain.Entity;
import com.vladmihalcea.hibernate.type.json.JsonType;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import java.time.LocalDateTime;
import java.util.Map;
/**
* K8S Deployment实体
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "deploy_k8s_deployment")
public class K8sDeployment extends Entity<Long> {
@Column(name = "external_system_id", nullable = false)
private Long externalSystemId;
@Column(name = "namespace_id", nullable = false)
private Long namespaceId;
@Column(name = "deployment_name", nullable = false)
private String deploymentName;
/**
* 期望副本数来自spec.replicas
* 用户声明的目标副本数
*/
@Column(name = "desired_replicas")
private Integer desiredReplicas;
/**
* 当前副本数来自status.replicas
* 所有Pod总数包括Running/Pending/Failed等所有状态
*/
@Column(name = "current_replicas")
private Integer currentReplicas;
/**
* 可用副本数来自status.availableReplicas
* Ready且可接收流量的Pod数
*/
@Column(name = "available_replicas")
private Integer availableReplicas;
/**
* 就绪副本数来自status.readyReplicas
* 健康检查通过的Pod数
*/
@Column(name = "ready_replicas")
private Integer readyReplicas;
/**
* 已更新副本数来自status.updatedReplicas
* 已更新到最新版本的Pod数
*/
@Column(name = "updated_replicas")
private Integer updatedReplicas;
/**
* 不可用副本数来自status.unavailableReplicas
* 不可用的Pod数
*/
@Column(name = "unavailable_replicas")
private Integer unavailableReplicas;
/**
* 总重启次数所有Pod的重启次数总和
*/
@Column(name = "total_restart_count")
private Integer totalRestartCount;
/**
* 实际Pod总数通过Pod API统计包括所有状态的PodRunning/Failed/Pending等
*/
@Column(name = "actual_pod_count")
private Integer actualPodCount;
/**
* Running状态的Pod数量
*/
@Column(name = "running_pod_count")
private Integer runningPodCount;
/**
* Pending状态的Pod数量
*/
@Column(name = "pending_pod_count")
private Integer pendingPodCount;
/**
* Failed状态的Pod数量
*/
@Column(name = "failed_pod_count")
private Integer failedPodCount;
/**
* Succeeded状态的Pod数量
*/
@Column(name = "succeeded_pod_count")
private Integer succeededPodCount;
/**
* Unknown状态的Pod数量
*/
@Column(name = "unknown_pod_count")
private Integer unknownPodCount;
/**
* Ready状态的Pod数量所有容器健康检查通过
*/
@Column(name = "ready_pod_count")
private Integer readyPodCount;
/**
* Not Ready状态的Pod数量至少一个容器健康检查未通过
*/
@Column(name = "not_ready_pod_count")
private Integer notReadyPodCount;
/**
* CPU请求总和"2000m""2"
*/
@Column(name = "total_cpu_request", length = 50)
private String totalCpuRequest;
/**
* 内存请求总和"4Gi"
*/
@Column(name = "total_memory_request", length = 50)
private String totalMemoryRequest;
/**
* CPU限制总和
*/
@Column(name = "total_cpu_limit", length = 50)
private String totalCpuLimit;
/**
* 内存限制总和
*/
@Column(name = "total_memory_limit", length = 50)
private String totalMemoryLimit;
@Column(name = "image")
private String image;
@Type(JsonType.class)
@Column(name = "labels", columnDefinition = "JSON")
private Map<String, String> labels;
@Type(JsonType.class)
@Column(name = "selector", columnDefinition = "JSON")
private Map<String, String> selector;
@Column(name = "k8s_create_time")
private LocalDateTime k8sCreateTime;
@Column(name = "yaml_config", columnDefinition = "TEXT")
private String yamlConfig;
/**
* K8s资源版本号用于增量同步判断资源是否变化
* 对应K8s的metadata.resourceVersion字段
*/
@Column(name = "resource_version", length = 50)
private String resourceVersion;
}

View File

@ -0,0 +1,37 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.framework.domain.Entity;
import com.vladmihalcea.hibernate.type.json.JsonType;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import java.util.Map;
/**
* K8S命名空间实体
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "deploy_k8s_namespace")
public class K8sNamespace extends Entity<Long> {
@Column(name = "external_system_id", nullable = false)
private Long externalSystemId;
@Column(name = "namespace_name", nullable = false)
private String namespaceName;
@Column(name = "status")
private String status;
@Type(JsonType.class)
@Column(name = "labels", columnDefinition = "JSON")
private Map<String, String> labels;
@Column(name = "yaml_config", columnDefinition = "TEXT")
private String yamlConfig;
}

View File

@ -0,0 +1,48 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.deploy.enums.ExternalSystemSyncStatus;
import com.qqchen.deploy.backend.deploy.enums.K8sSyncType;
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.Column;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* K8S同步历史实体
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "deploy_k8s_sync_history")
@LogicDelete
public class K8sSyncHistory extends Entity<Long> {
@Column(name = "sync_history_number", nullable = false)
private String number;
@Column(name = "sync_type", nullable = false)
@Enumerated(EnumType.STRING)
private K8sSyncType syncType;
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private ExternalSystemSyncStatus status;
@Column(name = "start_time", nullable = false)
private LocalDateTime startTime;
@Column(name = "end_time")
private LocalDateTime endTime;
@Column(name = "error_message", columnDefinition = "TEXT")
private String errorMessage;
@Column(name = "external_system_id", nullable = false)
private Long externalSystemId;
}

View File

@ -0,0 +1,116 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* SSH终端审计日志实体
*
* 审计日志逻辑删除设计
* - 支持逻辑删除@LogicDelete而非物理删除
* - 确保审计记录的可追溯性和合规性
* - 删除的审计日志仍可通过后台管理查询
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "deploy_ssh_audit_log")
@LogicDelete
public class SSHAuditLog extends Entity<Long> {
/**
* 用户ID
*/
@Column(name = "user_id", nullable = false)
private Long userId;
/**
* 用户名冗余
*/
@Column(name = "username", length = 50)
private String username;
/**
* 服务器ID
*/
@Column(name = "server_id", nullable = false)
private Long serverId;
/**
* 服务器名称冗余
*/
@Column(name = "server_name", length = 100)
private String serverName;
/**
* 服务器IP冗余
*/
@Column(name = "server_ip", length = 50)
private String serverIp;
/**
* WebSocket会话ID
*/
@Column(name = "session_id", nullable = false, length = 100)
private String sessionId;
/**
* 连接时间
*/
@Column(name = "connect_time", nullable = false)
private LocalDateTime connectTime;
/**
* 断开时间
*/
@Column(name = "disconnect_time")
private LocalDateTime disconnectTime;
/**
* 会话时长
*/
@Column(name = "duration_seconds")
private Integer durationSeconds;
/**
* 客户端IP
*/
@Column(name = "client_ip", length = 50)
private String clientIp;
/**
* 浏览器UA
*/
@Column(name = "user_agent", length = 500)
private String userAgent;
/**
* 执行命令数量
*/
@Column(name = "command_count", nullable = false)
private Integer commandCount = 0;
/**
* 执行的命令记录JSON数组
*/
@Column(name = "commands", columnDefinition = "LONGTEXT")
private String commands;
/**
* 状态SUCCESS成功, FAILED失败, TIMEOUT超时, INTERRUPTED中断
*/
@Column(name = "status", length = 20)
private String status;
/**
* 错误信息
*/
@Column(name = "error_message", length = 500)
private String errorMessage;
}

View File

@ -1,13 +1,16 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.deploy.enums.AuthTypeEnum;
import com.qqchen.deploy.backend.deploy.enums.OsTypeEnum;
import com.qqchen.deploy.backend.framework.dto.DiskInfo;
import com.qqchen.deploy.backend.framework.converter.DiskInfoListConverter;
import com.qqchen.deploy.backend.framework.enums.AuthTypeEnum;
import com.qqchen.deploy.backend.framework.enums.OsTypeEnum;
import com.qqchen.deploy.backend.deploy.enums.ServerStatusEnum;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
import java.util.List;
/**
* 服务器实体
@ -121,11 +124,20 @@ public class Server extends Entity<Long> {
private Integer memorySize;
/**
* 磁盘大小(GB)
* 磁盘总容量(GB)
* 所有磁盘分区容量的总和方便排序和统计
*/
@Column(name = "disk_size")
private Integer diskSize;
/**
* 磁盘详细信息列表
* 存储多个磁盘的详细信息包括挂载点容量
*/
@Convert(converter = DiskInfoListConverter.class)
@Column(name = "disk_info", columnDefinition = "JSON")
private List<DiskInfo> diskInfo;
/**
* 标签JSON格式
*/

View File

@ -0,0 +1,104 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.framework.enums.MonitorAlertLevelEnum;
import com.qqchen.deploy.backend.framework.enums.MonitorMetricEnum;
import com.qqchen.deploy.backend.framework.enums.ServerAlertStatusEnum;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 服务器告警记录实体
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "deploy_server_alert_log")
public class ServerAlertLog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 服务器ID
*/
@Column(name = "server_id", nullable = false)
private Long serverId;
/**
* 规则ID
*/
@Column(name = "rule_id")
private Long ruleId;
/**
* 监控指标类型
*/
@Enumerated(EnumType.STRING)
@Column(name = "alert_type", nullable = false, length = 20)
private MonitorMetricEnum alertType;
/**
* 告警级别
*/
@Enumerated(EnumType.STRING)
@Column(name = "alert_level", nullable = false, length = 20)
private MonitorAlertLevelEnum alertLevel;
/**
* 当前值
*/
@Column(name = "alert_value", nullable = false, precision = 5, scale = 2)
private BigDecimal alertValue;
/**
* 阈值
*/
@Column(name = "threshold_value", nullable = false, precision = 5, scale = 2)
private BigDecimal thresholdValue;
/**
* 告警消息
*/
@Column(name = "alert_message", length = 500)
private String alertMessage;
/**
* 状态: PENDING/ACTIVE/RESOLVED
*/
@Enumerated(EnumType.STRING)
@Column(name = "status", length = 20)
private ServerAlertStatusEnum status;
/**
* 告警时间
*/
@Column(name = "alert_time", nullable = false)
private LocalDateTime alertTime;
/**
* 恢复时间
*/
@Column(name = "resolve_time")
private LocalDateTime resolveTime;
/**
* 是否已通知
*/
@Column(name = "notified")
private Boolean notified = false;
/**
* 通知时间
*/
@Column(name = "notify_time")
private LocalDateTime notifyTime;
}

View File

@ -0,0 +1,74 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
import com.qqchen.deploy.backend.framework.enums.MonitorMetricEnum;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
* 服务器告警规则实体
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
@jakarta.persistence.Entity
@Table(name = "deploy_server_alert_rule")
@LogicDelete(true)
public class ServerAlertRule extends Entity<Long> {
/**
* 服务器IDNULL表示全局规则
*/
@Column(name = "server_id")
private Long serverId;
/**
* 规则名称
*/
@Column(name = "rule_name", nullable = false, length = 100)
private String ruleName;
/**
* 监控指标类型
*/
@Enumerated(EnumType.STRING)
@Column(name = "alert_type", nullable = false, length = 20)
private MonitorMetricEnum alertType;
/**
* 警告阈值(%)
*/
@Column(name = "warning_threshold", nullable = false, precision = 5, scale = 2)
private BigDecimal warningThreshold;
/**
* 严重阈值(%)
*/
@Column(name = "critical_threshold", nullable = false, precision = 5, scale = 2)
private BigDecimal criticalThreshold;
/**
* 持续时长(分钟)
*/
@Column(name = "duration_minutes")
private Integer durationMinutes = 5;
/**
* 是否启用
*/
@Column(name = "enabled")
private Boolean enabled = true;
/**
* 规则描述
*/
@Column(name = "description", length = 500)
private String description;
}

View File

@ -0,0 +1,84 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.framework.enums.StatusEnum;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 服务器监控记录实体
* 注意监控记录不需要审计字段和软删除使用简单实体
*/
@Data
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
@lombok.Builder
@Entity
@Table(name = "deploy_server_monitor")
public class ServerMonitor {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 服务器ID
*/
@Column(name = "server_id", nullable = false)
private Long serverId;
/**
* CPU使用率(%)
*/
@Column(name = "cpu_usage", precision = 5, scale = 2)
private BigDecimal cpuUsage;
/**
* 内存使用率(%)
*/
@Column(name = "memory_usage", precision = 5, scale = 2)
private BigDecimal memoryUsage;
/**
* 已用内存(GB)
*/
@Column(name = "memory_used")
private Integer memoryUsed;
/**
* 磁盘使用情况JSON格式
*/
@Column(name = "disk_usage", columnDefinition = "JSON")
private String diskUsage;
/**
* 网络接收(KB/s)
*/
@Column(name = "network_rx")
private Long networkRx;
/**
* 网络发送(KB/s)
*/
@Column(name = "network_tx")
private Long networkTx;
/**
* 采集时间
*/
@Column(name = "collect_time", nullable = false)
private LocalDateTime collectTime;
/**
* 采集状态
*/
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false, length = 20)
private StatusEnum status = StatusEnum.SUCCESS;
}

View File

@ -1,7 +1,10 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.deploy.enums.DevelopmentModeEnum;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.Column;
import jakarta.persistence.Enumerated;
import jakarta.persistence.EnumType;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -35,5 +38,18 @@ public class Team extends Entity<Long> {
@Column(name = "sort", nullable = false)
private Integer sort = 0;
/**
* 开发模式
*/
@Enumerated(EnumType.STRING)
@Column(name = "development_mode", length = 30, nullable = false)
private DevelopmentModeEnum developmentMode = DevelopmentModeEnum.STANDARD;
/**
* 是否启用Git同步检测仅SYNC_MODE模式有效
*/
@Column(name = "enable_git_sync_check", nullable = false)
private Boolean enableGitSyncCheck = false;
}

View File

@ -1,9 +1,9 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.deploy.enums.BuildTypeEnum;
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -32,6 +32,13 @@ public class TeamApplication extends Entity<Long> {
@Column(name = "application_id", nullable = false)
private Long applicationId;
/**
* 应用关联用于查询不参与保存和更新
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "application_id", insertable = false, updatable = false)
private Application application;
/**
* 环境ID
*/
@ -39,19 +46,66 @@ public class TeamApplication extends Entity<Long> {
private Long environmentId;
/**
* 分支名称
* 构建类型JENKINS-Jenkins构建NATIVE-脚本部署
*/
@Column(name = "branch", length = 100)
private String branch;
@Enumerated(EnumType.STRING)
@Column(name = "build_type", length = 50)
private BuildTypeEnum buildType;
// ==================== 源Git配置公司内部Git ====================
/**
* 源Git系统ID公司Git关联sys_external_systemtype=GIT
*/
@Column(name = "source_git_system_id")
private Long sourceGitSystemId;
/**
* 源Git项目ID公司Git项目ID
*/
@Column(name = "source_git_project_id")
private Long sourceGitProjectId;
/**
* 源分支名称公司Git分支
*/
@Column(name = "source_branch", length = 100)
private String sourceBranch;
// ==================== 目标Git配置客户环境Git ====================
/**
* 目标Git系统ID客户环境Git关联sys_external_system
* 仅在团队开发模式为SYNC_MODE时需要配置
*/
@Column(name = "target_git_system_id")
private Long targetGitSystemId;
/**
* 目标Git项目ID客户环境Git项目
*/
@Column(name = "target_git_project_id")
private Long targetGitProjectId;
/**
* 目标分支名称客户环境分支
* 为空时默认与源分支sourceBranch字段同名
*/
@Column(name = "target_branch", length = 255)
private String targetBranch;
// ==================== 部署配置 ====================
/**
* 部署系统ID关联sys_external_systemtype=JENKINS
* 仅当 buildType=JENKINS 时使用
*/
@Column(name = "deploy_system_id")
private Long deploySystemId;
/**
* 部署任务ID关联deploy_jenkins_job
* 部署任务名称Jenkins Job名称
* 仅当 buildType=JENKINS 时使用
*/
@Column(name = "deploy_job", length = 100)
private String deployJob;
@ -61,5 +115,71 @@ public class TeamApplication extends Entity<Long> {
*/
@Column(name = "workflow_definition_id")
private Long workflowDefinitionId;
// ==================== 运行时配置 ====================
/**
* 运行时类型K8S-Kubernetes集群DOCKER-Docker容器SERVER-传统服务器
*/
@Enumerated(EnumType.STRING)
@Column(name = "runtime_type", length = 50)
private com.qqchen.deploy.backend.deploy.enums.RuntimeTypeEnum runtimeType;
// ==================== K8s运行时配置 ====================
/**
* K8s系统ID关联sys_external_systemtype=K8S
* 仅当 runtimeType=K8S 时使用
*/
@Column(name = "k8s_system_id")
private Long k8sSystemId;
/**
* K8s命名空间名称
* 仅当 runtimeType=K8S 时使用
*/
@Column(name = "k8s_namespace_name", length = 255)
private String k8sNamespaceName;
/**
* K8s Deployment名称
* 仅当 runtimeType=K8S 时使用
*/
@Column(name = "k8s_deployment_name", length = 255)
private String k8sDeploymentName;
// ==================== Docker运行时配置 ====================
/**
* Docker服务器ID关联sys_server
* 仅当 runtimeType=DOCKER 时使用
*/
@Column(name = "docker_server_id")
private Long dockerServerId;
/**
* Docker容器名称
* 仅当 runtimeType=DOCKER 时使用
*/
@Column(name = "docker_container_name", length = 255)
private String dockerContainerName;
// ==================== 服务器运行时配置 ====================
/**
* 服务器ID关联sys_server
* 仅当 runtimeType=SERVER 时使用
*/
@Column(name = "server_id")
private Long serverId;
/**
* 日志查询命令支持占位符
* 例如tail -n {lines} /var/log/app.log
* 例如supervisorctl tail -n {lines} {appName}
* 仅当 runtimeType=SERVER 时使用
*/
@Column(name = "log_query_command", length = 500)
private String logQueryCommand;
}

View File

@ -0,0 +1,100 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
import java.util.List;
/**
* 团队导航书签实体
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "deploy_team_bookmark")
public class TeamBookmark extends Entity<Long> {
/**
* 团队ID
*/
@Column(name = "team_id", nullable = false)
private Long teamId;
/**
* 分类ID
*/
@Column(name = "category_id")
private Long categoryId;
/**
* 书签标题
*/
@Column(name = "title", nullable = false, length = 100)
private String title;
/**
* 书签URL
*/
@Column(name = "url", nullable = false, length = 500)
private String url;
/**
* 图标URL
*/
@Column(name = "icon", length = 200)
private String icon;
/**
* 描述
*/
@Column(name = "description", length = 500)
private String description;
/**
* 是否需要认证
*/
@Column(name = "need_auth", nullable = false)
private Boolean needAuth = false;
/**
* 账号
*/
@Column(name = "username", length = 100)
private String username;
/**
* 密码加密存储
*/
@Column(name = "password", length = 500)
private String password;
/**
* 标签JSON数组
*/
@JdbcTypeCode(SqlTypes.JSON)
@Column(name = "tags", columnDefinition = "JSON")
private List<String> tags;
/**
* 排序
*/
@Column(name = "sort", nullable = false)
private Integer sort = 0;
/**
* 是否启用
*/
@Column(name = "enabled", nullable = false)
private Boolean enabled = true;
/**
* 是否公开
*/
@Column(name = "is_public", nullable = false)
private Boolean isPublic = true;
}

View File

@ -0,0 +1,47 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 团队导航分类实体
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "deploy_team_bookmark_category")
public class TeamBookmarkCategory extends Entity<Long> {
/**
* 团队ID
*/
@Column(name = "team_id", nullable = false)
private Long teamId;
/**
* 分类名称
*/
@Column(name = "name", nullable = false, length = 50)
private String name;
/**
* 分类图标
*/
@Column(name = "icon", length = 100)
private String icon;
/**
* 排序
*/
@Column(name = "sort", nullable = false)
private Integer sort = 0;
/**
* 是否启用
*/
@Column(name = "enabled", nullable = false)
private Boolean enabled = true;
}

View File

@ -70,22 +70,6 @@ public class TeamEnvironmentConfig extends Entity<Long> {
@Column(name = "approver_user_ids", columnDefinition = "JSON")
private List<Long> approverUserIds;
// ===== 通知配置 =====
/**
* 通知渠道ID
* <p>关联 sys_notification_channel
*/
@Column(name = "notification_channel_id")
private Long notificationChannelId;
/**
* 是否启用部署通知
*/
@Column(name = "notification_enabled", nullable = false, columnDefinition = "BOOLEAN DEFAULT TRUE")
private Boolean notificationEnabled = true;
// ===== 安全策略 =====
/**
* 是否要求代码审查通过才能部署

View File

@ -0,0 +1,77 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 团队环境通知配置实体
*
* <p>管理团队在特定环境下的通知配置
* <ul>
* <li>通知渠道配置</li>
* <li>部署通知开关</li>
* <li>构建通知开关</li>
* </ul>
*
* @author qqchen
* @since 2025-11-11
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "deploy_team_environment_notification_config")
@LogicDelete(false)
public class TeamEnvironmentNotificationConfig extends Entity<Long> {
/**
* 团队ID
*/
@Column(name = "team_id", nullable = false)
private Long teamId;
/**
* 环境ID
*/
@Column(name = "environment_id", nullable = false)
private Long environmentId;
/**
* 通知渠道ID关联sys_notification_channel
*/
@Column(name = "notification_channel_id")
private Long notificationChannelId;
/**
* 是否启用审批前提醒
*/
@Column(name = "pre_approval_notification_enabled", nullable = false)
private Boolean preApprovalNotificationEnabled = false;
/**
* 审批前提醒模板ID关联sys_notification_template
*/
@Column(name = "pre_approval_notification_template_id")
private Long preApprovalNotificationTemplateId;
/**
* 是否启用构建通知
*/
@Column(name = "build_notification_enabled", nullable = false)
private Boolean buildNotificationEnabled = false;
/**
* 构建通知模板ID关联sys_notification_template
*/
@Column(name = "build_notification_template_id")
private Long buildNotificationTemplateId;
/**
* 构建失败时是否发送日志文件到企业微信
*/
@Column(name = "build_failure_file_enabled", nullable = false)
private Boolean buildFailureFileEnabled = false;
}

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
@ -15,6 +16,7 @@ import java.time.LocalDateTime;
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "deploy_team_member")
@LogicDelete(false)
public class TeamMember extends Entity<Long> {
@Column(name = "team_id", nullable = false)

View File

@ -1,19 +0,0 @@
package com.qqchen.deploy.backend.deploy.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* SSH认证方式枚举
*/
@Getter
@AllArgsConstructor
public enum AuthTypeEnum {
PASSWORD("密码认证", "使用用户名和密码进行SSH认证"),
KEY("密钥认证", "使用SSH私钥进行认证");
private final String name;
private final String description;
}

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