# Deploy Ease Platform

Deploy Ease Platform 是一个现代化的部署管理平台，旨在简化和自动化应用程序的部署流程。

## 已实现功能

### 1. 外部系统管理
- [x] 基础功能
  - 创建外部系统（支持Jenkins、Git等类型）
  - 更新系统信息
  - 删除系统
  - 查询系统详情
  - 分页查询系统列表
  - 条件筛选查询
- [x] Jenkins集成
  - 连接测试
  - 任务同步
  - 状态检查
  - 支持用户名密码认证
  - 支持Token认证
- [x] Git集成
  - 仓库连接测试
  - 代码分支同步
  - 提交历史查询
  - 支持用户名密码认证
  - 支持SSH密钥认证
  - 支持Token认证

### 2. 用户权限管理
- [x] 用户管理
  - 用户注册
  - 用户登录
  - 密码重置
  - 用户信息修改
  - 用户状态管理
  - 部门分配
- [x] 角色管理
  - 角色创建与维护
  - 角色标签管理
  - 角色权限分配
  - 用户角色分配
- [x] 权限管理
  - 权限创建与维护
  - 权限分类管理
  - 权限与菜单关联
  - 权限检查与控制

### 3. 菜单管理
- [x] 基础功能
  - 菜单树维护
  - 菜单排序
  - 菜单显示控制
- [x] 权限集成
  - 菜单权限配置
  - 按钮权限配置
  - 数据权限配置
- [x] 前端集成
  - 动态路由生成
  - 菜单组件加载
  - 权限指令集成

### 4. 部门管理
- [x] 基础功能
  - 部门树维护
  - 部门编码管理
  - 部门描述维护
  - 部门排序
- [x] 人员管理
  - 部门负责人设置
  - 部门人员管理
  - 部门权限控制

### 5. 系统功能
- [x] 认证授权
  - JWT令牌认证
  - 权限拦截器
  - 角色检查器
  - 权限注解支持
- [x] 数据审计
  - 创建人记录
  - 创建时间记录
  - 更新人记录
  - 更新时间记录
  - 版本控制
- [x] 异常处理
  - 统一异常处理
  - 业务异常处理
  - 系统异常处理
  - 参数校验异常处理
- [x] 数据库支持
  - Flyway数据库版本控制
  - 基础表结构
  - 初始化数据
  - 软删除支持

### 6. 开发支持
- [x] 基础框架
  - 统一响应格式
  - 统一分页格式
  - 基础CRUD封装
  - DTO自动转换
- [x] 代码规范
  - 开发规范文档
  - 接口文档规范
  - 测试规范
  - 注释规范
- [x] 测试支持
  - 单元测试框架
  - 集成测试支持
  - 测试数据构建
  - Mock支持

### 7. 租户管理API

#### 7.1 分页查询租户
```http
GET /api/v1/tenant/page

请求参数：
{
    "pageNum": 1,              // 页码（从1开始）
    "pageSize": 10,            // 每页大小
    "sortField": "createTime", // 排序字段
    "sortOrder": "desc",       // 排序方式
    "code": "string",          // 租户编码（可选）
    "name": "string",          // 租户名称（可选）
    "enabled": true           // 是否启用（可选）
}

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": {
        "content": [{
            "id": 1,
            "createTime": "2024-01-01 12:00:00",
            "createBy": "admin",
            "updateTime": "2024-01-01 12:00:00",
            "updateBy": "admin",
            "code": "TEST",
            "name": "测试租户",
            "address": "北京市朝阳区",
            "contactName": "张三",
            "contactPhone": "13800138000",
            "email": "test@example.com",
            "enabled": true
        }],
        "totalElements": 100,
        "totalPages": 10,
        "size": 10,
        "number": 0,
        "first": true,
        "last": false,
        "empty": false
    }
}
```

#### 7.2 创建租户
```http
POST /api/v1/tenant

请求参数：
{
    "code": "TEST",                    // 租户编码（必填）
    "name": "测试租户",                // 租户名称（必填）
    "address": "北京市朝阳区",         // 地址（可选）
    "contactName": "张三",            // 联系人姓名（可选）
    "contactPhone": "13800138000",    // 联系人电话（可选）
    "email": "test@example.com",      // 联系人邮箱（可选）
    "enabled": true                   // 是否启用（可选，默认true）
}

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": {
        "id": 1,
        // ... 其他字段同上
    }
}
```

#### 7.3 更新租户
```http
PUT /api/v1/tenant/{id}

请求参数：
{
    // 同创建接口
}

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": {
        "id": 1,
        // ... 其他字段同上
    }
}
```

