打印了JENKINS节点日志
This commit is contained in:
parent
c3ee349760
commit
e27bbca4b3
@ -1,660 +0,0 @@
|
|||||||
# Deploy Ease Platform
|
|
||||||
|
|
||||||
Deploy Ease Platform 是一个基于Spring Boot 3.x和Flowable 7.x的现代化部署管理平台,提供工作流驱动的自动化部署解决方案。
|
|
||||||
|
|
||||||
## 技术栈
|
|
||||||
|
|
||||||
### 后端
|
|
||||||
- **Java 21** + **Spring Boot 3.2.0**
|
|
||||||
- **Spring Security 6.2.0** + JWT认证
|
|
||||||
- **Spring Data JPA** + Hibernate
|
|
||||||
- **Flowable 7.2.0** - 工作流引擎
|
|
||||||
- **MySQL 8.0+** + Flyway数据库版本控制
|
|
||||||
- **MapStruct 1.5.5** - 对象映射
|
|
||||||
- **QueryDSL 5.0.0** - 类型安全查询
|
|
||||||
- **OpenAPI/Swagger** - API文档
|
|
||||||
- **Lombok** - 代码简化
|
|
||||||
- **Hutool** - 工具类库
|
|
||||||
|
|
||||||
### 构建工具
|
|
||||||
- **Maven 3.8+**
|
|
||||||
|
|
||||||
## 核心功能模块
|
|
||||||
|
|
||||||
### ✅ 1. 系统管理(已完成)
|
|
||||||
|
|
||||||
#### 1.1 用户管理
|
|
||||||
- [x] 用户注册和登录
|
|
||||||
- [x] JWT Token认证
|
|
||||||
- [x] 密码重置
|
|
||||||
- [x] 用户信息维护
|
|
||||||
- [x] 用户状态管理(启用/禁用)
|
|
||||||
- [x] 部门分配
|
|
||||||
- [x] 基础CRUD操作(分页查询、创建、更新、删除)
|
|
||||||
|
|
||||||
**主要接口:**
|
|
||||||
```
|
|
||||||
POST /api/v1/user/login - 用户登录
|
|
||||||
GET /api/v1/user/current - 获取当前用户信息
|
|
||||||
POST /api/v1/user/{id}/reset-password - 重置密码
|
|
||||||
PUT /api/v1/user/{id}/department - 分配部门
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 1.2 角色管理
|
|
||||||
- [x] 角色创建与维护
|
|
||||||
- [x] 角色标签管理
|
|
||||||
- [x] 角色权限分配
|
|
||||||
- [x] 用户角色分配
|
|
||||||
- [x] 角色权限查询
|
|
||||||
|
|
||||||
**主要接口:**
|
|
||||||
```
|
|
||||||
POST /api/v1/role - 创建角色
|
|
||||||
POST /api/v1/role/{id}/tags - 分配标签
|
|
||||||
POST /api/v1/role/{userId}/assignRoles - 分配角色
|
|
||||||
GET /api/v1/role/{id}/permissions - 获取角色权限
|
|
||||||
POST /api/v1/role/{id}/permissions - 分配权限
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 1.3 权限管理
|
|
||||||
- [x] 权限创建与维护
|
|
||||||
- [x] 权限分类管理(菜单权限、按钮权限)
|
|
||||||
- [x] 权限与菜单关联
|
|
||||||
- [x] 基于权限的访问控制
|
|
||||||
|
|
||||||
#### 1.4 菜单管理
|
|
||||||
- [x] 菜单树维护
|
|
||||||
- [x] 菜单排序
|
|
||||||
- [x] 菜单显示控制
|
|
||||||
- [x] 动态菜单加载
|
|
||||||
- [x] 菜单权限配置
|
|
||||||
- [x] 权限树查询
|
|
||||||
|
|
||||||
**主要接口:**
|
|
||||||
```
|
|
||||||
GET /api/v1/menu/current - 获取当前用户菜单
|
|
||||||
GET /api/v1/menu/tree - 获取菜单树
|
|
||||||
GET /api/v1/menu/permission-tree - 获取权限树
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 1.5 部门管理
|
|
||||||
- [x] 部门树形结构维护
|
|
||||||
- [x] 部门编码管理
|
|
||||||
- [x] 部门负责人设置
|
|
||||||
- [x] 部门人员管理
|
|
||||||
- [x] 部门排序
|
|
||||||
|
|
||||||
#### 1.6 租户管理
|
|
||||||
- [x] 多租户支持
|
|
||||||
- [x] 租户信息管理
|
|
||||||
- [x] 租户状态控制
|
|
||||||
|
|
||||||
**实体统计:** 9个核心实体
|
|
||||||
- User(用户)
|
|
||||||
- Role(角色)
|
|
||||||
- RoleTag(角色标签)
|
|
||||||
- Permission(权限)
|
|
||||||
- PermissionTemplate(权限模板)
|
|
||||||
- Menu(菜单)
|
|
||||||
- Department(部门)
|
|
||||||
- Tenant(租户)
|
|
||||||
- SysParam(系统参数)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ✅ 2. 外部系统集成(已完成)
|
|
||||||
|
|
||||||
#### 2.1 外部系统管理
|
|
||||||
- [x] 外部系统统一管理(Jenkins、Git等)
|
|
||||||
- [x] 多种认证方式支持
|
|
||||||
- 用户名密码认证
|
|
||||||
- Token认证
|
|
||||||
- SSH密钥认证
|
|
||||||
- [x] 连接测试
|
|
||||||
- [x] 同步状态追踪
|
|
||||||
|
|
||||||
**主要接口:**
|
|
||||||
```
|
|
||||||
POST /api/v1/external-system - 创建外部系统
|
|
||||||
GET /api/v1/external-system/{id}/test-connection - 测试连接
|
|
||||||
POST /api/v1/external-system/{id}/sync - 同步数据
|
|
||||||
GET /api/v1/external-system/{id}/status - 获取同步状态
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.2 Jenkins集成
|
|
||||||
- [x] Jenkins实例管理
|
|
||||||
- [x] 视图(View)同步
|
|
||||||
- [x] 任务(Job)同步
|
|
||||||
- [x] 构建(Build)信息同步
|
|
||||||
- [x] 构建历史记录
|
|
||||||
- [x] 同步历史追踪
|
|
||||||
|
|
||||||
**主要接口:**
|
|
||||||
```
|
|
||||||
POST /api/v1/jenkins-manager/{externalSystemId}/sync-all - 同步所有数据
|
|
||||||
POST /api/v1/jenkins-manager/{externalSystemId}/sync-views - 同步视图
|
|
||||||
POST /api/v1/jenkins-manager/{externalSystemId}/sync-jobs - 同步任务
|
|
||||||
POST /api/v1/jenkins-manager/{externalSystemId}/sync-builds - 同步构建
|
|
||||||
GET /api/v1/jenkins-manager/{externalSystemId}/instance - 获取实例信息
|
|
||||||
```
|
|
||||||
|
|
||||||
**相关实体:**
|
|
||||||
- JenkinsView(Jenkins视图)
|
|
||||||
- JenkinsJob(Jenkins任务)
|
|
||||||
- JenkinsBuild(Jenkins构建)
|
|
||||||
- JenkinsSyncHistory(同步历史)
|
|
||||||
|
|
||||||
#### 2.3 Git仓库集成
|
|
||||||
- [x] Git仓库组(Group)管理
|
|
||||||
- [x] Git项目(Project)管理
|
|
||||||
- [x] Git分支(Branch)管理
|
|
||||||
- [x] 多级同步(组 → 项目 → 分支)
|
|
||||||
- [x] 同步历史记录
|
|
||||||
|
|
||||||
**主要接口:**
|
|
||||||
```
|
|
||||||
POST /api/v1/repository-manager/{externalSystemId}/sync-groups - 同步仓库组
|
|
||||||
POST /api/v1/repository-manager/{externalSystemId}/groups/sync-projects - 同步项目
|
|
||||||
POST /api/v1/repository-manager/{externalSystemId}/projects/sync-branches - 同步分支
|
|
||||||
GET /api/v1/repository-manager/{externalSystemId}/instance - 获取实例信息
|
|
||||||
```
|
|
||||||
|
|
||||||
**相关实体:**
|
|
||||||
- RepositoryGroup(仓库组)
|
|
||||||
- RepositoryProject(仓库项目)
|
|
||||||
- RepositoryBranch(仓库分支)
|
|
||||||
- RepositorySyncHistory(同步历史)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ✅ 3. 部署管理(已完成)
|
|
||||||
|
|
||||||
#### 3.1 应用管理
|
|
||||||
- [x] 应用创建与维护
|
|
||||||
- [x] 应用编码管理
|
|
||||||
- [x] 开发语言类型支持(Java、Python、Node.js等)
|
|
||||||
- [x] 应用与仓库关联
|
|
||||||
- [x] 应用分组管理
|
|
||||||
|
|
||||||
**主要接口:**
|
|
||||||
```
|
|
||||||
POST /api/v1/applications - 创建应用
|
|
||||||
GET /api/v1/applications/development-languages - 获取开发语言列表
|
|
||||||
```
|
|
||||||
|
|
||||||
**相关实体:**
|
|
||||||
- Application(应用)
|
|
||||||
- ProjectGroup(项目组)
|
|
||||||
|
|
||||||
#### 3.2 环境管理
|
|
||||||
- [x] 环境配置管理(开发、测试、生产等)
|
|
||||||
- [x] 环境编码管理
|
|
||||||
- [x] 构建类型配置(Jenkins、GitLab Runner、GitHub Action)
|
|
||||||
- [x] 部署方式配置(K8S、Docker、VM)
|
|
||||||
- [x] 项目组与环境关联
|
|
||||||
|
|
||||||
**相关实体:**
|
|
||||||
- Environment(环境)
|
|
||||||
|
|
||||||
#### 3.3 部署配置
|
|
||||||
- [x] 应用部署配置管理
|
|
||||||
- [x] 构建配置Schema定义
|
|
||||||
- [x] 部署操作
|
|
||||||
- [x] 部署日志记录
|
|
||||||
|
|
||||||
**主要接口:**
|
|
||||||
```
|
|
||||||
POST /api/v1/deploy-app-config - 创建部署配置
|
|
||||||
GET /api/v1/deploy-app-config/defined - 获取构建类型Schema
|
|
||||||
POST /api/v1/deploy-app-config/deploy - 执行部署
|
|
||||||
```
|
|
||||||
|
|
||||||
**相关实体:**
|
|
||||||
- DeployAppConfig(部署配置)
|
|
||||||
- DeployLog(部署日志)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ✅ 4. 工作流引擎(已完成,基于Flowable)
|
|
||||||
|
|
||||||
#### 4.1 工作流定义
|
|
||||||
- [x] 工作流设计和保存
|
|
||||||
- [x] 工作流发布管理
|
|
||||||
- [x] 工作流启用/禁用
|
|
||||||
- [x] 工作流状态管理(草稿、已发布、已禁用)
|
|
||||||
- [x] 图形化配置(GraphJSON)
|
|
||||||
- [x] BPMN XML自动生成
|
|
||||||
- [x] 工作流分类管理
|
|
||||||
|
|
||||||
**主要接口:**
|
|
||||||
```
|
|
||||||
POST /api/v1/workflow/definition/design - 保存工作流设计
|
|
||||||
GET /api/v1/workflow/definition/published - 查询已发布工作流
|
|
||||||
POST /api/v1/workflow/definition/{id}/published - 发布工作流
|
|
||||||
POST /api/v1/workflow/definition/{id}/disable - 禁用工作流
|
|
||||||
POST /api/v1/workflow/definition/{id}/enable - 启用工作流
|
|
||||||
GET /api/v1/workflow/definition/categories - 获取工作流分类
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4.2 工作流实例
|
|
||||||
- [x] 工作流实例启动
|
|
||||||
- [x] 工作流实例挂起
|
|
||||||
- [x] 工作流实例恢复
|
|
||||||
- [x] 工作流执行状态查询
|
|
||||||
- [x] 历史实例查询
|
|
||||||
- [x] 实例与模板关联查询
|
|
||||||
|
|
||||||
**主要接口:**
|
|
||||||
```
|
|
||||||
POST /api/v1/workflow/instance/start - 启动工作流
|
|
||||||
GET /api/v1/workflow/instance/templates-with-instances - 查询模板及实例
|
|
||||||
GET /api/v1/workflow/instance/historical-instances - 查询历史实例
|
|
||||||
POST /api/v1/workflow/definition/{processInstanceId}/suspend - 挂起实例
|
|
||||||
POST /api/v1/workflow/definition/{processInstanceId}/resume - 恢复实例
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4.3 节点定义与执行
|
|
||||||
- [x] 节点类型管理
|
|
||||||
- [x] 节点配置Schema
|
|
||||||
- [x] 节点实例状态追踪
|
|
||||||
- [x] 节点重试机制
|
|
||||||
- [x] 节点跳过机制
|
|
||||||
|
|
||||||
**主要接口:**
|
|
||||||
```
|
|
||||||
POST /api/v1/workflow/node-instance/{id}/retry - 重试节点
|
|
||||||
POST /api/v1/workflow/node-instance/{id}/skip - 跳过节点
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4.4 节点类型实现(Delegate)
|
|
||||||
- [x] **ShellNodeDelegate** - Shell脚本执行
|
|
||||||
- [x] **DeployNodeDelegate** - 部署节点执行
|
|
||||||
- [x] **ApprovalNodeDelegate** - 审批节点
|
|
||||||
- [x] **NotificationNodeDelegate** - 通知节点
|
|
||||||
- [x] **BaseNodeDelegate** - 基础节点抽象
|
|
||||||
|
|
||||||
#### 4.5 工作流日志
|
|
||||||
- [x] 实时日志记录
|
|
||||||
- [x] 节点级日志
|
|
||||||
- [x] 日志查询
|
|
||||||
|
|
||||||
**相关实体:**
|
|
||||||
- WorkflowDefinition(工作流定义)
|
|
||||||
- WorkflowNodeDefinition(节点定义)
|
|
||||||
- WorkflowInstance(工作流实例)
|
|
||||||
- WorkflowNodeInstance(节点实例)
|
|
||||||
- WorkflowLog(工作流日志)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ✅ 5. 基础框架(已完成)
|
|
||||||
|
|
||||||
#### 5.1 通用CRUD框架
|
|
||||||
- [x] BaseController - 统一REST控制器
|
|
||||||
- [x] BaseServiceImpl - 通用服务实现
|
|
||||||
- [x] IBaseRepository - 通用数据访问接口
|
|
||||||
- [x] BaseConverter - 对象转换抽象
|
|
||||||
- [x] 统一响应格式(Response<T>)
|
|
||||||
- [x] 统一分页格式(Page<T>)
|
|
||||||
|
|
||||||
**标准CRUD接口(所有Controller继承):**
|
|
||||||
```
|
|
||||||
POST /{resource} - 创建
|
|
||||||
PUT /{resource}/{id} - 更新
|
|
||||||
DELETE /{resource}/{id} - 删除
|
|
||||||
GET /{resource}/{id} - 查询详情
|
|
||||||
GET /{resource} - 查询列表
|
|
||||||
GET /{resource}/page - 分页查询
|
|
||||||
GET /{resource}/list - 条件查询
|
|
||||||
POST /{resource}/batch - 批量处理
|
|
||||||
GET /{resource}/export - 导出数据
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 5.2 查询框架
|
|
||||||
- [x] 基于QueryDSL的类型安全查询
|
|
||||||
- [x] 动态查询条件构建
|
|
||||||
- [x] @QueryField注解支持
|
|
||||||
- [x] 多种查询类型(EQUAL、LIKE、BETWEEN、IN等)
|
|
||||||
- [x] 软删除查询支持
|
|
||||||
|
|
||||||
#### 5.3 安全框架
|
|
||||||
- [x] Spring Security集成
|
|
||||||
- [x] JWT Token认证
|
|
||||||
- [x] 权限拦截器
|
|
||||||
- [x] 自定义认证入口点
|
|
||||||
- [x] JWT过滤器
|
|
||||||
|
|
||||||
#### 5.4 审计框架
|
|
||||||
- [x] 审计注解(@Audited)
|
|
||||||
- [x] 自动记录创建人/创建时间
|
|
||||||
- [x] 自动记录更新人/更新时间
|
|
||||||
- [x] 乐观锁版本控制
|
|
||||||
- [x] 审计事件监听
|
|
||||||
|
|
||||||
#### 5.5 异常处理
|
|
||||||
- [x] 统一异常处理(GlobalExceptionHandler)
|
|
||||||
- [x] 业务异常(BusinessException)
|
|
||||||
- [x] 系统异常(SystemException)
|
|
||||||
- [x] 唯一约束异常(UniqueConstraintException)
|
|
||||||
- [x] 实体未找到异常(EntityNotFoundException)
|
|
||||||
- [x] 国际化错误消息
|
|
||||||
|
|
||||||
#### 5.6 数据库版本控制
|
|
||||||
- [x] Flyway集成
|
|
||||||
- [x] 表结构版本管理(V1.0.0__init_schema.sql)
|
|
||||||
- [x] 初始数据管理(V1.0.1__init_data.sql)
|
|
||||||
- [x] 28个数据库表
|
|
||||||
|
|
||||||
#### 5.7 其他框架功能
|
|
||||||
- [x] 国际化支持(i18n)
|
|
||||||
- [x] JSON序列化配置
|
|
||||||
- [x] 租户过滤器
|
|
||||||
- [x] 逻辑删除支持(@LogicDelete)
|
|
||||||
- [x] 依赖注入后处理器
|
|
||||||
- [x] Formily Schema生成(用于前端表单)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 数据库表结构(28张表)
|
|
||||||
|
|
||||||
### 系统管理表(7张)
|
|
||||||
1. `sys_tenant` - 租户表
|
|
||||||
2. `sys_department` - 部门表
|
|
||||||
3. `sys_user` - 用户表
|
|
||||||
4. `sys_param` - 系统参数表
|
|
||||||
5. `sys_role` - 角色表
|
|
||||||
6. `sys_role_tag` - 角色标签表
|
|
||||||
7. `sys_menu` - 菜单表
|
|
||||||
|
|
||||||
### 权限管理表(5张)
|
|
||||||
8. `sys_role_tag_relation` - 角色标签关系表
|
|
||||||
9. `sys_user_role` - 用户角色关系表
|
|
||||||
10. `sys_role_menu` - 角色菜单关系表
|
|
||||||
11. `sys_permission_template` - 权限模板表
|
|
||||||
12. `sys_template_menu` - 模板菜单关系表
|
|
||||||
13. `sys_permission` - 权限表
|
|
||||||
|
|
||||||
### 外部系统表(1张)
|
|
||||||
14. `sys_external_system` - 外部系统表
|
|
||||||
|
|
||||||
### Git仓库表(3张)
|
|
||||||
15. `deploy_repo_group` - 仓库组表
|
|
||||||
16. `deploy_repo_project` - 仓库项目表
|
|
||||||
17. `deploy_repo_branch` - 仓库分支表
|
|
||||||
|
|
||||||
### Jenkins表(4张)
|
|
||||||
18. Jenkins相关表(通过代码推断,未在SQL中完整展示)
|
|
||||||
|
|
||||||
### 工作流表(5张)
|
|
||||||
19. `workflow_definition` - 工作流定义表
|
|
||||||
20. `workflow_node_definition` - 节点定义表
|
|
||||||
21. `workflow_instance` - 工作流实例表
|
|
||||||
22. `workflow_node_instance` - 节点实例表
|
|
||||||
23. `workflow_log` - 工作流日志表
|
|
||||||
|
|
||||||
### 部署管理表(6张)
|
|
||||||
24. `deploy_project_group` - 项目组表
|
|
||||||
25. `deploy_application` - 应用表
|
|
||||||
26. `deploy_environment` - 环境表
|
|
||||||
27. `deploy_project_group_environment` - 项目组环境关系表
|
|
||||||
28. `deploy_log` - 部署日志表
|
|
||||||
29. `deploy_app_config` - 应用部署配置表
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## API接口统计
|
|
||||||
|
|
||||||
### Controller统计(26个)
|
|
||||||
**系统管理(7个):**
|
|
||||||
- UserApiController
|
|
||||||
- RoleApiController
|
|
||||||
- RoleTagApiController
|
|
||||||
- PermissionApiController
|
|
||||||
- MenuApiController
|
|
||||||
- DepartmentApiController
|
|
||||||
- TenantApiController
|
|
||||||
|
|
||||||
**部署管理(8个):**
|
|
||||||
- ApplicationApiController
|
|
||||||
- ProjectGroupApiController
|
|
||||||
- EnvironmentApiController
|
|
||||||
- DeployAppConfigApiController
|
|
||||||
- DeployLogApiController
|
|
||||||
- ExternalSystemApiController
|
|
||||||
- RepositoryManagerApiController
|
|
||||||
- RepositoryProjectApiController
|
|
||||||
- RepositoryGroupApiController
|
|
||||||
- RepositoryBranchApiController
|
|
||||||
|
|
||||||
**Jenkins管理(5个):**
|
|
||||||
- JenkinsManagerApiController
|
|
||||||
- JenkinsViewApiController
|
|
||||||
- JenkinsJobApiController
|
|
||||||
- JenkinsBuildApiController
|
|
||||||
- JenkinsSyncHistoryApiController
|
|
||||||
|
|
||||||
**工作流管理(4个):**
|
|
||||||
- WorkflowDefinitionApiController
|
|
||||||
- WorkflowNodeDefinitionApiController
|
|
||||||
- WorkflowInstanceApiController
|
|
||||||
- WorkflowNodeInstanceApiController
|
|
||||||
|
|
||||||
**接口方法统计:**
|
|
||||||
- 约49个自定义@Operation接口方法
|
|
||||||
- 每个Controller继承BaseController,获得9个标准CRUD方法
|
|
||||||
- **总计约280+个API接口**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚧 待完善功能
|
|
||||||
|
|
||||||
### 1. 测试覆盖
|
|
||||||
- [ ] Service层单元测试
|
|
||||||
- [ ] Controller层集成测试
|
|
||||||
- [ ] Repository层数据访问测试
|
|
||||||
- [ ] 工作流引擎测试
|
|
||||||
- **当前测试覆盖率:约1%(仅1个测试类)**
|
|
||||||
|
|
||||||
### 2. 缓存机制
|
|
||||||
- [ ] Redis集成
|
|
||||||
- [ ] 工作流定义缓存
|
|
||||||
- [ ] 权限数据缓存
|
|
||||||
- [ ] 菜单数据缓存
|
|
||||||
- **当前状态:已引入Caffeine依赖,但未使用**
|
|
||||||
|
|
||||||
### 3. 监控和运维
|
|
||||||
- [ ] 健康检查端点完善
|
|
||||||
- [ ] 性能监控(APM)集成
|
|
||||||
- [ ] 日志聚合(ELK)
|
|
||||||
- [ ] 告警机制
|
|
||||||
- [ ] 链路追踪
|
|
||||||
|
|
||||||
### 4. 部署和DevOps
|
|
||||||
- [ ] Docker镜像构建
|
|
||||||
- [ ] Kubernetes部署配置
|
|
||||||
- [ ] CI/CD Pipeline
|
|
||||||
- [ ] 环境配置分离(dev/test/prod)
|
|
||||||
- **当前配置:application.yml包含硬编码数据库密码和JWT密钥**
|
|
||||||
|
|
||||||
### 5. 文档完善
|
|
||||||
- [ ] API文档完善(Swagger注释)
|
|
||||||
- [ ] 架构图和流程图
|
|
||||||
- [ ] 部署手册
|
|
||||||
- [ ] 运维手册
|
|
||||||
- [ ] 最佳实践指南
|
|
||||||
|
|
||||||
### 6. 代码质量
|
|
||||||
- [ ] 代码质量检查工具集成(Checkstyle、PMD、SonarQube)
|
|
||||||
- [ ] TODO注释处理(14处待处理)
|
|
||||||
- [ ] System.out.println清理(14处)
|
|
||||||
- [ ] 泛化异常处理优化(95处catch Exception)
|
|
||||||
|
|
||||||
### 7. 安全加固
|
|
||||||
- [ ] 敏感配置外部化
|
|
||||||
- [ ] JWT密钥加密存储
|
|
||||||
- [ ] 数据库连接加密
|
|
||||||
- [ ] API访问频率限制
|
|
||||||
- [ ] SQL注入防护验证
|
|
||||||
- [ ] XSS攻击防护
|
|
||||||
|
|
||||||
### 8. 性能优化
|
|
||||||
- [ ] 数据库索引优化
|
|
||||||
- [ ] N+1查询问题排查
|
|
||||||
- [ ] 连接池配置优化(当前仅10个)
|
|
||||||
- [ ] 大数据量分页优化
|
|
||||||
- [ ] 异步任务处理
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 快速开始
|
|
||||||
|
|
||||||
### 环境要求
|
|
||||||
- JDK 21+
|
|
||||||
- Maven 3.8+
|
|
||||||
- MySQL 8.0+
|
|
||||||
- Node.js 16+(前端)
|
|
||||||
|
|
||||||
### 1. 数据库准备
|
|
||||||
```sql
|
|
||||||
CREATE DATABASE `deploy-ease-platform`
|
|
||||||
DEFAULT CHARACTER SET utf8mb4
|
|
||||||
COLLATE utf8mb4_unicode_ci;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 配置修改
|
|
||||||
编辑 `src/main/resources/application.yml`:
|
|
||||||
```yaml
|
|
||||||
spring:
|
|
||||||
datasource:
|
|
||||||
url: jdbc:mysql://localhost:3306/deploy-ease-platform?...
|
|
||||||
username: your_username
|
|
||||||
password: your_password
|
|
||||||
|
|
||||||
jwt:
|
|
||||||
secret: 'your_secret_key' # 建议使用环境变量
|
|
||||||
```
|
|
||||||
|
|
||||||
⚠️ **安全提示:** 生产环境请使用环境变量或配置中心管理敏感信息!
|
|
||||||
|
|
||||||
### 3. 启动项目
|
|
||||||
```bash
|
|
||||||
# 编译项目
|
|
||||||
mvn clean package -DskipTests
|
|
||||||
|
|
||||||
# 启动服务
|
|
||||||
mvn spring-boot:run
|
|
||||||
|
|
||||||
# 或直接运行
|
|
||||||
java -jar target/deploy-cd-1.0-SNAPSHOT.jar
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 访问服务
|
|
||||||
- **API文档:** http://localhost:8080/swagger-ui.html
|
|
||||||
- **健康检查:** http://localhost:8080/actuator/health
|
|
||||||
|
|
||||||
### 5. 默认用户(需通过初始化SQL配置)
|
|
||||||
```
|
|
||||||
用户名:admin
|
|
||||||
密码:(请查看V1.0.1__init_data.sql)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 项目规范
|
|
||||||
|
|
||||||
### 代码规范
|
|
||||||
详见项目根目录下的规范文件:
|
|
||||||
- `.cursor/rules/project.mdc` - 完整开发规范
|
|
||||||
- `frontend.rules` - 前端开发规范
|
|
||||||
|
|
||||||
### 核心规范要点
|
|
||||||
|
|
||||||
#### 包结构
|
|
||||||
```
|
|
||||||
com.qqchen.deploy.backend
|
|
||||||
├── framework/ # 框架核心
|
|
||||||
│ ├── annotation/ # 注解定义
|
|
||||||
│ ├── controller/ # 基础控制器
|
|
||||||
│ ├── service/ # 基础服务
|
|
||||||
│ ├── repository/ # 基础仓库
|
|
||||||
│ └── ...
|
|
||||||
├── system/ # 系统模块
|
|
||||||
│ ├── api/ # REST接口
|
|
||||||
│ ├── entity/ # 实体类
|
|
||||||
│ ├── service/ # 业务服务
|
|
||||||
│ └── ...
|
|
||||||
├── deploy/ # 部署模块
|
|
||||||
└── workflow/ # 工作流模块
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 命名规范
|
|
||||||
- **Entity:** 名词,如User、Role
|
|
||||||
- **DTO:** 实体名+DTO,如UserDTO
|
|
||||||
- **Service接口:** I+实体名+Service,如IUserService
|
|
||||||
- **Service实现:** 实体名+ServiceImpl,如UserServiceImpl
|
|
||||||
- **Controller:** 实体名+ApiController,如UserApiController
|
|
||||||
- **Repository:** I+实体名+Repository,如IUserRepository
|
|
||||||
|
|
||||||
#### 注解使用
|
|
||||||
- **@ServiceType(DATABASE)** - 数据库服务
|
|
||||||
- **@ServiceType(INTEGRATION)** - 集成服务(禁用数据库操作)
|
|
||||||
- **@LogicDelete** - 启用逻辑删除
|
|
||||||
- **@Audited** - 启用审计
|
|
||||||
- **@QueryField** - 查询字段映射
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 贡献指南
|
|
||||||
|
|
||||||
### 提交规范
|
|
||||||
```
|
|
||||||
feat: 新功能
|
|
||||||
fix: Bug修复
|
|
||||||
docs: 文档更新
|
|
||||||
style: 代码格式调整
|
|
||||||
refactor: 重构
|
|
||||||
perf: 性能优化
|
|
||||||
test: 测试相关
|
|
||||||
chore: 构建/工具变动
|
|
||||||
```
|
|
||||||
|
|
||||||
### 分支管理
|
|
||||||
- `master` - 生产环境分支
|
|
||||||
- `develop` - 开发分支
|
|
||||||
- `feature/*` - 功能分支
|
|
||||||
- `bugfix/*` - Bug修复分支
|
|
||||||
- `hotfix/*` - 热修复分支
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 许可证
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 联系方式
|
|
||||||
项目维护者:Deploy Ease Team
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 更新日志
|
|
||||||
|
|
||||||
### v1.0.0(当前版本)
|
|
||||||
- ✅ 完成系统管理核心功能
|
|
||||||
- ✅ 完成Jenkins和Git集成
|
|
||||||
- ✅ 完成工作流引擎基础功能
|
|
||||||
- ✅ 完成部署管理框架
|
|
||||||
- ✅ 完成28张数据库表设计
|
|
||||||
- ✅ 完成280+个API接口
|
|
||||||
- ⚠️ 测试覆盖率待提升
|
|
||||||
- ⚠️ 缓存机制待实现
|
|
||||||
- ⚠️ 生产环境配置待优化
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**注意事项:**
|
|
||||||
1. 本项目处于开发阶段,部分功能仍在完善中
|
|
||||||
2. 生产环境使用前请务必完成安全加固
|
|
||||||
3. 建议补充完整的单元测试和集成测试
|
|
||||||
4. 数据库配置和JWT密钥需要外部化管理
|
|
||||||
5. 建议集成专业的日志分析和监控方案
|
|
||||||
@ -1,659 +0,0 @@
|
|||||||
# API 接口文档
|
|
||||||
|
|
||||||
## 1. 通用说明
|
|
||||||
|
|
||||||
### 1.1 接口规范
|
|
||||||
|
|
||||||
- 基础路径: `/api/v1`
|
|
||||||
- 请求方式: REST风格
|
|
||||||
- 数据格式: JSON
|
|
||||||
- 字符编码: UTF-8
|
|
||||||
- 时间格式: ISO8601 (YYYY-MM-DDTHH:mm:ss.SSSZ)
|
|
||||||
|
|
||||||
### 1.2 通用响应格式
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 0, // 响应码,0表示成功,非0表示失败
|
|
||||||
"message": "成功", // 响应消息
|
|
||||||
"data": { // 响应数据
|
|
||||||
// 具体数据结构
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.3 通用查询参数
|
|
||||||
|
|
||||||
分页查询参数:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"page": 1, // 页码,从1开始
|
|
||||||
"size": 10, // 每页大小
|
|
||||||
"sort": ["id,desc"], // 排序字段,可选
|
|
||||||
"keyword": "", // 关键字搜索,可选
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.4 通用错误码
|
|
||||||
|
|
||||||
| 错误码 | 说明 | 处理建议 |
|
|
||||||
|--------|------|----------|
|
|
||||||
| 0 | 成功 | - |
|
|
||||||
| 1000 | 系统内部错误 | 联系管理员 |
|
|
||||||
| 1001 | 数据库操作失败 | 重试或联系管理员 |
|
|
||||||
| 1002 | 并发操作冲突 | 刷新后重试 |
|
|
||||||
| 2000 | 参数验证失败 | 检查参数 |
|
|
||||||
| 2001 | 数据不存在 | 检查参数 |
|
|
||||||
| 2002 | 数据已存在 | 检查参数 |
|
|
||||||
|
|
||||||
## 2. 工作流定义<E5AE9A><E4B989>口
|
|
||||||
|
|
||||||
### 2.1 创建工作流定义
|
|
||||||
|
|
||||||
**接口说明**:创建新的工作流定义
|
|
||||||
|
|
||||||
**请求路径**:POST /workflow-definitions
|
|
||||||
|
|
||||||
**请求参数**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": "string", // 工作流编码,必填,唯一
|
|
||||||
"name": "string", // 工作流名称,必填
|
|
||||||
"description": "string", // 描述,可选
|
|
||||||
"nodeConfig": { // 节点配置,必填
|
|
||||||
"nodes": [{
|
|
||||||
"id": "string", // 节点ID
|
|
||||||
"type": "string", // 节点类型
|
|
||||||
"name": "string", // 节点名称
|
|
||||||
"config": { // 节点配置
|
|
||||||
// 具体配置项根据节点类型定义
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
"transitionConfig": { // 流转配置,必填
|
|
||||||
"transitions": [{
|
|
||||||
"from": "string", // 来源节点ID
|
|
||||||
"to": "string", // 目标节点ID
|
|
||||||
"condition": "string" // 流转条件
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
"formDefinition": { // 表单定义,必填
|
|
||||||
// 表单配置项
|
|
||||||
},
|
|
||||||
"graphDefinition": { // 图形定义,必填
|
|
||||||
// 图形布局配置
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 0,
|
|
||||||
"message": "成功",
|
|
||||||
"data": {
|
|
||||||
"id": "long", // 工作流定义ID
|
|
||||||
"code": "string", // 工作流编码
|
|
||||||
"name": "string", // 工作流名称
|
|
||||||
"description": "string", // 描述
|
|
||||||
"version": 1, // 版本号
|
|
||||||
"status": "DRAFT", // 状态:DRAFT-草稿、PUBLISHED-已发布、DISABLED-已禁用
|
|
||||||
"enabled": true, // 是否启用
|
|
||||||
"nodeConfig": {}, // 节点配置
|
|
||||||
"transitionConfig": {}, // 流转配置
|
|
||||||
"formDefinition": {}, // 表单定义
|
|
||||||
"graphDefinition": {}, // 图形定义
|
|
||||||
"createTime": "string", // 创建时间
|
|
||||||
"updateTime": "string" // 更新时间
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 更新工作流定义
|
|
||||||
|
|
||||||
**接口说明**:更新工作流定义,仅草稿状态可更新
|
|
||||||
|
|
||||||
**请求路径**:PUT /workflow-definitions/{id}
|
|
||||||
|
|
||||||
**路径参数**:
|
|
||||||
- id: 工作流定义ID
|
|
||||||
|
|
||||||
**请求参数**:同创建接口
|
|
||||||
|
|
||||||
**响应数据**:同创建接口
|
|
||||||
|
|
||||||
### 2.3 发布工作流定义
|
|
||||||
|
|
||||||
**接口说明**:发布工作流定义,使其可被使用
|
|
||||||
|
|
||||||
**请求路径**:POST /workflow-definitions/{id}/publish
|
|
||||||
|
|
||||||
**路径参数**:
|
|
||||||
- id: 工作流定义ID
|
|
||||||
|
|
||||||
**响应数据**:同创建接口
|
|
||||||
|
|
||||||
### 2.4 禁用工作流定义
|
|
||||||
|
|
||||||
**接口说明**:禁用工作流定义,禁止新建实例
|
|
||||||
|
|
||||||
**请求路径**:POST /workflow-definitions/{id}/disable
|
|
||||||
|
|
||||||
**路径参数**:
|
|
||||||
- id: 工作流定义ID
|
|
||||||
|
|
||||||
**响应数据**:同创建接口
|
|
||||||
|
|
||||||
### 2.5 启用工作流定义
|
|
||||||
|
|
||||||
**接口说明**:启用已<E794A8><E5B7B2>用的工作流定义
|
|
||||||
|
|
||||||
**请求路径**:POST /workflow-definitions/{id}/enable
|
|
||||||
|
|
||||||
**路径参数**:
|
|
||||||
- id: 工作流定义ID
|
|
||||||
|
|
||||||
**响应数据**:同创建接口
|
|
||||||
|
|
||||||
### 2.6 创建新版本
|
|
||||||
|
|
||||||
**接口说明**:基于现有工作流定义创建新版本
|
|
||||||
|
|
||||||
**请求路径**:POST /workflow-definitions/{id}/versions
|
|
||||||
|
|
||||||
**路径参数**:
|
|
||||||
- id: 工作流定义ID
|
|
||||||
|
|
||||||
**响应数据**:同创建接口
|
|
||||||
|
|
||||||
### 2.7 查询工作流定义列表
|
|
||||||
|
|
||||||
**接口说明**:分页查询工作流定义列表
|
|
||||||
|
|
||||||
**请求路径**:GET /workflow-definitions
|
|
||||||
|
|
||||||
**请求参数**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"page": 1, // 页码,从1开始
|
|
||||||
"size": 10, // 每页大小
|
|
||||||
"sort": ["id,desc"], // 排序字段
|
|
||||||
"keyword": "", // 关键字搜索
|
|
||||||
"status": "DRAFT", // 状态过滤
|
|
||||||
"enabled": true // 是否启用
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 0,
|
|
||||||
"message": "成功",
|
|
||||||
"data": {
|
|
||||||
"content": [{ // 列表数据
|
|
||||||
// 工作流定义数据,同创建接口
|
|
||||||
}],
|
|
||||||
"totalElements": 100, // 总记录数
|
|
||||||
"totalPages": 10, // 总页数
|
|
||||||
"size": 10, // 每页大小
|
|
||||||
"number": 1 // 当前页码
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. 工作流实例接口
|
|
||||||
|
|
||||||
### 3.1 创建工作流实例
|
|
||||||
|
|
||||||
**接口说明**:创建工作流实例
|
|
||||||
|
|
||||||
**请求路径**:POST /workflow-instances
|
|
||||||
|
|
||||||
**请求参数**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"definitionId": "long", // 工作流定义ID,必填
|
|
||||||
"businessKey": "string", // 业务标识,可选
|
|
||||||
"variables": { // 初始变量,可选
|
|
||||||
"key": "value"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 0,
|
|
||||||
"message": "成功",
|
|
||||||
"data": {
|
|
||||||
"id": "long", // 实例ID
|
|
||||||
"definitionId": "long", // 定义ID
|
|
||||||
"businessKey": "string",// 业务标识
|
|
||||||
"status": "CREATED", // 状态:CREATED-已创建、RUNNING-运行中、SUSPENDED-已暂停、COMPLETED-已完成、TERMINATED-已终止
|
|
||||||
"startTime": "string", // 开始时间
|
|
||||||
"endTime": "string", // 结束时间
|
|
||||||
"variables": {}, // 变量
|
|
||||||
"createTime": "string", // 创建时间
|
|
||||||
"updateTime": "string" // 更新时间
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 启动工作流实例
|
|
||||||
|
|
||||||
**接口说明**:启动工作流实例
|
|
||||||
|
|
||||||
**请求路径**:POST /workflow-instances/{id}/start
|
|
||||||
|
|
||||||
**路径参数**:
|
|
||||||
- id: 实例ID
|
|
||||||
|
|
||||||
**响应数据**:同创建接口
|
|
||||||
|
|
||||||
### 3.3 暂停工作流实例
|
|
||||||
|
|
||||||
**接口说明**:暂停运行中的工作流实例
|
|
||||||
|
|
||||||
**请求路径**:POST /workflow-instances/{id}/suspend
|
|
||||||
|
|
||||||
**路径参数**:
|
|
||||||
- id: 实例ID
|
|
||||||
|
|
||||||
**响应数据**:同创建接口
|
|
||||||
|
|
||||||
### 3.4 恢复工作流实例
|
|
||||||
|
|
||||||
**接口说明**:恢复已暂停的工作流实例
|
|
||||||
|
|
||||||
**请求路径**:POST /workflow-instances/{id}/resume
|
|
||||||
|
|
||||||
**路径参数**:
|
|
||||||
- id: 实例ID
|
|
||||||
|
|
||||||
**响应数据**:同创建接口
|
|
||||||
|
|
||||||
### 3.5 终止工作流实例
|
|
||||||
|
|
||||||
**接口说明**:强制终止工作流实例
|
|
||||||
|
|
||||||
**请求路径**:POST /workflow-instances/{id}/terminate
|
|
||||||
|
|
||||||
**路径参数**:
|
|
||||||
- id: 实例ID
|
|
||||||
|
|
||||||
**响应数据**:同创建接口
|
|
||||||
|
|
||||||
### 3.6 查询工作流实例列表
|
|
||||||
|
|
||||||
**接口说明**:分页查询工作流实例列表
|
|
||||||
|
|
||||||
**请求路径**:GET /workflow-instances
|
|
||||||
|
|
||||||
**请求参数**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"page": 1, // 页码,从1开始
|
|
||||||
"size": 10, // 每页大小
|
|
||||||
"sort": ["id,desc"], // 排序字段
|
|
||||||
"keyword": "", // 关键字搜索
|
|
||||||
"status": "RUNNING", // 状态过滤
|
|
||||||
"definitionId": "long", // 定义ID过滤
|
|
||||||
"businessKey": "string" // 业务标识过滤
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 0,
|
|
||||||
"message": "成功",
|
|
||||||
"data": {
|
|
||||||
"content": [{ // 列表数据
|
|
||||||
// 工作流实例数据,同创建接口
|
|
||||||
}],
|
|
||||||
"totalElements": 100, // 总记录数
|
|
||||||
"totalPages": 10, // 总页数
|
|
||||||
"size": 10, // 每页大小
|
|
||||||
"number": 1 // 当前页码
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. 节点实例接口
|
|
||||||
|
|
||||||
### 4.1 查询节点实例列表
|
|
||||||
|
|
||||||
**接口说明**:查询工作流实例的节点列表
|
|
||||||
|
|
||||||
**请求路径**:GET /workflow-instances/{instanceId}/nodes
|
|
||||||
|
|
||||||
**路径参数**:
|
|
||||||
- instanceId: 工作流实例ID
|
|
||||||
|
|
||||||
**请求参数**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"status": "RUNNING" // 状态过滤,可选
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 0,
|
|
||||||
"message": "成功",
|
|
||||||
"data": [{
|
|
||||||
"id": "long", // 节点实例ID
|
|
||||||
"workflowInstanceId": "long", // 工作流实例ID
|
|
||||||
"nodeId": "string", // 节点定义ID
|
|
||||||
"nodeName": "string", // 节点名称
|
|
||||||
"nodeType": "string", // 节点类型
|
|
||||||
"status": "string", // 状态:CREATED、RUNNING、COMPLETED、FAILED
|
|
||||||
"startTime": "string", // 开始时间
|
|
||||||
"endTime": "string", // 结束时间
|
|
||||||
"output": "string", // 输出结果
|
|
||||||
"error": "string", // 错误信息
|
|
||||||
"createTime": "string", // 创建时间
|
|
||||||
"updateTime": "string" // 更新时间
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. 工作流变量接口
|
|
||||||
|
|
||||||
### 5.1 设置变量
|
|
||||||
|
|
||||||
**接口说明**:设置工作流实例变量
|
|
||||||
|
|
||||||
**请求路径**:POST /workflow-instances/{instanceId}/variables
|
|
||||||
|
|
||||||
**路径参数**:
|
|
||||||
- instanceId: 工作流实例ID
|
|
||||||
|
|
||||||
**请求参数**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "string", // 变量名,必填
|
|
||||||
"value": "object", // 变量值,必<EFBC8C><E5BF85>
|
|
||||||
"scope": "GLOBAL" // 作用域:GLOBAL-全局、NODE-节点,可选,默认GLOBAL
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 0,
|
|
||||||
"message": "成功"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.2 获取变量
|
|
||||||
|
|
||||||
**接口说明**:获取工作流实例变量
|
|
||||||
|
|
||||||
**请求路径**:GET /workflow-instances/{instanceId}/variables
|
|
||||||
|
|
||||||
**路径参数**:
|
|
||||||
- instanceId: 工作流实例ID
|
|
||||||
|
|
||||||
**请求参数**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"scope": "GLOBAL" // 作用域过滤,可选
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 0,
|
|
||||||
"message": "成功",
|
|
||||||
"data": {
|
|
||||||
"variableName": "variableValue"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6. 工作流日志接口
|
|
||||||
|
|
||||||
### 6.1 查询日志列表
|
|
||||||
|
|
||||||
**接口说明**:查询工作流实例日志
|
|
||||||
|
|
||||||
**请求路径**:GET /workflow-instances/{instanceId}/logs
|
|
||||||
|
|
||||||
**路径参数**:
|
|
||||||
- instanceId: 工作流实例ID
|
|
||||||
|
|
||||||
**请求参数**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"nodeId": "string", // 节点ID过滤,可选
|
|
||||||
"level": "INFO", // 日志级别过滤:DEBUG、INFO、WARN、ERROR,可选
|
|
||||||
"startTime": "string", // 开始时间过滤,可选
|
|
||||||
"endTime": "string" // 结束时间过滤,可选
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 0,
|
|
||||||
"message": "成功",
|
|
||||||
"data": [{
|
|
||||||
"id": "long", // 日志ID
|
|
||||||
"workflowInstanceId": "long", // 工作流实例ID
|
|
||||||
"nodeId": "string", // 节点ID
|
|
||||||
"level": "string", // 日志级别
|
|
||||||
"content": "string", // 日志内容
|
|
||||||
"detail": "string", // 详细信息
|
|
||||||
"createTime": "string" // 创建时间
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 7. 节点类型接口
|
|
||||||
|
|
||||||
### 7.1 查询节点类型列表
|
|
||||||
|
|
||||||
**接口说明**:查询可用的节点类型列表
|
|
||||||
|
|
||||||
**请求路径**:GET /node-types
|
|
||||||
|
|
||||||
**请求参数**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"enabled": true, // 是否启用过滤,可选
|
|
||||||
"category": "TASK" // 类型分类过滤:TASK、EVENT、GATEWAY,可选
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 0,
|
|
||||||
"message": "成功",
|
|
||||||
"data": [{
|
|
||||||
"id": "long", // 节点类型ID
|
|
||||||
"code": "string", // 节点类型编码
|
|
||||||
"name": "string", // 节点类型名称
|
|
||||||
"category": "string", // 分类
|
|
||||||
"description": "string", // 描述
|
|
||||||
"enabled": true, // 是否启用
|
|
||||||
"icon": "string", // 图标
|
|
||||||
"color": "string", // 颜色
|
|
||||||
"executors": [{ // 执行器列表(仅TASK类型)
|
|
||||||
"code": "string", // 执行器编码
|
|
||||||
"name": "string", // 执行器名称
|
|
||||||
"description": "string", // 描述
|
|
||||||
"configSchema": {} // 配置模式
|
|
||||||
}],
|
|
||||||
"createTime": "string", // 创建时间
|
|
||||||
"updateTime": "string" // 更新时间
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7.2 获取节点执行器列表
|
|
||||||
|
|
||||||
**接口说明**:获取指定节点类型支持的执行器列表
|
|
||||||
|
|
||||||
**请求路径**:GET /node-types/{code}/executors
|
|
||||||
|
|
||||||
**路径参数**:
|
|
||||||
- code: 节点类型编码
|
|
||||||
|
|
||||||
**响应数据**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 0,
|
|
||||||
"message": "成功",
|
|
||||||
"data": [{
|
|
||||||
"code": "string", // 执行器编码
|
|
||||||
"name": "string", // 执行器名称
|
|
||||||
"description": "string", // 描述
|
|
||||||
"configSchema": { // 配置模式
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
// 具体配置项定义
|
|
||||||
},
|
|
||||||
"required": [] // 必填项
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 8. 错误码说明
|
|
||||||
|
|
||||||
### 8.1 系统错误 (1xxx)
|
|
||||||
- 1000: 系统内部错误
|
|
||||||
- 1001: 数据库操作失败
|
|
||||||
- 1002: 并发操作冲突
|
|
||||||
|
|
||||||
### 8.2 通用业务错误 (2xxx)
|
|
||||||
- 2000: 参数验证失败
|
|
||||||
- 2001: 数据不存在
|
|
||||||
- 2002: 数据已存在
|
|
||||||
|
|
||||||
### 8.3 工作流定义错误 (3xxx)
|
|
||||||
- 3000: 工作流定义不存在
|
|
||||||
- 3001: 工作流编码已存在
|
|
||||||
- 3002: 工作流定义非草稿状态
|
|
||||||
- 3003: 工作流定义未发布
|
|
||||||
- 3004: 工作流定义已禁用
|
|
||||||
- 3005: 节点配置无效
|
|
||||||
- 3006: 流转配置无效
|
|
||||||
- 3007: 表单配置无效
|
|
||||||
|
|
||||||
### 8.4 工作流实例错误 (4xxx)
|
|
||||||
- 4000: 工作流实例不存在
|
|
||||||
- 4001: 工作流实例状态无效
|
|
||||||
- 4002: 工作流实例已终止
|
|
||||||
- 4003: 节点实例不存在
|
|
||||||
- 4004: 变量类型无效
|
|
||||||
|
|
||||||
## 9. 数据结构说明
|
|
||||||
|
|
||||||
### 9.1 工作流状态
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
enum WorkflowStatus {
|
|
||||||
DRAFT = "DRAFT", // 草稿
|
|
||||||
PUBLISHED = "PUBLISHED", // 已发布
|
|
||||||
DISABLED = "DISABLED" // 已禁用
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.2 实例状态
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
enum InstanceStatus {
|
|
||||||
CREATED = "CREATED", // 已创建
|
|
||||||
RUNNING = "RUNNING", // 运行中
|
|
||||||
SUSPENDED = "SUSPENDED", // 已暂停
|
|
||||||
COMPLETED = "COMPLETED", // 已完成
|
|
||||||
TERMINATED = "TERMINATED"// 已终止
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.3 节点状态
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
enum NodeStatus {
|
|
||||||
CREATED = "CREATED", // 已创建
|
|
||||||
RUNNING = "RUNNING", // 运行中
|
|
||||||
COMPLETED = "COMPLETED", // 已完成
|
|
||||||
FAILED = "FAILED" // 失败
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.4 日志级别
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
enum LogLevel {
|
|
||||||
DEBUG = "DEBUG",
|
|
||||||
INFO = "INFO",
|
|
||||||
WARN = "WARN",
|
|
||||||
ERROR = "ERROR"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.5 变量作用域
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
enum VariableScope {
|
|
||||||
GLOBAL = "GLOBAL", // 全局变量
|
|
||||||
NODE = "NODE" // 节点变量
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 10. 最佳实践
|
|
||||||
|
|
||||||
### 10.1 工作流设计
|
|
||||||
|
|
||||||
1. 工作流定义创建流程:
|
|
||||||
- 创建草稿
|
|
||||||
- 配置节点和流转
|
|
||||||
- 验证配置
|
|
||||||
- 发布使用
|
|
||||||
|
|
||||||
2. 节点类型选择:
|
|
||||||
- 根据业务场景选择合适的节点类型
|
|
||||||
- 配置合适的执行器
|
|
||||||
- 设置必要的变量
|
|
||||||
|
|
||||||
### 10.2 实例管理
|
|
||||||
|
|
||||||
1. 实例生命周期管理:
|
|
||||||
- 创建 -> 启动 -> 运行 -> 完成
|
|
||||||
- 必要时可暂停或终止
|
|
||||||
- 记录关键节点日志
|
|
||||||
|
|
||||||
2. 变量使用:
|
|
||||||
- 合理使用变量作用域
|
|
||||||
- 及时清理无用变量
|
|
||||||
- 注意变量类型匹配
|
|
||||||
|
|
||||||
### 10.3 错误处理
|
|
||||||
|
|
||||||
1. 异常处理:
|
|
||||||
- 捕获所有可能的错误码
|
|
||||||
- 提供友好的错误提示
|
|
||||||
- 记录详细的错误日志
|
|
||||||
|
|
||||||
2. 重试机制:
|
|
||||||
- 对非致命错误进行重试
|
|
||||||
- 设置合理的重试间隔
|
|
||||||
- 限制最大重试次数
|
|
||||||
|
|
||||||
### 10.4 性能优化
|
|
||||||
|
|
||||||
1. 查询优化:
|
|
||||||
- 合理使用分页
|
|
||||||
- 避免大量数据查询
|
|
||||||
- 使用合适的查询条件
|
|
||||||
|
|
||||||
2. 缓存使用:
|
|
||||||
- 缓存常用数据
|
|
||||||
- 及时更新缓存
|
|
||||||
- 避免缓存穿透
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,176 +0,0 @@
|
|||||||
# 后端开发指南
|
|
||||||
|
|
||||||
## 待开发接口清单
|
|
||||||
|
|
||||||
### 1. 工作流定义管理 (WorkflowDefinitionApiController)
|
|
||||||
|
|
||||||
#### 1.1 更新工作流定义
|
|
||||||
```typescript
|
|
||||||
PUT /api/v1/workflow-definitions/{id}
|
|
||||||
Request: {
|
|
||||||
// 基本信息
|
|
||||||
code: string; // 工作流编码
|
|
||||||
name: string; // 工作流名称
|
|
||||||
description: string; // 工作流描述
|
|
||||||
version: number; // 版本号
|
|
||||||
|
|
||||||
// 设计数据
|
|
||||||
design?: {
|
|
||||||
nodes: [{
|
|
||||||
id: string;
|
|
||||||
type: string;
|
|
||||||
position: { x: number; y: number };
|
|
||||||
data: {
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
config: Record<string, any>;
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
edges: [{
|
|
||||||
id: string;
|
|
||||||
source: string;
|
|
||||||
target: string;
|
|
||||||
type: string;
|
|
||||||
data?: {
|
|
||||||
condition?: string;
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Response: {
|
|
||||||
code: number;
|
|
||||||
data: WorkflowDefinitionDTO;
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 1.2 导入工作流定义(待实现)
|
|
||||||
```typescript
|
|
||||||
POST /api/v1/workflow-definitions/import
|
|
||||||
Request: FormData {
|
|
||||||
file: File; // JSON格式的工作流定义文件
|
|
||||||
}
|
|
||||||
Response: {
|
|
||||||
code: number;
|
|
||||||
data: WorkflowDefinitionDTO;
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 1.3 导出工作流定义(待实现)
|
|
||||||
```typescript
|
|
||||||
GET /api/v1/workflow-definitions/{id}/export
|
|
||||||
Response: File // JSON格式的工作流定义文件
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 工作流实例管理 (WorkflowInstanceApiController)
|
|
||||||
|
|
||||||
#### 2.1 导出工作流实例
|
|
||||||
```typescript
|
|
||||||
GET /api/v1/workflow-instances/export
|
|
||||||
Query Parameters:
|
|
||||||
- startTime?: string // 开始时间
|
|
||||||
- endTime?: string // 结束时间
|
|
||||||
- status?: string // 实例状态
|
|
||||||
- keyword?: string // 关键字搜索
|
|
||||||
Response: File // Excel格式的工作流实例数据
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 节点实例管理 (NodeInstanceApiController)
|
|
||||||
|
|
||||||
#### 3.1 导出节点实例
|
|
||||||
```typescript
|
|
||||||
GET /api/v1/node-instances/export
|
|
||||||
Query Parameters:
|
|
||||||
- workflowInstanceId?: string // 工作流实例ID
|
|
||||||
- nodeType?: string // 节点类型
|
|
||||||
- status?: string // 节点状态
|
|
||||||
- startTime?: string // 开始时间
|
|
||||||
- endTime?: string // 结束时间
|
|
||||||
Response: File // Excel格式的节点实例数据
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 节点类型管理 (NodeTypeApiController)
|
|
||||||
|
|
||||||
#### 4.1 导出节点类型
|
|
||||||
```typescript
|
|
||||||
GET /api/v1/node-types/export
|
|
||||||
Query Parameters:
|
|
||||||
- category?: string // 节点类型分类
|
|
||||||
- enabled?: boolean // 是否启用
|
|
||||||
Response: File // Excel格式的节点类型数据
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 工作流日志管理 (WorkflowLogApiController)
|
|
||||||
|
|
||||||
#### 5.1 导出日志
|
|
||||||
```typescript
|
|
||||||
GET /api/v1/workflow-logs/export
|
|
||||||
Query Parameters:
|
|
||||||
- workflowInstanceId?: string // 工作流实例ID
|
|
||||||
- nodeId?: string // 节点ID
|
|
||||||
- level?: string // 日志级别
|
|
||||||
- startTime?: string // 开始时间
|
|
||||||
- endTime?: string // 结束时间
|
|
||||||
Response: File // Excel格式的日志数据
|
|
||||||
```
|
|
||||||
|
|
||||||
## 开发注意事项
|
|
||||||
|
|
||||||
### 1. 工作流设计数据存储
|
|
||||||
- 工作流设计数据应该存储在 `workflow_definition` 表的 `design_data` 字段中
|
|
||||||
- 数据格式为JSON字符串
|
|
||||||
- 需要实现JSON序列化和反序列化
|
|
||||||
|
|
||||||
### 2. 数据导出功能
|
|
||||||
- 使用 Apache POI 处理Excel文件
|
|
||||||
- 支持大数据量导出
|
|
||||||
- 添加导出进度提示
|
|
||||||
- 支持自定义导出字段
|
|
||||||
|
|
||||||
### 3. 数据验证
|
|
||||||
- 工作流设计数据的完整性验证
|
|
||||||
- 节点连接的有效性验证
|
|
||||||
- 配置数据的格式验证
|
|
||||||
|
|
||||||
### 4. 错误处理
|
|
||||||
- 添加详细的错误提示
|
|
||||||
- 记录操作日志
|
|
||||||
- 支持事务回滚
|
|
||||||
|
|
||||||
### 5. 性能优化
|
|
||||||
- 大数据量导出时使用分页
|
|
||||||
- 添加必要的索引
|
|
||||||
- 使用缓存优化查询性能
|
|
||||||
|
|
||||||
## 开发优先级建议
|
|
||||||
|
|
||||||
1. 工作流设计数据的保存和获取
|
|
||||||
- 这是工作流设计器的核心功能
|
|
||||||
- 前端需要这些接口来实现基本功能
|
|
||||||
|
|
||||||
2. 工作流设计的验证
|
|
||||||
- 确保工作流设计的正确性
|
|
||||||
- 避免错误的工作流定义被发布
|
|
||||||
|
|
||||||
3. 工作流定义的导入导出
|
|
||||||
- 支持工作流定义的迁移
|
|
||||||
- 方便工作流的备份和恢复
|
|
||||||
|
|
||||||
4. 各个模块的数据导出
|
|
||||||
- 用于数据分析和备份
|
|
||||||
- 相对优先级较低
|
|
||||||
|
|
||||||
## 测试要求
|
|
||||||
|
|
||||||
1. 单元测试
|
|
||||||
- 测试各个Service层的核心方法
|
|
||||||
- 验证数据处理逻辑的正确性
|
|
||||||
|
|
||||||
2. 集成测试
|
|
||||||
- 测试API接口的功能
|
|
||||||
- 验证数据库操作的正确性
|
|
||||||
|
|
||||||
3. 性能测试
|
|
||||||
- 测试大数据量导出的性能
|
|
||||||
- 验证并发处理能力
|
|
||||||
@ -1,347 +0,0 @@
|
|||||||
# Deploy Ease Platform V2 设计文档
|
|
||||||
|
|
||||||
## 一、系统概述
|
|
||||||
|
|
||||||
### 1.1 系统目标
|
|
||||||
- 构建一个高度可扩展的自动化部署平台
|
|
||||||
- 支持灵活的工作流配置和审批流程
|
|
||||||
- 实现多环境、多租户的部署管理
|
|
||||||
- 提供完整的日志追踪和监控能力
|
|
||||||
|
|
||||||
### 1.2 核心功能
|
|
||||||
1. 项目管理
|
|
||||||
- 项目集管理
|
|
||||||
- 项目配置
|
|
||||||
- Git仓库集成
|
|
||||||
- Jenkins集成
|
|
||||||
|
|
||||||
2. 环境管理
|
|
||||||
- 环境配置
|
|
||||||
- 环境克隆
|
|
||||||
- 环境变量管理
|
|
||||||
- 权限控制
|
|
||||||
|
|
||||||
3. 工作流引擎
|
|
||||||
- 流程设计
|
|
||||||
- 节点执行
|
|
||||||
- 状态管理
|
|
||||||
- 日志追踪
|
|
||||||
|
|
||||||
4. 审批系统
|
|
||||||
- 审批流程定义
|
|
||||||
- 审批规则配置
|
|
||||||
- 审批节点管理
|
|
||||||
- 审批记录追踪
|
|
||||||
|
|
||||||
5. 配置中心
|
|
||||||
- Nacos配置管理
|
|
||||||
- 配置同步
|
|
||||||
- 版本控制
|
|
||||||
- 变更追踪
|
|
||||||
|
|
||||||
## 二、技术架构
|
|
||||||
|
|
||||||
### 2.1 技术选型
|
|
||||||
- 后端框架:Spring Boot 3.2.0
|
|
||||||
- Java版本:JDK 21
|
|
||||||
- ORM框架:JPA + QueryDSL
|
|
||||||
- 安全框架:Spring Security + JWT
|
|
||||||
- 数据库:MySQL 8.0
|
|
||||||
- 缓存:Redis
|
|
||||||
- 消息队列:RabbitMQ
|
|
||||||
- 服务注册:Nacos
|
|
||||||
|
|
||||||
### 2.2 系统架构
|
|
||||||
1. 微服务架构
|
|
||||||
- 网关服务
|
|
||||||
- 认证服务
|
|
||||||
- 工作流引擎
|
|
||||||
- 审批服务
|
|
||||||
- 配置中心
|
|
||||||
|
|
||||||
2. 分层架构
|
|
||||||
- 接口层(Controller)
|
|
||||||
- 业务层(Service)
|
|
||||||
- 数据访问层(Repository)
|
|
||||||
- 领域模型层(Domain)
|
|
||||||
|
|
||||||
3. 插件化架构
|
|
||||||
- 节点插件
|
|
||||||
- 审批插件
|
|
||||||
- 通知插件
|
|
||||||
|
|
||||||
## 三、数据库设计
|
|
||||||
|
|
||||||
### 3.1 核心表设计
|
|
||||||
{{ ... }}
|
|
||||||
|
|
||||||
### 3.2 工作流相关表
|
|
||||||
{{ ... }}
|
|
||||||
|
|
||||||
### 3.3 审批系统表
|
|
||||||
1. 审批定义表(sys_approval_definition)
|
|
||||||
- 基础字段(id, tenant_id等)
|
|
||||||
- 流程编码(code)
|
|
||||||
- 流程名称(name)
|
|
||||||
- 流程描述(description)
|
|
||||||
- 业务类型(business_type)
|
|
||||||
- 节点配置(node_config)
|
|
||||||
- 表单配置(form_config)
|
|
||||||
|
|
||||||
2. 审批流程表(sys_approval_flow)
|
|
||||||
- 基础字段
|
|
||||||
- 业务类型(business_type)
|
|
||||||
- 业务键(business_key)
|
|
||||||
- 模板编码(template_code)
|
|
||||||
- 当前节点(current_node)
|
|
||||||
- 流程状态(status)
|
|
||||||
- 流程变量(variables)
|
|
||||||
|
|
||||||
3. 审批节点表(sys_approval_node)
|
|
||||||
- 基础字段
|
|
||||||
- 流程ID(flow_id)
|
|
||||||
- 节点编码(node_code)
|
|
||||||
- 节点名称(node_name)
|
|
||||||
- 节点类型(node_type)
|
|
||||||
- 审批人类型(approver_type)
|
|
||||||
- 审批人列表(approver_ids)
|
|
||||||
- 节点状态(status)
|
|
||||||
- 执行时间(start_time, end_time)
|
|
||||||
|
|
||||||
4. 审批意见表(sys_approval_comment)
|
|
||||||
- 基础字段
|
|
||||||
- 流程ID(flow_id)
|
|
||||||
- 节点ID(node_id)
|
|
||||||
- 审批人(user_id)
|
|
||||||
- 审批动作(action)
|
|
||||||
- 审批意见(comment)
|
|
||||||
- 附件信息(attachments)
|
|
||||||
|
|
||||||
## 四、审批系统设计
|
|
||||||
|
|
||||||
### 4.1 审批系统架构
|
|
||||||
1. 核心组件
|
|
||||||
- 审批引擎(ApprovalEngine)
|
|
||||||
- 审批规则解析器(RuleParser)
|
|
||||||
- 审批节点执行器(NodeExecutor)
|
|
||||||
- 审批事件总线(EventBus)
|
|
||||||
|
|
||||||
2. 扩展点
|
|
||||||
- 审批规则插件
|
|
||||||
- 审批节点类型
|
|
||||||
- 审批表单定义
|
|
||||||
- 审批通知方式
|
|
||||||
|
|
||||||
### 4.2 审批流程设计
|
|
||||||
1. 流程定义
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "start",
|
|
||||||
"type": "start",
|
|
||||||
"next": "approve1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "approve1",
|
|
||||||
"type": "approve",
|
|
||||||
"config": {
|
|
||||||
"approverType": "ROLE",
|
|
||||||
"approverIds": ["ROLE_LEADER"],
|
|
||||||
"counterSign": false
|
|
||||||
},
|
|
||||||
"next": "approve2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "approve2",
|
|
||||||
"type": "approve",
|
|
||||||
"config": {
|
|
||||||
"approverType": "USER",
|
|
||||||
"approverIds": ["user1", "user2"],
|
|
||||||
"counterSign": true
|
|
||||||
},
|
|
||||||
"next": "end"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "end",
|
|
||||||
"type": "end"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 审批规则
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"condition": {
|
|
||||||
"field": "environment",
|
|
||||||
"operator": "equals",
|
|
||||||
"value": "PROD"
|
|
||||||
},
|
|
||||||
"template": "PROD_DEPLOY_APPROVAL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"condition": {
|
|
||||||
"field": "changeType",
|
|
||||||
"operator": "in",
|
|
||||||
"value": ["CONFIG", "DATABASE"]
|
|
||||||
},
|
|
||||||
"template": "CHANGE_APPROVAL"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.3 审批流程实现
|
|
||||||
1. 流程启动
|
|
||||||
- 规则匹配
|
|
||||||
- 创建流程实例
|
|
||||||
- 初始化节点
|
|
||||||
- 发送通知
|
|
||||||
|
|
||||||
2. 节点执行
|
|
||||||
- 权限校验
|
|
||||||
- 节点执行
|
|
||||||
- 状态更新
|
|
||||||
- 下一节点
|
|
||||||
|
|
||||||
3. 流程结束
|
|
||||||
- 结果处理
|
|
||||||
- 状态更新
|
|
||||||
- 通知相关人
|
|
||||||
|
|
||||||
### 4.4 与工作流集成
|
|
||||||
1. 工作流定义扩展
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "deploy",
|
|
||||||
"type": "deploy",
|
|
||||||
"config": {
|
|
||||||
"approval": {
|
|
||||||
"enabled": true,
|
|
||||||
"template": "DEPLOY_APPROVAL"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 节点执行器实现
|
|
||||||
```java
|
|
||||||
public class DeployNodeExecutor implements NodeExecutor {
|
|
||||||
@Override
|
|
||||||
public void execute(NodeInstance node) {
|
|
||||||
// 1. 检查是否需要审批
|
|
||||||
if (needApproval(node)) {
|
|
||||||
// 2. 创建审批流程
|
|
||||||
createApprovalFlow(node);
|
|
||||||
// 3. 暂停节点执行
|
|
||||||
node.suspend();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 4. 执行部署
|
|
||||||
executeDeploy(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 五、系统特性
|
|
||||||
|
|
||||||
### 5.1 多租户支持
|
|
||||||
1. 数据隔离
|
|
||||||
- 租户ID字段
|
|
||||||
- 查询过滤
|
|
||||||
- 权限控制
|
|
||||||
|
|
||||||
2. 资源隔离
|
|
||||||
- 独立配置
|
|
||||||
- 独立存储
|
|
||||||
- 独立缓存
|
|
||||||
|
|
||||||
### 5.2 插件化设计
|
|
||||||
1. 节点插件
|
|
||||||
- Git操作
|
|
||||||
- Jenkins构建
|
|
||||||
- 配置同步
|
|
||||||
- 数据库变更
|
|
||||||
|
|
||||||
2. 审批插件
|
|
||||||
- 角色审批
|
|
||||||
- 用户审批
|
|
||||||
- 条件审批
|
|
||||||
- 自动审批
|
|
||||||
|
|
||||||
### 5.3 安全特性
|
|
||||||
1. 认证授权
|
|
||||||
- JWT认证
|
|
||||||
- RBAC权限
|
|
||||||
- 租户隔离
|
|
||||||
|
|
||||||
2. 数据安全
|
|
||||||
- 敏感信息加密
|
|
||||||
- 操作审计
|
|
||||||
- 数据备份
|
|
||||||
|
|
||||||
### 5.4 可观测性
|
|
||||||
1. 日志管理
|
|
||||||
- 操作日志
|
|
||||||
- 审计日志
|
|
||||||
- 性能日志
|
|
||||||
|
|
||||||
2. 监控告警
|
|
||||||
- 系统监控
|
|
||||||
- 业务监控
|
|
||||||
- 异常告警
|
|
||||||
|
|
||||||
## 六、部署架构
|
|
||||||
|
|
||||||
### 6.1 系统部署
|
|
||||||
1. 容器化部署
|
|
||||||
- Docker镜像
|
|
||||||
- K8s编排
|
|
||||||
- 服务网格
|
|
||||||
|
|
||||||
2. 高可用设计
|
|
||||||
- 多副本
|
|
||||||
- 负载均衡
|
|
||||||
- 故障转移
|
|
||||||
|
|
||||||
### 6.2 数据存储
|
|
||||||
1. 数据库
|
|
||||||
- 主从复制
|
|
||||||
- 数据备份
|
|
||||||
- 分库分表
|
|
||||||
|
|
||||||
2. 文件存储
|
|
||||||
- 分布式存储
|
|
||||||
- 文件备份
|
|
||||||
- 访问控制
|
|
||||||
|
|
||||||
## 七、后续规划
|
|
||||||
|
|
||||||
### 7.1 功能增强
|
|
||||||
1. 审批功能
|
|
||||||
- 移动端审批
|
|
||||||
- 审批委托
|
|
||||||
- 审批催办
|
|
||||||
|
|
||||||
2. 部署功能
|
|
||||||
- 灰度发布
|
|
||||||
- 回滚机制
|
|
||||||
- 批量部署
|
|
||||||
|
|
||||||
### 7.2 性能优化
|
|
||||||
1. 系统性能
|
|
||||||
- 缓存优化
|
|
||||||
- SQL优化
|
|
||||||
- 并发处理
|
|
||||||
|
|
||||||
2. 用户体验
|
|
||||||
- 响应速度
|
|
||||||
- 操作便捷
|
|
||||||
- 界面优化
|
|
||||||
@ -1,887 +0,0 @@
|
|||||||
# Deploy Ease 自动化部署平台设计文档
|
|
||||||
|
|
||||||
## 一、系统概述
|
|
||||||
|
|
||||||
### 1.1 功能模块
|
|
||||||
1. 项目集管理
|
|
||||||
- 项目集配置
|
|
||||||
- 项目管理
|
|
||||||
- Git仓库集成
|
|
||||||
- Jenkins集成
|
|
||||||
|
|
||||||
2. 环境管理
|
|
||||||
- 环境配置
|
|
||||||
- 环境克隆
|
|
||||||
- 环境变量管理
|
|
||||||
- 权限控制
|
|
||||||
|
|
||||||
3. 工作流引擎
|
|
||||||
- 流程设计
|
|
||||||
- 节点执行
|
|
||||||
- 状态管理
|
|
||||||
- 日志追踪
|
|
||||||
|
|
||||||
4. 审批流程
|
|
||||||
- 审批配置
|
|
||||||
- 审批执行
|
|
||||||
- 权限管理
|
|
||||||
- 日志记录
|
|
||||||
|
|
||||||
5. 配置中心
|
|
||||||
- Nacos配置管理
|
|
||||||
- 配置同步
|
|
||||||
- 版本控制
|
|
||||||
- 变更追踪
|
|
||||||
|
|
||||||
## 二、数据库设计
|
|
||||||
|
|
||||||
### 2.1 项目管理相关表
|
|
||||||
```sql
|
|
||||||
-- 项目集表
|
|
||||||
CREATE TABLE sys_project_group (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
tenant_id BIGINT NOT NULL COMMENT '租户ID',
|
|
||||||
|
|
||||||
code VARCHAR(50) NOT NULL COMMENT '项目集编码',
|
|
||||||
name VARCHAR(100) NOT NULL COMMENT '项目集名称',
|
|
||||||
description TEXT COMMENT '描述',
|
|
||||||
git_group_url VARCHAR(255) NOT NULL COMMENT 'Git组地址',
|
|
||||||
jenkins_folder VARCHAR(255) COMMENT 'Jenkins文件夹',
|
|
||||||
deploy_type VARCHAR(20) NOT NULL COMMENT '部署类型:JENKINS/SELF_BUILD',
|
|
||||||
status VARCHAR(20) NOT NULL DEFAULT 'ENABLED' COMMENT '状态:ENABLED/DISABLED',
|
|
||||||
|
|
||||||
CONSTRAINT uk_group_code UNIQUE (tenant_id, code),
|
|
||||||
CONSTRAINT uk_group_name UNIQUE (tenant_id, name)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='项目集表';
|
|
||||||
|
|
||||||
-- 项目表
|
|
||||||
CREATE TABLE sys_project (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
tenant_id BIGINT NOT NULL COMMENT '租户ID',
|
|
||||||
|
|
||||||
group_id BIGINT NOT NULL COMMENT '项目集ID',
|
|
||||||
code VARCHAR(50) NOT NULL COMMENT '项目编码',
|
|
||||||
name VARCHAR(100) NOT NULL COMMENT '项目名称',
|
|
||||||
description TEXT COMMENT '描述',
|
|
||||||
git_url VARCHAR(255) NOT NULL COMMENT 'Git地址',
|
|
||||||
jenkins_job VARCHAR(255) COMMENT 'Jenkins任务名',
|
|
||||||
build_type VARCHAR(20) NOT NULL COMMENT '构建类型:MAVEN/GRADLE/NPM',
|
|
||||||
enable_build BIT NOT NULL DEFAULT 1 COMMENT '是否启用构建',
|
|
||||||
status VARCHAR(20) NOT NULL DEFAULT 'ENABLED' COMMENT '状态:ENABLED/DISABLED',
|
|
||||||
|
|
||||||
CONSTRAINT fk_project_group FOREIGN KEY (group_id) REFERENCES sys_project_group(id),
|
|
||||||
CONSTRAINT uk_project_code UNIQUE (tenant_id, code),
|
|
||||||
CONSTRAINT uk_project_name UNIQUE (tenant_id, group_id, name)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='项目表';
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 环境管理相关表
|
|
||||||
```sql
|
|
||||||
-- 环境表
|
|
||||||
CREATE TABLE sys_environment (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
tenant_id BIGINT NOT NULL COMMENT '租户ID',
|
|
||||||
|
|
||||||
code VARCHAR(50) NOT NULL COMMENT '环境编码',
|
|
||||||
name VARCHAR(100) NOT NULL COMMENT '环境名称',
|
|
||||||
description TEXT COMMENT '描述',
|
|
||||||
type VARCHAR(20) NOT NULL COMMENT '环境类型:DEV/TEST/UAT/PROD',
|
|
||||||
need_approval BIT NOT NULL DEFAULT 0 COMMENT '是否需要审批',
|
|
||||||
status VARCHAR(20) NOT NULL DEFAULT 'ENABLED' COMMENT '状态:ENABLED/DISABLED',
|
|
||||||
|
|
||||||
CONSTRAINT uk_env_code UNIQUE (tenant_id, code),
|
|
||||||
CONSTRAINT uk_env_name UNIQUE (tenant_id, name)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='环境表';
|
|
||||||
|
|
||||||
-- 项目环境配置表
|
|
||||||
CREATE TABLE sys_project_env (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
tenant_id BIGINT NOT NULL COMMENT '租户ID',
|
|
||||||
|
|
||||||
project_id BIGINT NOT NULL COMMENT '项目ID',
|
|
||||||
env_id BIGINT NOT NULL COMMENT '环境ID',
|
|
||||||
workflow_id BIGINT COMMENT '工作流定义ID',
|
|
||||||
config TEXT COMMENT '环境配置(JSON)',
|
|
||||||
status VARCHAR(20) NOT NULL DEFAULT 'ENABLED' COMMENT '状态:ENABLED/DISABLED',
|
|
||||||
|
|
||||||
CONSTRAINT fk_env_project FOREIGN KEY (project_id) REFERENCES sys_project(id),
|
|
||||||
CONSTRAINT fk_env_environment FOREIGN KEY (env_id) REFERENCES sys_environment(id),
|
|
||||||
CONSTRAINT uk_project_env UNIQUE (tenant_id, project_id, env_id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='项目环境配置表';
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.3 工作流相关表
|
|
||||||
```sql
|
|
||||||
-- 工作流定义表
|
|
||||||
CREATE TABLE sys_workflow_definition (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
tenant_id BIGINT NOT NULL COMMENT '租户ID',
|
|
||||||
|
|
||||||
code VARCHAR(50) NOT NULL COMMENT '工作流编码',
|
|
||||||
name VARCHAR(100) NOT NULL COMMENT '工作流名称',
|
|
||||||
description TEXT COMMENT '描述',
|
|
||||||
content TEXT NOT NULL COMMENT '工作流定义(JSON)',
|
|
||||||
type VARCHAR(20) NOT NULL COMMENT '类型:DEPLOY/CONFIG_SYNC',
|
|
||||||
status VARCHAR(20) NOT NULL DEFAULT 'DRAFT' COMMENT '状态:DRAFT/PUBLISHED/DISABLED',
|
|
||||||
|
|
||||||
CONSTRAINT uk_workflow_code UNIQUE (tenant_id, code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流定义表';
|
|
||||||
|
|
||||||
-- 工作流实例表
|
|
||||||
CREATE TABLE sys_workflow_instance (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
tenant_id BIGINT NOT NULL COMMENT '租户ID',
|
|
||||||
|
|
||||||
definition_id BIGINT NOT NULL COMMENT '工作流定义ID',
|
|
||||||
project_env_id BIGINT NOT NULL COMMENT '项目环境ID',
|
|
||||||
status VARCHAR(20) NOT NULL COMMENT '状态:RUNNING/COMPLETED/FAILED/CANCELED',
|
|
||||||
start_time DATETIME NOT NULL COMMENT '开始时间',
|
|
||||||
end_time DATETIME COMMENT '结束时间',
|
|
||||||
variables TEXT COMMENT '工作流变量(JSON)',
|
|
||||||
error TEXT COMMENT '错误信息',
|
|
||||||
|
|
||||||
CONSTRAINT fk_instance_definition FOREIGN KEY (definition_id) REFERENCES sys_workflow_definition(id),
|
|
||||||
CONSTRAINT fk_instance_project_env FOREIGN KEY (project_env_id) REFERENCES sys_project_env(id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流实例表';
|
|
||||||
|
|
||||||
-- 节点实例表
|
|
||||||
CREATE TABLE sys_node_instance (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
tenant_id BIGINT NOT NULL COMMENT '租户ID',
|
|
||||||
|
|
||||||
workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID',
|
|
||||||
node_id VARCHAR(50) NOT NULL COMMENT '节点ID',
|
|
||||||
node_type VARCHAR(50) NOT NULL COMMENT '节点类型',
|
|
||||||
name VARCHAR(100) NOT NULL COMMENT '节点名称',
|
|
||||||
status VARCHAR(20) NOT NULL COMMENT '状态:PENDING/RUNNING/COMPLETED/FAILED/CANCELED',
|
|
||||||
start_time DATETIME COMMENT '开始时间',
|
|
||||||
end_time DATETIME COMMENT '结束时间',
|
|
||||||
input TEXT COMMENT '输入参数(JSON)',
|
|
||||||
output TEXT COMMENT '输出结果(JSON)',
|
|
||||||
error TEXT COMMENT '错误信息',
|
|
||||||
|
|
||||||
CONSTRAINT fk_node_workflow_instance FOREIGN KEY (workflow_instance_id) REFERENCES sys_workflow_instance(id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='节点实例表';
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.4 审批相关表
|
|
||||||
```sql
|
|
||||||
-- 审批流程定义表
|
|
||||||
CREATE TABLE sys_approval_flow (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
tenant_id BIGINT NOT NULL COMMENT '租户ID',
|
|
||||||
|
|
||||||
code VARCHAR(50) NOT NULL COMMENT '流程编码',
|
|
||||||
name VARCHAR(100) NOT NULL COMMENT '流程名称',
|
|
||||||
description TEXT COMMENT '描述',
|
|
||||||
type VARCHAR(20) NOT NULL COMMENT '流程类型:DEPLOY/LEAVE/NACOS_CONFIG',
|
|
||||||
content TEXT NOT NULL COMMENT '流程定义(JSON)',
|
|
||||||
status VARCHAR(20) NOT NULL DEFAULT 'ENABLED' COMMENT '状态:ENABLED/DISABLED',
|
|
||||||
|
|
||||||
CONSTRAINT uk_flow_code UNIQUE (tenant_id, code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批流程定义表';
|
|
||||||
|
|
||||||
-- 审批任务表
|
|
||||||
CREATE TABLE sys_approval_task (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
tenant_id BIGINT NOT NULL COMMENT '租户ID',
|
|
||||||
|
|
||||||
flow_id BIGINT NOT NULL COMMENT '流程定义ID',
|
|
||||||
node_id BIGINT NOT NULL COMMENT '当前节点ID',
|
|
||||||
title VARCHAR(200) NOT NULL COMMENT '审批标题',
|
|
||||||
status VARCHAR(20) NOT NULL COMMENT '状态:PENDING/APPROVED/REJECTED/CANCELED',
|
|
||||||
start_time DATETIME NOT NULL COMMENT '开始时间',
|
|
||||||
end_time DATETIME COMMENT '结束时间',
|
|
||||||
variables TEXT COMMENT '审批变量(JSON)',
|
|
||||||
|
|
||||||
CONSTRAINT fk_task_flow FOREIGN KEY (flow_id) REFERENCES sys_approval_flow(id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批任务表';
|
|
||||||
|
|
||||||
-- 审批记录表
|
|
||||||
CREATE TABLE sys_approval_record (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
tenant_id BIGINT NOT NULL COMMENT '租户ID',
|
|
||||||
|
|
||||||
task_id BIGINT NOT NULL COMMENT '审批任务ID',
|
|
||||||
node_id BIGINT NOT NULL COMMENT '审批节点ID',
|
|
||||||
approver VARCHAR(100) NOT NULL COMMENT '审批人',
|
|
||||||
action VARCHAR(20) NOT NULL COMMENT '操作:APPROVE/REJECT',
|
|
||||||
comment TEXT COMMENT '审批意见',
|
|
||||||
|
|
||||||
CONSTRAINT fk_record_task FOREIGN KEY (task_id) REFERENCES sys_approval_task(id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批记录表';
|
|
||||||
```
|
|
||||||
|
|
||||||
## 三、API接口设计
|
|
||||||
|
|
||||||
### 3.1 项目管理接口
|
|
||||||
|
|
||||||
#### 3.1.1 项目集管理
|
|
||||||
```yaml
|
|
||||||
/api/v1/projectGroup-group:
|
|
||||||
post:
|
|
||||||
summary: 创建项目集
|
|
||||||
description: |
|
|
||||||
创建新的项目集,需要提供以下信息:
|
|
||||||
1. 项目集基本信息(编码、名称、描述)
|
|
||||||
2. Git仓库信息
|
|
||||||
3. Jenkins配置(可选)
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ProjectGroupDTO'
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: 成功创建项目集
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/Response'
|
|
||||||
|
|
||||||
get:
|
|
||||||
summary: 查询项目集列表
|
|
||||||
description: |
|
|
||||||
支持以下查询条件:
|
|
||||||
1. 名称模糊搜索
|
|
||||||
2. 状态过滤
|
|
||||||
3. 分页参数
|
|
||||||
parameters:
|
|
||||||
- name: name
|
|
||||||
in: query
|
|
||||||
description: 项目集名称
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
- name: status
|
|
||||||
in: query
|
|
||||||
description: 状态
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
enum: [ENABLED, DISABLED]
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: 项目集列表
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/PageResponse'
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.1.2 项目管理
|
|
||||||
```yaml
|
|
||||||
/api/v1/projectGroup:
|
|
||||||
post:
|
|
||||||
summary: 创建项目
|
|
||||||
description: |
|
|
||||||
创建新项目,需要提供:
|
|
||||||
1. 项目基本信息
|
|
||||||
2. 所属项目集
|
|
||||||
3. Git仓库信息
|
|
||||||
4. 构建配置
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/ProjectDTO'
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: 成功创建项目
|
|
||||||
|
|
||||||
get:
|
|
||||||
summary: 查询项目列表
|
|
||||||
description: |
|
|
||||||
支持条件:
|
|
||||||
1. 项目集ID
|
|
||||||
2. 名称搜索
|
|
||||||
3. 状态过滤
|
|
||||||
parameters:
|
|
||||||
- name: groupId
|
|
||||||
in: query
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
- name: name
|
|
||||||
in: query
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: 项目列表
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 工作流管理接口
|
|
||||||
|
|
||||||
#### 3.2.1 工作流设计
|
|
||||||
```yaml
|
|
||||||
/api/v1/workflow/definition:
|
|
||||||
post:
|
|
||||||
summary: 创建工作流
|
|
||||||
description: |
|
|
||||||
创建工作流定义,支持:
|
|
||||||
1. 节点配置
|
|
||||||
2. 连线规则
|
|
||||||
3. 变量定义
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/WorkflowDefinitionDTO'
|
|
||||||
|
|
||||||
get:
|
|
||||||
summary: 获取工作流定义
|
|
||||||
description: |
|
|
||||||
查询工作流定义,支持:
|
|
||||||
1. 基本信息
|
|
||||||
2. 节点信息
|
|
||||||
3. 连线信息
|
|
||||||
parameters:
|
|
||||||
- name: id
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.2.2 工作流执行
|
|
||||||
```yaml
|
|
||||||
/api/v1/workflow/instance:
|
|
||||||
post:
|
|
||||||
summary: 启动工作流
|
|
||||||
description: |
|
|
||||||
启动工作流实例,需要:
|
|
||||||
1. 工作流定义ID
|
|
||||||
2. 项目环境ID
|
|
||||||
3. 执行参数
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/WorkflowStartDTO'
|
|
||||||
|
|
||||||
get:
|
|
||||||
summary: 查询执行状态
|
|
||||||
description: |
|
|
||||||
查询工作流执行状态,包括:
|
|
||||||
1. 整体状态
|
|
||||||
2. 节点状态
|
|
||||||
3. 执行日志
|
|
||||||
parameters:
|
|
||||||
- name: id
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
```
|
|
||||||
|
|
||||||
## 四、前端集成指南
|
|
||||||
|
|
||||||
### 4.1 工作流设计器
|
|
||||||
|
|
||||||
#### 4.1.1 组件结构
|
|
||||||
```typescript
|
|
||||||
// 工作流设计器组件
|
|
||||||
const WorkflowDesigner: React.FC = () => {
|
|
||||||
// 1. 画布配置
|
|
||||||
const graphConfig = {
|
|
||||||
container: 'workflow-container',
|
|
||||||
grid: true,
|
|
||||||
history: true,
|
|
||||||
connecting: {
|
|
||||||
snap: true,
|
|
||||||
allowBlank: false,
|
|
||||||
allowLoop: false,
|
|
||||||
highlight: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// 2. 节点配置
|
|
||||||
const nodeConfig = {
|
|
||||||
// Git同步节点
|
|
||||||
gitSync: {
|
|
||||||
width: 120,
|
|
||||||
height: 60,
|
|
||||||
attrs: {
|
|
||||||
body: {
|
|
||||||
fill: '#E7F7FF',
|
|
||||||
stroke: '#1890FF',
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
text: 'Git同步',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// 其他节点配置...
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="workflow-designer">
|
|
||||||
<Toolbar>
|
|
||||||
<NodePalette nodes={nodeConfig} />
|
|
||||||
<div className="actions">
|
|
||||||
<Button onClick={handleSave}>保存</Button>
|
|
||||||
<Button onClick={handleDeploy}>部署</Button>
|
|
||||||
</div>
|
|
||||||
</Toolbar>
|
|
||||||
|
|
||||||
<div className="designer-container">
|
|
||||||
<div id="workflow-container" className="graph-container" />
|
|
||||||
<NodeConfig node={selectedNode} onChange={handleNodeChange} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<LogViewer instance={currentInstance} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4.1.2 节点配置
|
|
||||||
```typescript
|
|
||||||
// 节点配置组件
|
|
||||||
const NodeConfig: React.FC<{node: Node}> = ({ node }) => {
|
|
||||||
// 1. 动态加载配置组件
|
|
||||||
const ConfigComponent = nodeConfigComponents[node.type];
|
|
||||||
|
|
||||||
// 2. 处理配置变更
|
|
||||||
const handleConfigChange = (config: any) => {
|
|
||||||
// 更新节点配置
|
|
||||||
node.updateConfig(config);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="node-config">
|
|
||||||
<ConfigComponent
|
|
||||||
value={node.config}
|
|
||||||
onChange={handleConfigChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Git同步节点配置
|
|
||||||
const GitSyncConfig: React.FC<{value: any, onChange: (config: any) => void}> = ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Form layout="vertical">
|
|
||||||
<Form.Item label="分支" required>
|
|
||||||
<Input
|
|
||||||
value={value.branch}
|
|
||||||
onChange={e => onChange({ ...value, branch: e.target.value })}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="本地路径" required>
|
|
||||||
<Input
|
|
||||||
value={value.localPath}
|
|
||||||
onChange={e => onChange({ ...value, localPath: e.target.value })}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4.1.3 日志查看
|
|
||||||
```typescript
|
|
||||||
// 日志查看器组件
|
|
||||||
const LogViewer: React.FC<{instance: WorkflowInstance}> = ({ instance }) => {
|
|
||||||
const [logs, setLogs] = useState<LogMessage[]>([]);
|
|
||||||
|
|
||||||
// 1. 建立WebSocket连接
|
|
||||||
useEffect(() => {
|
|
||||||
const ws = new WebSocket(`ws://localhost:8080/api/logs/${instance.id}`);
|
|
||||||
|
|
||||||
ws.onmessage = (event) => {
|
|
||||||
const log = JSON.parse(event.data);
|
|
||||||
setLogs(prev => [...prev, log]);
|
|
||||||
};
|
|
||||||
|
|
||||||
return () => ws.close();
|
|
||||||
}, [instance]);
|
|
||||||
|
|
||||||
// 2. 渲染日志
|
|
||||||
return (
|
|
||||||
<div className="log-viewer">
|
|
||||||
<Tabs>
|
|
||||||
<TabPane tab="流程日志" key="workflow">
|
|
||||||
<LogList logs={logs.filter(log => log.type === 'workflow')} />
|
|
||||||
</TabPane>
|
|
||||||
<TabPane tab="节点日志" key="node">
|
|
||||||
<LogList logs={logs.filter(log => log.type === 'node')} />
|
|
||||||
</TabPane>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.2 状态管理
|
|
||||||
|
|
||||||
#### 4.2.1 工作流状态
|
|
||||||
```typescript
|
|
||||||
// 工作流状态管理
|
|
||||||
const useWorkflowStore = create<WorkflowStore>((set) => ({
|
|
||||||
definitions: [],
|
|
||||||
instances: [],
|
|
||||||
|
|
||||||
// 加载工作流定义
|
|
||||||
loadDefinitions: async () => {
|
|
||||||
const response = await api.get('/api/v1/workflow/definition');
|
|
||||||
set({ definitions: response.data });
|
|
||||||
},
|
|
||||||
|
|
||||||
// 创建工作流实例
|
|
||||||
createInstance: async (params) => {
|
|
||||||
const response = await api.post('/api/v1/workflow/instance', params);
|
|
||||||
set(state => ({
|
|
||||||
instances: [...state.instances, response.data],
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
// 更新实例状态
|
|
||||||
updateInstanceStatus: (id, status) => {
|
|
||||||
set(state => ({
|
|
||||||
instances: state.instances.map(instance =>
|
|
||||||
instance.id === id
|
|
||||||
? { ...instance, status }
|
|
||||||
: instance
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4.2.2 审批状态
|
|
||||||
```typescript
|
|
||||||
// 审批状态管理
|
|
||||||
const useApprovalStore = create<ApprovalStore>((set) => ({
|
|
||||||
tasks: [],
|
|
||||||
records: [],
|
|
||||||
|
|
||||||
// 加载待办任务
|
|
||||||
loadTasks: async () => {
|
|
||||||
const response = await api.get('/api/v1/approval/task');
|
|
||||||
set({ tasks: response.data });
|
|
||||||
},
|
|
||||||
|
|
||||||
// 审批通过
|
|
||||||
approve: async (taskId, comment) => {
|
|
||||||
await api.post(`/api/v1/approval/task/${taskId}/approve`, { comment });
|
|
||||||
set(state => ({
|
|
||||||
tasks: state.tasks.filter(task => task.id !== taskId),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
// 审批拒绝
|
|
||||||
reject: async (taskId, comment) => {
|
|
||||||
await api.post(`/api/v1/approval/task/${taskId}/reject`, { comment });
|
|
||||||
set(state => ({
|
|
||||||
tasks: state.tasks.filter(task => task.id !== taskId),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
```
|
|
||||||
|
|
||||||
## 五、部署指南
|
|
||||||
|
|
||||||
### 5.1 环境要求
|
|
||||||
1. Java 21
|
|
||||||
2. MySQL 8.0
|
|
||||||
3. Redis 7.x
|
|
||||||
4. Node.js 18+
|
|
||||||
|
|
||||||
### 5.2 配置说明
|
|
||||||
```yaml
|
|
||||||
# application.yml
|
|
||||||
spring:
|
|
||||||
datasource:
|
|
||||||
url: jdbc:mysql://localhost:3306/deploy_ease
|
|
||||||
username: root
|
|
||||||
password: root
|
|
||||||
|
|
||||||
redis:
|
|
||||||
host: localhost
|
|
||||||
port: 6379
|
|
||||||
|
|
||||||
rabbitmq:
|
|
||||||
host: localhost
|
|
||||||
port: 5672
|
|
||||||
|
|
||||||
workflow:
|
|
||||||
executor:
|
|
||||||
core-pool-size: 10
|
|
||||||
max-pool-size: 20
|
|
||||||
queue-capacity: 100
|
|
||||||
|
|
||||||
node:
|
|
||||||
timeout: 3600 # 节点执行超时时间(秒)
|
|
||||||
retry:
|
|
||||||
max-attempts: 3
|
|
||||||
delay: 5000
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.3 部署步骤
|
|
||||||
1. 数据库初始化
|
|
||||||
```bash
|
|
||||||
mysql -u root -p deploy_ease < schema.sql
|
|
||||||
mysql -u root -p deploy_ease < data.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 后端部署
|
|
||||||
```bash
|
|
||||||
mvn clean package
|
|
||||||
java -jar deploy-ease.jar
|
|
||||||
```
|
|
||||||
|
|
||||||
3. 前端部署
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.4 监控配置
|
|
||||||
1. Prometheus监控
|
|
||||||
```yaml
|
|
||||||
scrape_configs:
|
|
||||||
- job_name: 'deploy-ease'
|
|
||||||
metrics_path: '/actuator/prometheus'
|
|
||||||
static_configs:
|
|
||||||
- targets: ['localhost:8080']
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Grafana面板
|
|
||||||
- JVM监控
|
|
||||||
- 接口监控
|
|
||||||
- 业务监控
|
|
||||||
|
|
||||||
## 六、系统优化增强
|
|
||||||
|
|
||||||
### 6.1 分布式工作流支持
|
|
||||||
```java
|
|
||||||
/**
|
|
||||||
* 分布式工作流引擎增强
|
|
||||||
*/
|
|
||||||
public interface DistributedWorkflowEngine extends WorkflowEngine {
|
|
||||||
// 分布式锁支持
|
|
||||||
void acquireWorkflowLock(String lockKey, Duration timeout);
|
|
||||||
void releaseWorkflowLock(String lockKey);
|
|
||||||
|
|
||||||
// 集群协调
|
|
||||||
void registerWorker(WorkerInfo workerInfo);
|
|
||||||
void updateWorkerStatus(String workerId, WorkerStatus status);
|
|
||||||
|
|
||||||
// 任务分发
|
|
||||||
void assignTask(String workerId, WorkflowTask task);
|
|
||||||
void reassignTask(String fromWorkerId, String toWorkerId, WorkflowTask task);
|
|
||||||
|
|
||||||
// 故障转移
|
|
||||||
void handleWorkerFailure(String workerId);
|
|
||||||
void recoverTasks(String failedWorkerId);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.2 工作流版本控制
|
|
||||||
```java
|
|
||||||
/**
|
|
||||||
* 工作流版本管理
|
|
||||||
*/
|
|
||||||
public interface WorkflowVersionManager {
|
|
||||||
// 版本控制
|
|
||||||
String createVersion(WorkflowDefinition definition);
|
|
||||||
WorkflowDefinition getVersion(String versionId);
|
|
||||||
List<WorkflowVersion> getVersionHistory(String code);
|
|
||||||
|
|
||||||
// 版本迁移
|
|
||||||
void migrateInstances(String fromVersion, String toVersion);
|
|
||||||
void rollbackVersion(String versionId);
|
|
||||||
|
|
||||||
// 版本比较
|
|
||||||
WorkflowDiff compareVersions(String version1, String version2);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.3 动态节点支持
|
|
||||||
```java
|
|
||||||
/**
|
|
||||||
* 动态节点管理器
|
|
||||||
*/
|
|
||||||
public interface DynamicNodeManager {
|
|
||||||
// 动态节点注册
|
|
||||||
void registerNodeType(String type, NodeExecutor executor);
|
|
||||||
void unregisterNodeType(String type);
|
|
||||||
|
|
||||||
// 动态配置
|
|
||||||
void updateNodeConfig(String type, NodeConfig config);
|
|
||||||
NodeConfig getNodeConfig(String type);
|
|
||||||
|
|
||||||
// 节点发现
|
|
||||||
List<NodeTypeInfo> discoverNodes();
|
|
||||||
boolean isNodeTypeAvailable(String type);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.4 工作流监控增强
|
|
||||||
```java
|
|
||||||
/**
|
|
||||||
* 增强的工作流监控
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@Slf4j
|
|
||||||
public class EnhancedWorkflowMonitor extends PerformanceMonitor {
|
|
||||||
// 业务监控指标
|
|
||||||
private final Counter businessErrorCounter;
|
|
||||||
private final Counter approvalTimeoutCounter;
|
|
||||||
private final Timer approvalDurationTimer;
|
|
||||||
|
|
||||||
// 资源监控
|
|
||||||
private final Gauge databaseConnectionGauge;
|
|
||||||
private final Gauge cacheUsageGauge;
|
|
||||||
private final Gauge messageQueueDepthGauge;
|
|
||||||
|
|
||||||
// SLA监控
|
|
||||||
private final Timer workflowSLATimer;
|
|
||||||
private final Counter slaViolationCounter;
|
|
||||||
|
|
||||||
public void recordBusinessError(String errorType, String errorCode) {
|
|
||||||
businessErrorCounter.increment();
|
|
||||||
// 记录详细错误信息
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkSLA(WorkflowInstance instance) {
|
|
||||||
Duration duration = Duration.between(instance.getStartTime(), LocalDateTime.now());
|
|
||||||
workflowSLATimer.record(duration);
|
|
||||||
|
|
||||||
if (duration.compareTo(instance.getSLADuration()) > 0) {
|
|
||||||
slaViolationCounter.increment();
|
|
||||||
// 触发SLA违规告警
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.5 工作流数据分析
|
|
||||||
```java
|
|
||||||
/**
|
|
||||||
* 工作流分析服务
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
@Slf4j
|
|
||||||
public class WorkflowAnalyticsService {
|
|
||||||
private final WorkflowInstanceRepository instanceRepository;
|
|
||||||
private final NodeInstanceRepository nodeRepository;
|
|
||||||
private final AnalyticsEngine analyticsEngine;
|
|
||||||
|
|
||||||
// 性能分析
|
|
||||||
public PerformanceReport analyzePerformance(String workflowType, DateRange range) {
|
|
||||||
// 分析工作流执行性能
|
|
||||||
return analyticsEngine.analyzePerformance(workflowType, range);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 瓶颈分析
|
|
||||||
public List<BottleneckNode> findBottlenecks(String workflowType) {
|
|
||||||
// 识别执行瓶颈
|
|
||||||
return analyticsEngine.findBottlenecks(workflowType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 趋势分析
|
|
||||||
public TrendReport analyzeTrend(String metric, DateRange range) {
|
|
||||||
// 分析指标趋势
|
|
||||||
return analyticsEngine.analyzeTrend(metric, range);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.6 工作流调度优化
|
|
||||||
```java
|
|
||||||
/**
|
|
||||||
* 优化的工作流调度器
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@Slf4j
|
|
||||||
public class OptimizedWorkflowScheduler {
|
|
||||||
private final WorkloadBalancer workloadBalancer;
|
|
||||||
private final PriorityQueue<WorkflowTask> taskQueue;
|
|
||||||
|
|
||||||
// 优先级调度
|
|
||||||
public void schedulePriorityTask(WorkflowTask task) {
|
|
||||||
task.setPriority(calculatePriority(task));
|
|
||||||
taskQueue.offer(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 负载均衡
|
|
||||||
public void balanceWorkload() {
|
|
||||||
List<WorkerNode> workers = workloadBalancer.getActiveWorkers();
|
|
||||||
for (WorkerNode worker : workers) {
|
|
||||||
if (worker.isOverloaded()) {
|
|
||||||
redistributeTasks(worker);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 资源预留
|
|
||||||
public void reserveResources(WorkflowTask task) {
|
|
||||||
ResourceRequirements requirements = task.getResourceRequirements();
|
|
||||||
resourceManager.reserve(requirements);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@ -1 +0,0 @@
|
|||||||
|
|
||||||
@ -1,869 +0,0 @@
|
|||||||
# 工作流前端对接文档
|
|
||||||
|
|
||||||
## 一、系统架构
|
|
||||||
|
|
||||||
### 1.1 整体架构
|
|
||||||
```typescript
|
|
||||||
// 系统分层
|
|
||||||
interface SystemArchitecture {
|
|
||||||
presentation: {
|
|
||||||
views: '页面组件', // 负责页面展示和用户交互
|
|
||||||
components: '通用组件', // 可复用的UI组件
|
|
||||||
hooks: '业务钩子', // 封装业务逻辑的Hooks
|
|
||||||
};
|
|
||||||
domain: {
|
|
||||||
models: '数据模型', // 核心业务模型定义
|
|
||||||
services: '领域服务', // 业务逻辑封装
|
|
||||||
stores: '状态管理', // 全局状态管理
|
|
||||||
};
|
|
||||||
infrastructure: {
|
|
||||||
api: 'API封装', // 后端接口调用
|
|
||||||
utils: '工具函数', // 通用工具方法
|
|
||||||
constants: '常量定义', // 系统常量
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.2 核心模块
|
|
||||||
1. **工作流设计器**
|
|
||||||
- 负责工作流的可视化设计
|
|
||||||
- 节点拖拽和连线
|
|
||||||
- 属性配置面板
|
|
||||||
- 数据验证和保存
|
|
||||||
|
|
||||||
2. **表单设计器**
|
|
||||||
- 工作流表单的可视化设计
|
|
||||||
- 字段配置和验证规则
|
|
||||||
- 布局设计
|
|
||||||
- 表单预览
|
|
||||||
|
|
||||||
3. **工作流引擎**
|
|
||||||
- 工作流实例管理
|
|
||||||
- 节点状态控制
|
|
||||||
- 变量管理
|
|
||||||
- 日志记录
|
|
||||||
|
|
||||||
## 二、数据模型
|
|
||||||
|
|
||||||
### 2.1 工作流定义
|
|
||||||
```typescript
|
|
||||||
interface WorkflowDefinition {
|
|
||||||
// 基本信息
|
|
||||||
id: number; // 工作流定义ID
|
|
||||||
code: string; // 工作流编码(唯一标识)
|
|
||||||
name: string; // 工作流名称
|
|
||||||
description: string; // 工作流描述
|
|
||||||
version: number; // 版本号
|
|
||||||
status: WorkflowStatus; // 状态(DRAFT/PUBLISHED/DISABLED)
|
|
||||||
enabled: boolean; // 是否启用
|
|
||||||
|
|
||||||
// 节点定义
|
|
||||||
nodes: Array<{
|
|
||||||
id: number; // 节点定义ID
|
|
||||||
nodeId: string; // 节点标识(在图中的唯一标识)
|
|
||||||
name: string; // 节点名称
|
|
||||||
type: NodeType; // 节点类型(START/END/TASK/GATEWAY)
|
|
||||||
config: string; // 节点配置(JSON字符串)
|
|
||||||
description: string; // 节点描述
|
|
||||||
orderNum: number; // 排序号
|
|
||||||
}>;
|
|
||||||
|
|
||||||
// 配置数据
|
|
||||||
nodeConfig: string; // 节点关系配置(JSON字符串)
|
|
||||||
transitionConfig: string; // 流转配置(JSON字符串)
|
|
||||||
formDefinition: string; // 表单定义(JSON字符串)
|
|
||||||
graphDefinition: string; // 图形布局(JSON字符串)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 节点配置示例(JSON格式)
|
|
||||||
interface NodeConfig {
|
|
||||||
// 开始节点配置
|
|
||||||
startNode: {
|
|
||||||
type: 'START';
|
|
||||||
name: string;
|
|
||||||
config?: Record<string, any>;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 任务节点配置
|
|
||||||
taskNodes: Array<{
|
|
||||||
id: string;
|
|
||||||
type: 'TASK';
|
|
||||||
name: string;
|
|
||||||
executor?: string; // 执行器类型
|
|
||||||
config: {
|
|
||||||
// Shell执行器配置
|
|
||||||
script?: string; // 脚本内容
|
|
||||||
timeout?: number; // 超时时间
|
|
||||||
workingDir?: string; // 工作目录
|
|
||||||
|
|
||||||
// Jenkins执行器配置
|
|
||||||
jenkinsJob?: string; // Jenkins任务名
|
|
||||||
parameters?: Record<string, any>; // 构建参数
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
|
|
||||||
// 网关节点配置
|
|
||||||
gatewayNodes: Array<{
|
|
||||||
id: string;
|
|
||||||
type: 'GATEWAY';
|
|
||||||
name: string;
|
|
||||||
gatewayType: 'EXCLUSIVE' | 'PARALLEL' | 'INCLUSIVE'; // 网关类型
|
|
||||||
conditions?: Array<{
|
|
||||||
expression: string; // 条件表达式
|
|
||||||
description: string; // 条件描述
|
|
||||||
}>;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
// 结束节点配置
|
|
||||||
endNode: {
|
|
||||||
type: 'END';
|
|
||||||
name: string;
|
|
||||||
config?: Record<string, any>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 流转配置示例
|
|
||||||
interface TransitionConfig {
|
|
||||||
transitions: Array<{
|
|
||||||
from: string; // 源节点ID
|
|
||||||
to: string; // 目标节点ID
|
|
||||||
condition?: string; // 流转条件(网关节点使用)
|
|
||||||
priority?: number; // 优先级(条件分支使用)
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 图形布局示例
|
|
||||||
interface GraphDefinition {
|
|
||||||
nodes: Array<{
|
|
||||||
id: string; // 节点ID
|
|
||||||
type: string; // 节点类型
|
|
||||||
x: number; // X坐标
|
|
||||||
y: number; // Y坐标
|
|
||||||
properties?: { // 节点属性
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
color?: string;
|
|
||||||
icon?: string;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
edges: Array<{
|
|
||||||
source: string; // 源节点ID
|
|
||||||
target: string; // 目标节点ID
|
|
||||||
points?: Array<{ // 连线控制点
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
}>;
|
|
||||||
properties?: { // 连线属性
|
|
||||||
style?: string;
|
|
||||||
arrow?: string;
|
|
||||||
label?: string;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 工作流实例
|
|
||||||
```typescript
|
|
||||||
interface WorkflowInstance {
|
|
||||||
id: number; // 实例ID
|
|
||||||
definitionId: number; // 工作流定义ID
|
|
||||||
businessKey: string; // 业务标识
|
|
||||||
status: InstanceStatus; // 状态
|
|
||||||
startTime: string; // 开始时间
|
|
||||||
endTime: string; // 结束时间
|
|
||||||
variables: string; // 工作流变量(JSON字符串)
|
|
||||||
error: string; // 错误信息
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NodeInstance {
|
|
||||||
id: number; // 实例ID
|
|
||||||
workflowInstanceId: number; // 工作流实例ID
|
|
||||||
nodeId: string; // 节点ID
|
|
||||||
nodeType: NodeType; // 节点类型
|
|
||||||
status: NodeStatus; // 节点状态
|
|
||||||
startTime: string; // 开始时间
|
|
||||||
endTime: string; // 结束时间
|
|
||||||
input: string; // 输入参数(JSON字符串)
|
|
||||||
output: string; // 输出结果(JSON字符串)
|
|
||||||
error: string; // 错误信息
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 三、接口定义
|
|
||||||
|
|
||||||
### 3.1 工作流定义接口
|
|
||||||
```typescript
|
|
||||||
interface WorkflowDefinitionAPI {
|
|
||||||
// 基础CRUD
|
|
||||||
create: {
|
|
||||||
url: '/api/v1/workflow-definitions',
|
|
||||||
method: 'POST',
|
|
||||||
data: WorkflowDefinitionDTO,
|
|
||||||
response: Response<WorkflowDefinitionDTO>
|
|
||||||
};
|
|
||||||
|
|
||||||
update: {
|
|
||||||
url: '/api/v1/workflow-definitions/{id}',
|
|
||||||
method: 'PUT',
|
|
||||||
data: WorkflowDefinitionDTO,
|
|
||||||
response: Response<WorkflowDefinitionDTO>
|
|
||||||
};
|
|
||||||
|
|
||||||
delete: {
|
|
||||||
url: '/api/v1/workflow-definitions/{id}',
|
|
||||||
method: 'DELETE',
|
|
||||||
response: Response<void>
|
|
||||||
};
|
|
||||||
|
|
||||||
getById: {
|
|
||||||
url: '/api/v1/workflow-definitions/{id}',
|
|
||||||
method: 'GET',
|
|
||||||
response: Response<WorkflowDefinitionDTO>
|
|
||||||
};
|
|
||||||
|
|
||||||
page: {
|
|
||||||
url: '/api/v1/workflow-definitions/page',
|
|
||||||
method: 'GET',
|
|
||||||
params: PageQuery,
|
|
||||||
response: Response<Page<WorkflowDefinitionDTO>>
|
|
||||||
};
|
|
||||||
|
|
||||||
// 特殊操作
|
|
||||||
publish: {
|
|
||||||
url: '/api/v1/workflow-definitions/{id}/publish',
|
|
||||||
method: 'POST',
|
|
||||||
response: Response<WorkflowDefinitionDTO>
|
|
||||||
};
|
|
||||||
|
|
||||||
disable: {
|
|
||||||
url: '/api/v1/workflow-definitions/{id}/disable',
|
|
||||||
method: 'POST',
|
|
||||||
response: Response<WorkflowDefinitionDTO>
|
|
||||||
};
|
|
||||||
|
|
||||||
enable: {
|
|
||||||
url: '/api/v1/workflow-definitions/{id}/enable',
|
|
||||||
method: 'POST',
|
|
||||||
response: Response<WorkflowDefinitionDTO>
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 工作流实例接口
|
|
||||||
```typescript
|
|
||||||
interface WorkflowInstanceAPI {
|
|
||||||
// 实例操作
|
|
||||||
start: {
|
|
||||||
url: '/api/v1/workflow-instances/start',
|
|
||||||
method: 'POST',
|
|
||||||
data: {
|
|
||||||
workflowCode: string; // 工作流编码
|
|
||||||
businessKey: string; // 业务标识
|
|
||||||
variables?: Record<string, any>; // 工作流变量
|
|
||||||
},
|
|
||||||
response: Response<WorkflowInstanceDTO>
|
|
||||||
};
|
|
||||||
|
|
||||||
terminate: {
|
|
||||||
url: '/api/v1/workflow-instances/{id}/terminate',
|
|
||||||
method: 'POST',
|
|
||||||
data: {
|
|
||||||
reason?: string; // 终止原因
|
|
||||||
},
|
|
||||||
response: Response<void>
|
|
||||||
};
|
|
||||||
|
|
||||||
pause: {
|
|
||||||
url: '/api/v1/workflow-instances/{id}/pause',
|
|
||||||
method: 'POST',
|
|
||||||
response: Response<void>
|
|
||||||
};
|
|
||||||
|
|
||||||
resume: {
|
|
||||||
url: '/api/v1/workflow-instances/{id}/resume',
|
|
||||||
method: 'POST',
|
|
||||||
response: Response<void>
|
|
||||||
};
|
|
||||||
|
|
||||||
// 查询接口
|
|
||||||
getById: {
|
|
||||||
url: '/api/v1/workflow-instances/{id}',
|
|
||||||
method: 'GET',
|
|
||||||
response: Response<WorkflowInstanceDTO>
|
|
||||||
};
|
|
||||||
|
|
||||||
page: {
|
|
||||||
url: '/api/v1/workflow-instances/page',
|
|
||||||
method: 'GET',
|
|
||||||
params: PageQuery & {
|
|
||||||
status?: InstanceStatus;
|
|
||||||
startTime?: string;
|
|
||||||
endTime?: string;
|
|
||||||
keyword?: string;
|
|
||||||
},
|
|
||||||
response: Response<Page<WorkflowInstanceDTO>>
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 四、组件设计
|
|
||||||
|
|
||||||
### 4.1 工作流设计器
|
|
||||||
```typescript
|
|
||||||
// 1. 设计器组件
|
|
||||||
interface WorkflowDesigner {
|
|
||||||
// 属性定义
|
|
||||||
props: {
|
|
||||||
value?: WorkflowDefinition; // 工作流定义数据
|
|
||||||
readOnly?: boolean; // 是否只读
|
|
||||||
onChange?: (value: WorkflowDefinition) => void; // 数据变更回调
|
|
||||||
};
|
|
||||||
|
|
||||||
// 子组件
|
|
||||||
components: {
|
|
||||||
ToolBar: '工具栏', // 撤销、重做、缩放等操作
|
|
||||||
NodePanel: '节点面板', // 可用节点列表
|
|
||||||
Canvas: '画布', // 节点拖拽和连线
|
|
||||||
ConfigPanel: '配置面板', // 节点和连线配置
|
|
||||||
MiniMap: '小地图' // 导航缩略图
|
|
||||||
};
|
|
||||||
|
|
||||||
// 核心方法
|
|
||||||
methods: {
|
|
||||||
// 初始化设计器
|
|
||||||
initialize(definition: WorkflowDefinition): void;
|
|
||||||
|
|
||||||
// 添加节点
|
|
||||||
addNode(nodeData: NodeData): void;
|
|
||||||
|
|
||||||
// 连接节点
|
|
||||||
connect(source: string, target: string): void;
|
|
||||||
|
|
||||||
// 更新节点配置
|
|
||||||
updateNodeConfig(nodeId: string, config: any): void;
|
|
||||||
|
|
||||||
// 更新连线配置
|
|
||||||
updateEdgeConfig(edgeId: string, config: any): void;
|
|
||||||
|
|
||||||
// 验证数据
|
|
||||||
validate(): ValidationResult;
|
|
||||||
|
|
||||||
// 导出数据
|
|
||||||
exportData(): WorkflowDefinition;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 节点配置面板
|
|
||||||
interface NodeConfigPanel {
|
|
||||||
props: {
|
|
||||||
node: NodeData; // 节点数据
|
|
||||||
nodeType: NodeType; // 节点类型
|
|
||||||
onChange: (config: any) => void; // 配置变更回调
|
|
||||||
};
|
|
||||||
|
|
||||||
// 配置表单定义
|
|
||||||
forms: {
|
|
||||||
// 基础配置(所有节点都有)
|
|
||||||
BaseConfig: {
|
|
||||||
name: string; // 节点名称
|
|
||||||
description?: string; // 节点描述
|
|
||||||
};
|
|
||||||
|
|
||||||
// 任务节点配置
|
|
||||||
TaskConfig: {
|
|
||||||
executor: string; // 执行器类型
|
|
||||||
config: Record<string, any>; // 执行器配置
|
|
||||||
};
|
|
||||||
|
|
||||||
// 网关节点配置
|
|
||||||
GatewayConfig: {
|
|
||||||
type: 'EXCLUSIVE' | 'PARALLEL' | 'INCLUSIVE';
|
|
||||||
conditions?: Array<{
|
|
||||||
expression: string;
|
|
||||||
description: string;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 连线配置面板
|
|
||||||
interface EdgeConfigPanel {
|
|
||||||
props: {
|
|
||||||
edge: EdgeData; // 连线数据
|
|
||||||
sourceNode: NodeData; // 源节点
|
|
||||||
targetNode: NodeData; // 目标节点
|
|
||||||
onChange: (config: any) => void; // 配置变更回调
|
|
||||||
};
|
|
||||||
|
|
||||||
// 配置表单定义
|
|
||||||
forms: {
|
|
||||||
condition?: string; // 流转条件
|
|
||||||
priority?: number; // 优先级
|
|
||||||
description?: string; // 说明
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.2 表单设计器
|
|
||||||
```typescript
|
|
||||||
interface FormDesigner {
|
|
||||||
props: {
|
|
||||||
value?: FormDefinition; // 表单定义数据
|
|
||||||
onChange?: (value: FormDefinition) => void; // 数据变更回调
|
|
||||||
};
|
|
||||||
|
|
||||||
// 可用的字段类型
|
|
||||||
fieldTypes: {
|
|
||||||
input: '单行文本',
|
|
||||||
textarea: '多行文本',
|
|
||||||
number: '数字',
|
|
||||||
select: '下拉选择',
|
|
||||||
radio: '单选',
|
|
||||||
checkbox: '多选',
|
|
||||||
date: '日期',
|
|
||||||
datetime: '日期时间',
|
|
||||||
file: '文件上传'
|
|
||||||
};
|
|
||||||
|
|
||||||
// 字段属性定义
|
|
||||||
fieldProperties: {
|
|
||||||
name: string; // 字段名
|
|
||||||
label: string; // 字段标签
|
|
||||||
type: string; // 字段类型
|
|
||||||
required?: boolean; // 是否必填
|
|
||||||
defaultValue?: any; // 默认值
|
|
||||||
placeholder?: string; // 占位提示
|
|
||||||
description?: string; // 字段描述
|
|
||||||
options?: Array<{ // 选项(用于select/radio/checkbox)
|
|
||||||
label: string;
|
|
||||||
value: any;
|
|
||||||
}>;
|
|
||||||
validation?: Array<{ // 验证规则
|
|
||||||
type: string; // 规则类型
|
|
||||||
message: string; // 错误消息
|
|
||||||
params?: any; // 规则参数
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 五、状态管理
|
|
||||||
|
|
||||||
### 5.1 工作流设计器状态
|
|
||||||
```typescript
|
|
||||||
interface DesignerState {
|
|
||||||
// 画布状态
|
|
||||||
canvas: {
|
|
||||||
scale: number; // 缩放比例
|
|
||||||
position: { // 画布位置
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
};
|
|
||||||
selectedElements: { // 选中的元素
|
|
||||||
nodes: string[]; // 节点ID列表
|
|
||||||
edges: string[]; // 连线ID列表
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// 历史记录
|
|
||||||
history: {
|
|
||||||
undoStack: HistoryItem[]; // 撤销栈
|
|
||||||
redoStack: HistoryItem[]; // 重做栈
|
|
||||||
canUndo: boolean; // 是否可撤销
|
|
||||||
canRedo: boolean; // 是否可重做
|
|
||||||
};
|
|
||||||
|
|
||||||
// 节点数据
|
|
||||||
nodes: Record<string, NodeData>; // 节点数据映射
|
|
||||||
edges: Record<string, EdgeData>; // 连线数据映射
|
|
||||||
|
|
||||||
// 配置面板
|
|
||||||
configPanel: {
|
|
||||||
visible: boolean; // 是否显示
|
|
||||||
type: 'node' | 'edge'; // 配置类型
|
|
||||||
elementId?: string; // 当前配置的元素ID
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.2 工作流实例状态
|
|
||||||
```typescript
|
|
||||||
interface InstanceState {
|
|
||||||
// 实例数据
|
|
||||||
instance: {
|
|
||||||
current?: WorkflowInstance; // 当前实例
|
|
||||||
nodes: NodeInstance[]; // 节点实例列表
|
|
||||||
variables: Record<string, any>; // 工作流变量
|
|
||||||
};
|
|
||||||
|
|
||||||
// 操作权限
|
|
||||||
permissions: {
|
|
||||||
canTerminate: boolean; // 是否可终止
|
|
||||||
canPause: boolean; // 是否可暂停
|
|
||||||
canResume: boolean; // 是否可恢复
|
|
||||||
};
|
|
||||||
|
|
||||||
// 日志数据
|
|
||||||
logs: {
|
|
||||||
workflow: WorkflowLog[]; // 工作流日志
|
|
||||||
nodes: Record<string, NodeLog[]>; // 节点日志
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 六、工作流设计器使用示例
|
|
||||||
|
|
||||||
### 6.1 基础使用
|
|
||||||
```typescript
|
|
||||||
// 1. 创建工作流
|
|
||||||
const createWorkflow = async (definition: WorkflowDefinition) => {
|
|
||||||
try {
|
|
||||||
// 验证数据
|
|
||||||
const validationResult = workflowDesigner.validate();
|
|
||||||
if (!validationResult.valid) {
|
|
||||||
message.error('工作流数据验证失败:' + validationResult.errors.join(', '));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交数据
|
|
||||||
const response = await WorkflowDefinitionAPI.create(definition);
|
|
||||||
if (response.code === 200) {
|
|
||||||
message.success('工作流创建成功');
|
|
||||||
return response.data;
|
|
||||||
} else {
|
|
||||||
message.error('工作流创建失败:' + response.message);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
message.error('系统错误:' + error.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 2. 加载工作流
|
|
||||||
const loadWorkflow = async (id: number) => {
|
|
||||||
try {
|
|
||||||
const response = await WorkflowDefinitionAPI.getById(id);
|
|
||||||
if (response.code === 200) {
|
|
||||||
// 初始化设计器
|
|
||||||
workflowDesigner.initialize(response.data);
|
|
||||||
return response.data;
|
|
||||||
} else {
|
|
||||||
message.error('加载工作流失败:' + response.message);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
message.error('系统错误:' + error.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 3. 保存工作流
|
|
||||||
const saveWorkflow = async (id: number) => {
|
|
||||||
try {
|
|
||||||
// 获取设计器数据
|
|
||||||
const definition = workflowDesigner.exportData();
|
|
||||||
|
|
||||||
// 验证数据
|
|
||||||
const validationResult = workflowDesigner.validate();
|
|
||||||
if (!validationResult.valid) {
|
|
||||||
message.error('工作流数据验证失败:' + validationResult.errors.join(', '));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交数据
|
|
||||||
const response = await WorkflowDefinitionAPI.update(id, definition);
|
|
||||||
if (response.code === 200) {
|
|
||||||
message.success('工作流保存成功');
|
|
||||||
return response.data;
|
|
||||||
} else {
|
|
||||||
message.error('工作流保存失败:' + response.message);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
message.error('系统错误:' + error.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.2 节点配置示例
|
|
||||||
```typescript
|
|
||||||
// 1. Shell节点配置
|
|
||||||
const ShellNodeConfig = {
|
|
||||||
type: 'TASK',
|
|
||||||
name: 'Shell脚本',
|
|
||||||
executor: 'SHELL',
|
|
||||||
config: {
|
|
||||||
script: '#!/bin/bash\necho "Hello World"',
|
|
||||||
timeout: 300,
|
|
||||||
workingDir: '/tmp'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 2. Jenkins节点配置
|
|
||||||
const JenkinsNodeConfig = {
|
|
||||||
type: 'TASK',
|
|
||||||
name: 'Jenkins构建',
|
|
||||||
executor: 'JENKINS',
|
|
||||||
config: {
|
|
||||||
jenkinsJob: 'my-projectGroup-build',
|
|
||||||
parameters: {
|
|
||||||
BRANCH: 'master',
|
|
||||||
ENV: 'prod'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 3. 网关节点配置
|
|
||||||
const GatewayNodeConfig = {
|
|
||||||
type: 'GATEWAY',
|
|
||||||
name: '条件分支',
|
|
||||||
gatewayType: 'EXCLUSIVE',
|
|
||||||
conditions: [
|
|
||||||
{
|
|
||||||
expression: '${status} == "SUCCESS"',
|
|
||||||
description: '执行成功'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
expression: '${status} == "FAILED"',
|
|
||||||
description: '执行失败'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.3 数据验证示例
|
|
||||||
```typescript
|
|
||||||
// 1. 节点配置验证
|
|
||||||
const validateNodeConfig = (node: NodeData): ValidationResult => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
|
|
||||||
// 基础验证
|
|
||||||
if (!node.name) {
|
|
||||||
errors.push('节点名称不能为空');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 任务节点特殊验证
|
|
||||||
if (node.type === 'TASK') {
|
|
||||||
if (!node.executor) {
|
|
||||||
errors.push('请选择执行器');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shell执行器验证
|
|
||||||
if (node.executor === 'SHELL') {
|
|
||||||
if (!node.config.script) {
|
|
||||||
errors.push('脚本内容不能为空');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Jenkins执行器验证
|
|
||||||
if (node.executor === 'JENKINS') {
|
|
||||||
if (!node.config.jenkinsJob) {
|
|
||||||
errors.push('Jenkins任务名不能为空');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 网关节点特殊验证
|
|
||||||
if (node.type === 'GATEWAY') {
|
|
||||||
if (!node.gatewayType) {
|
|
||||||
errors.push('请选择网关类型');
|
|
||||||
}
|
|
||||||
if (node.gatewayType === 'EXCLUSIVE' && (!node.conditions || node.conditions.length === 0)) {
|
|
||||||
errors.push('请配置分支条件');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
valid: errors.length === 0,
|
|
||||||
errors
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// 2. 流程验证
|
|
||||||
const validateWorkflow = (definition: WorkflowDefinition): ValidationResult => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
|
|
||||||
// 1. 基础信息验证
|
|
||||||
if (!definition.name) {
|
|
||||||
errors.push('工作流名称不能为空');
|
|
||||||
}
|
|
||||||
if (!definition.code) {
|
|
||||||
errors.push('工作流编码不能为空');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 节点验证
|
|
||||||
const nodes = JSON.parse(definition.nodeConfig);
|
|
||||||
|
|
||||||
// 验证开始节点
|
|
||||||
if (!nodes.startNode) {
|
|
||||||
errors.push('必须有一个开始节点');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证结束节点
|
|
||||||
if (!nodes.endNode) {
|
|
||||||
errors.push('必须有一个结束节点');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证任务节点
|
|
||||||
if (!nodes.taskNodes || nodes.taskNodes.length === 0) {
|
|
||||||
errors.push('至少需要一个任务节点');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 连线验证
|
|
||||||
const transitions = JSON.parse(definition.transitionConfig).transitions;
|
|
||||||
|
|
||||||
// 验证是否有孤立节点
|
|
||||||
const connectedNodes = new Set<string>();
|
|
||||||
transitions.forEach(t => {
|
|
||||||
connectedNodes.add(t.from);
|
|
||||||
connectedNodes.add(t.to);
|
|
||||||
});
|
|
||||||
|
|
||||||
const allNodes = [
|
|
||||||
nodes.startNode,
|
|
||||||
...nodes.taskNodes,
|
|
||||||
...(nodes.gatewayNodes || []),
|
|
||||||
nodes.endNode
|
|
||||||
];
|
|
||||||
|
|
||||||
allNodes.forEach(node => {
|
|
||||||
if (!connectedNodes.has(node.id)) {
|
|
||||||
errors.push(`节点 ${node.name}(${node.id}) 未连接`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4. 验证是否有环
|
|
||||||
if (hasCircle(transitions)) {
|
|
||||||
errors.push('流程中存在循环');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
valid: errors.length === 0,
|
|
||||||
errors
|
|
||||||
};
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## 七、最佳实践
|
|
||||||
|
|
||||||
### 7.1 性能优化
|
|
||||||
1. **大数据量处理**
|
|
||||||
- 使用虚拟滚动
|
|
||||||
- 分页加载数据
|
|
||||||
- 按需渲染节点
|
|
||||||
|
|
||||||
2. **状态管理**
|
|
||||||
- 合理使用缓存
|
|
||||||
- 避免频繁更新
|
|
||||||
- 使用不可变数据
|
|
||||||
|
|
||||||
3. **渲染优化**
|
|
||||||
- 组件懒加载
|
|
||||||
- 合理使用memo
|
|
||||||
- 避免不必要的重渲染
|
|
||||||
|
|
||||||
### 7.2 错误处理
|
|
||||||
1. **前端验证**
|
|
||||||
- 实时验证
|
|
||||||
- 提供错误提示
|
|
||||||
- 防止无效操作
|
|
||||||
|
|
||||||
2. **异常捕获**
|
|
||||||
- 全局错误处理
|
|
||||||
- 友好的错误提示
|
|
||||||
- 错误日志记录
|
|
||||||
|
|
||||||
3. **数据恢复**
|
|
||||||
- 自动保存
|
|
||||||
- 本地缓存
|
|
||||||
- 操作回滚
|
|
||||||
|
|
||||||
### 7.3 用户体验
|
|
||||||
1. **交互设计**
|
|
||||||
- 拖拽操作流畅
|
|
||||||
- 实时预览效果
|
|
||||||
- 快捷键支持
|
|
||||||
|
|
||||||
2. **反馈机制**
|
|
||||||
- 操作提示
|
|
||||||
- 加载状态
|
|
||||||
- 成功/失败反馈
|
|
||||||
|
|
||||||
3. **辅助功能**
|
|
||||||
- 自动布局
|
|
||||||
- 查找/替换
|
|
||||||
- 导入/导出
|
|
||||||
|
|
||||||
## 八、常见问题
|
|
||||||
|
|
||||||
### 8.1 配置相关
|
|
||||||
1. **节点配置问题**
|
|
||||||
- Q: 如何处理不同类型节点的配置?
|
|
||||||
- A: 使用统一的配置接口,通过类型区分不同的配置表单
|
|
||||||
|
|
||||||
2. **数据同步问题**
|
|
||||||
- Q: 如何保持前端展示数据与后端数据一致?
|
|
||||||
- A: 实现定期自动保存和加载机制
|
|
||||||
|
|
||||||
3. **验证问题**
|
|
||||||
- Q: 如何确保工作流数据的正确性?
|
|
||||||
- A: 实现多层次的验证机制,包括实时验证和提交验证
|
|
||||||
|
|
||||||
### 8.2 性能相关
|
|
||||||
1. **大型工作流**
|
|
||||||
- Q: 如何处理节点数量很多的工作流?
|
|
||||||
- A: 实现分区渲染和虚拟滚动
|
|
||||||
|
|
||||||
2. **频繁操作**
|
|
||||||
- Q: 如何处理频繁的拖拽和连线操作?
|
|
||||||
- A: 使用节流和防抖优化性能
|
|
||||||
|
|
||||||
3. **数据量大**
|
|
||||||
- Q: 如何处理大量的历史数据?
|
|
||||||
- A: 实现分页加载和懒加载机制
|
|
||||||
|
|
||||||
## 九、开发规范
|
|
||||||
|
|
||||||
### 9.1 代码规范
|
|
||||||
1. **命名规范**
|
|
||||||
- 组件使用大驼峰命名
|
|
||||||
- 方法使用小驼峰命名
|
|
||||||
- 常量使用大写下划线
|
|
||||||
|
|
||||||
2. **注释规范**
|
|
||||||
- 组件必须有文档注释
|
|
||||||
- 复杂逻辑需要注释
|
|
||||||
- 保持注释的及时更新
|
|
||||||
|
|
||||||
3. **类型规范**
|
|
||||||
- 使用TypeScript
|
|
||||||
- 定义清晰的接口
|
|
||||||
- 避免any类型
|
|
||||||
|
|
||||||
### 9.2 提交规范
|
|
||||||
1. **提交信息**
|
|
||||||
- feat: 新功能
|
|
||||||
- fix: 修复bug
|
|
||||||
- docs: 文档更新
|
|
||||||
- style: 代码格式
|
|
||||||
- refactor: 重构
|
|
||||||
- test: 测试
|
|
||||||
- chore: 构建过程或辅助工具的变动
|
|
||||||
|
|
||||||
2. **分支管理**
|
|
||||||
- master: 主分支
|
|
||||||
- develop: 开发分支
|
|
||||||
- feature: 功能分支
|
|
||||||
- hotfix: 紧急修复分支
|
|
||||||
|
|
||||||
### 9.3 测试规范
|
|
||||||
1. **单元测试**
|
|
||||||
- 组件测试
|
|
||||||
- 方法测试
|
|
||||||
- 工具函数测试
|
|
||||||
|
|
||||||
2. **集成测试**
|
|
||||||
- 流程测试
|
|
||||||
- 接口测试
|
|
||||||
- 兼容性测试
|
|
||||||
|
|
||||||
3. **E2E测试**
|
|
||||||
- 用户操作测试
|
|
||||||
- 场景测试
|
|
||||||
- 性能测试
|
|
||||||
@ -1,332 +0,0 @@
|
|||||||
# 工作流数据格式说明
|
|
||||||
|
|
||||||
## 1. 节点类型数据示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": 2003,
|
|
||||||
"createTime": "2024-12-05 12:40:03",
|
|
||||||
"createBy": "system",
|
|
||||||
"updateTime": "2024-12-05 12:40:03",
|
|
||||||
"updateBy": "system",
|
|
||||||
"version": 1,
|
|
||||||
"deleted": false,
|
|
||||||
"extraData": null,
|
|
||||||
"code": "SHELL",
|
|
||||||
"name": "Shell脚本节点",
|
|
||||||
"description": "执行Shell脚本的任务节点",
|
|
||||||
"category": "TASK",
|
|
||||||
"icon": "code",
|
|
||||||
"color": "#1890ff",
|
|
||||||
"executors": [
|
|
||||||
{
|
|
||||||
"code": "SHELL",
|
|
||||||
"name": "Shell脚本执行器",
|
|
||||||
"description": "执行Shell脚本,支持配置超时时间和工作目录",
|
|
||||||
"configSchema": "{\"type\":\"object\",\"required\":[\"script\"],\"properties\":{\"script\":{\"type\":\"string\",\"title\":\"脚本内容\",\"format\":\"shell\",\"description\":\"需要执行的Shell脚本内容\"},\"timeout\":{\"type\":\"number\",\"title\":\"超时时间\",\"description\":\"脚本执行的最大时间(秒)\",\"minimum\":1,\"maximum\":3600,\"default\":300},\"workingDir\":{\"type\":\"string\",\"title\":\"工作目录\",\"description\":\"脚本执行的工作目录\",\"default\":\"/tmp\"}}}",
|
|
||||||
"defaultConfig": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"configSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"title\": \"节点名称\",\n \"minLength\": 1,\n \"maxLength\": 50\n },\n \"description\": {\n \"type\": \"string\",\n \"title\": \"节点描述\",\n \"maxLength\": 200\n },\n \"executor\": {\n \"type\": \"string\",\n \"title\": \"执行器\",\n \"enum\": [\"SHELL\"],\n \"enumNames\": [\"Shell脚本执行器\"]\n },\n \"retryTimes\": {\n \"type\": \"number\",\n \"title\": \"重试次数\",\n \"minimum\": 0,\n \"maximum\": 3,\n \"default\": 0\n },\n \"retryInterval\": {\n \"type\": \"number\",\n \"title\": \"重试间隔(秒)\",\n \"minimum\": 1,\n \"maximum\": 300,\n \"default\": 60\n }\n },\n \"required\": [\"name\", \"executor\"]\n}",
|
|
||||||
"defaultConfig": "{\"name\": \"Shell脚本\", \"executor\": \"SHELL\", \"retryTimes\": 0, \"retryInterval\": 60}",
|
|
||||||
"enabled": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. 流程设计数据示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "node_1",
|
|
||||||
"type": "START",
|
|
||||||
"position": { "x": 100, "y": 100 },
|
|
||||||
"data": {
|
|
||||||
"name": "开始",
|
|
||||||
"description": "流程开始节点",
|
|
||||||
"config": {
|
|
||||||
"name": "开始",
|
|
||||||
"description": "这是一个开始节点"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"id": "edge_1",
|
|
||||||
"source": "node_1",
|
|
||||||
"target": "node_2",
|
|
||||||
"type": "default",
|
|
||||||
"data": {
|
|
||||||
"condition": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. 关键字段说明
|
|
||||||
|
|
||||||
### configSchema(节点配置模式)
|
|
||||||
**用途**:
|
|
||||||
1. 前端动态生成配置表单
|
|
||||||
2. 后端验证配置数据的合法性
|
|
||||||
3. 提供配置项的约束和验证规则
|
|
||||||
|
|
||||||
### defaultConfig(默认配置)
|
|
||||||
**用途**:
|
|
||||||
1. 新建节点时的默认值
|
|
||||||
2. 重置配置时的参考值
|
|
||||||
3. 必须符合configSchema定义的格式
|
|
||||||
|
|
||||||
### executors(执行器定义)
|
|
||||||
**用途**:
|
|
||||||
1. 定义节点支持的执行器类型
|
|
||||||
2. 每个执行器的配置要求
|
|
||||||
3. 用于任务节点的具体执行逻辑
|
|
||||||
|
|
||||||
## 4. 字段关系说明
|
|
||||||
|
|
||||||
1. configSchema定义节点的整体配置结构
|
|
||||||
2. defaultConfig提供符合configSchema的默认值
|
|
||||||
3. executors中的configSchema定义具体执行器的配置结构
|
|
||||||
4. 实际节点配置时,executorConfig需要符合选定执行器的configSchema
|
|
||||||
|
|
||||||
## 5. 前端实现指南
|
|
||||||
|
|
||||||
### 5.1 工作流设计器组件架构
|
|
||||||
|
|
||||||
推荐使用组件化设计,主要包含以下组件:
|
|
||||||
|
|
||||||
1. **WorkflowDesigner(工作流设计器)**
|
|
||||||
- 整体容器组件
|
|
||||||
- 负责状态管理
|
|
||||||
- 处理快捷键
|
|
||||||
- 工具栏集成
|
|
||||||
|
|
||||||
2. **NodePanel(节点面板)**
|
|
||||||
- 显示可用节点类型
|
|
||||||
- 支持拖拽创建节点
|
|
||||||
- 节点分类展示
|
|
||||||
|
|
||||||
3. **Canvas(画布)**
|
|
||||||
- 节点和连线的可视化
|
|
||||||
- 处理拖拽和连线
|
|
||||||
- 网格背景
|
|
||||||
- 缩放和平移
|
|
||||||
|
|
||||||
4. **NodeConfig(节点配置)**
|
|
||||||
- 动态表单生成
|
|
||||||
- 配置验证
|
|
||||||
- 实时预览
|
|
||||||
|
|
||||||
### 5.2 接口说明
|
|
||||||
|
|
||||||
#### 5.2.1 节点类型接口
|
|
||||||
```typescript
|
|
||||||
// 获取节点类型列表
|
|
||||||
GET /api/v1/node-types
|
|
||||||
Response: {
|
|
||||||
code: number;
|
|
||||||
data: NodeType[];
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取单个节点类型详情
|
|
||||||
GET /api/v1/node-types/{id}
|
|
||||||
Response: {
|
|
||||||
code: number;
|
|
||||||
data: NodeType;
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 5.2.2 工作流设计接口
|
|
||||||
```typescript
|
|
||||||
// 保存工作流设计
|
|
||||||
POST /api/v1/workflows/{id}/design
|
|
||||||
Request: {
|
|
||||||
nodes: Node[];
|
|
||||||
edges: Edge[];
|
|
||||||
}
|
|
||||||
Response: {
|
|
||||||
code: number;
|
|
||||||
data: boolean;
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取工作流设计
|
|
||||||
GET /api/v1/workflows/{id}/design
|
|
||||||
Response: {
|
|
||||||
code: number;
|
|
||||||
data: {
|
|
||||||
nodes: Node[];
|
|
||||||
edges: Edge[];
|
|
||||||
};
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.3 节点连线实现
|
|
||||||
|
|
||||||
#### 5.3.1 连接点(Anchors)
|
|
||||||
每种类型节点的连接点定义:
|
|
||||||
```typescript
|
|
||||||
interface NodeAnchor {
|
|
||||||
id: string;
|
|
||||||
type: 'input' | 'output';
|
|
||||||
position: 'top' | 'right' | 'bottom' | 'left';
|
|
||||||
allowMultiple?: boolean; // 是否允许多条连线
|
|
||||||
}
|
|
||||||
|
|
||||||
const nodeAnchors = {
|
|
||||||
START: [
|
|
||||||
{ id: 'output', type: 'output', position: 'bottom', allowMultiple: true }
|
|
||||||
],
|
|
||||||
END: [
|
|
||||||
{ id: 'input', type: 'input', position: 'top', allowMultiple: false }
|
|
||||||
],
|
|
||||||
TASK: [
|
|
||||||
{ id: 'input', type: 'input', position: 'top', allowMultiple: false },
|
|
||||||
{ id: 'output', type: 'output', position: 'bottom', allowMultiple: true }
|
|
||||||
],
|
|
||||||
GATEWAY: [
|
|
||||||
{ id: 'input', type: 'input', position: 'top', allowMultiple: false },
|
|
||||||
{ id: 'output', type: 'output', position: 'bottom', allowMultiple: true }
|
|
||||||
]
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 5.3.2 连线验证规则
|
|
||||||
```typescript
|
|
||||||
interface ConnectionValidation {
|
|
||||||
sourceNode: Node;
|
|
||||||
targetNode: Node;
|
|
||||||
sourceAnchor: NodeAnchor;
|
|
||||||
targetAnchor: NodeAnchor;
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateConnection({
|
|
||||||
sourceNode,
|
|
||||||
targetNode,
|
|
||||||
sourceAnchor,
|
|
||||||
targetAnchor
|
|
||||||
}: ConnectionValidation): boolean {
|
|
||||||
// 1. 检查源节点和目标节点是否有效
|
|
||||||
if (!sourceNode || !targetNode) return false;
|
|
||||||
|
|
||||||
// 2. 检查是否形成循环
|
|
||||||
if (wouldCreateCycle(sourceNode, targetNode)) return false;
|
|
||||||
|
|
||||||
// 3. 检查锚点类型匹配
|
|
||||||
if (sourceAnchor.type !== 'output' || targetAnchor.type !== 'input') return false;
|
|
||||||
|
|
||||||
// 4. 检查目标锚点是否已被占用(如果不允许多重连接)
|
|
||||||
if (!targetAnchor.allowMultiple && hasExistingConnection(targetNode, targetAnchor)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 5.3.3 连线样式配置
|
|
||||||
```typescript
|
|
||||||
const edgeStyles = {
|
|
||||||
default: {
|
|
||||||
type: 'smoothstep', // 平滑阶梯线
|
|
||||||
animated: false,
|
|
||||||
style: {
|
|
||||||
stroke: '#b1b1b7',
|
|
||||||
strokeWidth: 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selected: {
|
|
||||||
style: {
|
|
||||||
stroke: '#1890ff',
|
|
||||||
strokeWidth: 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
conditional: {
|
|
||||||
type: 'smoothstep',
|
|
||||||
animated: true,
|
|
||||||
style: {
|
|
||||||
stroke: '#722ed1',
|
|
||||||
strokeWidth: 2,
|
|
||||||
strokeDasharray: '5,5'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.4 状态管理建议
|
|
||||||
|
|
||||||
推荐使用状态管理库(如Redux或MobX)管理以下状态:
|
|
||||||
|
|
||||||
1. **全局状态**
|
|
||||||
- 当前工作流设计
|
|
||||||
- 可用节点类型
|
|
||||||
- 画布缩放级别
|
|
||||||
- 选中的节点/连线
|
|
||||||
|
|
||||||
2. **节点状态**
|
|
||||||
- 位置信息
|
|
||||||
- 配置数据
|
|
||||||
- 验证状态
|
|
||||||
|
|
||||||
3. **连线状态**
|
|
||||||
- 连接关系
|
|
||||||
- 条件配置
|
|
||||||
- 样式信息
|
|
||||||
|
|
||||||
### 5.5 性能优化建议
|
|
||||||
|
|
||||||
1. **渲染优化**
|
|
||||||
- 使用React.memo()优化节点渲染
|
|
||||||
- 实现虚拟滚动
|
|
||||||
- 大量节点时使用分层渲染
|
|
||||||
|
|
||||||
2. **状态更新优化**
|
|
||||||
- 批量更新状态
|
|
||||||
- 使用不可变数据结构
|
|
||||||
- 实现节点位置的防抖
|
|
||||||
|
|
||||||
3. **交互优化**
|
|
||||||
- 拖拽时使用节点预览
|
|
||||||
- 连线时显示对齐参考线
|
|
||||||
- 支持快捷键操作
|
|
||||||
|
|
||||||
### 5.6 错误处理
|
|
||||||
|
|
||||||
1. **前端验证**
|
|
||||||
- 节点配置验证
|
|
||||||
- 连线规则验证
|
|
||||||
- 数据完整性检查
|
|
||||||
|
|
||||||
2. **错误提示**
|
|
||||||
- 友好的错误信息
|
|
||||||
- 错误定位高亮
|
|
||||||
- 操作建议提示
|
|
||||||
|
|
||||||
3. **异常恢复**
|
|
||||||
- 自动保存
|
|
||||||
- 操作撤销/重做
|
|
||||||
- 状态恢复机制
|
|
||||||
|
|
||||||
## 6. 最佳实践建议
|
|
||||||
|
|
||||||
1. **代码组织**
|
|
||||||
- 使用TypeScript确保类型安全
|
|
||||||
- 遵循组件设计原则
|
|
||||||
- 实现完整的测试覆盖
|
|
||||||
|
|
||||||
2. **用户体验**
|
|
||||||
- 实现撤销/重做功能
|
|
||||||
- 支持键盘快捷键
|
|
||||||
- 添加操作引导
|
|
||||||
|
|
||||||
3. **可扩展性**
|
|
||||||
- 支持自定义节点
|
|
||||||
- 支持自定义连线样式
|
|
||||||
- 预留扩展接口
|
|
||||||
@ -1,494 +0,0 @@
|
|||||||
# 工作流引擎开发文档
|
|
||||||
|
|
||||||
## 一、已完成工作
|
|
||||||
|
|
||||||
### 1. 核心实体设计
|
|
||||||
- WorkflowDefinition: 工作流定义
|
|
||||||
- 包含基本信息(编码、名称、描述等)
|
|
||||||
- 包含流程定义内容(JSON格式)
|
|
||||||
- 支持版本控制和状态管理
|
|
||||||
|
|
||||||
- WorkflowInstance: 工作流实例
|
|
||||||
- 关联工作流定义
|
|
||||||
- 记录执行状态和进度
|
|
||||||
- 支持暂停、恢复、取消等操作
|
|
||||||
|
|
||||||
- NodeInstance: 节点实例
|
|
||||||
- 关联工作流实例
|
|
||||||
- 记录节点执行状态和结果
|
|
||||||
- 支持重试和跳过等操作
|
|
||||||
|
|
||||||
### 2. 节点类型体系
|
|
||||||
- 基础节点类型:
|
|
||||||
- START: 开始节点
|
|
||||||
- END: 结束节点
|
|
||||||
- CONDITION: 条件节点
|
|
||||||
- PARALLEL: 并行节点
|
|
||||||
|
|
||||||
- 功能节点类型:
|
|
||||||
- APPROVAL: 审批节点
|
|
||||||
- JENKINS: Jenkins构建节点
|
|
||||||
- SCRIPT: 脚本执行节点
|
|
||||||
- GIT: Git操作节点
|
|
||||||
- NACOS: 配置中心节点
|
|
||||||
- HTTP: HTTP请求节点
|
|
||||||
- NOTIFY: 通知节点
|
|
||||||
|
|
||||||
### 3. 工作流引擎实现
|
|
||||||
- 核心接口设计
|
|
||||||
- WorkflowEngine: 工作流引擎接口
|
|
||||||
- NodeExecutor: 节点执行器接口
|
|
||||||
|
|
||||||
- 默认实现
|
|
||||||
- DefaultWorkflowEngine: 默认工作流引擎实现
|
|
||||||
- AbstractNodeExecutor: 抽象节点执行器
|
|
||||||
- ShellNodeExecutor等具体执行器
|
|
||||||
|
|
||||||
### 4. 变量与日志管理
|
|
||||||
- WorkflowVariable: 工作流变量
|
|
||||||
- 支持全局变量和节点变量
|
|
||||||
- 支持变量引用和替换
|
|
||||||
|
|
||||||
- WorkflowLog: 工作流日志
|
|
||||||
- 支持不同日志类型和级别
|
|
||||||
- 支持详细的执行记录
|
|
||||||
|
|
||||||
### 5. 权限管理
|
|
||||||
- WorkflowPermission: 工作流权限
|
|
||||||
- 支持角色和用户级别的权限控制
|
|
||||||
- 支持操作级别的权限控制
|
|
||||||
|
|
||||||
### 6. API接口设计
|
|
||||||
- 工作流定义管理
|
|
||||||
- 工作流实例操作
|
|
||||||
- 节点实例管理
|
|
||||||
- 日志查询等功能
|
|
||||||
|
|
||||||
### 7. 错误码和消息
|
|
||||||
- 工作流相关错误码(2700-2799)
|
|
||||||
- 详细的错误消息定义
|
|
||||||
|
|
||||||
## 二、待完成工作
|
|
||||||
|
|
||||||
### 1. 高级功能实现
|
|
||||||
|
|
||||||
#### 1.1 节点执行引擎增强
|
|
||||||
```java
|
|
||||||
public interface NodeExecutor {
|
|
||||||
// 新增预执行检查
|
|
||||||
boolean preCheck(NodeInstance node, WorkflowContext context);
|
|
||||||
|
|
||||||
// 新增补偿操作
|
|
||||||
void compensate(NodeInstance node, WorkflowContext context);
|
|
||||||
|
|
||||||
// 新增超时处理
|
|
||||||
void handleTimeout(NodeInstance node, WorkflowContext context);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 节点执行状态追踪
|
|
||||||
public class NodeExecutionTracker {
|
|
||||||
private Long nodeInstanceId;
|
|
||||||
private Date startTime;
|
|
||||||
private Date endTime;
|
|
||||||
private String status;
|
|
||||||
private Map<String, Object> metrics;
|
|
||||||
private List<String> logs;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 1.2 工作流调度管理
|
|
||||||
```java
|
|
||||||
@Service
|
|
||||||
public class WorkflowScheduleService {
|
|
||||||
// 创建调度任务
|
|
||||||
public void createSchedule(WorkflowSchedule schedule);
|
|
||||||
|
|
||||||
// 修改调度配置
|
|
||||||
public void updateSchedule(WorkflowSchedule schedule);
|
|
||||||
|
|
||||||
// 启用/禁用调度
|
|
||||||
public void toggleSchedule(Long scheduleId, boolean enabled);
|
|
||||||
|
|
||||||
// 手动触发
|
|
||||||
public void triggerSchedule(Long scheduleId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调度配置表
|
|
||||||
CREATE TABLE wf_workflow_schedule (
|
|
||||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID',
|
|
||||||
name VARCHAR(100) NOT NULL COMMENT '调度名称',
|
|
||||||
cron VARCHAR(100) NOT NULL COMMENT 'cron表达式',
|
|
||||||
variables JSON COMMENT '工作流变量',
|
|
||||||
enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用',
|
|
||||||
last_fire_time DATETIME COMMENT '上次触发时间',
|
|
||||||
next_fire_time DATETIME COMMENT '下次触发时间',
|
|
||||||
-- 其他基础字段
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 1.3 工作流监控告警
|
|
||||||
```java
|
|
||||||
public interface WorkflowMonitor {
|
|
||||||
// 收集性能指标
|
|
||||||
void collectMetrics(WorkflowInstance instance);
|
|
||||||
|
|
||||||
// 检查健康状态
|
|
||||||
HealthStatus checkHealth();
|
|
||||||
|
|
||||||
// 触发告警
|
|
||||||
void triggerAlert(AlertType type, String message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监控指标表
|
|
||||||
CREATE TABLE wf_workflow_metrics (
|
|
||||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
workflow_instance_id BIGINT NOT NULL,
|
|
||||||
metric_name VARCHAR(100) NOT NULL,
|
|
||||||
metric_value DECIMAL(19,2) NOT NULL,
|
|
||||||
collect_time DATETIME NOT NULL,
|
|
||||||
-- 其他基础字段
|
|
||||||
);
|
|
||||||
|
|
||||||
// 告警记录表
|
|
||||||
CREATE TABLE wf_workflow_alert (
|
|
||||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
alert_type VARCHAR(50) NOT NULL,
|
|
||||||
target_type VARCHAR(50) NOT NULL,
|
|
||||||
target_id BIGINT NOT NULL,
|
|
||||||
level VARCHAR(20) NOT NULL,
|
|
||||||
message TEXT NOT NULL,
|
|
||||||
status VARCHAR(20) NOT NULL,
|
|
||||||
-- 其他基础字段
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 1.4 工作流分析统计
|
|
||||||
```java
|
|
||||||
public interface WorkflowAnalytics {
|
|
||||||
// 执行时长分析
|
|
||||||
Map<String, Duration> analyzeExecutionTime(Long workflowId);
|
|
||||||
|
|
||||||
// 成功率分析
|
|
||||||
Map<String, Double> analyzeSuccessRate(Long workflowId);
|
|
||||||
|
|
||||||
// 节点耗时分析
|
|
||||||
List<NodeTimeAnalysis> analyzeNodeTime(Long workflowId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分析结果表
|
|
||||||
CREATE TABLE wf_workflow_analytics (
|
|
||||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
workflow_definition_id BIGINT NOT NULL,
|
|
||||||
analysis_type VARCHAR(50) NOT NULL,
|
|
||||||
time_range VARCHAR(50) NOT NULL,
|
|
||||||
metrics JSON NOT NULL,
|
|
||||||
-- 其他基础字段
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 功能优化计划
|
|
||||||
|
|
||||||
#### 2.1 性能优化
|
|
||||||
- 引入本地缓存和分布式缓存
|
|
||||||
- 实现工作流实例分片执行
|
|
||||||
- 优化日志存储和查询
|
|
||||||
```java
|
|
||||||
@Configuration
|
|
||||||
public class WorkflowCacheConfig {
|
|
||||||
@Bean
|
|
||||||
public Cache<String, WorkflowDefinition> definitionCache() {
|
|
||||||
return CacheBuilder.newBuilder()
|
|
||||||
.maximumSize(1000)
|
|
||||||
.expireAfterWrite(1, TimeUnit.HOURS)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ShardingWorkflowEngine implements WorkflowEngine {
|
|
||||||
// 分片执行实现
|
|
||||||
public void executeWithSharding(WorkflowInstance instance) {
|
|
||||||
String shardingKey = calculateShardingKey(instance);
|
|
||||||
ShardingContext context = createShardingContext(shardingKey);
|
|
||||||
executeInShard(instance, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.2 可靠性增强
|
|
||||||
- 实现节点执行幂等性
|
|
||||||
- 增加全局事务控制
|
|
||||||
- 完善补偿机制
|
|
||||||
```java
|
|
||||||
public abstract class IdempotentNodeExecutor implements NodeExecutor {
|
|
||||||
// 幂等性检查
|
|
||||||
protected boolean checkIdempotent(String executionId) {
|
|
||||||
return redisTemplate.opsForValue()
|
|
||||||
.setIfAbsent("node:execution:" + executionId, "1", 24, TimeUnit.HOURS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class CompensationService {
|
|
||||||
// 注册补偿操作
|
|
||||||
public void registerCompensation(NodeInstance node, Runnable compensation);
|
|
||||||
|
|
||||||
// 执行补偿
|
|
||||||
public void executeCompensation(WorkflowInstance instance);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.3 扩展性优化
|
|
||||||
- 支持自定义节点类型
|
|
||||||
- 支持插件化扩展
|
|
||||||
- 提供更多扩展点
|
|
||||||
```java
|
|
||||||
public interface WorkflowPlugin {
|
|
||||||
// 插件初始化
|
|
||||||
void init(WorkflowEngine engine);
|
|
||||||
|
|
||||||
// 注册扩展点
|
|
||||||
void registerExtensions();
|
|
||||||
|
|
||||||
// 清理资源
|
|
||||||
void destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class PluginManager {
|
|
||||||
// 加载插件
|
|
||||||
public void loadPlugins();
|
|
||||||
|
|
||||||
// 启用插件
|
|
||||||
public void enablePlugin(String pluginId);
|
|
||||||
|
|
||||||
// 禁用插件
|
|
||||||
public void disablePlugin(String pluginId);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 新特性规划
|
|
||||||
|
|
||||||
#### 3.1 工作流模板功能
|
|
||||||
```java
|
|
||||||
// 模板定义表
|
|
||||||
CREATE TABLE wf_workflow_template (
|
|
||||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
code VARCHAR(100) NOT NULL UNIQUE,
|
|
||||||
name VARCHAR(100) NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
content JSON NOT NULL,
|
|
||||||
category VARCHAR(50),
|
|
||||||
tags JSON,
|
|
||||||
-- 其他基础字段
|
|
||||||
);
|
|
||||||
|
|
||||||
// 模板参数表
|
|
||||||
CREATE TABLE wf_template_parameter (
|
|
||||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
template_id BIGINT NOT NULL,
|
|
||||||
name VARCHAR(100) NOT NULL,
|
|
||||||
type VARCHAR(50) NOT NULL,
|
|
||||||
required BIT NOT NULL DEFAULT 0,
|
|
||||||
default_value VARCHAR(255),
|
|
||||||
validation_rule VARCHAR(255),
|
|
||||||
-- 其他基础字段
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.2 工作流版本管理
|
|
||||||
```java
|
|
||||||
// 版本管理表
|
|
||||||
CREATE TABLE wf_workflow_version (
|
|
||||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
workflow_definition_id BIGINT NOT NULL,
|
|
||||||
version VARCHAR(20) NOT NULL,
|
|
||||||
content JSON NOT NULL,
|
|
||||||
change_log TEXT,
|
|
||||||
status VARCHAR(20) NOT NULL,
|
|
||||||
-- 其他基础字段
|
|
||||||
);
|
|
||||||
|
|
||||||
public interface VersionManager {
|
|
||||||
// 创建新版本
|
|
||||||
String createVersion(Long workflowId, String content);
|
|
||||||
|
|
||||||
// 发布版本
|
|
||||||
void publishVersion(Long workflowId, String version);
|
|
||||||
|
|
||||||
// 回滚版本
|
|
||||||
void rollbackVersion(Long workflowId, String version);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.3 工作流迁移功能
|
|
||||||
```java
|
|
||||||
public interface WorkflowMigration {
|
|
||||||
// 导出工作流
|
|
||||||
byte[] exportWorkflow(Long workflowId);
|
|
||||||
|
|
||||||
// 导入工作流
|
|
||||||
Long importWorkflow(byte[] content);
|
|
||||||
|
|
||||||
// 迁移实例到新版本
|
|
||||||
void migrateInstance(Long instanceId, String targetVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 迁移记录表
|
|
||||||
CREATE TABLE wf_workflow_migration (
|
|
||||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
source_workflow_id BIGINT NOT NULL,
|
|
||||||
target_workflow_id BIGINT NOT NULL,
|
|
||||||
migration_type VARCHAR(50) NOT NULL,
|
|
||||||
status VARCHAR(20) NOT NULL,
|
|
||||||
error_message TEXT,
|
|
||||||
-- 其他基础字段
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.4 工作流测试功能
|
|
||||||
```java
|
|
||||||
public interface WorkflowTesting {
|
|
||||||
// 模拟执行
|
|
||||||
TestResult simulateExecution(Long workflowId, Map<String, Object> variables);
|
|
||||||
|
|
||||||
// 节点单元测试
|
|
||||||
TestResult testNode(NodeConfig config, Map<String, Object> inputs);
|
|
||||||
|
|
||||||
// 生成测试报告
|
|
||||||
TestReport generateReport(Long testExecutionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测试用例表
|
|
||||||
CREATE TABLE wf_workflow_test_case (
|
|
||||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
workflow_definition_id BIGINT NOT NULL,
|
|
||||||
name VARCHAR(100) NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
variables JSON,
|
|
||||||
expected_result JSON,
|
|
||||||
-- 其他基础字段
|
|
||||||
);
|
|
||||||
|
|
||||||
// 测试执行记录表
|
|
||||||
CREATE TABLE wf_workflow_test_execution (
|
|
||||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
test_case_id BIGINT NOT NULL,
|
|
||||||
execution_time DATETIME NOT NULL,
|
|
||||||
status VARCHAR(20) NOT NULL,
|
|
||||||
actual_result JSON,
|
|
||||||
error_message TEXT,
|
|
||||||
-- 其他基础字段
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 三、技术架构
|
|
||||||
|
|
||||||
### 1. 整体架构
|
|
||||||
```
|
|
||||||
+------------------+
|
|
||||||
| API Layer |
|
|
||||||
+------------------+
|
|
||||||
| Service Layer |
|
|
||||||
+------------------+
|
|
||||||
| Engine Core |
|
|
||||||
+------------------+
|
|
||||||
| Storage Layer |
|
|
||||||
+------------------+
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 关键组件
|
|
||||||
- 工作流引擎核心
|
|
||||||
- 节点执行引擎
|
|
||||||
- 变量管理器
|
|
||||||
- 日志管理器
|
|
||||||
- 权限管理器
|
|
||||||
- 调度管理器
|
|
||||||
- 监控告警组件
|
|
||||||
- 分析统计组件
|
|
||||||
|
|
||||||
### 3. 存储设计
|
|
||||||
- 核心业务表
|
|
||||||
- 执行记录表
|
|
||||||
- 监控指标表
|
|
||||||
- 分析统计表
|
|
||||||
- 日志记录表
|
|
||||||
|
|
||||||
### 4. 缓存设计
|
|
||||||
- 本地缓存
|
|
||||||
- 工作流定义缓存
|
|
||||||
- 节点配置缓存
|
|
||||||
|
|
||||||
- 分布式缓存
|
|
||||||
- 执行状态缓存
|
|
||||||
- 变量数据缓存
|
|
||||||
|
|
||||||
## 四、部署运维
|
|
||||||
|
|
||||||
### 1. 部署架构
|
|
||||||
```
|
|
||||||
+---------------+ +---------------+
|
|
||||||
| API Server | | API Server |
|
|
||||||
+---------------+ +---------------+
|
|
||||||
| |
|
|
||||||
+---------------+ +---------------+
|
|
||||||
| Engine Worker | | Engine Worker |
|
|
||||||
+---------------+ +---------------+
|
|
||||||
| |
|
|
||||||
+---------------+ +---------------+
|
|
||||||
| Node Worker | | Node Worker |
|
|
||||||
+---------------+ +---------------+
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 监控方案
|
|
||||||
- 系统监控
|
|
||||||
- JVM指标
|
|
||||||
- 线程池状态
|
|
||||||
- 数据库连接池
|
|
||||||
|
|
||||||
- 业务监控
|
|
||||||
- 工作流执行状态
|
|
||||||
- 节点执行性能
|
|
||||||
- 错误率统计
|
|
||||||
|
|
||||||
### 3. 告警方案
|
|
||||||
- 系统告警
|
|
||||||
- 资源使用率
|
|
||||||
- 错误率阈值
|
|
||||||
- 响应时间
|
|
||||||
|
|
||||||
- 业务告警
|
|
||||||
- 执行超时
|
|
||||||
- 节点失败
|
|
||||||
- 异常终止
|
|
||||||
|
|
||||||
### 4. 运维工具
|
|
||||||
- 管理控制台
|
|
||||||
- 监控面板
|
|
||||||
- 运维脚本
|
|
||||||
- 诊断工具
|
|
||||||
|
|
||||||
## 五、项目管理
|
|
||||||
|
|
||||||
### 1. 开发计划
|
|
||||||
- Phase 1: 核心功能实现 (已完成)
|
|
||||||
- Phase 2: 高级特性开发 (进行中)
|
|
||||||
- Phase 3: 性能优化和稳定性提升
|
|
||||||
- Phase 4: 运维工具和监控体系建设
|
|
||||||
|
|
||||||
### 2. 测试策略
|
|
||||||
- 单元测试
|
|
||||||
- 集成测试
|
|
||||||
- 性能测试
|
|
||||||
- 稳定性测试
|
|
||||||
|
|
||||||
### 3. 文档规划
|
|
||||||
- 设计文档
|
|
||||||
- API文档
|
|
||||||
- 使用手册
|
|
||||||
- 运维手册
|
|
||||||
|
|
||||||
### 4. 版本规划
|
|
||||||
- v1.0: 基础功能版本
|
|
||||||
- v1.1: 高级特性版本
|
|
||||||
- v1.2: 性能优化版本
|
|
||||||
- v2.0: 企业版本
|
|
||||||
@ -1,670 +0,0 @@
|
|||||||
# 工作流引擎改造方案设计文档
|
|
||||||
|
|
||||||
## 1. 现状分析
|
|
||||||
|
|
||||||
### 1.1 现有组件
|
|
||||||
|
|
||||||
1. 工作流引擎核心组件
|
|
||||||
- `AbstractNodeExecutor`: 节点执行器抽象类,提供基础的节点执行逻辑
|
|
||||||
- `WorkflowGraph`: 工作流图模型,管理节点和转换关系
|
|
||||||
- `INodeExecutor`: 节点执行器接口
|
|
||||||
- `WorkflowInstance`: 工作流实例实体
|
|
||||||
- `NodeInstance`: 节点实例实体
|
|
||||||
|
|
||||||
2. 数据模型
|
|
||||||
```sql
|
|
||||||
-- 工作流定义表
|
|
||||||
CREATE TABLE wf_workflow_definition (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
|
|
||||||
code VARCHAR(100) NOT NULL COMMENT '工作流编码',
|
|
||||||
name VARCHAR(100) NOT NULL COMMENT '工作流名称',
|
|
||||||
description VARCHAR(255) NULL COMMENT '工作流描述',
|
|
||||||
status TINYINT NOT NULL COMMENT '工作流状态(0:草稿,1:已发布,2:已禁用)',
|
|
||||||
version_no INT NOT NULL DEFAULT 1 COMMENT '版本号',
|
|
||||||
node_config TEXT NULL COMMENT '节点配置(JSON)',
|
|
||||||
transition_config TEXT NULL COMMENT '流转配置(JSON)',
|
|
||||||
form_definition TEXT NULL COMMENT '表单定义(JSON)',
|
|
||||||
graph_definition TEXT NULL COMMENT '图形信息(JSON)',
|
|
||||||
enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用',
|
|
||||||
|
|
||||||
CONSTRAINT UK_workflow_definition_code_version UNIQUE (code, version_no)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流定义表';
|
|
||||||
|
|
||||||
-- 工作流实例表
|
|
||||||
CREATE TABLE wf_workflow_instance (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
|
|
||||||
workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID',
|
|
||||||
business_key VARCHAR(100) NOT NULL COMMENT '业务标识',
|
|
||||||
status TINYINT NOT NULL COMMENT '状态(0:已创建,1:等待执行,2:执行中,3:已完成,4:执行失败,5:已取消,6:已暂停,7:已终止)',
|
|
||||||
start_time DATETIME(6) NULL COMMENT '开始时间',
|
|
||||||
end_time DATETIME(6) NULL COMMENT '结束时间',
|
|
||||||
error TEXT NULL COMMENT '错误信息',
|
|
||||||
|
|
||||||
CONSTRAINT FK_workflow_instance_definition FOREIGN KEY (workflow_definition_id) REFERENCES wf_workflow_definition (id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流实例表';
|
|
||||||
|
|
||||||
-- 节点实例表
|
|
||||||
CREATE TABLE wf_node_instance (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
|
|
||||||
workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID',
|
|
||||||
node_id VARCHAR(100) NOT NULL COMMENT '节点ID',
|
|
||||||
node_type TINYINT NOT NULL COMMENT '节点类型(0:开始节点,1:结束节点,2:任务节点,3:网关节点,4:子流程节点,5:Shell脚本节点,6:审批节点,7:Jenkins任务节点,8:Git操作节点)',
|
|
||||||
name VARCHAR(100) NOT NULL COMMENT '节点名称',
|
|
||||||
status TINYINT NOT NULL COMMENT '状态(0:已创建,1:等待执行,2:执行中,3:已完成,4:执行失败,5:已取消,6:已暂停,7:已终止)',
|
|
||||||
start_time DATETIME(6) NULL COMMENT '开始时间',
|
|
||||||
end_time DATETIME(6) NULL COMMENT '结束时间',
|
|
||||||
config TEXT NULL COMMENT '节点配置(JSON)',
|
|
||||||
description TEXT NULL COMMENT '节点描述',
|
|
||||||
input TEXT NULL COMMENT '输入参数(JSON)',
|
|
||||||
output TEXT NULL COMMENT '输出结果(JSON)',
|
|
||||||
error TEXT NULL COMMENT '错误信息',
|
|
||||||
pre_node_id VARCHAR(100) NULL COMMENT '前置节点ID',
|
|
||||||
|
|
||||||
CONSTRAINT FK_node_instance_workflow FOREIGN KEY (workflow_instance_id) REFERENCES wf_workflow_instance (id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='节点实例表';
|
|
||||||
|
|
||||||
-- 工作流变量表
|
|
||||||
CREATE TABLE wf_workflow_variable (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
|
|
||||||
workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID',
|
|
||||||
name VARCHAR(100) NOT NULL COMMENT '变量名',
|
|
||||||
value TEXT NULL COMMENT '变量值',
|
|
||||||
type VARCHAR(20) NOT NULL COMMENT '变量类型',
|
|
||||||
|
|
||||||
CONSTRAINT FK_workflow_variable_instance FOREIGN KEY (workflow_instance_id) REFERENCES wf_workflow_instance (id),
|
|
||||||
CONSTRAINT UK_workflow_variable_instance_name UNIQUE (workflow_instance_id, name)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工作流变量表';
|
|
||||||
|
|
||||||
-- 工作流日志表
|
|
||||||
CREATE TABLE wf_log (
|
|
||||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
workflow_instance_id BIGINT NOT NULL COMMENT '工作流实例ID',
|
|
||||||
node_id VARCHAR(50) NULL COMMENT '节点ID',
|
|
||||||
level VARCHAR(10) NOT NULL COMMENT '日志级别',
|
|
||||||
message TEXT NOT NULL COMMENT '日志内容',
|
|
||||||
detail TEXT,
|
|
||||||
create_time DATETIME NOT NULL,
|
|
||||||
create_by VARCHAR(50),
|
|
||||||
update_time DATETIME,
|
|
||||||
update_by VARCHAR(50),
|
|
||||||
version INT DEFAULT 0,
|
|
||||||
deleted BOOLEAN DEFAULT FALSE,
|
|
||||||
CONSTRAINT FK_workflow_log_instance FOREIGN KEY (workflow_instance_id) REFERENCES wf_workflow_instance (id)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 工作流权限表
|
|
||||||
CREATE TABLE wf_workflow_permission (
|
|
||||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
|
|
||||||
workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID',
|
|
||||||
type VARCHAR(50) NOT NULL COMMENT '权限类型',
|
|
||||||
user_id BIGINT COMMENT '用户ID',
|
|
||||||
role_id BIGINT COMMENT '角色ID',
|
|
||||||
department_id BIGINT COMMENT '部门ID',
|
|
||||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
|
||||||
create_by BIGINT NOT NULL COMMENT '创建人',
|
|
||||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
|
||||||
update_by BIGINT NOT NULL COMMENT '更新人',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '版本号',
|
|
||||||
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
|
|
||||||
INDEX idx_workflow_definition_id (workflow_definition_id),
|
|
||||||
INDEX idx_user_id (user_id),
|
|
||||||
INDEX idx_role_id (role_id),
|
|
||||||
INDEX idx_department_id (department_id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流权限';
|
|
||||||
|
|
||||||
-- 节点类型表
|
|
||||||
CREATE TABLE wf_node_type (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
|
|
||||||
code VARCHAR(100) NOT NULL COMMENT '节点类型编码',
|
|
||||||
name VARCHAR(100) NOT NULL COMMENT '节点类型名称',
|
|
||||||
description TEXT NULL COMMENT '节点类型描述',
|
|
||||||
category VARCHAR(50) NOT NULL COMMENT '节点类型分类',
|
|
||||||
icon VARCHAR(100) NULL COMMENT '节点图标',
|
|
||||||
color VARCHAR(20) NULL COMMENT '节点颜色',
|
|
||||||
executors TEXT NULL COMMENT '执行器列表(JSON)',
|
|
||||||
config_schema TEXT NULL COMMENT '节点配置模式(JSON)',
|
|
||||||
default_config TEXT NULL COMMENT '默认配置(JSON)',
|
|
||||||
enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用',
|
|
||||||
|
|
||||||
CONSTRAINT UK_node_type_code UNIQUE (code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='节点类型表';
|
|
||||||
|
|
||||||
## 2. 改造目标
|
|
||||||
|
|
||||||
### 2.1 核心需求
|
|
||||||
1. 支持审批节点
|
|
||||||
2. 实现实时日志
|
|
||||||
3. 工作流状态持久化
|
|
||||||
4. 支持程序重启后恢复执行
|
|
||||||
|
|
||||||
### 2.2 技术要求
|
|
||||||
1. 遵循现有代码规范
|
|
||||||
2. 保持向后兼容
|
|
||||||
3. 确保高可用性
|
|
||||||
4. 支持并发执行
|
|
||||||
|
|
||||||
## 3. 详细设计
|
|
||||||
|
|
||||||
### 3.1 数据库改造
|
|
||||||
|
|
||||||
#### 3.1.1 修改现有表
|
|
||||||
```sql
|
|
||||||
-- 修改工作流实例表,添加执行状态字段
|
|
||||||
ALTER TABLE wf_workflow_instance
|
|
||||||
ADD COLUMN current_node_id varchar(100) COMMENT '当前执行的节点ID',
|
|
||||||
ADD COLUMN execution_status varchar(32) COMMENT '执行状态';
|
|
||||||
|
|
||||||
-- 修改节点实例表,添加重试相关字段
|
|
||||||
ALTER TABLE wf_node_instance
|
|
||||||
ADD COLUMN retry_count INT DEFAULT 0 COMMENT '重试次数',
|
|
||||||
ADD COLUMN max_retry_count INT DEFAULT 0 COMMENT '最大重试次数',
|
|
||||||
ADD COLUMN retry_interval INT DEFAULT 0 COMMENT '重试间隔(秒)',
|
|
||||||
ADD COLUMN last_retry_time TIMESTAMP COMMENT '最后重试时间';
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.1.2 新增审批相关表
|
|
||||||
```sql
|
|
||||||
-- 审批配置表
|
|
||||||
CREATE TABLE wf_approval_config (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
|
|
||||||
project_id VARCHAR(64) NOT NULL COMMENT '项目ID',
|
|
||||||
env_type VARCHAR(20) NOT NULL COMMENT '环境类型',
|
|
||||||
approval_type VARCHAR(20) NOT NULL COMMENT '审批类型(SINGLE/MULTI/ANY)',
|
|
||||||
approver_ids TEXT NOT NULL COMMENT '审批人ID列表(JSON)',
|
|
||||||
required_count INT NOT NULL COMMENT '需要的审批人数',
|
|
||||||
timeout_hours INT NOT NULL COMMENT '审批超时时间(小时)',
|
|
||||||
enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用',
|
|
||||||
|
|
||||||
CONSTRAINT UK_approval_config_project_env UNIQUE (project_id, env_type)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批配置表';
|
|
||||||
|
|
||||||
-- 审批实例表
|
|
||||||
CREATE TABLE wf_approval_instance (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
|
|
||||||
node_instance_id VARCHAR(64) NOT NULL COMMENT '节点实例ID',
|
|
||||||
workflow_instance_id VARCHAR(64) NOT NULL COMMENT '工作流实例ID',
|
|
||||||
project_id VARCHAR(64) NOT NULL COMMENT '项目ID',
|
|
||||||
env_type VARCHAR(20) NOT NULL COMMENT '环境类型',
|
|
||||||
status VARCHAR(20) NOT NULL COMMENT '状态(PENDING/APPROVED/REJECTED/TIMEOUT)',
|
|
||||||
initiator_id VARCHAR(64) NOT NULL COMMENT '发起人ID',
|
|
||||||
approved_time DATETIME(6) NULL COMMENT '审批通过时间',
|
|
||||||
approver_id VARCHAR(64) NULL COMMENT '审批人ID',
|
|
||||||
comment TEXT NULL COMMENT '审批意见',
|
|
||||||
|
|
||||||
INDEX idx_workflow_instance (workflow_instance_id),
|
|
||||||
INDEX idx_node_instance (node_instance_id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批实例表';
|
|
||||||
|
|
||||||
-- 审批记录表
|
|
||||||
CREATE TABLE wf_approval_record (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
|
||||||
create_by VARCHAR(255) NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
|
||||||
update_by VARCHAR(255) NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
|
||||||
|
|
||||||
approval_instance_id BIGINT NOT NULL COMMENT '审批实例ID',
|
|
||||||
approver_id VARCHAR(64) NOT NULL COMMENT '审批人ID',
|
|
||||||
action VARCHAR(20) NOT NULL COMMENT '审批动作(APPROVE/REJECT)',
|
|
||||||
comment TEXT NULL COMMENT '审批意见',
|
|
||||||
|
|
||||||
CONSTRAINT FK_approval_record_instance FOREIGN KEY (approval_instance_id)
|
|
||||||
REFERENCES wf_approval_instance (id),
|
|
||||||
INDEX idx_approval_instance (approval_instance_id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批记录表';
|
|
||||||
|
|
||||||
### 3.2 代码改造
|
|
||||||
|
|
||||||
#### 3.2.1 新增枚举
|
|
||||||
```java
|
|
||||||
/**
|
|
||||||
* 节点类型枚举扩展
|
|
||||||
*/
|
|
||||||
public enum NodeTypeEnum {
|
|
||||||
SCRIPT, // 脚本节点
|
|
||||||
HTTP, // HTTP请求节点
|
|
||||||
APPROVAL, // 审批节点
|
|
||||||
// ... 其他节点类型
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 审批类型枚举
|
|
||||||
*/
|
|
||||||
public enum ApprovalTypeEnum {
|
|
||||||
SINGLE, // 单人审批
|
|
||||||
MULTI, // 多人会签
|
|
||||||
ANY // 任意人审批
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 审批状态枚举
|
|
||||||
*/
|
|
||||||
public enum ApprovalStatusEnum {
|
|
||||||
PENDING, // 待审批
|
|
||||||
APPROVED, // 已通过
|
|
||||||
REJECTED, // 已拒绝
|
|
||||||
TIMEOUT // 已超时
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.2.2 实体类
|
|
||||||
```java
|
|
||||||
/**
|
|
||||||
* 审批配置实体
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
@Entity
|
|
||||||
@Table(name = "wf_approval_config")
|
|
||||||
@LogicDelete
|
|
||||||
public class ApprovalConfig extends BaseEntity<Long> {
|
|
||||||
@Column(nullable = false)
|
|
||||||
private String projectId;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
private String envType;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
private ApprovalTypeEnum approvalType;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private String approverIds;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private Integer requiredCount;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private Integer timeoutHours;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 审批实例实体
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
@Entity
|
|
||||||
@Table(name = "wf_approval_instance")
|
|
||||||
@LogicDelete
|
|
||||||
public class ApprovalInstance extends BaseEntity<Long> {
|
|
||||||
@Column(nullable = false)
|
|
||||||
private String nodeInstanceId;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private String workflowInstanceId;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private String projectId;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
private String envType;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
private ApprovalStatusEnum status;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private String initiatorId;
|
|
||||||
|
|
||||||
private LocalDateTime approvedTime;
|
|
||||||
private String approverId;
|
|
||||||
private String comment;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.2.3 Repository接口
|
|
||||||
```java
|
|
||||||
/**
|
|
||||||
* 审批配置Repository
|
|
||||||
*/
|
|
||||||
@Repository
|
|
||||||
public interface IApprovalConfigRepository extends IBaseRepository<ApprovalConfig, Long> {
|
|
||||||
ApprovalConfig findByProjectIdAndEnvType(String projectId, String envType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 审批实例Repository
|
|
||||||
*/
|
|
||||||
@Repository
|
|
||||||
public interface IApprovalInstanceRepository extends IBaseRepository<ApprovalInstance, Long> {
|
|
||||||
List<ApprovalInstance> findByStatus(ApprovalStatusEnum status);
|
|
||||||
ApprovalInstance findByWorkflowInstanceIdAndNodeInstanceId(String workflowInstanceId, String nodeInstanceId);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.2.4 Service接口
|
|
||||||
```java
|
|
||||||
/**
|
|
||||||
* 审批服务接口
|
|
||||||
*/
|
|
||||||
public interface IApprovalService {
|
|
||||||
/**
|
|
||||||
* 创建审批实例
|
|
||||||
*/
|
|
||||||
ApprovalInstance createApprovalInstance(String nodeInstanceId, String workflowInstanceId,
|
|
||||||
String projectId, ApprovalConfig config);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理审批结果
|
|
||||||
*/
|
|
||||||
void handleApproval(Long approvalInstanceId, String approverId,
|
|
||||||
boolean approved, String comment);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查审批状态
|
|
||||||
*/
|
|
||||||
ApprovalStatusEnum checkApprovalStatus(Long approvalInstanceId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送审批通知
|
|
||||||
*/
|
|
||||||
void sendApprovalNotifications(ApprovalInstance instance);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.2.5 工作流执行管理器
|
|
||||||
```java
|
|
||||||
/**
|
|
||||||
* 工作流执行管理器
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@Slf4j
|
|
||||||
public class WorkflowExecutionManager {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private ThreadPoolExecutor workflowExecutor;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private WorkflowInstanceRepository instanceRepository;
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void init() {
|
|
||||||
// 初始化线程池
|
|
||||||
workflowExecutor = new ThreadPoolExecutor(
|
|
||||||
10, 20, 60L, TimeUnit.SECONDS,
|
|
||||||
new LinkedBlockingQueue<>(100),
|
|
||||||
new ThreadFactoryBuilder().setNameFormat("workflow-executor-%d").build()
|
|
||||||
);
|
|
||||||
|
|
||||||
// 恢复运行中的工作流
|
|
||||||
resumeRunningWorkflows();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... 其他方法实现
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.2.6 审批节点执行器
|
|
||||||
```java
|
|
||||||
/**
|
|
||||||
* 审批节点执行器
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@Slf4j
|
|
||||||
public class ApprovalNodeExecutor extends AbstractNodeExecutor {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private IApprovalService approvalService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public NodeTypeEnum getNodeType() {
|
|
||||||
return NodeTypeEnum.APPROVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doExecute(WorkflowInstance workflowInstance, NodeInstance nodeInstance) {
|
|
||||||
// ... 实现审批节点执行逻辑
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.3 实时日志实现
|
|
||||||
|
|
||||||
#### 3.3.1 日志收集器
|
|
||||||
```java
|
|
||||||
/**
|
|
||||||
* 日志收集器
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@Slf4j
|
|
||||||
public class ProcessLogManager {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private WorkflowLogService workflowLogService;
|
|
||||||
|
|
||||||
private final Map<String, CircularBuffer> logBuffers = new ConcurrentHashMap<>();
|
|
||||||
private final Map<String, List<Consumer<String>>> subscribers = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
// ... 实现日志收集和推送逻辑
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.3.2 SSE控制器
|
|
||||||
```java
|
|
||||||
/**
|
|
||||||
* 日志SSE控制器
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/v1/logs")
|
|
||||||
@Tag(name = "工作流日志", description = "工作流日志相关接口")
|
|
||||||
public class LogController {
|
|
||||||
|
|
||||||
@GetMapping(value = "/{nodeInstanceId}/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
|
||||||
public SseEmitter streamLogs(@PathVariable String nodeInstanceId) {
|
|
||||||
// ... 实现SSE日志推送
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. 开发计划
|
|
||||||
|
|
||||||
### 4.1 核心功能改造
|
|
||||||
|
|
||||||
#### 4.1.1 工作流引擎核心
|
|
||||||
1. **状态管理优化**
|
|
||||||
- 实现工作流实例状态机,支持更细粒度的状态转换
|
|
||||||
- 增加节点实例的状态追踪和历史记录
|
|
||||||
- 添加工作流暂停/恢复机制
|
|
||||||
|
|
||||||
2. **并发控制增强**
|
|
||||||
- 实现基于数据库的分布式锁
|
|
||||||
- 添加节点级别的并发控制
|
|
||||||
- 优化多实例并行执行的性能
|
|
||||||
|
|
||||||
3. **错误处理机制**
|
|
||||||
- 完善节点执行失败的重试机制
|
|
||||||
- 实现节点回滚功能
|
|
||||||
- 添加全局异常处理和日志记录
|
|
||||||
- 支持自定义错误处理策略
|
|
||||||
|
|
||||||
#### 4.1.2 审批功能实现
|
|
||||||
1. **审批节点开发**
|
|
||||||
- 实现审批节点执行器
|
|
||||||
- 支持多种审批模式(单人、多人、任意人)
|
|
||||||
- 添加审批超时处理机制
|
|
||||||
- 实现审批回调通知
|
|
||||||
|
|
||||||
2. **审批流程管理**
|
|
||||||
- 开发审批配置管理接口
|
|
||||||
- 实现审批人动态配置
|
|
||||||
- 支持审批规则的版本控制
|
|
||||||
- 添加审批历史查询功能
|
|
||||||
|
|
||||||
3. **审批UI组件**
|
|
||||||
- 开发审批任务列表
|
|
||||||
- 实现审批详情页面
|
|
||||||
- 添加审批操作接口
|
|
||||||
- 支持审批意见和附件上传
|
|
||||||
|
|
||||||
### 4.2 新增功能
|
|
||||||
|
|
||||||
#### 4.2.1 工作流监控
|
|
||||||
1. **实时监控**
|
|
||||||
- 实现工作流执行状态实时展示
|
|
||||||
- 添加节点执行时间统计
|
|
||||||
- 支持执行瓶颈分析
|
|
||||||
- 实现资源使用监控
|
|
||||||
|
|
||||||
2. **报警机制**
|
|
||||||
- 添加执行超时告警
|
|
||||||
- 实现错误率监控告警
|
|
||||||
- 支持自定义告警规则
|
|
||||||
- 集成多种告警通道(邮件、短信、webhook等)
|
|
||||||
|
|
||||||
#### 4.2.2 工作流分析
|
|
||||||
1. **统计分析**
|
|
||||||
- 实现工作流执行趋势分析
|
|
||||||
- 添加节点执行效率分析
|
|
||||||
- 支持审批效率统计
|
|
||||||
- 开发自定义报表功能
|
|
||||||
|
|
||||||
2. **可视化展示**
|
|
||||||
- 实现工作流执行路径可视化
|
|
||||||
- 添加状态转换图表展示
|
|
||||||
- 支持审批流程可视化
|
|
||||||
- 开发数据大屏功能
|
|
||||||
|
|
||||||
### 4.3 性能优化
|
|
||||||
|
|
||||||
1. **数据库优化**
|
|
||||||
- 优化表结构和索引
|
|
||||||
- 实现分库分表方案
|
|
||||||
- 添加缓存机制
|
|
||||||
- 优化大数据量查询性能
|
|
||||||
|
|
||||||
2. **执行引擎优化**
|
|
||||||
- 优化线程池管理
|
|
||||||
- 实现节点执行预热
|
|
||||||
- 添加资源限流机制
|
|
||||||
- 优化内存使用
|
|
||||||
|
|
||||||
### 4.4 开发排期
|
|
||||||
|
|
||||||
#### Phase 1: 核心功能改造(4周)
|
|
||||||
- Week 1-2: 工作流引擎核心改造
|
|
||||||
- Week 3-4: 审批功能实现
|
|
||||||
|
|
||||||
#### Phase 2: 新增功能开发(3周)
|
|
||||||
- Week 1-2: 工作流监控功能
|
|
||||||
- Week 3: 工作流分析功能
|
|
||||||
|
|
||||||
#### Phase 3: 性能优化(2周)
|
|
||||||
- Week 1: 数据库优化
|
|
||||||
- Week 2: 执行引擎优化
|
|
||||||
|
|
||||||
#### Phase 4: 测试与上线(1周)
|
|
||||||
- Day 1-3: 功能测试与bug修复
|
|
||||||
- Day 4-5: 性能测试与优化
|
|
||||||
- Day 6-7: 准备上线与部署
|
|
||||||
|
|
||||||
### 4.5 风险评估
|
|
||||||
|
|
||||||
1. **技术风险**
|
|
||||||
- 分布式环境下的状态一致性
|
|
||||||
- 大规模并发下的性能表现
|
|
||||||
- 复杂工作流的稳定性保证
|
|
||||||
|
|
||||||
2. **项目风险**
|
|
||||||
- 开发周期可能超出预期
|
|
||||||
- 新旧版本兼容性问题
|
|
||||||
- 现有业务的平滑迁移
|
|
||||||
|
|
||||||
### 4.6 后续规划
|
|
||||||
|
|
||||||
1. **功能扩展**
|
|
||||||
- 支持更多类型的节点
|
|
||||||
- 添加工作流模板功能
|
|
||||||
- 实现工作流版本管理
|
|
||||||
- 支持跨系统工作流
|
|
||||||
|
|
||||||
2. **生态建设**
|
|
||||||
- 开发SDK工具包
|
|
||||||
- 提供更多开箱即用的组件
|
|
||||||
- 完善开发文档
|
|
||||||
- 建设示例中心
|
|
||||||
|
|
||||||
## 5. 注意事项
|
|
||||||
|
|
||||||
1. 数据迁移
|
|
||||||
- 需要处理已有的运行中工作流
|
|
||||||
- 确保数据一致性
|
|
||||||
|
|
||||||
2. 性能考虑
|
|
||||||
- 控制线程池大小
|
|
||||||
- 合理设置日志缓冲区
|
|
||||||
- 定期清理历史数据
|
|
||||||
|
|
||||||
3. 监控告警
|
|
||||||
- 添加工作流执行监控
|
|
||||||
- 配置审批超时告警
|
|
||||||
- 监控系统资源使用
|
|
||||||
|
|
||||||
## 6. 后续优化
|
|
||||||
|
|
||||||
1. 功能优化
|
|
||||||
- 支持更多审批策略
|
|
||||||
- 优化日志查询性能
|
|
||||||
- 添加工作流统计分析
|
|
||||||
|
|
||||||
2. 性能优化
|
|
||||||
- 引入分布式锁
|
|
||||||
- 优化数据库查询
|
|
||||||
- 添加缓存机制
|
|
||||||
|
|
||||||
3. 运维优化
|
|
||||||
- 完善监控指标
|
|
||||||
- 优化日志清理
|
|
||||||
- 添加性能诊断工具
|
|
||||||
@ -1,380 +0,0 @@
|
|||||||
# 工作流实例同步机制设计文档
|
|
||||||
|
|
||||||
## 目录
|
|
||||||
1. [整体数据流向](#整体数据流向)
|
|
||||||
2. [Flowable与系统映射关系](#Flowable与系统映射关系)
|
|
||||||
3. [数据同步实现](#数据同步实现)
|
|
||||||
4. [事件监听机制](#事件监听机制)
|
|
||||||
5. [状态管理](#状态管理)
|
|
||||||
6. [最佳实践](#最佳实践)
|
|
||||||
|
|
||||||
## 整体数据流向
|
|
||||||
|
|
||||||
```
|
|
||||||
[前端流程设计器]
|
|
||||||
|
|
|
||||||
| 1. 用户拖拽设计流程
|
|
||||||
| - 选择节点类型(从workflow_node_definition获取可用节点)
|
|
||||||
| - 配置节点属性(根据节点的form_config渲染表单)
|
|
||||||
| - 连接节点、设置条件
|
|
||||||
↓
|
|
||||||
[前端数据模型]
|
|
||||||
| - 图形数据(nodes、edges)
|
|
||||||
| - 表单数据(各节点的配置信息)
|
|
||||||
| - 流程基本信息(名称、描述等)
|
|
||||||
|
|
|
||||||
| 2. 保存流程设计
|
|
||||||
↓
|
|
||||||
[后端 WorkflowDefinitionController]
|
|
||||||
|
|
|
||||||
| 3. 接收流程设计数据
|
|
||||||
↓
|
|
||||||
[后端 WorkflowDefinitionService]
|
|
||||||
|
|
|
||||||
| 4. 处理流程数据
|
|
||||||
| - 保存流程定义基本信息到 workflow_definition
|
|
||||||
| - 转换图形数据为BPMN XML
|
|
||||||
| - 解析节点配置,更新workflow_node_definition
|
|
||||||
| * 对于新的节点类型:创建新记录
|
|
||||||
| * 对于已有节点:更新配置
|
|
||||||
|
|
|
||||||
| 5. 部署到Flowable
|
|
||||||
↓
|
|
||||||
[Flowable引擎]
|
|
||||||
|
|
|
||||||
| 6. 部署流程
|
|
||||||
| - 保存BPMN XML
|
|
||||||
| - 生成流程定义ID
|
|
||||||
|
|
|
||||||
| 7. 启动流程实例
|
|
||||||
↓
|
|
||||||
[后端 WorkflowInstanceService]
|
|
||||||
|
|
|
||||||
| 8. 创建流程实例
|
|
||||||
| - 创建workflow_instance记录
|
|
||||||
| - 关联workflow_definition
|
|
||||||
|
|
|
||||||
| 9. 节点实例化
|
|
||||||
| - 创建workflow_node_instance记录
|
|
||||||
| - 关联workflow_node_definition
|
|
||||||
↓
|
|
||||||
[数据库]
|
|
||||||
|
|
|
||||||
| 实时同步的表:
|
|
||||||
| - workflow_definition(流程定义)
|
|
||||||
| - workflow_node_definition(节点定义)
|
|
||||||
| - workflow_instance(流程实例)
|
|
||||||
| - workflow_node_instance(节点实例)
|
|
||||||
|
|
|
||||||
| Flowable表:
|
|
||||||
| - ACT_RE_*(流程定义相关表)
|
|
||||||
| - ACT_RU_*(运行时数据表)
|
|
||||||
| - ACT_HI_*(历史数据表)
|
|
||||||
↓
|
|
||||||
[前端任务列表/监控页面]
|
|
||||||
|
|
|
||||||
| 10. 展示流程实例
|
|
||||||
| - 查询实例状态
|
|
||||||
| - 显示节点执行情况
|
|
||||||
| - 处理用户任务
|
|
||||||
| - 查看历史记录
|
|
||||||
```
|
|
||||||
|
|
||||||
## Flowable与系统映射关系
|
|
||||||
|
|
||||||
### 表结构映射
|
|
||||||
|
|
||||||
#### Flowable核心表
|
|
||||||
```
|
|
||||||
[流程定义相关]
|
|
||||||
ACT_RE_DEPLOYMENT: 流程部署表
|
|
||||||
ACT_RE_PROCDEF: 流程定义表
|
|
||||||
|
|
||||||
[流程实例相关]
|
|
||||||
ACT_RU_EXECUTION: 运行时流程实例表
|
|
||||||
ACT_RU_TASK: 运行时任务表
|
|
||||||
ACT_RU_VARIABLE: 运行时变量表
|
|
||||||
|
|
||||||
[历史数据]
|
|
||||||
ACT_HI_PROCINST: 历史流程实例表
|
|
||||||
ACT_HI_TASKINST: 历史任务实例表
|
|
||||||
ACT_HI_ACTINST: 历史活动实例表
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 系统表与Flowable映射关系
|
|
||||||
```
|
|
||||||
我们的表 Flowable的表
|
|
||||||
--------------------------------------------------
|
|
||||||
workflow_definition <-> ACT_RE_PROCDEF
|
|
||||||
workflow_instance <-> ACT_RU_EXECUTION/ACT_HI_PROCINST
|
|
||||||
workflow_node_instance <-> ACT_RU_TASK/ACT_HI_TASKINST
|
|
||||||
```
|
|
||||||
|
|
||||||
## 数据同步实现
|
|
||||||
|
|
||||||
### 流程实例服务实现
|
|
||||||
```java
|
|
||||||
@Service
|
|
||||||
@Transactional
|
|
||||||
public class WorkflowInstanceService {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private RuntimeService runtimeService; // Flowable的运行时服务
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private TaskService taskService; // Flowable的任务服务
|
|
||||||
|
|
||||||
public WorkflowInstance startProcess(WorkflowInstanceCreateDTO createDTO) {
|
|
||||||
// 1. 启动Flowable流程实例
|
|
||||||
ProcessInstance processInstance = runtimeService.startProcessInstanceById(
|
|
||||||
createDTO.getProcessDefinitionId(),
|
|
||||||
createDTO.getBusinessKey(),
|
|
||||||
createDTO.getVariables()
|
|
||||||
);
|
|
||||||
|
|
||||||
// 2. 创建我们自己的流程实例记录
|
|
||||||
WorkflowInstance workflowInstance = new WorkflowInstance();
|
|
||||||
workflowInstance.setProcessInstanceId(processInstance.getId());
|
|
||||||
workflowInstance.setProcessDefinitionId(createDTO.getProcessDefinitionId());
|
|
||||||
workflowInstance.setBusinessKey(createDTO.getBusinessKey());
|
|
||||||
workflowInstance.setStatus(WorkflowInstanceStatusEnums.RUNNING);
|
|
||||||
workflowInstance.setVariables(JsonUtils.toJsonString(createDTO.getVariables()));
|
|
||||||
workflowInstance.setStartTime(LocalDateTime.now());
|
|
||||||
workflowInstanceRepository.save(workflowInstance);
|
|
||||||
|
|
||||||
// 3. 获取当前活动的任务
|
|
||||||
List<Task> tasks = taskService.createTaskQuery()
|
|
||||||
.processInstanceId(processInstance.getId())
|
|
||||||
.list();
|
|
||||||
|
|
||||||
// 4. 为每个活动的任务创建节点实例
|
|
||||||
for (Task task : tasks) {
|
|
||||||
WorkflowNodeInstance nodeInstance = new WorkflowNodeInstance();
|
|
||||||
nodeInstance.setWorkflowInstanceId(workflowInstance.getId());
|
|
||||||
nodeInstance.setNodeId(task.getTaskDefinitionKey());
|
|
||||||
nodeInstance.setNodeName(task.getName());
|
|
||||||
nodeInstance.setNodeType("userTask"); // 或者从定义中获取
|
|
||||||
nodeInstance.setStatus("ACTIVE");
|
|
||||||
nodeInstance.setStartTime(LocalDateTime.now());
|
|
||||||
workflowNodeInstanceRepository.save(nodeInstance);
|
|
||||||
}
|
|
||||||
|
|
||||||
return workflowInstance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 数据同步服务实现
|
|
||||||
```java
|
|
||||||
@Service
|
|
||||||
public class WorkflowSyncService {
|
|
||||||
|
|
||||||
@Scheduled(fixedRate = 60000) // 每分钟执行一次
|
|
||||||
public void syncWorkflowStatus() {
|
|
||||||
// 1. 查找所有运行中的实例
|
|
||||||
List<WorkflowInstance> runningInstances = workflowInstanceRepository
|
|
||||||
.findByStatus(WorkflowInstanceStatusEnums.RUNNING);
|
|
||||||
|
|
||||||
for (WorkflowInstance instance : runningInstances) {
|
|
||||||
// 2. 检查Flowable中的状态
|
|
||||||
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
|
|
||||||
.processInstanceId(instance.getProcessInstanceId())
|
|
||||||
.singleResult();
|
|
||||||
|
|
||||||
if (processInstance == null) {
|
|
||||||
// Flowable中实例已结束,更新我们的状态
|
|
||||||
HistoricProcessInstance historicInstance = historyService
|
|
||||||
.createHistoricProcessInstanceQuery()
|
|
||||||
.processInstanceId(instance.getProcessInstanceId())
|
|
||||||
.singleResult();
|
|
||||||
|
|
||||||
instance.setStatus(convertFlowableStatus(historicInstance.getEndActivityId()));
|
|
||||||
instance.setEndTime(LocalDateTime.now());
|
|
||||||
workflowInstanceRepository.save(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 同步节点实例状态
|
|
||||||
syncNodeInstances(instance.getId(), instance.getProcessInstanceId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void syncNodeInstances(Long workflowInstanceId, String processInstanceId) {
|
|
||||||
// 1. 获取当前活动的任务
|
|
||||||
List<Task> activeTasks = taskService.createTaskQuery()
|
|
||||||
.processInstanceId(processInstanceId)
|
|
||||||
.list();
|
|
||||||
|
|
||||||
// 2. 获取历史任务
|
|
||||||
List<HistoricTaskInstance> historicTasks = historyService
|
|
||||||
.createHistoricTaskInstanceQuery()
|
|
||||||
.processInstanceId(processInstanceId)
|
|
||||||
.finished()
|
|
||||||
.list();
|
|
||||||
|
|
||||||
// 3. 更新节点实例状态
|
|
||||||
Set<String> processedTaskIds = new HashSet<>();
|
|
||||||
|
|
||||||
// 处理活动任务
|
|
||||||
for (Task task : activeTasks) {
|
|
||||||
WorkflowNodeInstance nodeInstance = getOrCreateNodeInstance(
|
|
||||||
workflowInstanceId, task.getTaskDefinitionKey());
|
|
||||||
updateNodeInstance(nodeInstance, task, null);
|
|
||||||
processedTaskIds.add(task.getTaskDefinitionKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理历史任务
|
|
||||||
for (HistoricTaskInstance historicTask : historicTasks) {
|
|
||||||
if (!processedTaskIds.contains(historicTask.getTaskDefinitionKey())) {
|
|
||||||
WorkflowNodeInstance nodeInstance = getOrCreateNodeInstance(
|
|
||||||
workflowInstanceId, historicTask.getTaskDefinitionKey());
|
|
||||||
updateNodeInstance(nodeInstance, null, historicTask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 事件监听机制
|
|
||||||
|
|
||||||
### Flowable事件监听器
|
|
||||||
```java
|
|
||||||
@Component
|
|
||||||
public class FlowableEventListener implements TaskListener, ExecutionListener {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private WorkflowInstanceService workflowInstanceService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void notify(DelegateTask task) {
|
|
||||||
// 任务事件处理
|
|
||||||
String eventName = task.getEventName();
|
|
||||||
switch (eventName) {
|
|
||||||
case "create":
|
|
||||||
workflowInstanceService.onTaskCreate(task);
|
|
||||||
break;
|
|
||||||
case "complete":
|
|
||||||
workflowInstanceService.onTaskComplete(task);
|
|
||||||
break;
|
|
||||||
case "delete":
|
|
||||||
workflowInstanceService.onTaskDelete(task);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void notify(DelegateExecution execution) {
|
|
||||||
// 流程执行事件处理
|
|
||||||
String eventName = execution.getEventName();
|
|
||||||
switch (eventName) {
|
|
||||||
case "start":
|
|
||||||
workflowInstanceService.onProcessStart(execution);
|
|
||||||
break;
|
|
||||||
case "end":
|
|
||||||
workflowInstanceService.onProcessEnd(execution);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 状态管理
|
|
||||||
|
|
||||||
### 工作流实例状态枚举
|
|
||||||
```java
|
|
||||||
public enum WorkflowInstanceStatusEnums {
|
|
||||||
NOT_STARTED, // 未开始
|
|
||||||
RUNNING, // 运行中
|
|
||||||
SUSPENDED, // 已暂停
|
|
||||||
COMPLETED, // 已完成
|
|
||||||
TERMINATED, // 已终止
|
|
||||||
FAILED; // 执行失败
|
|
||||||
|
|
||||||
public static WorkflowInstanceStatusEnums fromFlowableStatus(String flowableStatus) {
|
|
||||||
switch (flowableStatus) {
|
|
||||||
case "active":
|
|
||||||
return RUNNING;
|
|
||||||
case "suspended":
|
|
||||||
return SUSPENDED;
|
|
||||||
case "completed":
|
|
||||||
return COMPLETED;
|
|
||||||
case "terminated":
|
|
||||||
return TERMINATED;
|
|
||||||
default:
|
|
||||||
return FAILED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
### 为什么需要自己维护实例表
|
|
||||||
|
|
||||||
1. **业务扩展性**
|
|
||||||
- 可以添加更多业务相关的字段
|
|
||||||
- 可以实现自定义的状态管理
|
|
||||||
- 可以关联更多业务数据
|
|
||||||
|
|
||||||
2. **性能优化**
|
|
||||||
- 避免频繁查询Flowable表
|
|
||||||
- 可以建立更适合业务查询的索引
|
|
||||||
- 可以实现更好的缓存策略
|
|
||||||
|
|
||||||
3. **数据完整性**
|
|
||||||
- 保存完整的业务上下文
|
|
||||||
- 记录更详细的审计信息
|
|
||||||
- 支持自定义的数据分析
|
|
||||||
|
|
||||||
### 数据一致性保证
|
|
||||||
|
|
||||||
1. **事务管理**
|
|
||||||
```java
|
|
||||||
@Transactional
|
|
||||||
public void startProcess() {
|
|
||||||
// 1. 启动Flowable流程
|
|
||||||
// 2. 创建系统流程实例
|
|
||||||
// 3. 创建节点实例
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **定时同步**
|
|
||||||
- 定期检查运行中的实例状态
|
|
||||||
- 自动修复不一致的数据
|
|
||||||
- 记录同步日志
|
|
||||||
|
|
||||||
3. **事件驱动**
|
|
||||||
- 监听Flowable事件
|
|
||||||
- 实时更新系统状态
|
|
||||||
- 保证数据实时性
|
|
||||||
|
|
||||||
### 性能优化建议
|
|
||||||
|
|
||||||
1. **索引优化**
|
|
||||||
- 为常用查询字段建立索引
|
|
||||||
- 使用复合索引优化多字段查询
|
|
||||||
- 避免过多索引影响写入性能
|
|
||||||
|
|
||||||
2. **缓存策略**
|
|
||||||
- 缓存活动的流程实例
|
|
||||||
- 缓存常用的节点定义
|
|
||||||
- 使用分布式缓存提高性能
|
|
||||||
|
|
||||||
3. **批量处理**
|
|
||||||
- 批量同步数据
|
|
||||||
- 批量更新状态
|
|
||||||
- 使用队列处理异步任务
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
|
|
||||||
1. Flowable负责流程的实际执行和调度
|
|
||||||
2. 系统维护业务层面的状态和数据
|
|
||||||
3. 通过事件监听和定时同步保证数据一致性
|
|
||||||
4. 使用状态映射处理不同系统间的状态转换
|
|
||||||
5. 通过事务确保关键操作的原子性
|
|
||||||
|
|
||||||
这样的设计可以:
|
|
||||||
- 保持与Flowable的数据同步
|
|
||||||
- 支持业务扩展
|
|
||||||
- 提供更好的性能
|
|
||||||
- 确保数据一致性
|
|
||||||
- 便于问题追踪和修复
|
|
||||||
@ -1,322 +0,0 @@
|
|||||||
# 工作流动态表单设计方案
|
|
||||||
|
|
||||||
## 1. 整体架构
|
|
||||||
|
|
||||||
### 1.1 数据流向
|
|
||||||
```
|
|
||||||
1. 流程设计阶段
|
|
||||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
||||||
│ 表单设计器 │ │ 流程设计器 │ │ 后端存储 │
|
|
||||||
│ Form Designer │ ──> │ Flow Designer │ ──> │ Database │
|
|
||||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
||||||
│ │ │
|
|
||||||
│ 设计表单 │ 配置节点表单 │ 保存定义
|
|
||||||
│ JSON Schema │ 配置数据映射 │ - 表单定义
|
|
||||||
│ UI Schema │ 配置权限 │ - 流程定义
|
|
||||||
▼ ▼ ▼
|
|
||||||
|
|
||||||
2. 流程运行阶段
|
|
||||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
||||||
│ 流程发起 │ │ 节点激活 │ │ 表单实例化 │
|
|
||||||
│ Start Flow │ ──> │ Node Activated │ ──> │ Form Instance │
|
|
||||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
||||||
│ │
|
|
||||||
│ 触发监听器 │ 创建表单实例
|
|
||||||
│ │ 初始化数据
|
|
||||||
▼ ▼
|
|
||||||
|
|
||||||
3. 表单处理阶段
|
|
||||||
┌─────────────<EFBFBD><EFBFBD>───┐ ┌─────────────────┐ ┌─────────────────┐
|
|
||||||
│ 表单渲染 │ │ 表单交互 │ │ 数据处理 │
|
|
||||||
│ Form Render │ ──> │ Form Interact │ ──> │ Data Process │
|
|
||||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
||||||
│ │ │
|
|
||||||
│ 加载表单定义 │ 用户输入 │ 数据验证
|
|
||||||
│ 加载实例数据 │ 数据提交 │ 数据转换
|
|
||||||
│ 应用权限 │ │ 变量映射
|
|
||||||
▼ ▼ ▼
|
|
||||||
|
|
||||||
4. 节点完成阶段
|
|
||||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
||||||
│ 表单提交 │ │ 节点完成 │ │ 流程推进 │
|
|
||||||
│ Form Submit │ ──> │ Node Complete │ ──> │ Flow Progress │
|
|
||||||
└─────────────────┘ └─────────────<E29480><E29480><EFBFBD>───┘ └─────────────────┘
|
|
||||||
│ │
|
|
||||||
│ 触发监听器 │ 流转到下一节点
|
|
||||||
│ 更新流程变量 │
|
|
||||||
▼ ▼
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.2 数据模型设计
|
|
||||||
|
|
||||||
1. **表单定义(FormDefinition)**
|
|
||||||
```java
|
|
||||||
@Data
|
|
||||||
@Table(name = "workflow_form_definition")
|
|
||||||
@Entity
|
|
||||||
@LogicDelete
|
|
||||||
public class FormDefinition extends Entity<Long> {
|
|
||||||
@Column(nullable = false)
|
|
||||||
private String name; // 表单名称
|
|
||||||
|
|
||||||
@Column(nullable = false, unique = true)
|
|
||||||
private String code; // 表单编码
|
|
||||||
|
|
||||||
@Column(columnDefinition = "TEXT")
|
|
||||||
private String description; // 表单描述
|
|
||||||
|
|
||||||
@Type(JsonType.class)
|
|
||||||
@Column(columnDefinition = "json", nullable = false)
|
|
||||||
private JsonNode schema; // 表单JSON Schema
|
|
||||||
|
|
||||||
@Type(JsonType.class)
|
|
||||||
@Column(columnDefinition = "json")
|
|
||||||
private JsonNode uiSchema; // UI渲染Schema
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private Boolean enabled = true; // 是否启用
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private Integer version = 1; // 版本号
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **表单实例(FormInstance)**
|
|
||||||
```java
|
|
||||||
@Data
|
|
||||||
@Table(name = "workflow_form_instance")
|
|
||||||
@Entity
|
|
||||||
@LogicDelete
|
|
||||||
public class FormInstance extends Entity<Long> {
|
|
||||||
@Column(nullable = false)
|
|
||||||
private Long formDefinitionId; // 表单定义ID
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private String processInstanceId; // 流程实例ID
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private String taskId; // 任务ID
|
|
||||||
|
|
||||||
@Type(JsonType.class)
|
|
||||||
@Column(columnDefinition = "json", nullable = false)
|
|
||||||
private JsonNode formData; // 表单数据
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
private FormInstanceStatus status; // 状态
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. 节点表单集成
|
|
||||||
|
|
||||||
### 2.1 工作流上下文设计
|
|
||||||
```java
|
|
||||||
@Data
|
|
||||||
public class WorkflowContext {
|
|
||||||
// 系统上下文
|
|
||||||
private SystemContext systemContext;
|
|
||||||
// 表单上下文
|
|
||||||
private FormContext formContext;
|
|
||||||
// 流程变量上下文
|
|
||||||
private ProcessContext processContext;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public static class SystemContext {
|
|
||||||
private JsonNode systemConfig; // 系统配置
|
|
||||||
private String executionId; // 执行ID
|
|
||||||
private String processInstanceId; // 流程实例ID
|
|
||||||
private String nodeId; // 节点ID
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public static class FormContext {
|
|
||||||
private Long formInstanceId; // 表单实例ID
|
|
||||||
private JsonNode formData; // 表单数据
|
|
||||||
private JsonNode formConfig; // 表单配置
|
|
||||||
private Map<String, Object> mappedData; // 映射后的数据
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public static class ProcessContext {
|
|
||||||
private Map<String, Object> variables; // 流程变量
|
|
||||||
private Map<String, Object> localVariables; // 节点本地变量
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 节点定义中的表单配置
|
|
||||||
```java
|
|
||||||
@Data
|
|
||||||
@Table(name = "workflow_node_definition")
|
|
||||||
@Entity
|
|
||||||
@LogicDelete
|
|
||||||
public class WorkflowNodeDefinition extends Entity<Long> {
|
|
||||||
// ... 现有字段 ...
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 表单配置
|
|
||||||
*/
|
|
||||||
@Type(JsonType.class)
|
|
||||||
@Column(columnDefinition = "json")
|
|
||||||
private JsonNode formConfig; // 节点默认的表单配置
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 表单Schema
|
|
||||||
*/
|
|
||||||
@Type(JsonType.class)
|
|
||||||
@Column(columnDefinition = "json")
|
|
||||||
private JsonNode formSchema; // 节点默认的表单结构
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 表单UI Schema
|
|
||||||
*/
|
|
||||||
@Type(JsonType.class)
|
|
||||||
@Column(columnDefinition = "json")
|
|
||||||
private JsonNode formUiSchema; // 节点默认的UI渲染配置
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. 动态接口支持
|
|
||||||
|
|
||||||
### 3.1 表单配置中的接口定义
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"formSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"department": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "部门",
|
|
||||||
"enum": "@api:getDepartments",
|
|
||||||
"enumNames": "@api:getDepartmentNames"
|
|
||||||
},
|
|
||||||
"employee": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "员工",
|
|
||||||
"enum": "@api:getEmployeesByDepartment(department)",
|
|
||||||
"enumNames": "@api:getEmployeeNamesByDepartment(department)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 动态接口注册
|
|
||||||
```java
|
|
||||||
@Service
|
|
||||||
public class DynamicApiRegistry {
|
|
||||||
private final Map<String, DynamicApi> apiRegistry = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void init() {
|
|
||||||
// 注册系统内置API
|
|
||||||
registerSystemApis();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册API
|
|
||||||
public void register(String apiKey, DynamicApi api) {
|
|
||||||
apiRegistry.put(apiKey, api);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取API
|
|
||||||
public DynamicApi getApi(String apiKey) {
|
|
||||||
return apiRegistry.get(apiKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.3 动态API定义
|
|
||||||
```java
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
public class DynamicApi {
|
|
||||||
private String url; // API地址
|
|
||||||
private HttpMethod method; // 请求方法
|
|
||||||
private Function<Map<String, Object>, Map<String, Object>> paramsBuilder; // 参数构建器
|
|
||||||
private String transform; // 数据转换路径
|
|
||||||
private Map<String, String> mapping; // 数据映射
|
|
||||||
private List<String> dependencies; // 依赖字段
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. 使用示例
|
|
||||||
|
|
||||||
### 4.1 定义节点表单
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"systemConfig": {
|
|
||||||
"timeout": 3600,
|
|
||||||
"retryTimes": 3,
|
|
||||||
"notifyEmail": "admin@example.com"
|
|
||||||
},
|
|
||||||
"formConfig": {
|
|
||||||
"formSchema": { /* 表单结构 */ },
|
|
||||||
"formUiSchema": { /* UI渲染配置 */ },
|
|
||||||
"permissions": { /* 权限配置 */ },
|
|
||||||
"dataMapping": {
|
|
||||||
"input": { /* 输入映射 */ },
|
|
||||||
"output": { /* 输出映射 */ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.2 注册动态接口
|
|
||||||
```java
|
|
||||||
@Component
|
|
||||||
public class CustomApiConfig {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private DynamicApiRegistry apiRegistry;
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void init() {
|
|
||||||
// 注册项目查询API
|
|
||||||
apiRegistry.register("getProjects", new DynamicApi.builder()
|
|
||||||
.url("/api/v1/projects")
|
|
||||||
.method(HttpMethod.GET)
|
|
||||||
.paramsBuilder(context -> Map.of(
|
|
||||||
"departmentId", context.get("department"),
|
|
||||||
"employeeId", context.get("employee")
|
|
||||||
))
|
|
||||||
.transform("data.list")
|
|
||||||
.mapping(Map.of("value", "id", "label", "name"))
|
|
||||||
.dependencies(List.of("department", "employee"))
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.3 实现委派类
|
|
||||||
```java
|
|
||||||
@Component
|
|
||||||
@Slf4j
|
|
||||||
public class ApprovalTaskDelegate extends BaseWorkflowDelegate {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doExecute(WorkflowContext context) throws Exception {
|
|
||||||
// 1. 获取表单数据
|
|
||||||
FormContext formContext = context.getFormContext();
|
|
||||||
Map<String, Object> mappedData = formContext.getMappedData();
|
|
||||||
|
|
||||||
String approvalResult = (String) mappedData.get("approvalResult");
|
|
||||||
String comments = (String) mappedData.get("comments");
|
|
||||||
|
|
||||||
// 2. 获取系统配置
|
|
||||||
SystemContext systemContext = context.getSystemContext();
|
|
||||||
JsonNode systemConfig = systemContext.getSystemConfig();
|
|
||||||
String notifyEmail = systemConfig.get("notifyEmail").asText();
|
|
||||||
|
|
||||||
// 3. 处理审批结果
|
|
||||||
ProcessContext processContext = context.getProcessContext();
|
|
||||||
processContext.getVariables().put("approved", "APPROVED".equals(approvalResult));
|
|
||||||
processContext.getVariables().put("approvalComments", comments);
|
|
||||||
|
|
||||||
// 4. 发送通知
|
|
||||||
if (notifyEmail != null) {
|
|
||||||
notificationService.sendApprovalNotification(notifyEmail, approvalResult, comments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.deploy.api;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.deploy.dto.TeamConfigDTO;
|
|
||||||
import com.qqchen.deploy.backend.deploy.entity.TeamConfig;
|
|
||||||
import com.qqchen.deploy.backend.deploy.query.TeamConfigQuery;
|
|
||||||
import com.qqchen.deploy.backend.deploy.service.ITeamConfigService;
|
|
||||||
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.GetMapping;
|
|
||||||
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 java.util.List;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 团队配置API控制器
|
|
||||||
*
|
|
||||||
* @author qqchen
|
|
||||||
* @since 2025-10-31
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/v1/team-configs")
|
|
||||||
@Tag(name = "团队配置管理", description = "团队配置的增删改查接口")
|
|
||||||
public class TeamConfigApiController extends BaseController<TeamConfig, TeamConfigDTO, Long, TeamConfigQuery> {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private ITeamConfigService teamConfigService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response<TeamConfigDTO> create(@Validated @RequestBody TeamConfigDTO dto) {
|
|
||||||
return super.create(dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response<TeamConfigDTO> update(@PathVariable Long id, @Validated @RequestBody TeamConfigDTO dto) {
|
|
||||||
return super.update(id, dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response<Void> delete(@PathVariable Long id) {
|
|
||||||
return super.delete(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response<TeamConfigDTO> findById(@PathVariable Long id) {
|
|
||||||
return super.findById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response<List<TeamConfigDTO>> findAll() {
|
|
||||||
return super.findAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response<Page<TeamConfigDTO>> page(TeamConfigQuery query) {
|
|
||||||
return super.page(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response<List<TeamConfigDTO>> findAll(TeamConfigQuery query) {
|
|
||||||
return super.findAll(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<Response<Void>> batchProcess(List<TeamConfigDTO> dtos) {
|
|
||||||
return super.batchProcess(dtos);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "根据团队ID获取配置", description = "获取指定团队的配置信息")
|
|
||||||
@GetMapping("/team/{teamId}")
|
|
||||||
public Response<TeamConfigDTO> getByTeamId(
|
|
||||||
@Parameter(description = "团队ID", required = true) @PathVariable Long teamId
|
|
||||||
) {
|
|
||||||
return Response.success(teamConfigService.getByTeamId(teamId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void exportData(HttpServletResponse response, List<TeamConfigDTO> data) {
|
|
||||||
// TODO: 实现导出功能
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.api;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.dto.TeamEnvironmentConfigDTO;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.TeamEnvironmentConfig;
|
||||||
|
import com.qqchen.deploy.backend.deploy.query.TeamEnvironmentConfigQuery;
|
||||||
|
import com.qqchen.deploy.backend.deploy.service.ITeamEnvironmentConfigService;
|
||||||
|
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 lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 团队环境配置Controller
|
||||||
|
*
|
||||||
|
* @author qqchen
|
||||||
|
* @since 2025-11-04
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/team-environment-config")
|
||||||
|
@Tag(name = "团队环境配置管理", description = "团队环境配置相关接口")
|
||||||
|
public class TeamEnvironmentConfigApiController extends BaseController<TeamEnvironmentConfig, TeamEnvironmentConfigDTO, Long, TeamEnvironmentConfigQuery> {
|
||||||
|
|
||||||
|
private final ITeamEnvironmentConfigService teamEnvironmentConfigService;
|
||||||
|
|
||||||
|
public TeamEnvironmentConfigApiController(ITeamEnvironmentConfigService service) {
|
||||||
|
this.teamEnvironmentConfigService = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "根据团队ID查询环境配置列表")
|
||||||
|
@GetMapping("/team/{teamId}")
|
||||||
|
public Response<List<TeamEnvironmentConfigDTO>> getByTeamId(
|
||||||
|
@Parameter(description = "团队ID", required = true) @PathVariable Long teamId
|
||||||
|
) {
|
||||||
|
return Response.success(teamEnvironmentConfigService.findByTeamId(teamId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "根据团队ID和环境ID查询配置")
|
||||||
|
@GetMapping("/team/{teamId}/environment/{environmentId}")
|
||||||
|
public Response<TeamEnvironmentConfigDTO> getByTeamIdAndEnvironmentId(
|
||||||
|
@Parameter(description = "团队ID", required = true) @PathVariable Long teamId,
|
||||||
|
@Parameter(description = "环境ID", required = true) @PathVariable Long environmentId
|
||||||
|
) {
|
||||||
|
return Response.success(teamEnvironmentConfigService.findByTeamIdAndEnvironmentId(teamId, environmentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "检查配置是否存在")
|
||||||
|
@GetMapping("/exists")
|
||||||
|
public Response<Boolean> exists(
|
||||||
|
@Parameter(description = "团队ID", required = true) @RequestParam Long teamId,
|
||||||
|
@Parameter(description = "环境ID", required = true) @RequestParam Long environmentId
|
||||||
|
) {
|
||||||
|
return Response.success(teamEnvironmentConfigService.exists(teamId, environmentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void exportData(jakarta.servlet.http.HttpServletResponse response, List<TeamEnvironmentConfigDTO> data) {
|
||||||
|
// TODO: 实现导出功能
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.deploy.converter;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.deploy.dto.TeamConfigDTO;
|
|
||||||
import com.qqchen.deploy.backend.deploy.entity.TeamConfig;
|
|
||||||
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
|
||||||
import org.mapstruct.Mapper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 团队配置转换器
|
|
||||||
*
|
|
||||||
* @author qqchen
|
|
||||||
* @since 2025-10-31
|
|
||||||
*/
|
|
||||||
@Mapper(config = BaseConverter.class)
|
|
||||||
public interface TeamConfigConverter extends BaseConverter<TeamConfig, TeamConfigDTO> {
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.converter;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.dto.TeamEnvironmentConfigDTO;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.TeamEnvironmentConfig;
|
||||||
|
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 团队环境配置转换器
|
||||||
|
*
|
||||||
|
* @author qqchen
|
||||||
|
* @since 2025-11-04
|
||||||
|
*/
|
||||||
|
@Mapper(config = BaseConverter.class)
|
||||||
|
public interface TeamEnvironmentConfigConverter extends BaseConverter<TeamEnvironmentConfig, TeamEnvironmentConfigDTO> {
|
||||||
|
}
|
||||||
|
|
||||||
@ -39,6 +39,12 @@ public class DeployableEnvironmentDTO {
|
|||||||
@Schema(description = "审批人列表")
|
@Schema(description = "审批人列表")
|
||||||
private List<ApproverDTO> approvers;
|
private List<ApproverDTO> approvers;
|
||||||
|
|
||||||
|
@Schema(description = "是否要求代码审查")
|
||||||
|
private Boolean requireCodeReview;
|
||||||
|
|
||||||
|
@Schema(description = "备注信息")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
@Schema(description = "可部署应用列表")
|
@Schema(description = "可部署应用列表")
|
||||||
private List<DeployableApplicationDTO> applications;
|
private List<DeployableApplicationDTO> applications;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,35 +0,0 @@
|
|||||||
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.NotNull;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 团队配置DTO
|
|
||||||
*
|
|
||||||
* @author qqchen
|
|
||||||
* @since 2025-10-31
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
@Schema(description = "团队配置信息")
|
|
||||||
public class TeamConfigDTO extends BaseDTO {
|
|
||||||
|
|
||||||
@Schema(description = "团队ID", required = true)
|
|
||||||
@NotNull(message = "团队ID不能为空")
|
|
||||||
private Long teamId;
|
|
||||||
|
|
||||||
@Schema(description = "允许访问的环境ID列表", example = "[1, 2, 3]")
|
|
||||||
private List<Long> allowedEnvironmentIds;
|
|
||||||
|
|
||||||
@Schema(description = "环境是否需要审批(与allowedEnvironmentIds位置对应)", example = "[false, false, true]")
|
|
||||||
private List<Boolean> environmentApprovalRequired;
|
|
||||||
|
|
||||||
@Schema(description = "各环境的审批人列表(与allowedEnvironmentIds位置对应,无审批人用null)", example = "[null, null, [1, 4]]")
|
|
||||||
private List<Object> approverUserIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.dto;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 团队环境配置DTO
|
||||||
|
*
|
||||||
|
* @author qqchen
|
||||||
|
* @since 2025-11-04
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class TeamEnvironmentConfigDTO extends BaseDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 团队ID
|
||||||
|
*/
|
||||||
|
private Long teamId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 环境ID
|
||||||
|
*/
|
||||||
|
private Long environmentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否需要审批
|
||||||
|
*/
|
||||||
|
private Boolean approvalRequired;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批人用户ID列表
|
||||||
|
*/
|
||||||
|
private List<Long> approverUserIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知渠道ID
|
||||||
|
*/
|
||||||
|
private Long notificationChannelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用部署通知
|
||||||
|
*/
|
||||||
|
private Boolean notificationEnabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否要求代码审查通过
|
||||||
|
*/
|
||||||
|
private Boolean requireCodeReview;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注信息
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
// ===== 扩展字段(非数据库字段) =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 环境名称(扩展字段)
|
||||||
|
*/
|
||||||
|
private String environmentName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知渠道名称(扩展字段)
|
||||||
|
*/
|
||||||
|
private String notificationChannelName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 该团队在该环境下关联的应用数量(扩展字段)
|
||||||
|
*/
|
||||||
|
private Long applicationCount;
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.deploy.entity;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
|
|
||||||
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.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 团队配置实体
|
|
||||||
*
|
|
||||||
* @author qqchen
|
|
||||||
* @since 2025-10-31
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
@jakarta.persistence.Entity
|
|
||||||
@Table(name = "deploy_team_config")
|
|
||||||
@LogicDelete
|
|
||||||
public class TeamConfig extends Entity<Long> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 团队ID
|
|
||||||
*/
|
|
||||||
@Column(name = "team_id", nullable = false)
|
|
||||||
private Long teamId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 允许访问的环境ID列表
|
|
||||||
*/
|
|
||||||
@Type(JsonType.class)
|
|
||||||
@Column(name = "allowed_environment_ids", columnDefinition = "JSON")
|
|
||||||
private List<Long> allowedEnvironmentIds;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 环境是否需要审批(boolean数组,与allowedEnvironmentIds位置对应)
|
|
||||||
* 例如:allowedEnvironmentIds=[1,2,3], environmentApprovalRequired=[false,false,true]
|
|
||||||
* 表示环境1和2不需要审批,环境3需要审批
|
|
||||||
*/
|
|
||||||
@Type(JsonType.class)
|
|
||||||
@Column(name = "environment_approval_required", columnDefinition = "JSON")
|
|
||||||
private List<Boolean> environmentApprovalRequired;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 各环境的审批人列表(数组,与allowedEnvironmentIds位置对应)
|
|
||||||
* 元素可以是:null(无审批人)或审批人ID数组
|
|
||||||
* 例如:allowedEnvironmentIds=[1,2,3], approverUserIds=[null,null,[1,4]]
|
|
||||||
* 表示环境1和2无审批人,环境3的审批人是用户1和4
|
|
||||||
*/
|
|
||||||
@Type(JsonType.class)
|
|
||||||
@Column(name = "approver_user_ids", columnDefinition = "JSON")
|
|
||||||
private List<Object> approverUserIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -0,0 +1,104 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.entity;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
|
||||||
|
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.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 团队环境配置实体
|
||||||
|
*
|
||||||
|
* <p>定义团队对特定环境的配置:
|
||||||
|
* <ul>
|
||||||
|
* <li>审批配置:是否需要审批、审批人列表</li>
|
||||||
|
* <li>通知配置:通知渠道、是否启用</li>
|
||||||
|
* <li>安全策略:代码审查要求</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>数据迁移说明:
|
||||||
|
* 本表由 deploy_team_config 重构而来,将原来的 JSON 数组结构展开为标准表结构:
|
||||||
|
* <pre>
|
||||||
|
* 原结构:1条记录 = 1个团队的多个环境配置(JSON数组)
|
||||||
|
* 新结构:1条记录 = 1个团队在1个环境的配置
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author qqchen
|
||||||
|
* @since 2025-11-04
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@jakarta.persistence.Entity
|
||||||
|
@Table(name = "deploy_team_environment_config")
|
||||||
|
@LogicDelete(false)
|
||||||
|
public class TeamEnvironmentConfig extends Entity<Long> {
|
||||||
|
|
||||||
|
// ===== 关联关系 =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 团队ID
|
||||||
|
*/
|
||||||
|
@Column(name = "team_id", nullable = false)
|
||||||
|
private Long teamId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 环境ID
|
||||||
|
*/
|
||||||
|
@Column(name = "environment_id", nullable = false)
|
||||||
|
private Long environmentId;
|
||||||
|
|
||||||
|
// ===== 审批配置 =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否需要审批
|
||||||
|
* <p>对应原 TeamConfig.environmentApprovalRequired[i]
|
||||||
|
*/
|
||||||
|
@Column(name = "approval_required", nullable = false, columnDefinition = "BOOLEAN DEFAULT FALSE")
|
||||||
|
private Boolean approvalRequired = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批人用户ID列表
|
||||||
|
* <p>对应原 TeamConfig.approverUserIds[i]
|
||||||
|
* <p>示例:[1, 4, 7]
|
||||||
|
*/
|
||||||
|
@Type(JsonType.class)
|
||||||
|
@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;
|
||||||
|
|
||||||
|
// ===== 安全策略 =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否要求代码审查通过才能部署
|
||||||
|
*/
|
||||||
|
@Column(name = "require_code_review", nullable = false, columnDefinition = "BOOLEAN DEFAULT FALSE")
|
||||||
|
private Boolean requireCodeReview = false;
|
||||||
|
|
||||||
|
// ===== 备注 =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注信息
|
||||||
|
*/
|
||||||
|
@Column(name = "remark", length = 500)
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.deploy.query;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.annotation.QueryField;
|
|
||||||
import com.qqchen.deploy.backend.framework.query.BaseQuery;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 团队配置查询条件
|
|
||||||
*
|
|
||||||
* @author qqchen
|
|
||||||
* @since 2025-10-31
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
@Schema(description = "团队配置查询条件")
|
|
||||||
public class TeamConfigQuery extends BaseQuery {
|
|
||||||
|
|
||||||
@QueryField(field = "teamId")
|
|
||||||
@Schema(description = "团队ID")
|
|
||||||
private Long teamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.query;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.annotation.QueryField;
|
||||||
|
import com.qqchen.deploy.backend.framework.query.BaseQuery;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 团队环境配置查询条件
|
||||||
|
*
|
||||||
|
* @author qqchen
|
||||||
|
* @since 2025-11-04
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class TeamEnvironmentConfigQuery extends BaseQuery {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 团队ID
|
||||||
|
*/
|
||||||
|
@QueryField(field = "teamId")
|
||||||
|
private Long teamId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 环境ID
|
||||||
|
*/
|
||||||
|
@QueryField(field = "environmentId")
|
||||||
|
private Long environmentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否需要审批
|
||||||
|
*/
|
||||||
|
@QueryField(field = "approvalRequired")
|
||||||
|
private Boolean approvalRequired;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用通知
|
||||||
|
*/
|
||||||
|
@QueryField(field = "notificationEnabled")
|
||||||
|
private Boolean notificationEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
@ -110,5 +110,11 @@ public interface IDeployRecordRepository extends IBaseRepository<DeployRecord, L
|
|||||||
List<DeployRecord> findRecentDeployRecordsByTeamApplicationIds(
|
List<DeployRecord> findRecentDeployRecordsByTeamApplicationIds(
|
||||||
@Param("teamApplicationIds") List<Long> teamApplicationIds,
|
@Param("teamApplicationIds") List<Long> teamApplicationIds,
|
||||||
@Param("limit") int limit);
|
@Param("limit") int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据团队应用ID查询所有部署记录(包括已删除的)
|
||||||
|
* 用于级联删除
|
||||||
|
*/
|
||||||
|
List<DeployRecord> findByTeamApplicationId(Long teamApplicationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -89,5 +89,24 @@ public interface ITeamApplicationRepository extends IBaseRepository<TeamApplicat
|
|||||||
* @return 团队应用配置列表
|
* @return 团队应用配置列表
|
||||||
*/
|
*/
|
||||||
List<TeamApplication> findByTeamIdIn(Collection<Long> teamIds);
|
List<TeamApplication> findByTeamIdIn(Collection<Long> teamIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计指定团队在指定环境下的应用数量
|
||||||
|
*
|
||||||
|
* @param teamId 团队ID
|
||||||
|
* @param environmentId 环境ID
|
||||||
|
* @return 应用数量
|
||||||
|
*/
|
||||||
|
Long countByTeamIdAndEnvironmentId(Long teamId, Long environmentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计指定团队列表在指定环境下的不同应用数量(去重 application_id)
|
||||||
|
*
|
||||||
|
* @param teamIds 团队ID列表
|
||||||
|
* @param environmentId 环境ID
|
||||||
|
* @return 应用数量
|
||||||
|
*/
|
||||||
|
@Query("SELECT COUNT(DISTINCT ta.applicationId) FROM TeamApplication ta WHERE ta.teamId IN :teamIds AND ta.environmentId = :environmentId")
|
||||||
|
Long countDistinctApplicationIdByTeamIdsAndEnvironmentId(@Param("teamIds") Collection<Long> teamIds, @Param("environmentId") Long environmentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,44 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.deploy.repository;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.deploy.entity.TeamConfig;
|
|
||||||
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
|
|
||||||
import org.springframework.stereotype.Repository;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 团队配置Repository
|
|
||||||
*
|
|
||||||
* @author qqchen
|
|
||||||
* @since 2025-10-31
|
|
||||||
*/
|
|
||||||
@Repository
|
|
||||||
public interface ITeamConfigRepository extends IBaseRepository<TeamConfig, Long> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据团队ID查询配置
|
|
||||||
*
|
|
||||||
* @param teamId 团队ID
|
|
||||||
* @return 团队配置
|
|
||||||
*/
|
|
||||||
Optional<TeamConfig> findByTeamIdAndDeletedFalse(Long teamId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查团队配置是否存在
|
|
||||||
*
|
|
||||||
* @param teamId 团队ID
|
|
||||||
* @return 是否存在
|
|
||||||
*/
|
|
||||||
boolean existsByTeamIdAndDeletedFalse(Long teamId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量根据团队ID查询配置
|
|
||||||
*
|
|
||||||
* @param teamIds 团队ID集合
|
|
||||||
* @return 团队配置列表
|
|
||||||
*/
|
|
||||||
List<TeamConfig> findByTeamIdIn(Collection<Long> teamIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.repository;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.TeamEnvironmentConfig;
|
||||||
|
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 团队环境配置Repository
|
||||||
|
*
|
||||||
|
* @author qqchen
|
||||||
|
* @since 2025-11-04
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public interface ITeamEnvironmentConfigRepository extends IBaseRepository<TeamEnvironmentConfig, Long> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据团队ID查询所有环境配置
|
||||||
|
*/
|
||||||
|
List<TeamEnvironmentConfig> findByTeamId(Long teamId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据团队ID列表批量查询
|
||||||
|
*/
|
||||||
|
List<TeamEnvironmentConfig> findByTeamIdIn(List<Long> teamIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据团队ID和环境ID查询
|
||||||
|
*/
|
||||||
|
Optional<TeamEnvironmentConfig> findByTeamIdAndEnvironmentId(Long teamId, Long environmentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据环境ID查询所有团队配置
|
||||||
|
*/
|
||||||
|
List<TeamEnvironmentConfig> findByEnvironmentId(Long environmentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查团队环境配置是否存在
|
||||||
|
*/
|
||||||
|
boolean existsByTeamIdAndEnvironmentId(Long teamId, Long environmentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计配置了指定环境的团队数量
|
||||||
|
*/
|
||||||
|
Long countByEnvironmentId(Long environmentId);
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.deploy.service;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.deploy.dto.TeamConfigDTO;
|
|
||||||
import com.qqchen.deploy.backend.deploy.entity.TeamConfig;
|
|
||||||
import com.qqchen.deploy.backend.deploy.query.TeamConfigQuery;
|
|
||||||
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 团队配置Service接口
|
|
||||||
*
|
|
||||||
* @author qqchen
|
|
||||||
* @since 2025-10-31
|
|
||||||
*/
|
|
||||||
public interface ITeamConfigService extends IBaseService<TeamConfig, TeamConfigDTO, TeamConfigQuery, Long> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据团队ID获取配置
|
|
||||||
*
|
|
||||||
* @param teamId 团队ID
|
|
||||||
* @return 团队配置
|
|
||||||
*/
|
|
||||||
TeamConfigDTO getByTeamId(Long teamId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.service;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.dto.TeamEnvironmentConfigDTO;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.TeamEnvironmentConfig;
|
||||||
|
import com.qqchen.deploy.backend.deploy.query.TeamEnvironmentConfigQuery;
|
||||||
|
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 团队环境配置Service接口
|
||||||
|
*
|
||||||
|
* @author qqchen
|
||||||
|
* @since 2025-11-04
|
||||||
|
*/
|
||||||
|
public interface ITeamEnvironmentConfigService extends IBaseService<TeamEnvironmentConfig, TeamEnvironmentConfigDTO, TeamEnvironmentConfigQuery, Long> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据团队ID查询所有环境配置
|
||||||
|
*/
|
||||||
|
List<TeamEnvironmentConfigDTO> findByTeamId(Long teamId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据团队ID和环境ID查询配置
|
||||||
|
*/
|
||||||
|
TeamEnvironmentConfigDTO findByTeamIdAndEnvironmentId(Long teamId, Long environmentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查团队环境配置是否存在
|
||||||
|
*/
|
||||||
|
boolean exists(Long teamId, Long environmentId);
|
||||||
|
}
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
private ITeamRepository teamRepository;
|
private ITeamRepository teamRepository;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ITeamConfigRepository teamConfigRepository;
|
private ITeamEnvironmentConfigRepository teamEnvironmentConfigRepository;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private IEnvironmentRepository environmentRepository;
|
private IEnvironmentRepository environmentRepository;
|
||||||
@ -82,6 +82,24 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
@Resource
|
@Resource
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据组装上下文(封装所有需要的Map,避免方法参数过多)
|
||||||
|
*/
|
||||||
|
private static class BuildContext {
|
||||||
|
Map<Long, Team> teamMap;
|
||||||
|
Map<Long, TeamMember> teamMemberMap;
|
||||||
|
Map<String, TeamEnvironmentConfig> teamEnvConfigMap;
|
||||||
|
Map<Long, Environment> envMap;
|
||||||
|
Map<Long, List<TeamApplication>> teamAppsMap;
|
||||||
|
Map<Long, Application> appMap;
|
||||||
|
Map<Long, ExternalSystem> systemMap;
|
||||||
|
Map<Long, WorkflowDefinition> workflowMap;
|
||||||
|
Map<Long, User> approverMap;
|
||||||
|
Map<Long, DeployStatisticsDTO> statisticsMap;
|
||||||
|
Map<Long, DeployRecord> latestRecordMap;
|
||||||
|
Map<Long, List<DeployRecord>> recentRecordsMap;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public UserDeployableDTO getDeployableEnvironments() {
|
public UserDeployableDTO getDeployableEnvironments() {
|
||||||
@ -95,68 +113,119 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
// 3. 查询用户作为负责人的团队
|
// 3. 查询用户作为负责人的团队
|
||||||
List<Team> ownedTeams = teamRepository.findByOwnerIdAndDeletedFalse(currentUserId);
|
List<Team> ownedTeams = teamRepository.findByOwnerIdAndDeletedFalse(currentUserId);
|
||||||
|
|
||||||
// 4. 合并团队ID(去重)
|
// 4. 合并团队ID并构建团队Map(优化:直接复用ownedTeams数据)
|
||||||
Set<Long> teamIdSet = new HashSet<>();
|
Set<Long> teamIdSet = new HashSet<>();
|
||||||
teamIdSet.addAll(teamMembers.stream().map(TeamMember::getTeamId).toList());
|
Map<Long, Team> teamMap = new HashMap<>();
|
||||||
teamIdSet.addAll(ownedTeams.stream().map(Team::getId).toList());
|
|
||||||
|
// 4.1 从负责人团队中提取并直接放入teamMap(避免重复查询)
|
||||||
|
for (Team team : ownedTeams) {
|
||||||
|
teamIdSet.add(team.getId());
|
||||||
|
teamMap.put(team.getId(), team);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4.2 从成员关系中提取teamId
|
||||||
|
Set<Long> memberTeamIds = teamMembers.stream()
|
||||||
|
.map(TeamMember::getTeamId)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
teamIdSet.addAll(memberTeamIds);
|
||||||
|
|
||||||
if (teamIdSet.isEmpty()) {
|
if (teamIdSet.isEmpty()) {
|
||||||
log.info("用户 {} 未加入任何团队且不是任何团队的负责人", user.getUsername());
|
log.info("用户 {} 未加入任何团队且不是任何团队的负责人", user.getUsername());
|
||||||
return buildEmptyResult(user);
|
return buildEmptyResult(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4.3 只查询成员团队中不在teamMap里的团队(减少查询量)
|
||||||
|
Set<Long> needQueryTeamIds = memberTeamIds.stream()
|
||||||
|
.filter(id -> !teamMap.containsKey(id))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
if (!needQueryTeamIds.isEmpty()) {
|
||||||
|
teamRepository.findAllById(needQueryTeamIds).forEach(team ->
|
||||||
|
teamMap.put(team.getId(), team)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
List<Long> teamIds = new ArrayList<>(teamIdSet);
|
List<Long> teamIds = new ArrayList<>(teamIdSet);
|
||||||
|
|
||||||
// 3. 批量查询团队信息
|
// 5. 批量查询所有团队的应用配置
|
||||||
Map<Long, Team> teamMap = teamRepository.findAllById(teamIds).stream().collect(toMap(Team::getId, t -> t));
|
List<TeamApplication> allTeamApps = teamApplicationRepository.findByTeamIdIn(teamIds);
|
||||||
|
|
||||||
// 4. 批量查询团队配置
|
// 5.1 提前判断:如果没有应用配置,直接返回
|
||||||
Map<Long, TeamConfig> configMap = teamConfigRepository.findByTeamIdIn(teamIds).stream().collect(toMap(TeamConfig::getTeamId, c -> c));
|
if (allTeamApps.isEmpty()) {
|
||||||
|
log.info("用户 {} 所属团队未配置任何应用", user.getUsername());
|
||||||
// 5. 收集所有环境ID
|
return buildEmptyResult(user);
|
||||||
Set<Long> allEnvIds = configMap.values().stream().filter(c -> c.getAllowedEnvironmentIds() != null).flatMap(c -> c.getAllowedEnvironmentIds().stream()).collect(Collectors.toSet());
|
}
|
||||||
|
|
||||||
|
// 6. 一次遍历提取所有需要的ID集合(优化:减少4次额外遍历)
|
||||||
|
Set<Long> allEnvIds = new HashSet<>();
|
||||||
|
Set<Long> appIds = new HashSet<>();
|
||||||
|
Set<Long> systemIds = new HashSet<>();
|
||||||
|
Set<Long> workflowIds = new HashSet<>();
|
||||||
|
List<Long> teamApplicationIds = new ArrayList<>(allTeamApps.size());
|
||||||
|
|
||||||
|
for (TeamApplication ta : allTeamApps) {
|
||||||
|
teamApplicationIds.add(ta.getId());
|
||||||
|
allEnvIds.add(ta.getEnvironmentId());
|
||||||
|
appIds.add(ta.getApplicationId());
|
||||||
|
if (ta.getDeploySystemId() != null) {
|
||||||
|
systemIds.add(ta.getDeploySystemId());
|
||||||
|
}
|
||||||
|
if (ta.getWorkflowDefinitionId() != null) {
|
||||||
|
workflowIds.add(ta.getWorkflowDefinitionId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (allEnvIds.isEmpty()) {
|
if (allEnvIds.isEmpty()) {
|
||||||
log.info("用户 {} 所属团队未配置任何环境", user.getUsername());
|
log.info("用户 {} 所属团队未配置任何环境", user.getUsername());
|
||||||
return buildEmptyResult(user);
|
return buildEmptyResult(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 批量查询环境信息
|
// 7. 应用配置按团队分组
|
||||||
Map<Long, Environment> envMap = environmentRepository.findAllById(allEnvIds).stream().collect(toMap(Environment::getId, e -> e));
|
Map<Long, List<TeamApplication>> teamAppsMap = allTeamApps.stream()
|
||||||
|
.collect(groupingBy(TeamApplication::getTeamId));
|
||||||
|
|
||||||
// 7. 批量查询所有团队的应用配置
|
// 8. 批量查询环境信息
|
||||||
List<TeamApplication> allTeamApps = teamApplicationRepository.findByTeamIdIn(teamIds);
|
Map<Long, Environment> envMap = environmentRepository.findAllById(allEnvIds).stream()
|
||||||
Map<Long, List<TeamApplication>> teamAppsMap = allTeamApps.stream().collect(groupingBy(TeamApplication::getTeamId));
|
.collect(toMap(Environment::getId, e -> e));
|
||||||
|
|
||||||
// 8. 批量查询应用信息
|
// 9. 批量查询应用信息
|
||||||
Set<Long> appIds = allTeamApps.stream().map(TeamApplication::getApplicationId).collect(Collectors.toSet());
|
|
||||||
final Map<Long, Application> appMap;
|
final Map<Long, Application> appMap;
|
||||||
if (!appIds.isEmpty()) {
|
if (!appIds.isEmpty()) {
|
||||||
appMap = applicationRepository.findAllById(appIds).stream().collect(toMap(Application::getId, a -> a));
|
appMap = applicationRepository.findAllById(appIds).stream()
|
||||||
|
.collect(toMap(Application::getId, a -> a));
|
||||||
} else {
|
} else {
|
||||||
appMap = Collections.emptyMap();
|
appMap = Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 9. 批量查询部署系统
|
// 10. 批量查询部署系统
|
||||||
Set<Long> systemIds = allTeamApps.stream().map(TeamApplication::getDeploySystemId).filter(Objects::nonNull).collect(Collectors.toSet());
|
|
||||||
final Map<Long, ExternalSystem> systemMap;
|
final Map<Long, ExternalSystem> systemMap;
|
||||||
if (!systemIds.isEmpty()) {
|
if (!systemIds.isEmpty()) {
|
||||||
systemMap = externalSystemRepository.findAllById(systemIds).stream().collect(toMap(ExternalSystem::getId, s -> s));
|
systemMap = externalSystemRepository.findAllById(systemIds).stream()
|
||||||
|
.collect(toMap(ExternalSystem::getId, s -> s));
|
||||||
} else {
|
} else {
|
||||||
systemMap = Collections.emptyMap();
|
systemMap = Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10. 批量查询工作流定义
|
// 11. 批量查询工作流定义
|
||||||
Set<Long> workflowIds = allTeamApps.stream().map(TeamApplication::getWorkflowDefinitionId).filter(Objects::nonNull).collect(Collectors.toSet());
|
|
||||||
final Map<Long, WorkflowDefinition> workflowMap;
|
final Map<Long, WorkflowDefinition> workflowMap;
|
||||||
if (!workflowIds.isEmpty()) {
|
if (!workflowIds.isEmpty()) {
|
||||||
workflowMap = workflowDefinitionRepository.findAllById(workflowIds).stream().collect(toMap(WorkflowDefinition::getId, w -> w));
|
workflowMap = workflowDefinitionRepository.findAllById(workflowIds).stream()
|
||||||
|
.collect(toMap(WorkflowDefinition::getId, w -> w));
|
||||||
} else {
|
} else {
|
||||||
workflowMap = Collections.emptyMap();
|
workflowMap = Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 11. 批量查询审批人信息
|
// 11. 批量查询团队环境配置
|
||||||
Set<Long> approverUserIds = configMap.values().stream().filter(c -> c.getApproverUserIds() != null).flatMap(c -> c.getApproverUserIds().stream()).filter(obj -> obj instanceof List).flatMap(obj -> ((List<?>) obj).stream()).filter(id -> id instanceof Number).map(id -> ((Number) id).longValue()).collect(Collectors.toSet());
|
List<TeamEnvironmentConfig> teamEnvConfigs = teamEnvironmentConfigRepository.findByTeamIdIn(teamIds);
|
||||||
|
// 按 (teamId, environmentId) 组织配置: key = teamId + "_" + environmentId
|
||||||
|
Map<String, TeamEnvironmentConfig> teamEnvConfigMap = teamEnvConfigs.stream()
|
||||||
|
.collect(toMap(c -> c.getTeamId() + "_" + c.getEnvironmentId(), c -> c));
|
||||||
|
|
||||||
|
// 12. 批量查询审批人信息
|
||||||
|
Set<Long> approverUserIds = teamEnvConfigs.stream()
|
||||||
|
.filter(c -> c.getApproverUserIds() != null)
|
||||||
|
.flatMap(c -> c.getApproverUserIds().stream())
|
||||||
|
.collect(Collectors.toSet());
|
||||||
final Map<Long, User> approverMap;
|
final Map<Long, User> approverMap;
|
||||||
if (!approverUserIds.isEmpty()) {
|
if (!approverUserIds.isEmpty()) {
|
||||||
approverMap = userRepository.findAllById(approverUserIds).stream().collect(toMap(User::getId, u -> u));
|
approverMap = userRepository.findAllById(approverUserIds).stream().collect(toMap(User::getId, u -> u));
|
||||||
@ -164,9 +233,7 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
approverMap = Collections.emptyMap();
|
approverMap = Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 12. 批量查询部署记录信息
|
// 13. 批量查询部署记录信息(teamApplicationIds 已在第6步提取)
|
||||||
List<Long> teamApplicationIds = allTeamApps.stream().map(TeamApplication::getId).collect(toList());
|
|
||||||
|
|
||||||
// 12.1 批量查询部署统计信息
|
// 12.1 批量查询部署统计信息
|
||||||
final Map<Long, DeployStatisticsDTO> statisticsMap = new HashMap<>();
|
final Map<Long, DeployStatisticsDTO> statisticsMap = new HashMap<>();
|
||||||
final Map<Long, DeployRecord> latestRecordMap = new HashMap<>();
|
final Map<Long, DeployRecord> latestRecordMap = new HashMap<>();
|
||||||
@ -220,10 +287,33 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
recentRecordsMap.putAll(recentRecords.stream().collect(groupingBy(DeployRecord::getTeamApplicationId)));
|
recentRecordsMap.putAll(recentRecords.stream().collect(groupingBy(DeployRecord::getTeamApplicationId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 13. 组装团队数据
|
// 13. 组装团队数据(优化:处理潜在的Key冲突,保留第一个成员记录)
|
||||||
Map<Long, TeamMember> teamMemberMap = teamMembers.stream().collect(toMap(TeamMember::getTeamId, tm -> tm));
|
Map<Long, TeamMember> teamMemberMap = teamMembers.stream()
|
||||||
|
.collect(toMap(
|
||||||
|
TeamMember::getTeamId,
|
||||||
|
tm -> tm,
|
||||||
|
(existing, replacement) -> existing // 如果有重复,保留第一个
|
||||||
|
));
|
||||||
|
|
||||||
List<TeamDeployableDTO> teamDTOs = teamIds.stream().map(teamId -> buildTeamDTO(teamId, currentUserId, teamMap, teamMemberMap, configMap, envMap, teamAppsMap, appMap, systemMap, workflowMap, approverMap, statisticsMap, latestRecordMap, recentRecordsMap)).filter(Objects::nonNull).collect(toList());
|
// 14. 构建上下文对象(避免方法参数过多)
|
||||||
|
BuildContext context = new BuildContext();
|
||||||
|
context.teamMap = teamMap;
|
||||||
|
context.teamMemberMap = teamMemberMap;
|
||||||
|
context.teamEnvConfigMap = teamEnvConfigMap;
|
||||||
|
context.envMap = envMap;
|
||||||
|
context.teamAppsMap = teamAppsMap;
|
||||||
|
context.appMap = appMap;
|
||||||
|
context.systemMap = systemMap;
|
||||||
|
context.workflowMap = workflowMap;
|
||||||
|
context.approverMap = approverMap;
|
||||||
|
context.statisticsMap = statisticsMap;
|
||||||
|
context.latestRecordMap = latestRecordMap;
|
||||||
|
context.recentRecordsMap = recentRecordsMap;
|
||||||
|
|
||||||
|
List<TeamDeployableDTO> teamDTOs = teamIds.stream()
|
||||||
|
.map(teamId -> buildTeamDTO(teamId, currentUserId, context))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(toList());
|
||||||
|
|
||||||
// 14. 组装最终结果
|
// 14. 组装最终结果
|
||||||
UserDeployableDTO result = new UserDeployableDTO();
|
UserDeployableDTO result = new UserDeployableDTO();
|
||||||
@ -251,14 +341,13 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
/**
|
/**
|
||||||
* 构建团队DTO
|
* 构建团队DTO
|
||||||
*/
|
*/
|
||||||
private TeamDeployableDTO buildTeamDTO(Long teamId, Long currentUserId, Map<Long, Team> teamMap, Map<Long, TeamMember> teamMemberMap, Map<Long, TeamConfig> configMap, Map<Long, Environment> envMap, Map<Long, List<TeamApplication>> teamAppsMap, Map<Long, Application> appMap, Map<Long, ExternalSystem> systemMap, Map<Long, WorkflowDefinition> workflowMap, Map<Long, User> approverMap, Map<Long, DeployStatisticsDTO> statisticsMap, Map<Long, DeployRecord> latestRecordMap, Map<Long, List<DeployRecord>> recentRecordsMap) {
|
private TeamDeployableDTO buildTeamDTO(Long teamId, Long currentUserId, BuildContext ctx) {
|
||||||
Team team = teamMap.get(teamId);
|
Team team = ctx.teamMap.get(teamId);
|
||||||
if (team == null) {
|
if (team == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
TeamMember member = teamMemberMap.get(teamId);
|
TeamMember member = ctx.teamMemberMap.get(teamId);
|
||||||
TeamConfig config = configMap.get(teamId);
|
|
||||||
|
|
||||||
TeamDeployableDTO teamDTO = new TeamDeployableDTO();
|
TeamDeployableDTO teamDTO = new TeamDeployableDTO();
|
||||||
teamDTO.setTeamId(team.getId());
|
teamDTO.setTeamId(team.getId());
|
||||||
@ -275,40 +364,45 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
teamDTO.setTeamRole(null);
|
teamDTO.setTeamRole(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 组装环境数据
|
// 组装环境数据(从 TeamApplication 中提取该团队的所有环境)
|
||||||
if (config != null && config.getAllowedEnvironmentIds() != null && !config.getAllowedEnvironmentIds().isEmpty()) {
|
List<TeamApplication> teamApps = ctx.teamAppsMap.getOrDefault(teamId, Collections.emptyList());
|
||||||
List<Long> allowedEnvIds = config.getAllowedEnvironmentIds();
|
|
||||||
List<TeamApplication> teamApps = teamAppsMap.getOrDefault(teamId, Collections.emptyList());
|
// 按环境分组应用
|
||||||
|
Map<Long, List<TeamApplication>> appsByEnv = teamApps.stream()
|
||||||
|
.collect(groupingBy(TeamApplication::getEnvironmentId));
|
||||||
|
|
||||||
// 按环境分组应用
|
List<DeployableEnvironmentDTO> envDTOs = new ArrayList<>();
|
||||||
Map<Long, List<TeamApplication>> appsByEnv = teamApps.stream().collect(groupingBy(TeamApplication::getEnvironmentId));
|
for (Long envId : appsByEnv.keySet()) {
|
||||||
|
Environment env = ctx.envMap.get(envId);
|
||||||
List<DeployableEnvironmentDTO> envDTOs = new ArrayList<>();
|
if (env == null) {
|
||||||
for (int i = 0; i < allowedEnvIds.size(); i++) {
|
continue;
|
||||||
Long envId = allowedEnvIds.get(i);
|
|
||||||
Environment env = envMap.get(envId);
|
|
||||||
if (env == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
DeployableEnvironmentDTO envDTO = buildEnvironmentDTO(env, config, i, appsByEnv.getOrDefault(envId, Collections.emptyList()), appMap, systemMap, workflowMap, approverMap, statisticsMap, latestRecordMap, recentRecordsMap);
|
|
||||||
|
|
||||||
envDTOs.add(envDTO);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
envDTOs.sort(Comparator.comparing(DeployableEnvironmentDTO::getSort));
|
// 查找该团队+环境的配置
|
||||||
teamDTO.setEnvironments(envDTOs);
|
String configKey = teamId + "_" + envId;
|
||||||
} else {
|
TeamEnvironmentConfig envConfig = ctx.teamEnvConfigMap.get(configKey);
|
||||||
teamDTO.setEnvironments(Collections.emptyList());
|
|
||||||
|
// 只显示在 deploy_team_environment_config 表中配置过的环境
|
||||||
|
if (envConfig == null) {
|
||||||
|
log.debug("团队 {} 的环境 {} 未在 team_environment_config 中配置,跳过", teamId, envId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeployableEnvironmentDTO envDTO = buildEnvironmentDTO(env, envConfig, appsByEnv.get(envId), ctx);
|
||||||
|
|
||||||
|
envDTOs.add(envDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
envDTOs.sort(Comparator.comparing(DeployableEnvironmentDTO::getSort));
|
||||||
|
teamDTO.setEnvironments(envDTOs);
|
||||||
|
|
||||||
return teamDTO;
|
return teamDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建环境DTO
|
* 构建环境DTO
|
||||||
*/
|
*/
|
||||||
private DeployableEnvironmentDTO buildEnvironmentDTO(Environment env, TeamConfig config, int envIndex, List<TeamApplication> teamApps, Map<Long, Application> appMap, Map<Long, ExternalSystem> systemMap, Map<Long, WorkflowDefinition> workflowMap, Map<Long, User> approverMap, Map<Long, DeployStatisticsDTO> statisticsMap, Map<Long, DeployRecord> latestRecordMap, Map<Long, List<DeployRecord>> recentRecordsMap) {
|
private DeployableEnvironmentDTO buildEnvironmentDTO(Environment env, TeamEnvironmentConfig envConfig, List<TeamApplication> teamApps, BuildContext ctx) {
|
||||||
DeployableEnvironmentDTO envDTO = new DeployableEnvironmentDTO();
|
DeployableEnvironmentDTO envDTO = new DeployableEnvironmentDTO();
|
||||||
envDTO.setEnvironmentId(env.getId());
|
envDTO.setEnvironmentId(env.getId());
|
||||||
envDTO.setEnvironmentCode(env.getEnvCode());
|
envDTO.setEnvironmentCode(env.getEnvCode());
|
||||||
@ -317,29 +411,27 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
envDTO.setEnabled(env.getEnabled());
|
envDTO.setEnabled(env.getEnabled());
|
||||||
envDTO.setSort(env.getSort());
|
envDTO.setSort(env.getSort());
|
||||||
|
|
||||||
// 审批配置
|
// 从 TeamEnvironmentConfig 读取配置信息
|
||||||
if (config.getEnvironmentApprovalRequired() != null && envIndex < config.getEnvironmentApprovalRequired().size()) {
|
envDTO.setRequiresApproval(envConfig != null ? envConfig.getApprovalRequired() : false);
|
||||||
envDTO.setRequiresApproval(config.getEnvironmentApprovalRequired().get(envIndex));
|
envDTO.setRequireCodeReview(envConfig != null ? envConfig.getRequireCodeReview() : false);
|
||||||
} else {
|
envDTO.setRemark(envConfig != null ? envConfig.getRemark() : null);
|
||||||
envDTO.setRequiresApproval(false);
|
|
||||||
}
|
// 审批人列表(需要从 List<Long> 转换为 List<ApproverDTO> 以包含用户详细信息)
|
||||||
|
if (envConfig != null && envConfig.getApproverUserIds() != null && !envConfig.getApproverUserIds().isEmpty()) {
|
||||||
// 审批人
|
List<ApproverDTO> approverDTOs = envConfig.getApproverUserIds().stream()
|
||||||
if (config.getApproverUserIds() != null && envIndex < config.getApproverUserIds().size()) {
|
.map(id -> buildApproverDTO(id, ctx.approverMap))
|
||||||
Object approverObj = config.getApproverUserIds().get(envIndex);
|
.filter(Objects::nonNull)
|
||||||
if (approverObj instanceof List) {
|
.collect(toList());
|
||||||
List<?> approverList = (List<?>) approverObj;
|
envDTO.setApprovers(approverDTOs);
|
||||||
List<ApproverDTO> approverDTOs = approverList.stream().filter(id -> id instanceof Number).map(id -> buildApproverDTO(((Number) id).longValue(), approverMap)).filter(Objects::nonNull).collect(toList());
|
|
||||||
envDTO.setApprovers(approverDTOs);
|
|
||||||
} else {
|
|
||||||
envDTO.setApprovers(Collections.emptyList());
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
envDTO.setApprovers(Collections.emptyList());
|
envDTO.setApprovers(Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用列表
|
// 应用列表
|
||||||
List<DeployableApplicationDTO> appDTOs = teamApps.stream().map(ta -> buildApplicationDTO(ta, appMap, systemMap, workflowMap, statisticsMap, latestRecordMap, recentRecordsMap)).filter(Objects::nonNull).collect(toList());
|
List<DeployableApplicationDTO> appDTOs = teamApps.stream()
|
||||||
|
.map(ta -> buildApplicationDTO(ta, ctx))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(toList());
|
||||||
|
|
||||||
envDTO.setApplications(appDTOs);
|
envDTO.setApplications(appDTOs);
|
||||||
|
|
||||||
@ -365,8 +457,8 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
/**
|
/**
|
||||||
* 构建应用DTO
|
* 构建应用DTO
|
||||||
*/
|
*/
|
||||||
private DeployableApplicationDTO buildApplicationDTO(TeamApplication ta, Map<Long, Application> appMap, Map<Long, ExternalSystem> systemMap, Map<Long, WorkflowDefinition> workflowMap, Map<Long, DeployStatisticsDTO> statisticsMap, Map<Long, DeployRecord> latestRecordMap, Map<Long, List<DeployRecord>> recentRecordsMap) {
|
private DeployableApplicationDTO buildApplicationDTO(TeamApplication ta, BuildContext ctx) {
|
||||||
Application app = appMap.get(ta.getApplicationId());
|
Application app = ctx.appMap.get(ta.getApplicationId());
|
||||||
if (app == null) {
|
if (app == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -382,7 +474,7 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
// 部署系统
|
// 部署系统
|
||||||
dto.setDeploySystemId(ta.getDeploySystemId());
|
dto.setDeploySystemId(ta.getDeploySystemId());
|
||||||
if (ta.getDeploySystemId() != null) {
|
if (ta.getDeploySystemId() != null) {
|
||||||
ExternalSystem system = systemMap.get(ta.getDeploySystemId());
|
ExternalSystem system = ctx.systemMap.get(ta.getDeploySystemId());
|
||||||
dto.setDeploySystemName(system != null ? system.getName() : null);
|
dto.setDeploySystemName(system != null ? system.getName() : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,7 +484,7 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
// 工作流定义
|
// 工作流定义
|
||||||
dto.setWorkflowDefinitionId(ta.getWorkflowDefinitionId());
|
dto.setWorkflowDefinitionId(ta.getWorkflowDefinitionId());
|
||||||
if (ta.getWorkflowDefinitionId() != null) {
|
if (ta.getWorkflowDefinitionId() != null) {
|
||||||
WorkflowDefinition workflow = workflowMap.get(ta.getWorkflowDefinitionId());
|
WorkflowDefinition workflow = ctx.workflowMap.get(ta.getWorkflowDefinitionId());
|
||||||
if (workflow != null) {
|
if (workflow != null) {
|
||||||
dto.setWorkflowDefinitionName(workflow.getName());
|
dto.setWorkflowDefinitionName(workflow.getName());
|
||||||
dto.setWorkflowDefinitionKey(workflow.getKey());
|
dto.setWorkflowDefinitionKey(workflow.getKey());
|
||||||
@ -400,12 +492,12 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 部署统计信息和记录
|
// 部署统计信息和记录
|
||||||
DeployStatisticsDTO statistics = statisticsMap.get(ta.getId());
|
DeployStatisticsDTO statistics = ctx.statisticsMap.get(ta.getId());
|
||||||
if (statistics != null) {
|
if (statistics != null) {
|
||||||
dto.setDeployStatistics(statistics);
|
dto.setDeployStatistics(statistics);
|
||||||
|
|
||||||
// 判断是否正在部署中
|
// 判断是否正在部署中
|
||||||
DeployRecord latestRecord = latestRecordMap.get(ta.getId());
|
DeployRecord latestRecord = ctx.latestRecordMap.get(ta.getId());
|
||||||
if (latestRecord != null) {
|
if (latestRecord != null) {
|
||||||
DeployRecordStatusEnums status = latestRecord.getStatus();
|
DeployRecordStatusEnums status = latestRecord.getStatus();
|
||||||
dto.setIsDeploying(status == DeployRecordStatusEnums.CREATED || status == DeployRecordStatusEnums.RUNNING);
|
dto.setIsDeploying(status == DeployRecordStatusEnums.CREATED || status == DeployRecordStatusEnums.RUNNING);
|
||||||
@ -424,7 +516,7 @@ public class DeployServiceImpl implements IDeployService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 最近部署记录列表
|
// 最近部署记录列表
|
||||||
List<DeployRecord> recentRecords = recentRecordsMap.getOrDefault(ta.getId(), Collections.emptyList());
|
List<DeployRecord> recentRecords = ctx.recentRecordsMap.getOrDefault(ta.getId(), Collections.emptyList());
|
||||||
List<DeployRecordSummaryDTO> recordSummaryList = recentRecords.stream().map(this::buildDeployRecordSummary).collect(toList());
|
List<DeployRecordSummaryDTO> recordSummaryList = recentRecords.stream().map(this::buildDeployRecordSummary).collect(toList());
|
||||||
dto.setRecentDeployRecords(recordSummaryList);
|
dto.setRecentDeployRecords(recordSummaryList);
|
||||||
|
|
||||||
|
|||||||
@ -4,9 +4,11 @@ import com.qqchen.deploy.backend.deploy.converter.EnvironmentConverter;
|
|||||||
import com.qqchen.deploy.backend.deploy.dto.EnvironmentDTO;
|
import com.qqchen.deploy.backend.deploy.dto.EnvironmentDTO;
|
||||||
import com.qqchen.deploy.backend.deploy.entity.Environment;
|
import com.qqchen.deploy.backend.deploy.entity.Environment;
|
||||||
import com.qqchen.deploy.backend.deploy.entity.QEnvironment;
|
import com.qqchen.deploy.backend.deploy.entity.QEnvironment;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.TeamEnvironmentConfig;
|
||||||
import com.qqchen.deploy.backend.deploy.query.EnvironmentQuery;
|
import com.qqchen.deploy.backend.deploy.query.EnvironmentQuery;
|
||||||
import com.qqchen.deploy.backend.deploy.repository.IEnvironmentRepository;
|
import com.qqchen.deploy.backend.deploy.repository.IEnvironmentRepository;
|
||||||
import com.qqchen.deploy.backend.deploy.repository.ITeamApplicationRepository;
|
import com.qqchen.deploy.backend.deploy.repository.ITeamApplicationRepository;
|
||||||
|
import com.qqchen.deploy.backend.deploy.repository.ITeamEnvironmentConfigRepository;
|
||||||
import com.qqchen.deploy.backend.deploy.service.IEnvironmentService;
|
import com.qqchen.deploy.backend.deploy.service.IEnvironmentService;
|
||||||
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||||
@ -38,21 +40,16 @@ public class EnvironmentServiceImpl extends BaseServiceImpl<Environment, Environ
|
|||||||
@Resource
|
@Resource
|
||||||
private ITeamApplicationRepository teamApplicationRepository;
|
private ITeamApplicationRepository teamApplicationRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ITeamEnvironmentConfigRepository teamEnvironmentConfigRepository;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Page<EnvironmentDTO> page(EnvironmentQuery query) {
|
public Page<EnvironmentDTO> page(EnvironmentQuery query) {
|
||||||
Page<EnvironmentDTO> page = super.page(query);
|
Page<EnvironmentDTO> page = super.page(query);
|
||||||
|
|
||||||
// 填充统计数据
|
// 填充统计数据
|
||||||
List<EnvironmentDTO> content = page.getContent().stream()
|
List<EnvironmentDTO> content = page.getContent().stream()
|
||||||
.peek(env -> {
|
.peek(env -> fillEnvironmentStatistics(env))
|
||||||
// 统计使用该环境的团队数量(去重)
|
|
||||||
Long teamCount = teamApplicationRepository.countDistinctTeamIdByEnvironmentId(env.getId());
|
|
||||||
env.setTeamCount(teamCount != null ? teamCount : 0L);
|
|
||||||
|
|
||||||
// 统计该环境关联的应用数量(去重)
|
|
||||||
Long appCount = teamApplicationRepository.countDistinctApplicationIdByEnvironmentId(env.getId());
|
|
||||||
env.setApplicationCount(appCount != null ? appCount : 0L);
|
|
||||||
})
|
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
return new PageImpl<>(content, page.getPageable(), page.getTotalElements());
|
return new PageImpl<>(content, page.getPageable(), page.getTotalElements());
|
||||||
@ -64,18 +61,38 @@ public class EnvironmentServiceImpl extends BaseServiceImpl<Environment, Environ
|
|||||||
|
|
||||||
// 填充统计数据
|
// 填充统计数据
|
||||||
return list.stream()
|
return list.stream()
|
||||||
.peek(env -> {
|
.peek(env -> fillEnvironmentStatistics(env))
|
||||||
// 统计使用该环境的团队数量(去重)
|
|
||||||
Long teamCount = teamApplicationRepository.countDistinctTeamIdByEnvironmentId(env.getId());
|
|
||||||
env.setTeamCount(teamCount != null ? teamCount : 0L);
|
|
||||||
|
|
||||||
// 统计该环境关联的应用数量(去重)
|
|
||||||
Long appCount = teamApplicationRepository.countDistinctApplicationIdByEnvironmentId(env.getId());
|
|
||||||
env.setApplicationCount(appCount != null ? appCount : 0L);
|
|
||||||
})
|
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 填充环境统计数据
|
||||||
|
* <p>统计逻辑基于 deploy_team_environment_config 表,只统计已配置该环境的团队</p>
|
||||||
|
*/
|
||||||
|
private void fillEnvironmentStatistics(EnvironmentDTO env) {
|
||||||
|
// 1. 统计配置了该环境的团队数量
|
||||||
|
Long teamCount = teamEnvironmentConfigRepository.countByEnvironmentId(env.getId());
|
||||||
|
env.setTeamCount(teamCount != null ? teamCount : 0L);
|
||||||
|
|
||||||
|
// 2. 统计这些团队在该环境下的应用数量(去重)
|
||||||
|
if (teamCount != null && teamCount > 0) {
|
||||||
|
// 查询配置了该环境的所有团队ID
|
||||||
|
List<Long> teamIds = teamEnvironmentConfigRepository.findByEnvironmentId(env.getId())
|
||||||
|
.stream()
|
||||||
|
.map(config -> config.getTeamId())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (!teamIds.isEmpty()) {
|
||||||
|
Long appCount = teamApplicationRepository.countDistinctApplicationIdByTeamIdsAndEnvironmentId(teamIds, env.getId());
|
||||||
|
env.setApplicationCount(appCount != null ? appCount : 0L);
|
||||||
|
} else {
|
||||||
|
env.setApplicationCount(0L);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
env.setApplicationCount(0L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<EnvironmentDTO> getProjectEnvironments(Long projectGroupId) {
|
public List<EnvironmentDTO> getProjectEnvironments(Long projectGroupId) {
|
||||||
QEnvironment environment = QEnvironment.environment;
|
QEnvironment environment = QEnvironment.environment;
|
||||||
|
|||||||
@ -3,10 +3,14 @@ package com.qqchen.deploy.backend.deploy.service.impl;
|
|||||||
import com.qqchen.deploy.backend.deploy.converter.TeamApplicationConverter;
|
import com.qqchen.deploy.backend.deploy.converter.TeamApplicationConverter;
|
||||||
import com.qqchen.deploy.backend.deploy.dto.TeamApplicationDTO;
|
import com.qqchen.deploy.backend.deploy.dto.TeamApplicationDTO;
|
||||||
import com.qqchen.deploy.backend.deploy.entity.Application;
|
import com.qqchen.deploy.backend.deploy.entity.Application;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.DeployRecord;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.Environment;
|
||||||
import com.qqchen.deploy.backend.deploy.entity.Team;
|
import com.qqchen.deploy.backend.deploy.entity.Team;
|
||||||
import com.qqchen.deploy.backend.deploy.entity.TeamApplication;
|
import com.qqchen.deploy.backend.deploy.entity.TeamApplication;
|
||||||
import com.qqchen.deploy.backend.deploy.query.TeamApplicationQuery;
|
import com.qqchen.deploy.backend.deploy.query.TeamApplicationQuery;
|
||||||
import com.qqchen.deploy.backend.deploy.repository.IApplicationRepository;
|
import com.qqchen.deploy.backend.deploy.repository.IApplicationRepository;
|
||||||
|
import com.qqchen.deploy.backend.deploy.repository.IDeployRecordRepository;
|
||||||
|
import com.qqchen.deploy.backend.deploy.repository.IEnvironmentRepository;
|
||||||
import com.qqchen.deploy.backend.deploy.repository.ITeamApplicationRepository;
|
import com.qqchen.deploy.backend.deploy.repository.ITeamApplicationRepository;
|
||||||
import com.qqchen.deploy.backend.deploy.repository.ITeamRepository;
|
import com.qqchen.deploy.backend.deploy.repository.ITeamRepository;
|
||||||
import com.qqchen.deploy.backend.deploy.service.ITeamApplicationService;
|
import com.qqchen.deploy.backend.deploy.service.ITeamApplicationService;
|
||||||
@ -20,14 +24,12 @@ import org.springframework.data.domain.PageImpl;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class TeamApplicationServiceImpl extends BaseServiceImpl<TeamApplication, TeamApplicationDTO, TeamApplicationQuery, Long>
|
public class TeamApplicationServiceImpl extends BaseServiceImpl<TeamApplication, TeamApplicationDTO, TeamApplicationQuery, Long> implements ITeamApplicationService {
|
||||||
implements ITeamApplicationService {
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ITeamApplicationRepository teamApplicationRepository;
|
private ITeamApplicationRepository teamApplicationRepository;
|
||||||
@ -38,6 +40,12 @@ public class TeamApplicationServiceImpl extends BaseServiceImpl<TeamApplication,
|
|||||||
@Resource
|
@Resource
|
||||||
private IApplicationRepository applicationRepository;
|
private IApplicationRepository applicationRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IDeployRecordRepository deployRecordRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IEnvironmentRepository environmentRepository;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private TeamApplicationConverter teamApplicationConverter;
|
private TeamApplicationConverter teamApplicationConverter;
|
||||||
|
|
||||||
@ -45,8 +53,7 @@ public class TeamApplicationServiceImpl extends BaseServiceImpl<TeamApplication,
|
|||||||
@Transactional
|
@Transactional
|
||||||
public TeamApplicationDTO create(TeamApplicationDTO dto) {
|
public TeamApplicationDTO create(TeamApplicationDTO dto) {
|
||||||
// 检查团队+应用+环境组合是否已存在
|
// 检查团队+应用+环境组合是否已存在
|
||||||
if (teamApplicationRepository.existsByTeamIdAndApplicationIdAndEnvironmentIdAndDeletedFalse(
|
if (teamApplicationRepository.existsByTeamIdAndApplicationIdAndEnvironmentIdAndDeletedFalse(dto.getTeamId(), dto.getApplicationId(), dto.getEnvironmentId())) {
|
||||||
dto.getTeamId(), dto.getApplicationId(), dto.getEnvironmentId())) {
|
|
||||||
throw new BusinessException(ResponseCode.TEAM_APPLICATION_ALREADY_EXISTS);
|
throw new BusinessException(ResponseCode.TEAM_APPLICATION_ALREADY_EXISTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,22 +63,137 @@ public class TeamApplicationServiceImpl extends BaseServiceImpl<TeamApplication,
|
|||||||
@Override
|
@Override
|
||||||
public Page<TeamApplicationDTO> page(TeamApplicationQuery query) {
|
public Page<TeamApplicationDTO> page(TeamApplicationQuery query) {
|
||||||
Page<TeamApplicationDTO> page = super.page(query);
|
Page<TeamApplicationDTO> page = super.page(query);
|
||||||
|
fillExtendedFields(page.getContent());
|
||||||
|
return new PageImpl<>(page.getContent(), page.getPageable(), page.getTotalElements());
|
||||||
|
}
|
||||||
|
|
||||||
// 填充团队名称和应用信息
|
@Override
|
||||||
List<TeamApplicationDTO> content = page.getContent().stream()
|
public List<TeamApplicationDTO> findAll(TeamApplicationQuery query) {
|
||||||
.peek(teamApp -> {
|
List<TeamApplicationDTO> list = super.findAll(query);
|
||||||
Optional<Team> teamOptional = teamRepository.findById(teamApp.getTeamId());
|
fillExtendedFields(list);
|
||||||
teamOptional.ifPresent(team -> teamApp.setTeamName(team.getTeamName()));
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
Optional<Application> appOptional = applicationRepository.findById(teamApp.getApplicationId());
|
/**
|
||||||
appOptional.ifPresent(app -> {
|
* 批量填充扩展字段:teamName、applicationName、applicationCode、environmentName
|
||||||
teamApp.setApplicationName(app.getAppName());
|
*
|
||||||
teamApp.setApplicationCode(app.getAppCode());
|
* <p>使用批量查询避免N+1问题
|
||||||
});
|
*/
|
||||||
})
|
private void fillExtendedFields(List<TeamApplicationDTO> teamApps) {
|
||||||
.collect(Collectors.toList());
|
if (teamApps.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return new PageImpl<>(content, page.getPageable(), page.getTotalElements());
|
// 1. 收集所有需要查询的ID
|
||||||
|
Set<Long> teamIds = teamApps.stream()
|
||||||
|
.map(TeamApplicationDTO::getTeamId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
Set<Long> applicationIds = teamApps.stream()
|
||||||
|
.map(TeamApplicationDTO::getApplicationId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
Set<Long> environmentIds = teamApps.stream()
|
||||||
|
.map(TeamApplicationDTO::getEnvironmentId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
// 2. 批量查询团队信息
|
||||||
|
Map<Long, Team> teamMap = new HashMap<>();
|
||||||
|
if (!teamIds.isEmpty()) {
|
||||||
|
teamRepository.findAllById(teamIds).forEach(team ->
|
||||||
|
teamMap.put(team.getId(), team)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 批量查询应用信息
|
||||||
|
Map<Long, Application> appMap = new HashMap<>();
|
||||||
|
if (!applicationIds.isEmpty()) {
|
||||||
|
applicationRepository.findAllById(applicationIds).forEach(app ->
|
||||||
|
appMap.put(app.getId(), app)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 批量查询环境信息
|
||||||
|
Map<Long, Environment> envMap = new HashMap<>();
|
||||||
|
if (!environmentIds.isEmpty()) {
|
||||||
|
environmentRepository.findAllById(environmentIds).forEach(env ->
|
||||||
|
envMap.put(env.getId(), env)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 填充扩展字段
|
||||||
|
teamApps.forEach(teamApp -> {
|
||||||
|
// 填充团队名称
|
||||||
|
if (teamApp.getTeamId() != null) {
|
||||||
|
Team team = teamMap.get(teamApp.getTeamId());
|
||||||
|
if (team != null) {
|
||||||
|
teamApp.setTeamName(team.getTeamName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充应用信息
|
||||||
|
if (teamApp.getApplicationId() != null) {
|
||||||
|
Application app = appMap.get(teamApp.getApplicationId());
|
||||||
|
if (app != null) {
|
||||||
|
teamApp.setApplicationName(app.getAppName());
|
||||||
|
teamApp.setApplicationCode(app.getAppCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充环境名称
|
||||||
|
if (teamApp.getEnvironmentId() != null) {
|
||||||
|
Environment env = envMap.get(teamApp.getEnvironmentId());
|
||||||
|
if (env != null) {
|
||||||
|
teamApp.setEnvironmentName(env.getEnvName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除团队应用及其关联的部署记录
|
||||||
|
*
|
||||||
|
* <p>删除逻辑:
|
||||||
|
* <ul>
|
||||||
|
* <li>1. 先逻辑删除所有关联的部署记录(DeployRecord)</li>
|
||||||
|
* <li>2. 再物理删除团队应用(TeamApplication)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>注意:
|
||||||
|
* <ul>
|
||||||
|
* <li>TeamApplication 使用物理删除(@LogicDelete(false))</li>
|
||||||
|
* <li>DeployRecord 使用逻辑删除(@LogicDelete)</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void delete(Long id) {
|
||||||
|
log.info("开始删除团队应用,ID: {}", id);
|
||||||
|
|
||||||
|
// 1. 查询关联的部署记录
|
||||||
|
List<DeployRecord> deployRecords = deployRecordRepository.findByTeamApplicationId(id);
|
||||||
|
|
||||||
|
if (!deployRecords.isEmpty()) {
|
||||||
|
log.info("团队应用 {} 关联了 {} 条部署记录,开始级联删除", id, deployRecords.size());
|
||||||
|
|
||||||
|
// 2. 逻辑删除所有关联的部署记录
|
||||||
|
deployRecords.forEach(record -> {
|
||||||
|
record.setDeleted(true);
|
||||||
|
deployRecordRepository.save(record);
|
||||||
|
});
|
||||||
|
|
||||||
|
log.info("成功逻辑删除 {} 条部署记录", deployRecords.size());
|
||||||
|
} else {
|
||||||
|
log.info("团队应用 {} 没有关联的部署记录", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 物理删除团队应用
|
||||||
|
super.delete(id);
|
||||||
|
|
||||||
|
log.info("成功删除团队应用,ID: {}", id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,42 +0,0 @@
|
|||||||
package com.qqchen.deploy.backend.deploy.service.impl;
|
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.deploy.converter.TeamConfigConverter;
|
|
||||||
import com.qqchen.deploy.backend.deploy.dto.TeamConfigDTO;
|
|
||||||
import com.qqchen.deploy.backend.deploy.entity.TeamConfig;
|
|
||||||
import com.qqchen.deploy.backend.deploy.query.TeamConfigQuery;
|
|
||||||
import com.qqchen.deploy.backend.deploy.repository.ITeamConfigRepository;
|
|
||||||
import com.qqchen.deploy.backend.deploy.service.ITeamConfigService;
|
|
||||||
import com.qqchen.deploy.backend.framework.annotation.ServiceType;
|
|
||||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
|
||||||
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
|
||||||
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 团队配置Service实现
|
|
||||||
*
|
|
||||||
* @author qqchen
|
|
||||||
* @since 2025-10-31
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Service
|
|
||||||
@ServiceType(ServiceType.Type.DATABASE)
|
|
||||||
public class TeamConfigServiceImpl extends BaseServiceImpl<TeamConfig, TeamConfigDTO, TeamConfigQuery, Long>
|
|
||||||
implements ITeamConfigService {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private ITeamConfigRepository teamConfigRepository;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private TeamConfigConverter teamConfigConverter;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TeamConfigDTO getByTeamId(Long teamId) {
|
|
||||||
TeamConfig config = teamConfigRepository.findByTeamIdAndDeletedFalse(teamId)
|
|
||||||
.orElseThrow(() -> new BusinessException(ResponseCode.TEAM_CONFIG_NOT_FOUND, new Object[]{teamId}));
|
|
||||||
return teamConfigConverter.toDto(config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -0,0 +1,152 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.service.impl;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.dto.TeamEnvironmentConfigDTO;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.Environment;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.TeamEnvironmentConfig;
|
||||||
|
import com.qqchen.deploy.backend.deploy.query.TeamEnvironmentConfigQuery;
|
||||||
|
import com.qqchen.deploy.backend.deploy.repository.IEnvironmentRepository;
|
||||||
|
import com.qqchen.deploy.backend.deploy.repository.ITeamApplicationRepository;
|
||||||
|
import com.qqchen.deploy.backend.deploy.repository.ITeamEnvironmentConfigRepository;
|
||||||
|
import com.qqchen.deploy.backend.deploy.service.ITeamEnvironmentConfigService;
|
||||||
|
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||||
|
import com.qqchen.deploy.backend.notification.entity.NotificationChannel;
|
||||||
|
import com.qqchen.deploy.backend.notification.repository.INotificationChannelRepository;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageImpl;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 团队环境配置Service实现
|
||||||
|
*
|
||||||
|
* @author qqchen
|
||||||
|
* @since 2025-11-04
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class TeamEnvironmentConfigServiceImpl
|
||||||
|
extends BaseServiceImpl<TeamEnvironmentConfig, TeamEnvironmentConfigDTO, TeamEnvironmentConfigQuery, Long>
|
||||||
|
implements ITeamEnvironmentConfigService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ITeamEnvironmentConfigRepository teamEnvironmentConfigRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IEnvironmentRepository environmentRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private INotificationChannelRepository notificationChannelRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ITeamApplicationRepository teamApplicationRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TeamEnvironmentConfigDTO> findByTeamId(Long teamId) {
|
||||||
|
List<TeamEnvironmentConfigDTO> list = teamEnvironmentConfigRepository.findByTeamId(teamId).stream()
|
||||||
|
.map(converter::toDto)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
fillExtendedFields(list);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TeamEnvironmentConfigDTO findByTeamIdAndEnvironmentId(Long teamId, Long environmentId) {
|
||||||
|
TeamEnvironmentConfigDTO dto = teamEnvironmentConfigRepository.findByTeamIdAndEnvironmentId(teamId, environmentId)
|
||||||
|
.map(converter::toDto)
|
||||||
|
.orElse(null);
|
||||||
|
if (dto != null) {
|
||||||
|
fillExtendedFields(Collections.singletonList(dto));
|
||||||
|
}
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean exists(Long teamId, Long environmentId) {
|
||||||
|
return teamEnvironmentConfigRepository.existsByTeamIdAndEnvironmentId(teamId, environmentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<TeamEnvironmentConfigDTO> page(TeamEnvironmentConfigQuery query) {
|
||||||
|
Page<TeamEnvironmentConfigDTO> page = super.page(query);
|
||||||
|
fillExtendedFields(page.getContent());
|
||||||
|
return new PageImpl<>(page.getContent(), page.getPageable(), page.getTotalElements());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TeamEnvironmentConfigDTO> findAll(TeamEnvironmentConfigQuery query) {
|
||||||
|
List<TeamEnvironmentConfigDTO> list = super.findAll(query);
|
||||||
|
fillExtendedFields(list);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量填充扩展字段:environmentName、notificationChannelName、applicationCount
|
||||||
|
*
|
||||||
|
* <p>使用批量查询避免N+1问题
|
||||||
|
*/
|
||||||
|
private void fillExtendedFields(List<TeamEnvironmentConfigDTO> configs) {
|
||||||
|
if (configs.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 收集所有环境ID和通知渠道ID
|
||||||
|
Set<Long> environmentIds = configs.stream()
|
||||||
|
.map(TeamEnvironmentConfigDTO::getEnvironmentId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
Set<Long> channelIds = configs.stream()
|
||||||
|
.map(TeamEnvironmentConfigDTO::getNotificationChannelId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
// 2. 批量查询环境信息
|
||||||
|
Map<Long, Environment> environmentMap = new HashMap<>();
|
||||||
|
if (!environmentIds.isEmpty()) {
|
||||||
|
environmentRepository.findAllById(environmentIds).forEach(env ->
|
||||||
|
environmentMap.put(env.getId(), env)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 批量查询通知渠道信息
|
||||||
|
Map<Long, NotificationChannel> channelMap = new HashMap<>();
|
||||||
|
if (!channelIds.isEmpty()) {
|
||||||
|
notificationChannelRepository.findAllById(channelIds).forEach(channel ->
|
||||||
|
channelMap.put(channel.getId(), channel)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 填充扩展字段
|
||||||
|
configs.forEach(config -> {
|
||||||
|
// 填充环境名称
|
||||||
|
if (config.getEnvironmentId() != null) {
|
||||||
|
Environment env = environmentMap.get(config.getEnvironmentId());
|
||||||
|
if (env != null) {
|
||||||
|
config.setEnvironmentName(env.getEnvName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充通知渠道名称
|
||||||
|
if (config.getNotificationChannelId() != null) {
|
||||||
|
NotificationChannel channel = channelMap.get(config.getNotificationChannelId());
|
||||||
|
if (channel != null) {
|
||||||
|
config.setNotificationChannelName(channel.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充应用数量统计
|
||||||
|
if (config.getTeamId() != null && config.getEnvironmentId() != null) {
|
||||||
|
Long appCount = teamApplicationRepository.countByTeamIdAndEnvironmentId(
|
||||||
|
config.getTeamId(),
|
||||||
|
config.getEnvironmentId()
|
||||||
|
);
|
||||||
|
config.setApplicationCount(appCount != null ? appCount : 0L);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -344,5 +344,198 @@ public class NestedMapUtils {
|
|||||||
|
|
||||||
return getValueFromExecution(execution, variablePath);
|
return getValueFromExecution(execution, variablePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 统一解析入口(推荐使用) ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一解析方法 - 智能处理所有类型的输入
|
||||||
|
*
|
||||||
|
* <p>自动识别并处理三种情况:
|
||||||
|
* <ul>
|
||||||
|
* <li>纯文本: "hello" → "hello"</li>
|
||||||
|
* <li>纯表达式: "${user.name}" → Object (原始类型,可能是 String/Integer/Map 等)</li>
|
||||||
|
* <li>模板字符串: "Hello ${user.name}, age: ${user.age}" → "Hello John, age: 30"</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>使用示例:
|
||||||
|
* <pre>
|
||||||
|
* // 纯表达式 - 返回原始类型
|
||||||
|
* Object status = NestedMapUtils.resolve(execution, "${jenkins.status}");
|
||||||
|
* // 返回: NodeExecutionStatusEnum.SUCCESS (枚举类型)
|
||||||
|
*
|
||||||
|
* // 模板字符串 - 返回替换后的字符串
|
||||||
|
* String message = (String) NestedMapUtils.resolve(execution, "构建${jenkins.status}, 编号: ${jenkins.buildNumber}");
|
||||||
|
* // 返回: "构建SUCCESS, 编号: 123"
|
||||||
|
*
|
||||||
|
* // 纯文本 - 直接返回
|
||||||
|
* String text = (String) NestedMapUtils.resolve(execution, "纯文本");
|
||||||
|
* // 返回: "纯文本"
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param execution Flowable 执行上下文
|
||||||
|
* @param input 输入字符串(可能包含表达式)
|
||||||
|
* @return 解析后的值(可能是任意类型)
|
||||||
|
*/
|
||||||
|
public static Object resolve(DelegateExecution execution, String input) {
|
||||||
|
if (input == null || !input.contains("${")) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否是纯表达式(整个字符串就是一个 ${xxx})
|
||||||
|
if (isPureExpression(input)) {
|
||||||
|
// 纯表达式:返回原始类型(可能是 String/Integer/Enum/Map 等)
|
||||||
|
return resolveExpression(execution, input);
|
||||||
|
} else {
|
||||||
|
// 模板字符串:返回替换后的字符串
|
||||||
|
return resolveTemplateString(execution, input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一解析方法 - DelegateTask 版本
|
||||||
|
*
|
||||||
|
* @param task Flowable 任务上下文
|
||||||
|
* @param input 输入字符串
|
||||||
|
* @return 解析后的值
|
||||||
|
*/
|
||||||
|
public static Object resolve(DelegateTask task, String input) {
|
||||||
|
if (input == null || !input.contains("${")) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPureExpression(input)) {
|
||||||
|
return resolveExpression(task, input);
|
||||||
|
} else {
|
||||||
|
return resolveTemplateString(task, input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否是纯表达式
|
||||||
|
* 纯表达式定义:整个字符串就是一个 ${xxx},前后没有其他内容
|
||||||
|
*
|
||||||
|
* @param input 输入字符串
|
||||||
|
* @return true 如果是纯表达式
|
||||||
|
*/
|
||||||
|
private static boolean isPureExpression(String input) {
|
||||||
|
if (!input.startsWith("${") || !input.endsWith("}")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否只有一个表达式(没有第二个 ${)
|
||||||
|
int secondExprStart = input.indexOf("${", 2);
|
||||||
|
return secondExprStart == -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 内部实现方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析模板字符串(支持多个嵌入式表达式)
|
||||||
|
*
|
||||||
|
* <p>内部方法,推荐使用 {@link #resolve(DelegateExecution, String)} 代替
|
||||||
|
*
|
||||||
|
* @param execution Flowable 执行上下文
|
||||||
|
* @param template 模板字符串
|
||||||
|
* @return 替换后的字符串
|
||||||
|
*/
|
||||||
|
private static String resolveTemplateString(DelegateExecution execution, String template) {
|
||||||
|
if (template == null || !template.contains("${")) {
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
int currentIndex = 0;
|
||||||
|
|
||||||
|
while (currentIndex < template.length()) {
|
||||||
|
int startIndex = template.indexOf("${", currentIndex);
|
||||||
|
|
||||||
|
// 没有更多的表达式了
|
||||||
|
if (startIndex == -1) {
|
||||||
|
result.append(template.substring(currentIndex));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加表达式前的普通文本
|
||||||
|
if (startIndex > currentIndex) {
|
||||||
|
result.append(template.substring(currentIndex, startIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找表达式的结束位置
|
||||||
|
int endIndex = template.indexOf("}", startIndex);
|
||||||
|
if (endIndex == -1) {
|
||||||
|
// 没有找到闭合的 },说明格式错误,保留原样
|
||||||
|
log.warn("未找到闭合的 }} 符号,保留原样: {}", template.substring(startIndex));
|
||||||
|
result.append(template.substring(startIndex));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取并解析表达式
|
||||||
|
String expression = template.substring(startIndex, endIndex + 1);
|
||||||
|
try {
|
||||||
|
Object value = resolveExpression(execution, expression);
|
||||||
|
result.append(value != null ? value.toString() : "");
|
||||||
|
log.debug(" 替换表达式: {} -> {}", expression, value);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn(" 表达式解析失败: {},保留原样", expression, e);
|
||||||
|
result.append(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentIndex = endIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析模板字符串(支持多个嵌入式表达式)- DelegateTask 版本
|
||||||
|
*
|
||||||
|
* <p>内部方法,推荐使用 {@link #resolve(DelegateTask, String)} 代替
|
||||||
|
*
|
||||||
|
* @param task Flowable 任务上下文
|
||||||
|
* @param template 模板字符串
|
||||||
|
* @return 替换后的字符串
|
||||||
|
*/
|
||||||
|
private static String resolveTemplateString(DelegateTask task, String template) {
|
||||||
|
if (template == null || !template.contains("${")) {
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
int currentIndex = 0;
|
||||||
|
|
||||||
|
while (currentIndex < template.length()) {
|
||||||
|
int startIndex = template.indexOf("${", currentIndex);
|
||||||
|
|
||||||
|
if (startIndex == -1) {
|
||||||
|
result.append(template.substring(currentIndex));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startIndex > currentIndex) {
|
||||||
|
result.append(template.substring(currentIndex, startIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
int endIndex = template.indexOf("}", startIndex);
|
||||||
|
if (endIndex == -1) {
|
||||||
|
log.warn("未找到闭合的 }} 符号,保留原样: {}", template.substring(startIndex));
|
||||||
|
result.append(template.substring(startIndex));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
String expression = template.substring(startIndex, endIndex + 1);
|
||||||
|
try {
|
||||||
|
Object value = resolveExpression(task, expression);
|
||||||
|
result.append(value != null ? value.toString() : "");
|
||||||
|
log.debug(" 替换表达式: {} -> {}", expression, value);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn(" 表达式解析失败: {},保留原样", expression, e);
|
||||||
|
result.append(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentIndex = endIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -50,21 +50,33 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter {
|
|||||||
|
|
||||||
// 4. 构建企业微信消息体
|
// 4. 构建企业微信消息体
|
||||||
Map<String, Object> messageBody = new HashMap<>();
|
Map<String, Object> messageBody = new HashMap<>();
|
||||||
messageBody.put("msgtype", "text");
|
|
||||||
|
|
||||||
Map<String, Object> textContent = new HashMap<>();
|
// 智能判断消息类型:如果包含 Markdown 标签,使用 markdown 类型
|
||||||
textContent.put("content", message);
|
boolean isMarkdown = isMarkdownMessage(message);
|
||||||
|
String msgType = isMarkdown ? "markdown" : "text";
|
||||||
|
messageBody.put("msgtype", msgType);
|
||||||
|
|
||||||
if (!CollectionUtils.isEmpty(mentionedList)) {
|
if (isMarkdown) {
|
||||||
textContent.put("mentioned_list", mentionedList);
|
// Markdown 格式消息
|
||||||
|
Map<String, Object> markdownContent = new HashMap<>();
|
||||||
|
markdownContent.put("content", message);
|
||||||
|
messageBody.put("markdown", markdownContent);
|
||||||
|
} else {
|
||||||
|
// 纯文本消息
|
||||||
|
Map<String, Object> textContent = new HashMap<>();
|
||||||
|
textContent.put("content", message);
|
||||||
|
|
||||||
|
if (!CollectionUtils.isEmpty(mentionedList)) {
|
||||||
|
textContent.put("mentioned_list", mentionedList);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CollectionUtils.isEmpty(mentionedMobileList)) {
|
||||||
|
textContent.put("mentioned_mobile_list", mentionedMobileList);
|
||||||
|
}
|
||||||
|
|
||||||
|
messageBody.put("text", textContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CollectionUtils.isEmpty(mentionedMobileList)) {
|
|
||||||
textContent.put("mentioned_mobile_list", mentionedMobileList);
|
|
||||||
}
|
|
||||||
|
|
||||||
messageBody.put("text", textContent);
|
|
||||||
|
|
||||||
// 5. 发送请求
|
// 5. 发送请求
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
@ -72,7 +84,7 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter {
|
|||||||
String jsonBody = objectMapper.writeValueAsString(messageBody);
|
String jsonBody = objectMapper.writeValueAsString(messageBody);
|
||||||
HttpEntity<String> entity = new HttpEntity<>(jsonBody, headers);
|
HttpEntity<String> entity = new HttpEntity<>(jsonBody, headers);
|
||||||
|
|
||||||
log.info("发送企业微信通知 - URL: {}, 消息: {}", weworkConfig.getWebhookUrl(), message);
|
log.info("发送企业微信通知 - URL: {}, 类型: {}, 消息: {}", weworkConfig.getWebhookUrl(), msgType, message);
|
||||||
|
|
||||||
String response = restTemplate.exchange(
|
String response = restTemplate.exchange(
|
||||||
weworkConfig.getWebhookUrl(),
|
weworkConfig.getWebhookUrl(),
|
||||||
@ -148,5 +160,28 @@ public class WeworkChannelAdapter implements INotificationChannelAdapter {
|
|||||||
|
|
||||||
return mentionedMobileList;
|
return mentionedMobileList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否是 Markdown 格式消息
|
||||||
|
* 企业微信支持的 Markdown 标签包括:
|
||||||
|
* - <font color="xxx">文本</font>
|
||||||
|
* - **加粗**
|
||||||
|
* - `代码`
|
||||||
|
* - [链接](url)
|
||||||
|
*
|
||||||
|
* @param message 消息内容
|
||||||
|
* @return true 如果包含 Markdown 标签
|
||||||
|
*/
|
||||||
|
private boolean isMarkdownMessage(String message) {
|
||||||
|
if (message == null || message.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测企业微信 Markdown 特征标签
|
||||||
|
return message.contains("<font") ||
|
||||||
|
message.contains("**") ||
|
||||||
|
message.contains("`") ||
|
||||||
|
message.matches(".*\\[.*\\]\\(.*\\).*"); // [text](url) 格式
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,8 @@ import jakarta.annotation.Resource;
|
|||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.security.core.parameters.P;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -35,8 +37,7 @@ import java.util.stream.Collectors;
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/notification-channel")
|
@RequestMapping("/api/v1/notification-channel")
|
||||||
@Tag(name = "通知渠道管理", description = "通知渠道管理相关接口")
|
@Tag(name = "通知渠道管理", description = "通知渠道管理相关接口")
|
||||||
public class NotificationChannelApiController
|
public class NotificationChannelApiController extends BaseController<NotificationChannel, NotificationChannelDTO, Long, NotificationChannelQuery> {
|
||||||
extends BaseController<NotificationChannel, NotificationChannelDTO, Long, NotificationChannelQuery> {
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private INotificationChannelService notificationChannelService;
|
private INotificationChannelService notificationChannelService;
|
||||||
@ -45,23 +46,23 @@ public class NotificationChannelApiController
|
|||||||
private INotificationSendService notificationSendService;
|
private INotificationSendService notificationSendService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response<NotificationChannelDTO> create(NotificationChannelDTO dto) {
|
public Response<NotificationChannelDTO> create(@Validated @RequestBody NotificationChannelDTO dto) {
|
||||||
return super.create(dto);
|
return super.create(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response<NotificationChannelDTO> update(Long aLong, NotificationChannelDTO dto) {
|
public Response<NotificationChannelDTO> update(@PathVariable Long id,@Validated @RequestBody NotificationChannelDTO dto) {
|
||||||
return super.update(aLong, dto);
|
return super.update(id, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response<Void> delete(Long aLong) {
|
public Response<Void> delete(@PathVariable Long id) {
|
||||||
return super.delete(aLong);
|
return super.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response<NotificationChannelDTO> findById(Long aLong) {
|
public Response<NotificationChannelDTO> findById(@PathVariable Long id) {
|
||||||
return super.findById(aLong);
|
return super.findById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.qqchen.deploy.backend.workflow.delegate;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.qqchen.deploy.backend.framework.utils.NestedMapUtils;
|
||||||
import com.qqchen.deploy.backend.workflow.dto.outputs.BaseNodeOutputs;
|
import com.qqchen.deploy.backend.workflow.dto.outputs.BaseNodeOutputs;
|
||||||
import com.qqchen.deploy.backend.workflow.enums.NodeExecutionStatusEnum;
|
import com.qqchen.deploy.backend.workflow.enums.NodeExecutionStatusEnum;
|
||||||
import com.qqchen.deploy.backend.workflow.model.NodeContext;
|
import com.qqchen.deploy.backend.workflow.model.NodeContext;
|
||||||
@ -174,7 +175,7 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析Map中的表达式
|
* 解析Map中的表达式
|
||||||
* 使用简单的字符串替换方式,从execution变量中获取值
|
* 自动处理纯表达式、模板字符串、纯文本三种情况
|
||||||
*/
|
*/
|
||||||
protected Map<String, Object> resolveExpressions(Map<String, Object> inputMap, DelegateExecution execution) {
|
protected Map<String, Object> resolveExpressions(Map<String, Object> inputMap, DelegateExecution execution) {
|
||||||
Map<String, Object> resolvedMap = new HashMap<>();
|
Map<String, Object> resolvedMap = new HashMap<>();
|
||||||
@ -186,18 +187,13 @@ public abstract class BaseNodeDelegate<I, O> implements JavaDelegate {
|
|||||||
|
|
||||||
if (value instanceof String) {
|
if (value instanceof String) {
|
||||||
String strValue = (String) value;
|
String strValue = (String) value;
|
||||||
if (strValue.contains("${")) {
|
try {
|
||||||
try {
|
// ✅ 使用统一的 resolve 方法,自动智能处理所有情况
|
||||||
// 使用工具类直接解析表达式
|
Object resolvedValue = NestedMapUtils.resolve(execution, strValue);
|
||||||
Object resolvedValue = com.qqchen.deploy.backend.framework.utils.NestedMapUtils
|
log.debug("解析字段: {} = {} -> {}", entry.getKey(), strValue, resolvedValue);
|
||||||
.resolveExpression(execution, strValue);
|
resolvedMap.put(entry.getKey(), resolvedValue);
|
||||||
log.debug("解析表达式: {} = {} -> {}", entry.getKey(), strValue, resolvedValue);
|
} catch (Exception e) {
|
||||||
resolvedMap.put(entry.getKey(), resolvedValue);
|
log.warn("解析失败: {},使用原始值", strValue, e);
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("Failed to resolve expression: {}, using original value", strValue, e);
|
|
||||||
resolvedMap.put(entry.getKey(), value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resolvedMap.put(entry.getKey(), value);
|
resolvedMap.put(entry.getKey(), value);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -25,11 +25,7 @@ public class NotificationNodeDelegate extends BaseNodeDelegate<NotificationInput
|
|||||||
private INotificationSendService notificationSendService;
|
private INotificationSendService notificationSendService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected NotificationOutputs executeInternal(
|
protected NotificationOutputs executeInternal(DelegateExecution execution, Map<String, Object> configs, NotificationInputMapping input) {
|
||||||
DelegateExecution execution,
|
|
||||||
Map<String, Object> configs,
|
|
||||||
NotificationInputMapping input
|
|
||||||
) {
|
|
||||||
log.info("Sending notification - channel: {}, title: {}, content: {}", input.getChannelId(), input.getTitle(), input.getContent());
|
log.info("Sending notification - channel: {}, title: {}, content: {}", input.getChannelId(), input.getTitle(), input.getContent());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1160,13 +1160,6 @@ VALUES
|
|||||||
(2, 4, 'ops_manager', '负责人', NOW(), 'admin', NOW(), 'admin', NOW(), 1, 0),
|
(2, 4, 'ops_manager', '负责人', NOW(), 'admin', NOW(), 'admin', NOW(), 1, 0),
|
||||||
(2, 2, 'it_manager', '运维', NOW(), 'admin', NOW(), 'admin', NOW(), 1, 0);
|
(2, 2, 'it_manager', '运维', NOW(), 'admin', NOW(), 'admin', NOW(), 1, 0);
|
||||||
|
|
||||||
-- 初始化团队配置数据
|
|
||||||
INSERT INTO deploy_team_config (team_id, allowed_environment_ids, environment_approval_required, approver_user_ids, create_by, create_time, update_by, update_time, version, deleted)
|
|
||||||
VALUES
|
|
||||||
-- 平台研发团队配置(可访问开发和测试环境,都不需要审批)
|
|
||||||
(1, '[1, 2]', '[false, false]', '[null, null]', 'admin', NOW(), 'admin', NOW(), 1, 0),
|
|
||||||
-- DevOps团队配置(可访问所有环境,生产环境需要审批,审批人是用户1和4)
|
|
||||||
(2, '[1, 2, 3]', '[false, false, true]', '[null, null, [1, 4]]', 'admin', NOW(), 'admin', NOW(), 1, 0);
|
|
||||||
|
|
||||||
-- ====================================================================
|
-- ====================================================================
|
||||||
-- 定时任务初始化数据
|
-- 定时任务初始化数据
|
||||||
|
|||||||
@ -846,29 +846,40 @@ CREATE TABLE deploy_team_application
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='团队应用关联表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='团队应用关联表';
|
||||||
|
|
||||||
-- 团队配置表
|
-- 团队配置表
|
||||||
CREATE TABLE deploy_team_config
|
CREATE TABLE deploy_team_environment_config
|
||||||
(
|
(
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||||
create_by VARCHAR(100) NULL COMMENT '创建人',
|
create_by VARCHAR(100) NULL COMMENT '创建人',
|
||||||
create_time DATETIME(6) NULL COMMENT '创建时间',
|
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||||
update_by VARCHAR(100) NULL COMMENT '更新人',
|
update_by VARCHAR(100) NULL COMMENT '更新人',
|
||||||
update_time DATETIME(6) NULL COMMENT '更新时间',
|
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||||
version INT NOT NULL DEFAULT 1 COMMENT '版本号',
|
version INT NOT NULL DEFAULT 1 COMMENT '版本号',
|
||||||
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除',
|
||||||
|
|
||||||
team_id BIGINT NOT NULL COMMENT '团队ID',
|
team_id BIGINT NOT NULL COMMENT '团队ID',
|
||||||
|
environment_id BIGINT NOT NULL COMMENT '环境ID',
|
||||||
|
|
||||||
-- 环境权限配置
|
-- 审批配置
|
||||||
allowed_environment_ids JSON NULL COMMENT '团队可访问的环境ID列表,如:[1, 2, 3]',
|
approval_required BIT NOT NULL DEFAULT 0 COMMENT '是否需要审批',
|
||||||
|
approver_user_ids JSON NULL COMMENT '审批人用户ID列表,如:[1, 4, 7]',
|
||||||
|
|
||||||
-- 审批配置(与allowed_environment_ids位置对应)
|
-- 通知配置
|
||||||
environment_approval_required JSON NULL COMMENT '各环境是否需要审批(boolean数组),如:[false, false, true]',
|
notification_channel_id BIGINT NULL COMMENT '通知渠道ID',
|
||||||
approver_user_ids JSON NULL COMMENT '各环境的审批人列表(数组,无审批人用null),如:[null, null, [1, 4]]',
|
notification_enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用部署通知',
|
||||||
|
|
||||||
UNIQUE INDEX uk_team (team_id),
|
-- 安全策略
|
||||||
INDEX idx_deleted (deleted),
|
require_code_review BIT NOT NULL DEFAULT 0 COMMENT '是否要求代码审查通过',
|
||||||
CONSTRAINT fk_team_config_team FOREIGN KEY (team_id) REFERENCES deploy_team (id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='团队配置表';
|
-- 备注
|
||||||
|
remark VARCHAR(500) NULL COMMENT '备注信息',
|
||||||
|
|
||||||
|
UNIQUE INDEX uk_team_env (team_id, environment_id),
|
||||||
|
INDEX idx_team (team_id),
|
||||||
|
INDEX idx_env (environment_id),
|
||||||
|
INDEX idx_deleted (deleted),
|
||||||
|
CONSTRAINT fk_team_env_config_team FOREIGN KEY (team_id) REFERENCES deploy_team (id),
|
||||||
|
CONSTRAINT fk_team_env_config_env FOREIGN KEY (environment_id) REFERENCES deploy_environment (id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='团队环境配置表';
|
||||||
|
|
||||||
-- --------------------------------------------------------------------------------------
|
-- --------------------------------------------------------------------------------------
|
||||||
-- 通知渠道表
|
-- 通知渠道表
|
||||||
|
|||||||
@ -0,0 +1,527 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogFooter,
|
||||||
|
DialogBody,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||||
|
import { Search, Check, ChevronDown, Loader2 } from 'lucide-react';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
|
import type {
|
||||||
|
TeamApplication,
|
||||||
|
Application,
|
||||||
|
} from '../types';
|
||||||
|
import type { RepositoryBranchResponse } from '@/pages/Resource/Git/List/types';
|
||||||
|
import type { WorkflowDefinition } from '@/pages/Workflow/Definition/List/types';
|
||||||
|
|
||||||
|
interface TeamApplicationDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
mode: 'create' | 'edit';
|
||||||
|
teamId: number;
|
||||||
|
environmentId: number;
|
||||||
|
environmentName: string;
|
||||||
|
application?: TeamApplication; // 编辑时传入
|
||||||
|
applications: Application[]; // 可选择的应用列表
|
||||||
|
jenkinsSystems: any[];
|
||||||
|
workflowDefinitions: WorkflowDefinition[];
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
onSave: (data: {
|
||||||
|
id?: number;
|
||||||
|
appId: number;
|
||||||
|
branch: string;
|
||||||
|
deploySystemId: number | null;
|
||||||
|
deployJob: string;
|
||||||
|
workflowDefinitionId: number | null;
|
||||||
|
}) => Promise<void>;
|
||||||
|
onLoadBranches: (appId: number, app: Application) => Promise<RepositoryBranchResponse[]>;
|
||||||
|
onLoadJenkinsJobs: (systemId: number) => Promise<any[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||||
|
open,
|
||||||
|
mode,
|
||||||
|
teamId,
|
||||||
|
environmentId,
|
||||||
|
environmentName,
|
||||||
|
application,
|
||||||
|
applications,
|
||||||
|
jenkinsSystems,
|
||||||
|
workflowDefinitions,
|
||||||
|
onOpenChange,
|
||||||
|
onSave,
|
||||||
|
onLoadBranches,
|
||||||
|
onLoadJenkinsJobs,
|
||||||
|
}) => {
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
// 表单状态
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
appId: null as number | null,
|
||||||
|
branch: '',
|
||||||
|
deploySystemId: null as number | null,
|
||||||
|
deployJob: '',
|
||||||
|
workflowDefinitionId: null as number | null,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
const [branches, setBranches] = useState<RepositoryBranchResponse[]>([]);
|
||||||
|
const [loadingBranches, setLoadingBranches] = useState(false);
|
||||||
|
const [jenkinsJobs, setJenkinsJobs] = useState<any[]>([]);
|
||||||
|
const [loadingJobs, setLoadingJobs] = useState(false);
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
|
// 搜索和弹窗状态
|
||||||
|
const [branchSearchValue, setBranchSearchValue] = useState('');
|
||||||
|
const [branchPopoverOpen, setBranchPopoverOpen] = useState(false);
|
||||||
|
const [jobSearchValue, setJobSearchValue] = useState('');
|
||||||
|
const [jobPopoverOpen, setJobPopoverOpen] = useState(false);
|
||||||
|
|
||||||
|
// 初始化表单数据
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
if (mode === 'edit' && application) {
|
||||||
|
// 编辑模式:填充现有数据
|
||||||
|
setFormData({
|
||||||
|
appId: application.applicationId,
|
||||||
|
branch: application.branch || '',
|
||||||
|
deploySystemId: application.deploySystemId || null,
|
||||||
|
deployJob: application.deployJob || '',
|
||||||
|
workflowDefinitionId: application.workflowDefinitionId || null,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载分支和Jobs
|
||||||
|
const app = applications.find(a => a.id === application.applicationId);
|
||||||
|
if (app) {
|
||||||
|
loadBranches(application.applicationId, app);
|
||||||
|
}
|
||||||
|
if (application.deploySystemId) {
|
||||||
|
loadJenkinsJobs(application.deploySystemId);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 新建模式:重置表单
|
||||||
|
setFormData({
|
||||||
|
appId: null,
|
||||||
|
branch: '',
|
||||||
|
deploySystemId: null,
|
||||||
|
deployJob: '',
|
||||||
|
workflowDefinitionId: null,
|
||||||
|
});
|
||||||
|
setBranches([]);
|
||||||
|
setJenkinsJobs([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [open, mode, application]);
|
||||||
|
|
||||||
|
// 加载分支列表
|
||||||
|
const loadBranches = async (appId: number, app: Application) => {
|
||||||
|
setLoadingBranches(true);
|
||||||
|
try {
|
||||||
|
const branchList = await onLoadBranches(appId, app);
|
||||||
|
setBranches(branchList || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载分支失败:', error);
|
||||||
|
setBranches([]);
|
||||||
|
} finally {
|
||||||
|
setLoadingBranches(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载 Jenkins Jobs
|
||||||
|
const loadJenkinsJobs = async (systemId: number) => {
|
||||||
|
setLoadingJobs(true);
|
||||||
|
try {
|
||||||
|
const jobs = await onLoadJenkinsJobs(systemId);
|
||||||
|
setJenkinsJobs(jobs || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载Jenkins Jobs失败:', error);
|
||||||
|
setJenkinsJobs([]);
|
||||||
|
} finally {
|
||||||
|
setLoadingJobs(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理应用选择
|
||||||
|
const handleAppChange = (appId: number) => {
|
||||||
|
const app = applications.find(a => a.id === appId);
|
||||||
|
setFormData({
|
||||||
|
appId: appId,
|
||||||
|
branch: '',
|
||||||
|
deploySystemId: null,
|
||||||
|
deployJob: '',
|
||||||
|
workflowDefinitionId: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载该应用的分支列表
|
||||||
|
if (app) {
|
||||||
|
loadBranches(appId, app);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理 Jenkins 系统选择
|
||||||
|
const handleJenkinsSystemChange = (systemId: number) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
deploySystemId: systemId,
|
||||||
|
deployJob: '',
|
||||||
|
});
|
||||||
|
loadJenkinsJobs(systemId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存
|
||||||
|
const handleSave = async () => {
|
||||||
|
// 表单验证
|
||||||
|
if (!formData.appId) {
|
||||||
|
toast({
|
||||||
|
variant: 'destructive',
|
||||||
|
title: '请选择应用',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSaving(true);
|
||||||
|
try {
|
||||||
|
await onSave({
|
||||||
|
id: mode === 'edit' && application ? application.id : undefined,
|
||||||
|
appId: formData.appId,
|
||||||
|
branch: formData.branch,
|
||||||
|
deploySystemId: formData.deploySystemId,
|
||||||
|
deployJob: formData.deployJob,
|
||||||
|
workflowDefinitionId: formData.workflowDefinitionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: mode === 'edit' ? '保存成功' : '添加成功',
|
||||||
|
});
|
||||||
|
onOpenChange(false);
|
||||||
|
} catch (error: any) {
|
||||||
|
toast({
|
||||||
|
variant: 'destructive',
|
||||||
|
title: mode === 'edit' ? '保存失败' : '添加失败',
|
||||||
|
description: error.response?.data?.message || error.message,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 分支过滤
|
||||||
|
const filteredBranches = branches.filter(branch =>
|
||||||
|
branch.name.toLowerCase().includes(branchSearchValue.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
// Job 过滤
|
||||||
|
const filteredJobs = jenkinsJobs.filter(job =>
|
||||||
|
job.jobName.toLowerCase().includes(jobSearchValue.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="max-w-2xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
{mode === 'edit' ? '编辑' : '添加'}应用配置 - {environmentName}
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<DialogBody>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* 应用选择 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>
|
||||||
|
应用名称 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={formData.appId?.toString() || ''}
|
||||||
|
onValueChange={(value) => handleAppChange(Number(value))}
|
||||||
|
disabled={mode === 'edit'} // 编辑时不可修改应用
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择应用" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{applications.length === 0 ? (
|
||||||
|
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||||
|
暂无应用
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
applications.map((app) => (
|
||||||
|
<SelectItem key={app.id} value={app.id.toString()}>
|
||||||
|
{app.appName}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
{mode === 'edit' && (
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
编辑时不可修改应用,如需更换请删除后重新添加
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 分支选择 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>分支</Label>
|
||||||
|
{formData.appId ? (
|
||||||
|
<Popover
|
||||||
|
open={branchPopoverOpen}
|
||||||
|
onOpenChange={setBranchPopoverOpen}
|
||||||
|
>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
disabled={loadingBranches || branches.length === 0}
|
||||||
|
className={cn(
|
||||||
|
'w-full justify-between',
|
||||||
|
!formData.branch && 'text-muted-foreground'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{formData.branch
|
||||||
|
? (() => {
|
||||||
|
const selectedBranch = branches.find(
|
||||||
|
(b) => b.name === formData.branch
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<span className="flex items-center gap-2 truncate">
|
||||||
|
{selectedBranch?.name}
|
||||||
|
{selectedBranch?.isDefaultBranch && (
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
(默认)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})()
|
||||||
|
: loadingBranches
|
||||||
|
? '加载中...'
|
||||||
|
: branches.length === 0
|
||||||
|
? '无分支'
|
||||||
|
: '选择分支'}
|
||||||
|
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-full p-0">
|
||||||
|
<div className="flex items-center border-b px-3">
|
||||||
|
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
<input
|
||||||
|
placeholder="搜索分支..."
|
||||||
|
className="flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground"
|
||||||
|
value={branchSearchValue}
|
||||||
|
onChange={(e) => setBranchSearchValue(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ScrollArea className="h-[200px]">
|
||||||
|
<div className="p-1">
|
||||||
|
{filteredBranches.length === 0 ? (
|
||||||
|
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||||
|
未找到分支
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
filteredBranches.map((branch) => (
|
||||||
|
<div
|
||||||
|
key={branch.id}
|
||||||
|
className={cn(
|
||||||
|
'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent',
|
||||||
|
branch.name === formData.branch &&
|
||||||
|
'bg-accent'
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
setFormData({ ...formData, branch: branch.name });
|
||||||
|
setBranchSearchValue('');
|
||||||
|
setBranchPopoverOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="flex-1 truncate">
|
||||||
|
{branch.name}
|
||||||
|
{branch.isDefaultBranch && (
|
||||||
|
<span className="ml-2 text-xs text-muted-foreground">
|
||||||
|
(默认)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
{branch.name === formData.branch && (
|
||||||
|
<Check className="ml-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
) : (
|
||||||
|
<Input placeholder="请先选择应用" disabled />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Jenkins 系统 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Jenkins系统</Label>
|
||||||
|
<Select
|
||||||
|
value={formData.deploySystemId?.toString() || ''}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
handleJenkinsSystemChange(Number(value))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择Jenkins系统" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{jenkinsSystems.length === 0 ? (
|
||||||
|
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||||
|
暂无Jenkins系统
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
jenkinsSystems.map((system) => (
|
||||||
|
<SelectItem key={system.id} value={system.id.toString()}>
|
||||||
|
{system.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Jenkins Job */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Jenkins Job</Label>
|
||||||
|
{formData.deploySystemId ? (
|
||||||
|
<Popover open={jobPopoverOpen} onOpenChange={setJobPopoverOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
disabled={loadingJobs || jenkinsJobs.length === 0}
|
||||||
|
className={cn(
|
||||||
|
'w-full justify-between',
|
||||||
|
!formData.deployJob && 'text-muted-foreground'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="truncate">
|
||||||
|
{formData.deployJob ||
|
||||||
|
(loadingJobs
|
||||||
|
? '加载中...'
|
||||||
|
: jenkinsJobs.length === 0
|
||||||
|
? '无Job'
|
||||||
|
: '选择Job')}
|
||||||
|
</span>
|
||||||
|
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-full p-0">
|
||||||
|
<div className="flex items-center border-b px-3">
|
||||||
|
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
<input
|
||||||
|
placeholder="搜索Job..."
|
||||||
|
className="flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground"
|
||||||
|
value={jobSearchValue}
|
||||||
|
onChange={(e) => setJobSearchValue(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ScrollArea className="h-[200px]">
|
||||||
|
<div className="p-1">
|
||||||
|
{filteredJobs.length === 0 ? (
|
||||||
|
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||||
|
未找到Job
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
filteredJobs.map((job) => (
|
||||||
|
<div
|
||||||
|
key={job.id}
|
||||||
|
className={cn(
|
||||||
|
'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent',
|
||||||
|
job.jobName === formData.deployJob && 'bg-accent'
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
deployJob: job.jobName,
|
||||||
|
});
|
||||||
|
setJobSearchValue('');
|
||||||
|
setJobPopoverOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="flex-1 truncate">
|
||||||
|
{job.jobName}
|
||||||
|
</span>
|
||||||
|
{job.jobName === formData.deployJob && (
|
||||||
|
<Check className="ml-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
) : (
|
||||||
|
<Input placeholder="请先选择Jenkins系统" disabled />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 工作流定义 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>工作流定义</Label>
|
||||||
|
<Select
|
||||||
|
value={formData.workflowDefinitionId?.toString() || ''}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
workflowDefinitionId: Number(value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择工作流" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{workflowDefinitions.length === 0 ? (
|
||||||
|
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||||
|
暂无已发布的工作流
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
workflowDefinitions.map((workflow) => (
|
||||||
|
<SelectItem key={workflow.id} value={workflow.id.toString()}>
|
||||||
|
{workflow.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogBody>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleSave} disabled={saving}>
|
||||||
|
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
|
{mode === 'edit' ? '保存' : '添加'}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { TeamApplicationDialog };
|
||||||
|
export default TeamApplicationDialog;
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user