#### 7.4 删除租户
```http
DELETE /api/v1/tenant/{id}

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功"
}
```

#### 7.5 获取租户状态
```http
GET /api/v1/tenant/{id}/enabled

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": true    // true: 启用，false: 禁用
}
```

#### 7.6 更新租户状态
```http
PUT /api/v1/tenant/{id}/enabled

请求参数：
enabled=true    // 是否启用（必填）

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功"
}
```

## 技术栈

### 后端技术栈
- Spring Boot 3.x
- Spring Security + JWT
- Spring Data JPA
- Flyway数据库版本控制
- MapStruct对象映射
- Lombok
- JUnit 5

### 数据库
- MySQL 8.0+

## API文档

### 1. 外部系统管理API

#### 1.1 分页查询
```http
GET /api/v1/external-system/page

请求参数：
{
    "pageNum": 1,              // 页码（从1开始）
    "pageSize": 10,            // 每页大小
    "sortField": "createTime", // 排序字段
    "sortOrder": "desc",       // 排序方式
    "name": "string",          // 系统名称（可选）
    "type": "JENKINS",         // 系统类型（可选）
    "enabled": true           // 是否启用（可选）
}

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": {
        "content": [{
            "id": 1,
            "createTime": "2024-01-01 12:00:00",
            "createBy": "admin",
            "updateTime": "2024-01-01 12:00:00",
            "updateBy": "admin",
            "name": "Jenkins测试环境",
            "type": "JENKINS",
            "url": "http://jenkins.test.com",
            "remark": "测试环境Jenkins服务器",
            "sort": 1,
            "enabled": true,
            "authType": "USERNAME_PASSWORD",
            "username": "admin",
            "password": "******",
            "token": null,
            "syncStatus": "SUCCESS",
            "lastSyncTime": "2024-01-01 12:00:00",
            "config": "{}"
        }],
        "totalElements": 100,
        "totalPages": 10,
        "size": 10,
        "number": 0,
        "first": true,
        "last": false,
        "empty": false
    }
}
```

#### 1.2 创建外部系统
```http
POST /api/v1/external-system

请求参数：
{
    "name": "Jenkins测试环境",           // 系统名称（必填）
    "type": "JENKINS",                  // 系统类型（必填）
    "url": "http://jenkins.test.com",   // 系统地址（必填）
    "remark": "测试环境Jenkins服务器",    // 备注（可选）
    "sort": 1,                         // 排序（可选）
    "enabled": true,                   // 是否启用（可选，默认true）
    "authType": "USERNAME_PASSWORD",    // 认证方式（必填）
    "username": "admin",               // 用户名（可选）
    "password": "password123",         // 密码（可选）
    "token": null,                     // 访问令牌（可选）
    "config": "{}"                     // 配置信息（可选）
}

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": {
        "id": 1,
        // ... 其他字段同上
    }
}
```

#### 1.3 更新外部系统
```http
PUT /api/v1/external-system/{id}

请求参数：
{
    // 同创建接口
}

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": {
        "id": 1,
        // ... 其他字段同上
    }
}
```

#### 1.4 删除外部系统
```http
DELETE /api/v1/external-system/{id}

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功"
}
```

#### 1.5 测试连接
```http
POST /api/v1/external-system/{id}/test-connection

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": true
}
```

#### 1.6 同步数据
```http
POST /api/v1/external-system/{id}/sync

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功"
}
```

#### 1.7 检查状态
```http
GET /api/v1/external-system/{id}/status

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": {
        "syncStatus": "SUCCESS",
        "lastSyncTime": "2024-01-01 12:00:00"
    }
}
```

### 2. 用户认证API

#### 2.1 用户登录
```http
POST /api/v1/user/login

请求参数：
{
    "username": "admin",     // 用户名（必填）
    "password": "password"   // 密码（必填）
}

响应结果：
{
    "success": true,
    "code": 200,
    "message": "登录成功",
    "data": {
        "token": "eyJhbGciOiJIUzI1NiJ9...",
        "user": {
            "id": 1,
            "username": "admin",
            "nickname": "管理员",
            "email": "admin@example.com",
            "phone": "13800138000",
            "departmentId": 1,
            "departmentName": "技术部"
        }
    }
}
```

#### 2.2 获取当前用户信息
```http
GET /api/v1/user/current

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": {
        "id": 1,
        "username": "admin",
        "nickname": "管理员",
        "email": "admin@example.com",
        "phone": "13800138000",
        "departmentId": 1,
        "departmentName": "技术部"
    }
}
```

### 3. 菜单管理API

#### 3.1 获取当前用户菜单
```http
GET /api/v1/menu/current

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": [{
        "id": 1,
        "name": "系统管理",
        "path": "/system",
        "component": "Layout",
        "icon": "setting",
        "permission": "system:view",
        "type": 1,           // 1：目录 2：菜单 3：按钮
        "parentId": null,
        "sort": 1,
        "hidden": false,
        "children": [{
            "id": 2,
            "name": "用户管理",
            "path": "/system/user",
            "component": "system/user/index",
            "icon": "user",
            "permission": "system:user:view",
            "type": 2,
            "parentId": 1,
            "sort": 1,
            "hidden": false,
            "children": []
        }]
    }]
}
```

#### 3.2 获取菜单树
```http
GET /api/v1/menu/tree

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": [{
        // 结构同上
    }]
}
```

#### 3.3 获取权限树
```http
GET /api/v1/menu/permission-tree

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": [{
        "id": 1,
        "name": "系统管理",
        "type": 1,
        "children": [{
            "id": 2,
            "name": "用户管理",
            "type": 2,
            "permissions": [{
                "id": 3,
                "code": "system:user:add",
                "name": "新增用户",
                "type": "BUTTON"
            }]
        }]
    }]
}
```

### 4. 部门管理API

#### 4.1 获取部门树
```http
GET /api/v1/department/tree

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": [{
        "id": 1,
        "code": "TECH",
        "name": "技术部",
        "description": "技术研发部门",
        "parentId": null,
        "sort": 1,
        "enabled": true,
        "leaderId": 1,
        "leaderName": "张三",
        "children": [{
            "id": 2,
            "code": "DEV",
            "name": "研发组",
            "description": "研发团队",
            "parentId": 1,
            "sort": 1,
            "enabled": true,
            "leaderId": 2,
            "leaderName": "李四",
            "children": []
        }]
    }]
}
```

#### 4.2 创建部门
```http
POST /api/v1/department

请求参数：
{
    "code": "TECH",           // 部门编码（必填）
    "name": "技术部",         // 部门名称（必填）
    "description": "技术研发部门", // 描述（可选）
    "parentId": null,         // 上级部门ID（可选）
    "sort": 1,               // 排序（可选）
    "enabled": true,         // 是否启用（可选，默认true）
    "leaderId": 1,           // 负责人ID（可选）
    "leaderName": "张三"      // 负责人姓名（可选）
}

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": {
        "id": 1,
        // ... 其他字段同上
    }
}
```

### 5. 角色管理API

#### 5.1 分配角色标签
```http
POST /api/v1/role/{id}/tags

请求参数：
{
    "tagIds": [1, 2, 3]      // 标签ID列表
}

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功"
}
```

#### 5.2 分配用户角色
```http
POST /api/v1/role/{userId}/assignRoles

请求参数：
{
    "roleIds": [1, 2, 3]     // 角色ID列表
}

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功"
}
```

#### 5.3 获取角色权限
```http
GET /api/v1/role/{id}/permissions

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": [{
        "id": 1,
        "code": "system:user:view",
        "name": "查看用户",
        "type": "MENU",
        "sort": 1,
        "menuName": "用户管理"
    }]
}
```

#### 5.4 分配角色权限
```http
POST /api/v1/role/{id}/permissions

请求参数：
{
    "permissionIds": [1, 2, 3]    // 权限ID列表
}

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功"
}
```

### 6. 权限管理API

#### 6.1 分页查询权限
```http
GET /api/v1/permission/page

请求参数：
{
    "pageNum": 1,
    "pageSize": 10,
    "code": "system",        // 权限编码（可选）
    "name": "系统",          // 权限名称（可选）
    "type": "MENU",         // 权限类型（可选）
    "enabled": true,        // 是否启用（可选）
    "menuId": 1             // 菜单ID（可选）
}

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": {
        "content": [{
            "id": 1,
            "code": "system:user:view",
            "name": "查看用户",
            "type": "MENU",
            "sort": 1,
            "menuId": 2,
            "menuName": "用户管理"
        }],
        "totalElements": 100,
        "totalPages": 10,
        "size": 10,
        "number": 0,
        "first": true,
        "last": false,
        "empty": false
    }
}
```

#### 6.2 创建权限
```http
POST /api/v1/permission

请求参数：
{
    "code": "system:user:add",    // 权限编码（必填）
    "name": "新增用户",           // 权限名称（必填）
    "type": "BUTTON",            // 权限类型（必填）
    "menuId": 2,                 // 菜单ID（必填）
    "sort": 1                    // 排序（可选）
}

响应结果：
{
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": {
        "id": 1,
        // ... 其他字段同上
    }
}
```

## 开发规范

### 后端开发规范
- 遵循DDD设计思想
- 统一的异常处理
- 统一的返回格式
- 完整的单元测试
- 详细的接口文档
- 规范的代码注释

详细规范请参考：[后端开发规范](.cursorrules1)

### 前端开发规范
- 统一的接口调用方式
- 统一的错误处理
- 标准的时间格式
- 规范的国际化实现

详细规范请参考：[前端开发规范](frontend.rules)

## 注意事项

### 1. 数据库
- 所有表必须包含基础字段：
  ```sql
  id          BIGINT PRIMARY KEY   # 主键
  create_time DATETIME             # 创建时间
  create_by   VARCHAR(50)          # 创建人
  update_time DATETIME             # 更新时间
  update_by   VARCHAR(50)          # 更新人
  version     INT                  # 版本号
  deleted     BOOLEAN              # 是否删除
  ```
- 使用Flyway进行数据库版本控制
- 新建表结构写入V1.0.0__init_schema.sql
- 初始数据写入V1.0.1__init_data.sql

### 2. 接口开发
- 所有接口都需要JWT认证
- 接口版本统一使用v1
- 返回数据格式统一为：
  ```json
  {
    "success": true,
    "code": 200,
    "message": "操作成功",
    "data": {}
  }
  ```
- 分页查询参数统一使用：
  ```json
  {
    "pageNum": 1,
    "pageSize": 10,
    "sortField": "createTime",
    "sortOrder": "desc"
  }
  ```

### 3. 异常处理
- 业务异常统一使用BusinessException
- 异常码在ResponseCode中定义
- 异常信息在messages.properties中定义
- 所有异常都通过GlobalExceptionHandler处理

### 4. 安全性
- 敏感信息（密码、Token等）需要加密存储
- 外部系统认证信息需要加密传输
- 定期清理过期的Token
- 限制API调用频率

### 5. 测试
- 所有Service层代码必须有单元测试
- 所有Controller必须有集成测试
- 测试覆盖率要求80%以上
- 测试数据使用@Sql注入，避免影响生产数据

## 环境要求
- JDK 17+
- Maven 3.8+
- MySQL 8.0+
- Node.js 16+

## 快速开始

1. 克隆项目
```bash
git clone [项目地址]
```

2. 配置数据库
```bash
# 创建数据库
CREATE DATABASE deploy_ease DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```

3. 修改配置
```yaml
# application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/deploy_ease
    username: your_username
    password: your_password
```

4. 启动项目
```bash
mvn spring-boot:run
```

## 贡献指南
1. Fork 项目
2. 创建功能分支
3. 提交代码
4. 创建Pull Request

## 许可证
[MIT License](LICENSE)
```


workflow_definition（工作流定义表）
存储我们自定义的工作流定义信息
包含流程名称、标识、版本、BPMN XML内容等
与Flowable的act_re_procdef表是一对一的关系
额外存储了图形编辑器的JSON数据，方便前端展示和编辑
workflow_instance（工作流实例表）
记录工作流的执行实例信息
存储流程实例ID、业务标识、状态、变量等
与Flowable的act_ru_execution表是一对一的关系
提供了更多的业务字段，如开始时间、结束时间等
workflow_node_instance（工作流节点实例表）
记录工作流中每个节点的执行情况
存储节点ID、名称、类型、状态等
与Flowable的act_ru_task和act_hi_actinst表有关联
特别记录了Shell任务的执行结果、错误信息等
workflow_log（工作流日志表）
记录工作流执行过程中的详细日志
可以关联到具体的实例和节点
包含日志类型、级别、内容等
方便排查问题和审计
这些表与Flowable的原生表的主要区别是：

更贴近业务需求，字段设计更符合我们的使用场景
提供了更丰富的元数据和状态信息
支持更细粒度的日志记录
可以存储自定义的业务数据
这样的设计让我们可以在使用Flowable强大的工作流引擎的同时，也能满足特定的业务需求。


{
    "name": "Simple Shell Workflow6",
    "key": "simple_shell_workflow",
    "description": "A simple workflow with shell task",
    "graphJson": {
        "cells": [
            {
                "id": "start",
                "shape": "start",
                "data": {
                    "label": "开始"
                },
                "position": {
                    "x": 100,
                    "y": 100
                }
            },
            {
                "id": "shell",
                "shape": "serviceTask",
                "data": {
                    "label": "Shell脚本",
                    "serviceTask": {
                        "type": "shell",
                        "implementation": "${shellTaskDelegate}",
                        "fields": {
                            "script": "timeout 10000000",
                            "workDir": "/tmp",
                            "env": {
                                "TEST_VAR": "test_value"
                            }
                        }
                    }
                },
                "position": {
                    "x": 300,
                    "y": 100
                }
            },
            {
                "id": "shellErrorBoundary",
                "shape": "boundaryEvent",
                "data": {
                    "label": "Shell错误",
                    "boundaryEvent": {
                        "type": "error",
                        "attachedToRef": "shell",
                        "errorRef": "SHELL_EXECUTION_ERROR"
                    }
                },
                "position": {
                    "x": 300,
                    "y": 160
                }
            },
            {
                "id": "errorHandler",
                "shape": "serviceTask",
                "data": {
                    "label": "错误处理",
                    "serviceTask": {
                        "type": "service",
                        "implementation": "${errorHandlerDelegate}"
                    }
                },
                "position": {
                    "x": 300,
                    "y": 220
                }
            },
            {
                "id": "end",
                "shape": "end",
                "data": {
                    "label": "结束"
                },
                "position": {
                    "x": 500,
                    "y": 100
                }
            },
            {
                "id": "flow1",
                "shape": "edge",
                "source": "start",
                "target": "shell",
                "data": {
                    "label": "流转到Shell"
                }
            },
            {
                "id": "flow2",
                "shape": "edge",
                "source": "shell",
                "target": "end",
                "data": {
                    "label": "完成"
                }
            },
            {
                "id": "flow_error",
                "shape": "edge",
                "source": "shellErrorBoundary",
                "target": "errorHandler",
                "data": {
                    "label": "错误处理"
                }
            },
            {
                "id": "flow_error_end",
                "shape": "edge",
                "source": "errorHandler",
                "target": "end",
                "data": {
                    "label": "处理完成"
                }
            }
        ]
    }
}

死信队列的问题：
需要再异常里直接移除：
runtimeService.deleteProcessInstance(processInstanceId, errorMessage);

const eventSource = new EventSource(`/api/v1/shell-logs/${processInstanceId}`);

eventSource.addEventListener('STDOUT', (event) => {
    console.log('Output:', event.data);
});

eventSource.addEventListener('STDERR', (event) => {
    console.error('Error:', event.data);
});

// 当流程结束时关闭连接
eventSource.onerror = () => {
    eventSource.close();
};

SELECT
    hi.PROC_INST_ID_ as process_instance_id,
    hi.PROC_DEF_ID_ as process_definition_id,
    hi.START_TIME_ as start_time,
    hi.END_TIME_ as end_time,
    hi.DELETE_REASON_ as delete_reason,
    -- 使用 GROUP_CONCAT 合并错误信息
    GROUP_CONCAT(DISTINCT
        CASE
            WHEN var.NAME_ = 'errorDetail' THEN var.TEXT_
            WHEN var.NAME_ IN ('errorMessage', 'error', 'exception') THEN var.TEXT_
        END
        SEPARATOR '; '
    ) as error_message,
    -- 获取最后一个活动节点
    MAX(CASE WHEN act.END_TIME_ IS NULL THEN act.ACT_NAME_ END) as last_activity_name,
    MAX(CASE WHEN act.END_TIME_ IS NULL THEN act.ACT_ID_ END) as last_activity_id
FROM
    ACT_HI_PROCINST hi
LEFT JOIN
    ACT_HI_VARINST var ON hi.PROC_INST_ID_ = var.PROC_INST_ID_
LEFT JOIN
    ACT_HI_ACTINST act ON hi.PROC_INST_ID_ = act.PROC_INST_ID_
WHERE
    hi.PROC_INST_ID_ = '86faae44-bc47-11ef-a4cd-00155dcaffac'
    AND (
        var.NAME_ IN ('errorDetail', 'errorMessage', 'error', 'exception')
        OR var.TEXT_ LIKE '%error%'
        OR var.TEXT_ LIKE '%exception%'
        OR var.TEXT_ LIKE '%失败%'
    )
GROUP BY
    hi.PROC_INST_ID_,
    hi.PROC_DEF_ID_,
    hi.START_TIME_,
    hi.END_TIME_,
    hi.DELETE_REASON_
ORDER BY
    hi.START_TIME_ DESC;