RMDC系统设计文档 整体转换为SKILL
This commit is contained in:
425
1-AgentSkills/coding-go-gin-gorm/SKILL.md
Normal file
425
1-AgentSkills/coding-go-gin-gorm/SKILL.md
Normal file
@@ -0,0 +1,425 @@
|
||||
---
|
||||
name: developing-go-gin-gorm
|
||||
description: Generates and reviews Go backend code using GIN and GORM frameworks. Enforces layered architecture (handler→service→dao), unified API response format, POST+RequestBody API design, DTO naming conventions, Chinese comments, Asia/Shanghai timezone, structured logging, and framework best practices. Trigger on Go API development, GIN handler creation, GORM repository implementation, or code review requests.
|
||||
argument-hint: "<action> <target>" e.g., "create user-handler", "review service/order.go", "scaffold api/v1/product"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
---
|
||||
|
||||
# Go GIN/GORM 开发规范 Skill
|
||||
|
||||
## 触发条件
|
||||
- 用户请求创建/修改 Go 后端代码(GIN handler、GORM dao、service)
|
||||
- 用户请求代码审查
|
||||
- 用户提及 API 开发、数据库操作、统一响应、日志、时间处理
|
||||
- 用户请求设计 API 接口、DTO 结构
|
||||
|
||||
## 上下文收集
|
||||
|
||||
执行前先收集项目信息:
|
||||
|
||||
!`ls -la go.mod go.sum 2>/dev/null || echo "No go.mod found"`
|
||||
|
||||
!`head -20 go.mod 2>/dev/null || echo ""`
|
||||
|
||||
## $ARGUMENTS 解析
|
||||
|
||||
期望格式:`<action> <target>`
|
||||
|
||||
| action | 说明 |
|
||||
|--------|------|
|
||||
| `create` | 创建新文件(handler/service/dao/dto) |
|
||||
| `review` | 审查现有代码 |
|
||||
| `scaffold` | 生成完整模块骨架 |
|
||||
| `fix` | 修复不符合规范的代码 |
|
||||
|
||||
---
|
||||
|
||||
## Plan 阶段
|
||||
|
||||
### 产物清单(按 action 确定)
|
||||
|
||||
| action | 产物 |
|
||||
|--------|------|
|
||||
| `create handler` | `/api/xxx_handler.go` 或 `/internal/handler/xxx.go` |
|
||||
| `create service` | `/internal/service/xxx_service.go` |
|
||||
| `create dao` | `/internal/dao/xxx_dao.go` |
|
||||
| `create dto` | `/internal/model/dto/xxx_dto.go` |
|
||||
| `scaffold` | 上述全部 + entity |
|
||||
|
||||
### 决策点
|
||||
|
||||
1. **目录风格**:检查项目是用 `/api` 还是 `/internal/handler`
|
||||
2. **模块命名**:从 $ARGUMENTS 提取资源名(如 `user`、`order`)
|
||||
3. **是否已存在**:先 Glob 检查目标文件
|
||||
|
||||
---
|
||||
|
||||
## Execute 阶段
|
||||
|
||||
### Handler 层编写规则
|
||||
|
||||
```
|
||||
1. 仅做:参数解析 → 调用 service → 返回响应
|
||||
2. 禁止:编写业务逻辑、直接操作数据库
|
||||
3. 必须:使用 common.ResponseSuccess / common.ResponseError
|
||||
4. 错误处理:gorm.ErrRecordNotFound → CodeNotFound
|
||||
```
|
||||
|
||||
### Service 层编写规则
|
||||
|
||||
```
|
||||
1. 编排 dao 层完成业务
|
||||
2. 记录关键业务日志(Info 级别)
|
||||
3. 错误包装:fmt.Errorf("xxx: %w", err)
|
||||
4. 业务异常记录 Warning 级别日志
|
||||
```
|
||||
|
||||
### DAO 层编写规则
|
||||
|
||||
```
|
||||
1. 封装所有 GORM 操作
|
||||
2. 禁止在 service 层写 SQL
|
||||
3. 复杂查询用 Raw/Exec
|
||||
4. 善用链式调用,但复杂场景优先原生 SQL
|
||||
```
|
||||
|
||||
### 统一响应格式(强制)
|
||||
|
||||
```go
|
||||
// 成功
|
||||
common.ResponseSuccess(c, data)
|
||||
common.ResponseSuccessWithMessage(c, data, "创建成功")
|
||||
|
||||
// 失败
|
||||
common.ResponseError(c, common.CodeParamError, "参数错误")
|
||||
common.ResponseErrorWithDetail(c, common.CodeServerError, "系统错误", err)
|
||||
```
|
||||
|
||||
错误码定义 → 读取 `reference/error-codes.go`
|
||||
|
||||
### 注释规范(强制中文)
|
||||
|
||||
```go
|
||||
// GetUserByID 根据用户ID获取用户信息
|
||||
// @param ctx context.Context - 请求上下文
|
||||
// @param userID int64 - 用户唯一ID
|
||||
// @return *model.User - 用户信息,未找到返回nil
|
||||
// @return error - 查询错误
|
||||
func (s *UserService) GetUserByID(ctx context.Context, userID int64) (*model.User, error)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API 设计规范(强制)
|
||||
|
||||
### 核心原则:POST + RequestBody
|
||||
|
||||
```
|
||||
所有 API 优先使用 POST 方法,参数通过 RequestBody 传递
|
||||
避免使用 PathVariables 和 RequestParams
|
||||
```
|
||||
|
||||
### 禁止与推荐
|
||||
|
||||
| 禁止 | 推荐 |
|
||||
|------|------|
|
||||
| `GET /api/projects/{project_id}` | `POST /api/projects/detail` + RequestBody |
|
||||
| `GET /api/users?role=admin&page=1` | `POST /api/users/list` + RequestBody |
|
||||
| URL 中传递敏感信息 | RequestBody 传递所有参数 |
|
||||
|
||||
### API 路径命名规范
|
||||
|
||||
| 操作 | 后缀 | 示例 |
|
||||
|------|------|------|
|
||||
| 列表查询 | `/list` | `POST /api/projects/list` |
|
||||
| 详情查询 | `/detail` | `POST /api/projects/detail` |
|
||||
| 创建 | `/create` | `POST /api/projects/create` |
|
||||
| 更新 | `/update` | `POST /api/projects/update` |
|
||||
| 删除 | `/delete` | `POST /api/projects/delete` |
|
||||
| 同步 | `/sync` | `POST /api/jenkins/organizations/sync` |
|
||||
| 触发 | `/trigger` | `POST /api/builds/trigger` |
|
||||
|
||||
### DTO 命名规范
|
||||
|
||||
| 类型 | 命名格式 | 示例 |
|
||||
|------|----------|------|
|
||||
| 列表请求 | `List{资源}Request` | `ListBuildsRequest` |
|
||||
| 详情请求 | `Get{资源}Request` | `GetBuildRequest` |
|
||||
| 创建请求 | `Create{资源}Request` | `CreateProjectRequest` |
|
||||
| 更新请求 | `Update{资源}Request` | `UpdateProjectRequest` |
|
||||
| 删除请求 | `Delete{资源}Request` | `DeleteProjectRequest` |
|
||||
| 列表响应 | `List{资源}Response` | `ListBuildsResponse` |
|
||||
| 详情响应 | `{资源}DetailResponse` | `BuildDetailResponse` |
|
||||
|
||||
### 通用分页结构
|
||||
|
||||
```go
|
||||
// 请求
|
||||
type PageRequest struct {
|
||||
Page int `json:"page" binding:"required,min=1"`
|
||||
PageSize int `json:"page_size" binding:"required,min=1,max=100"`
|
||||
}
|
||||
|
||||
// 响应
|
||||
type ListResponse struct {
|
||||
List []interface{} `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
```
|
||||
|
||||
### 模块错误码范围
|
||||
|
||||
| 范围 | 模块 |
|
||||
|------|------|
|
||||
| 0 | 成功 |
|
||||
| 1000-1999 | 通用错误 |
|
||||
| 2000-2999 | 用户/权限 |
|
||||
| 3000-3999 | Jenkins |
|
||||
| 4000-4999 | 项目管理 |
|
||||
| 5000-5999 | Exchange-Hub |
|
||||
|
||||
详细规范 → 读取 `reference/api-design-spec.md`
|
||||
|
||||
---
|
||||
|
||||
## 日志规范(强制)
|
||||
|
||||
### 指定框架
|
||||
项目统一使用 `rmdc-common/wdd_log/log_utils.go`
|
||||
|
||||
### 日志级别使用场景
|
||||
|
||||
| 级别 | 使用场景 | 示例 |
|
||||
|------|----------|------|
|
||||
| `Debug` | 开发调试,详细流程、变量值 | `log.Debug(ctx, "查询参数", map[string]interface{}{"userID": id})` |
|
||||
| `Info` | 关键业务节点 | `log.Info(ctx, "用户登录成功", ...)` / `log.Info(ctx, "订单创建成功", ...)` |
|
||||
| `Warning` | 可预期非致命异常,程序可继续 | `log.Warning(ctx, "外部API超时,启用备用方案", ...)` |
|
||||
| `Error` | 严重错误,业务流程中断 | `log.Error(ctx, "数据库连接失败", ...)` 必须记录堆栈 |
|
||||
|
||||
### 日志内容要求
|
||||
```
|
||||
1. 简练、关键
|
||||
2. 必须包含 TraceID、UserID 等追溯信息
|
||||
3. Error 级别必须记录完整错误堆栈
|
||||
```
|
||||
|
||||
### 日志记录位置
|
||||
|
||||
| 层级 | 记录内容 |
|
||||
|------|----------|
|
||||
| Handler | 使用 `ResponseErrorWithDetail` 自动记录 Error 日志 |
|
||||
| Service | 关键业务操作记录 Info;业务异常记录 Warning |
|
||||
| DAO | 一般不记录日志,错误向上抛出 |
|
||||
|
||||
---
|
||||
|
||||
## 时间处理(强制东八区)
|
||||
|
||||
### 核心规则
|
||||
```
|
||||
时区:Asia/Shanghai (UTC+8)
|
||||
格式:RFC3339
|
||||
```
|
||||
|
||||
### 禁止与必须
|
||||
|
||||
| 禁止 | 必须使用 |
|
||||
|------|----------|
|
||||
| `time.Now()` | `TimeUtils.Now()` |
|
||||
| `time.Parse()` | `TimeUtils.Parse()` |
|
||||
| 直接格式化 | `TimeUtils.Format()` |
|
||||
|
||||
### 工具库位置
|
||||
- 后端:`rmdc-common/utils/TimeUtils.go`
|
||||
- 前端:`TonyMask/src/utils/timeUtils.ts`
|
||||
|
||||
### 使用示例
|
||||
```go
|
||||
// ✅ 正确
|
||||
now := TimeUtils.Now()
|
||||
timestamp := TimeUtils.Now().Format(time.RFC3339)
|
||||
|
||||
// ❌ 错误
|
||||
now := time.Now() // 禁止直接使用
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 框架使用规范
|
||||
|
||||
### GIN 框架
|
||||
|
||||
#### 路由组织(强制分组)
|
||||
```go
|
||||
// ✅ 正确:使用路由分组
|
||||
v1 := r.Group("/api/v1")
|
||||
{
|
||||
users := v1.Group("/users")
|
||||
{
|
||||
users.GET("/:id", userHandler.GetByID)
|
||||
users.POST("/", userHandler.Create)
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ 错误:扁平路由
|
||||
r.GET("/api/v1/users/:id", ...)
|
||||
```
|
||||
|
||||
#### 中间件使用
|
||||
```go
|
||||
// 全局中间件
|
||||
r.Use(middleware.Recovery()) // 恢复
|
||||
r.Use(middleware.Logger()) // 日志
|
||||
r.Use(middleware.CORS()) // 跨域
|
||||
|
||||
// 路由组中间件
|
||||
authGroup := r.Group("/admin")
|
||||
authGroup.Use(middleware.Auth())
|
||||
```
|
||||
|
||||
#### 响应规范
|
||||
```
|
||||
所有 API 响应必须通过 pkg/common 统一响应函数
|
||||
禁止直接使用 c.JSON()、c.String() 等
|
||||
```
|
||||
|
||||
### GORM 框架
|
||||
|
||||
#### 操作位置
|
||||
```
|
||||
所有 GORM 操作必须在 dao 层
|
||||
严禁在 service 层拼接查询
|
||||
```
|
||||
|
||||
#### 链式调用 vs 原生 SQL
|
||||
|
||||
| 场景 | 推荐方式 |
|
||||
|------|----------|
|
||||
| 简单 CRUD | 链式调用 `db.Where().First()` |
|
||||
| 复杂查询(多表 JOIN、子查询) | `Raw()` / `Exec()` 原生 SQL |
|
||||
| 批量操作 | `Raw()` / `Exec()` 保证性能 |
|
||||
|
||||
```go
|
||||
// 简单查询 - 链式调用
|
||||
db.Where("status = ?", 1).Find(&users)
|
||||
|
||||
// 复杂查询 - 原生 SQL
|
||||
db.Raw(`
|
||||
SELECT u.*, COUNT(o.id) as order_count
|
||||
FROM users u
|
||||
LEFT JOIN orders o ON u.id = o.user_id
|
||||
WHERE u.status = ?
|
||||
GROUP BY u.id
|
||||
`, 1).Scan(&results)
|
||||
```
|
||||
|
||||
#### 错误处理
|
||||
```go
|
||||
// 必须处理 ErrRecordNotFound
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
common.ResponseError(c, common.CodeNotFound, "资源不存在")
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verify 阶段 Checklist
|
||||
|
||||
### 结构检查
|
||||
- [ ] 依赖方向正确:handler → service → dao(无反向引用)
|
||||
- [ ] handler 层无业务逻辑
|
||||
- [ ] dao 层无 service 引用
|
||||
- [ ] 使用 internal 包保护私有代码
|
||||
|
||||
### 响应检查
|
||||
- [ ] 所有 API 使用 `common.ResponseSuccess/Error`
|
||||
- [ ] 错误码来自 `common.Code*` 常量
|
||||
- [ ] 时间戳格式为 RFC3339
|
||||
- [ ] 无直接 `c.JSON()` 调用
|
||||
|
||||
### 代码检查
|
||||
- [ ] 公开函数/结构体有中文注释
|
||||
- [ ] 注释格式:`// 函数名 功能描述`
|
||||
- [ ] 无直接 `time.Now()` 调用
|
||||
- [ ] 无丢弃的 error(`_ = err` 禁止)
|
||||
- [ ] 包名小写无下划线
|
||||
|
||||
### 日志检查
|
||||
- [ ] 使用项目统一日志库
|
||||
- [ ] Error 日志包含完整堆栈
|
||||
- [ ] 关键业务操作有 Info 日志
|
||||
- [ ] 日志包含 TraceID 等追溯信息
|
||||
|
||||
### GORM 检查
|
||||
- [ ] `gorm.ErrRecordNotFound` 已处理
|
||||
- [ ] 复杂查询在 dao 层使用 Raw/Exec
|
||||
- [ ] 无 service 层直接 DB 操作
|
||||
|
||||
### GIN 检查
|
||||
- [ ] 使用路由分组组织 API
|
||||
- [ ] 通用逻辑使用中间件处理
|
||||
- [ ] 响应通过统一函数返回
|
||||
|
||||
### API 设计检查
|
||||
- [ ] 使用 POST + RequestBody(非 GET + PathVariables)
|
||||
- [ ] API 路径使用正确后缀(/list, /detail, /create 等)
|
||||
- [ ] DTO 命名符合规范(List/Get/Create/Update/Delete + 资源 + Request/Response)
|
||||
- [ ] 分页请求嵌入 PageRequest
|
||||
- [ ] 分页响应包含 list/total/page/page_size
|
||||
- [ ] 敏感信息不在 URL 中
|
||||
- [ ] 请求体必须验证(ShouldBindJSON)
|
||||
|
||||
---
|
||||
|
||||
## 常见陷阱
|
||||
|
||||
| 陷阱 | 正确做法 |
|
||||
|------|----------|
|
||||
| handler 写业务逻辑 | 移到 service 层 |
|
||||
| 直接 `c.JSON()` | 用 `common.ResponseSuccess()` |
|
||||
| 忽略 `ErrRecordNotFound` | 转为 `CodeNotFound` 返回 |
|
||||
| `time.Now()` | `TimeUtils.Now()` |
|
||||
| 英文注释 | 改为中文 |
|
||||
| dao 引用 service | 违反依赖原则,重构 |
|
||||
| service 写 SQL | 移到 dao 层 |
|
||||
| 扁平路由 | 使用 Router Group |
|
||||
| 日志缺少上下文 | 添加 TraceID、UserID |
|
||||
| Error 日志无堆栈 | 记录完整错误信息 |
|
||||
| `GET /api/users/{id}` | `POST /api/users/detail` + RequestBody |
|
||||
| URL 传参数 `?page=1` | RequestBody 传递 |
|
||||
| DTO 命名不规范 | 使用 `List/Get/Create/Update/Delete` + 资源名 |
|
||||
| 敏感信息在 URL | 移到 RequestBody |
|
||||
|
||||
---
|
||||
|
||||
## Reference 文件索引
|
||||
|
||||
| 场景 | 读取文件 |
|
||||
|------|----------|
|
||||
| 需要完整目录结构说明 | `reference/project-structure.md` |
|
||||
| 需要响应结构体定义 | `reference/api-response-spec.md` |
|
||||
| 需要错误码完整列表 | `reference/error-codes.go` |
|
||||
| 需要编码规范细节 | `reference/coding-standards.md` |
|
||||
| 需要日志使用详细说明 | `reference/logging-standards.md` |
|
||||
| 需要时间处理详细说明 | `reference/time-handling.md` |
|
||||
| 需要框架使用详细说明 | `reference/framework-usage.md` |
|
||||
| 需要 API 设计详细说明 | `reference/api-design-spec.md` |
|
||||
| 需要代码示例 | `examples/*.go` |
|
||||
|
||||
---
|
||||
|
||||
## 快速命令
|
||||
|
||||
验证项目结构:
|
||||
```bash
|
||||
./scripts/validate-structure.sh
|
||||
```
|
||||
55
1-AgentSkills/coding-go-gin-gorm/examples/dao-example.go
Normal file
55
1-AgentSkills/coding-go-gin-gorm/examples/dao-example.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"my-project/internal/model/entity"
|
||||
)
|
||||
|
||||
// UserDAO 用户数据访问对象
|
||||
type UserDAO struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewUserDAO 创建用户DAO实例
|
||||
// @param db *gorm.DB - 数据库连接
|
||||
// @return *UserDAO - DAO实例
|
||||
func NewUserDAO(db *gorm.DB) *UserDAO {
|
||||
return &UserDAO{db: db}
|
||||
}
|
||||
|
||||
// FindByID 根据ID查询用户
|
||||
// @param ctx context.Context - 请求上下文
|
||||
// @param id int64 - 用户ID
|
||||
// @return *entity.User - 用户实体
|
||||
// @return error - 查询错误,未找到返回gorm.ErrRecordNotFound
|
||||
func (d *UserDAO) FindByID(ctx context.Context, id int64) (*entity.User, error) {
|
||||
var user entity.User
|
||||
if err := d.db.WithContext(ctx).First(&user, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// Create 创建用户
|
||||
// @param ctx context.Context - 请求上下文
|
||||
// @param user *entity.User - 用户实体
|
||||
// @return error - 创建错误
|
||||
func (d *UserDAO) Create(ctx context.Context, user *entity.User) error {
|
||||
return d.db.WithContext(ctx).Create(user).Error
|
||||
}
|
||||
|
||||
// FindByEmail 根据邮箱查询用户
|
||||
// @param ctx context.Context - 请求上下文
|
||||
// @param email string - 用户邮箱
|
||||
// @return *entity.User - 用户实体
|
||||
// @return error - 查询错误
|
||||
func (d *UserDAO) FindByEmail(ctx context.Context, email string) (*entity.User, error) {
|
||||
var user entity.User
|
||||
if err := d.db.WithContext(ctx).Where("email = ?", email).First(&user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
70
1-AgentSkills/coding-go-gin-gorm/examples/handler-example.go
Normal file
70
1-AgentSkills/coding-go-gin-gorm/examples/handler-example.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"my-project/internal/model/dto"
|
||||
"my-project/internal/service"
|
||||
"my-project/pkg/common"
|
||||
)
|
||||
|
||||
// UserHandler 用户相关API处理器
|
||||
type UserHandler struct {
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
// NewUserHandler 创建用户Handler实例
|
||||
// @param userService *service.UserService - 用户服务
|
||||
// @return *UserHandler - Handler实例
|
||||
func NewUserHandler(userService *service.UserService) *UserHandler {
|
||||
return &UserHandler{userService: userService}
|
||||
}
|
||||
|
||||
// GetUserByID 根据ID获取用户信息
|
||||
// @param c *gin.Context - GIN上下文
|
||||
func (h *UserHandler) GetUserByID(c *gin.Context) {
|
||||
// 1. 参数解析
|
||||
idStr := c.Param("id")
|
||||
userID, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil {
|
||||
common.ResponseError(c, common.CodeParamError, "用户ID格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 调用Service
|
||||
user, err := h.userService.GetUserByID(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
common.ResponseError(c, common.CodeNotFound, "用户不存在")
|
||||
return
|
||||
}
|
||||
common.ResponseErrorWithDetail(c, common.CodeServerError, "获取用户失败", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 成功响应
|
||||
common.ResponseSuccess(c, user)
|
||||
}
|
||||
|
||||
// CreateUser 创建用户
|
||||
// @param c *gin.Context - GIN上下文
|
||||
func (h *UserHandler) CreateUser(c *gin.Context) {
|
||||
var req dto.CreateUserRequest
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
common.ResponseErrorWithDetail(c, common.CodeValidationFail, "参数验证失败", err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.userService.CreateUser(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
common.ResponseErrorWithDetail(c, common.CodeBusiness, "创建用户失败", err)
|
||||
return
|
||||
}
|
||||
|
||||
common.ResponseSuccessWithMessage(c, user, "用户创建成功")
|
||||
}
|
||||
60
1-AgentSkills/coding-go-gin-gorm/examples/service-example.go
Normal file
60
1-AgentSkills/coding-go-gin-gorm/examples/service-example.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"my-project/internal/dao"
|
||||
"my-project/internal/model/dto"
|
||||
"my-project/internal/model/entity"
|
||||
"my-project/pkg/log"
|
||||
)
|
||||
|
||||
// UserService 用户业务服务
|
||||
type UserService struct {
|
||||
userDAO *dao.UserDAO
|
||||
}
|
||||
|
||||
// NewUserService 创建用户服务实例
|
||||
// @param userDAO *dao.UserDAO - 用户数据访问对象
|
||||
// @return *UserService - 服务实例
|
||||
func NewUserService(userDAO *dao.UserDAO) *UserService {
|
||||
return &UserService{userDAO: userDAO}
|
||||
}
|
||||
|
||||
// GetUserByID 根据用户ID获取用户信息
|
||||
// @param ctx context.Context - 请求上下文
|
||||
// @param userID int64 - 用户唯一ID
|
||||
// @return *entity.User - 用户实体
|
||||
// @return error - 查询错误
|
||||
func (s *UserService) GetUserByID(ctx context.Context, userID int64) (*entity.User, error) {
|
||||
user, err := s.userDAO.FindByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询用户失败: %w", err)
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// CreateUser 创建新用户
|
||||
// @param ctx context.Context - 请求上下文
|
||||
// @param req *dto.CreateUserRequest - 创建请求
|
||||
// @return *entity.User - 创建的用户实体
|
||||
// @return error - 创建错误
|
||||
func (s *UserService) CreateUser(ctx context.Context, req *dto.CreateUserRequest) (*entity.User, error) {
|
||||
user := &entity.User{
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
}
|
||||
|
||||
if err := s.userDAO.Create(ctx, user); err != nil {
|
||||
return nil, fmt.Errorf("创建用户失败: %w", err)
|
||||
}
|
||||
|
||||
// 记录关键业务日志
|
||||
log.Info(ctx, "用户创建成功", map[string]interface{}{
|
||||
"userID": user.ID,
|
||||
"username": user.Username,
|
||||
})
|
||||
|
||||
return user, nil
|
||||
}
|
||||
330
1-AgentSkills/coding-go-gin-gorm/reference/api-design-spec.md
Normal file
330
1-AgentSkills/coding-go-gin-gorm/reference/api-design-spec.md
Normal file
@@ -0,0 +1,330 @@
|
||||
# API 设计规范
|
||||
|
||||
## 核心原则
|
||||
|
||||
### 1. 使用 POST + RequestBody
|
||||
|
||||
> **核心规范**: 所有 API 优先使用 POST 方法,参数通过 RequestBody 传递
|
||||
|
||||
```go
|
||||
// ✅ 推荐方式
|
||||
POST /api/jenkins/builds/list
|
||||
{
|
||||
"organization_folder": "Backend",
|
||||
"repository_name": "cmii-fly-center",
|
||||
"branch_name": "master",
|
||||
"page": 1,
|
||||
"page_size": 10
|
||||
}
|
||||
|
||||
// ❌ 避免使用
|
||||
GET /api/jenkins/organizations/{org}/repositories/{repo}/branches/{branch}/builds?page=1&page_size=10
|
||||
```
|
||||
|
||||
### 2. 避免 PathVariables
|
||||
|
||||
```go
|
||||
// ❌ 不推荐
|
||||
GET /api/projects/{project_id}
|
||||
GET /api/builds/{build_id}/console
|
||||
|
||||
// ✅ 推荐
|
||||
POST /api/projects/detail
|
||||
{
|
||||
"project_id": "namespace_abc12345"
|
||||
}
|
||||
|
||||
POST /api/builds/console
|
||||
{
|
||||
"organization_folder": "Backend",
|
||||
"repository_name": "cmii-fly-center",
|
||||
"branch_name": "master",
|
||||
"build_number": 123
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 避免 RequestParams
|
||||
|
||||
```go
|
||||
// ❌ 不推荐
|
||||
GET /api/users/list?role=admin&status=active&page=1
|
||||
|
||||
// ✅ 推荐
|
||||
POST /api/users/list
|
||||
{
|
||||
"role": "admin",
|
||||
"status": "active",
|
||||
"page": 1,
|
||||
"page_size": 20
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 统一响应格式
|
||||
|
||||
### 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
// 业务数据
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 分页响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"list": [...],
|
||||
"total": 100,
|
||||
"page": 1,
|
||||
"page_size": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 1001,
|
||||
"message": "参数错误: organization_folder不能为空",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 请求结构规范
|
||||
|
||||
### 通用分页请求
|
||||
|
||||
```go
|
||||
type PageRequest struct {
|
||||
Page int `json:"page" binding:"required,min=1"`
|
||||
PageSize int `json:"page_size" binding:"required,min=1,max=100"`
|
||||
}
|
||||
```
|
||||
|
||||
### 通用筛选请求
|
||||
|
||||
```go
|
||||
type ListRequest struct {
|
||||
PageRequest
|
||||
Keyword string `json:"keyword,omitempty"` // 搜索关键词
|
||||
Status string `json:"status,omitempty"` // 状态筛选
|
||||
SortBy string `json:"sort_by,omitempty"` // 排序字段
|
||||
SortOrder string `json:"sort_order,omitempty"` // asc/desc
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API 命名规范
|
||||
|
||||
### 操作类型后缀
|
||||
|
||||
| 操作 | 后缀 | 示例 |
|
||||
|------|------|------|
|
||||
| 列表查询 | `/list` | `/api/projects/list` |
|
||||
| 详情查询 | `/detail` | `/api/projects/detail` |
|
||||
| 创建 | `/create` | `/api/projects/create` |
|
||||
| 更新 | `/update` | `/api/projects/update` |
|
||||
| 删除 | `/delete` | `/api/projects/delete` |
|
||||
| 同步 | `/sync` | `/api/jenkins/organizations/sync` |
|
||||
| 触发 | `/trigger` | `/api/builds/trigger` |
|
||||
| 导出 | `/export` | `/api/projects/export` |
|
||||
|
||||
### 模块前缀
|
||||
|
||||
| 模块 | 前缀 |
|
||||
|------|------|
|
||||
| Jenkins | `/api/jenkins/` |
|
||||
| 项目管理 | `/api/projects/` |
|
||||
| 用户 | `/api/users/` |
|
||||
| 权限 | `/api/permissions/` |
|
||||
| 审计 | `/api/audit/` |
|
||||
| Exchange-Hub | `/api/exchange-hub/` |
|
||||
| DCU | `/api/dcu/` |
|
||||
|
||||
---
|
||||
|
||||
## Handler 实现模板
|
||||
|
||||
```go
|
||||
// ListBuilds 获取构建列表
|
||||
// @Summary 获取构建列表
|
||||
// @Tags 构建管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body dto.ListBuildsRequest true "请求参数"
|
||||
// @Success 200 {object} response.Response{data=dto.ListBuildsResponse}
|
||||
// @Router /api/jenkins/builds/list [post]
|
||||
func (h *BuildHandler) ListBuilds(c *gin.Context) {
|
||||
var req dto.ListBuildsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ParamError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.buildService.ListBuilds(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, resp)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DTO 设计规范
|
||||
|
||||
### 请求 DTO 命名
|
||||
|
||||
```go
|
||||
// 列表请求: List{资源}Request
|
||||
type ListBuildsRequest struct {
|
||||
PageRequest
|
||||
OrganizationFolder string `json:"organization_folder" binding:"required"`
|
||||
RepositoryName string `json:"repository_name" binding:"required"`
|
||||
BranchName string `json:"branch_name,omitempty"`
|
||||
}
|
||||
|
||||
// 详情请求: Get{资源}Request 或 {资源}DetailRequest
|
||||
type GetBuildRequest struct {
|
||||
OrganizationFolder string `json:"organization_folder" binding:"required"`
|
||||
RepositoryName string `json:"repository_name" binding:"required"`
|
||||
BranchName string `json:"branch_name" binding:"required"`
|
||||
BuildNumber int `json:"build_number" binding:"required"`
|
||||
}
|
||||
|
||||
// 创建请求: Create{资源}Request
|
||||
type CreateProjectRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Namespace string `json:"namespace" binding:"required"`
|
||||
Province string `json:"province" binding:"required"`
|
||||
City string `json:"city" binding:"required"`
|
||||
}
|
||||
|
||||
// 更新请求: Update{资源}Request
|
||||
type UpdateProjectRequest struct {
|
||||
ProjectID string `json:"project_id" binding:"required"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Province string `json:"province,omitempty"`
|
||||
City string `json:"city,omitempty"`
|
||||
}
|
||||
|
||||
// 删除请求: Delete{资源}Request
|
||||
type DeleteProjectRequest struct {
|
||||
ProjectID string `json:"project_id" binding:"required"`
|
||||
}
|
||||
```
|
||||
|
||||
### 响应 DTO 命名
|
||||
|
||||
```go
|
||||
// 列表响应: List{资源}Response
|
||||
type ListBuildsResponse struct {
|
||||
List []*BuildDTO `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
// 详情响应: {资源}DetailResponse 或直接使用 {资源}DTO
|
||||
type BuildDetailResponse struct {
|
||||
*BuildDTO
|
||||
ConsoleOutput string `json:"console_output,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 错误码规范
|
||||
|
||||
### 错误码范围
|
||||
|
||||
| 范围 | 模块 |
|
||||
|------|------|
|
||||
| 1000-1999 | 通用错误 |
|
||||
| 2000-2999 | 用户/权限 |
|
||||
| 3000-3999 | Jenkins模块 |
|
||||
| 4000-4999 | 项目管理 |
|
||||
| 5000-5999 | Exchange-Hub |
|
||||
| 6000-6999 | Watchdog |
|
||||
|
||||
### 通用错误码
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| 0 | 成功 |
|
||||
| 1001 | 参数错误 |
|
||||
| 1002 | 未授权 |
|
||||
| 1003 | 禁止访问 |
|
||||
| 1004 | 资源不存在 |
|
||||
| 1005 | 内部错误 |
|
||||
|
||||
---
|
||||
|
||||
## 前端调用示例
|
||||
|
||||
```typescript
|
||||
// api/modules/jenkins.ts
|
||||
export const jenkinsApi = {
|
||||
// 获取构建列表
|
||||
listBuilds: (data: ListBuildsRequest) =>
|
||||
request.post<ListBuildsResponse>('/api/jenkins/builds/list', data),
|
||||
|
||||
// 触发构建
|
||||
triggerBuild: (data: TriggerBuildRequest) =>
|
||||
request.post<TriggerBuildResponse>('/api/jenkins/builds/trigger', data),
|
||||
|
||||
// 获取构建详情
|
||||
getBuildDetail: (data: GetBuildRequest) =>
|
||||
request.post<BuildDetailResponse>('/api/jenkins/builds/detail', data),
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 安全规范
|
||||
|
||||
### 1. 敏感字段不出现在 URL
|
||||
|
||||
```go
|
||||
// ❌ 敏感信息泄露到URL
|
||||
GET /api/auth/login?username=admin&password=123456
|
||||
|
||||
// ✅ 使用RequestBody
|
||||
POST /api/auth/login
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "123456"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 必须验证请求体
|
||||
|
||||
```go
|
||||
func (h *Handler) CreateProject(c *gin.Context) {
|
||||
var req dto.CreateProjectRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ParamError(c, err)
|
||||
return
|
||||
}
|
||||
// 后续处理...
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 审计敏感操作
|
||||
|
||||
所有写操作需通过审计中间件记录。
|
||||
@@ -0,0 +1,35 @@
|
||||
# API 响应规范
|
||||
|
||||
## 统一响应结构
|
||||
|
||||
```go
|
||||
type Response struct {
|
||||
Code int `json:"code"` // 业务状态码,0=成功
|
||||
Status int `json:"status"` // HTTP 状态码
|
||||
Timestamp string `json:"timestamp"` // RFC3339 东八区
|
||||
Data interface{} `json:"data"` // 业务数据
|
||||
Message string `json:"message,omitempty"` // 消息
|
||||
Error string `json:"error,omitempty"` // 错误详情
|
||||
}
|
||||
```
|
||||
|
||||
## 使用函数
|
||||
|
||||
| 场景 | 函数 |
|
||||
|------|------|
|
||||
| 查询成功 | `ResponseSuccess(c, data)` |
|
||||
| 操作成功 | `ResponseSuccessWithMessage(c, data, "msg")` |
|
||||
| 普通错误 | `ResponseError(c, code, "msg")` |
|
||||
| 详细错误 | `ResponseErrorWithDetail(c, code, "msg", err)` |
|
||||
|
||||
## HTTP 状态码映射
|
||||
|
||||
| 业务码 | HTTP 状态码 |
|
||||
|--------|-------------|
|
||||
| CodeSuccess | 200 |
|
||||
| CodeParamError, CodeValidationFail | 400 |
|
||||
| CodeUnauthorized | 401 |
|
||||
| CodeForbidden | 403 |
|
||||
| CodeNotFound | 404 |
|
||||
| CodeTimeout | 408 |
|
||||
| 其他 | 500 |
|
||||
@@ -0,0 +1,44 @@
|
||||
# 编码规范
|
||||
|
||||
## 命名规范
|
||||
|
||||
| 类型 | 规则 | 示例 |
|
||||
|------|------|------|
|
||||
| 包名 | 小写单词,无下划线 | `service`, `utils` |
|
||||
| 变量/函数 | 驼峰命名 | `getUserByID` |
|
||||
| 公开标识 | 首字母大写 | `GetUserByID` |
|
||||
| 接口 | 单方法以 `er` 结尾 | `Reader`, `Writer` |
|
||||
|
||||
## 注释规范(中文,必须)
|
||||
|
||||
```go
|
||||
// GetUserByID 根据用户ID获取用户信息
|
||||
// @param ctx context.Context - 请求上下文
|
||||
// @param userID int64 - 用户唯一ID
|
||||
// @return *model.User - 用户信息
|
||||
// @return error - 查询错误
|
||||
func (s *UserService) GetUserByID(ctx context.Context, userID int64) (*model.User, error)
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
1. 必须 `if err != nil` 处理
|
||||
2. 用 `fmt.Errorf("xxx: %w", err)` 包装
|
||||
3. 禁止 `_ = err` 丢弃错误
|
||||
4. Handler 层必须通过统一响应返回
|
||||
|
||||
## 日志级别
|
||||
|
||||
| 级别 | 用途 |
|
||||
|------|------|
|
||||
| Debug | 开发调试,详细流程 |
|
||||
| Info | 关键业务节点 |
|
||||
| Warning | 可预期非致命异常 |
|
||||
| Error | 严重错误,必须记录堆栈 |
|
||||
|
||||
## 时间处理
|
||||
|
||||
- 时区:Asia/Shanghai (UTC+8)
|
||||
- 格式:RFC3339
|
||||
- 禁止:`time.Now()`
|
||||
- 使用:`TimeUtils.Now()`
|
||||
35
1-AgentSkills/coding-go-gin-gorm/reference/error-codes.go
Normal file
35
1-AgentSkills/coding-go-gin-gorm/reference/error-codes.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package common
|
||||
|
||||
// 业务状态码常量
|
||||
const (
|
||||
CodeSuccess = 0 // 成功
|
||||
CodeServerError = 10001 // 服务器内部错误
|
||||
CodeParamError = 10002 // 参数错误
|
||||
CodeUnauthorized = 10003 // 未授权
|
||||
CodeForbidden = 10004 // 禁止访问
|
||||
CodeNotFound = 10005 // 资源不存在
|
||||
CodeTimeout = 10006 // 请求超时
|
||||
CodeValidationFail = 10007 // 验证失败
|
||||
CodeBusiness = 20001 // 业务逻辑错误 (20001-29999)
|
||||
)
|
||||
|
||||
// CodeMessage 错误码消息映射
|
||||
var CodeMessage = map[int]string{
|
||||
CodeSuccess: "success",
|
||||
CodeServerError: "服务器内部错误",
|
||||
CodeParamError: "参数错误",
|
||||
CodeUnauthorized: "未授权,请先登录",
|
||||
CodeForbidden: "权限不足,禁止访问",
|
||||
CodeNotFound: "请求的资源不存在",
|
||||
CodeTimeout: "请求超时",
|
||||
CodeValidationFail: "数据验证失败",
|
||||
CodeBusiness: "业务处理失败",
|
||||
}
|
||||
|
||||
// GetMessage 根据错误码获取默认消息
|
||||
func GetMessage(code int) string {
|
||||
if msg, ok := CodeMessage[code]; ok {
|
||||
return msg
|
||||
}
|
||||
return "未知错误"
|
||||
}
|
||||
264
1-AgentSkills/coding-go-gin-gorm/reference/framework-usage.md
Normal file
264
1-AgentSkills/coding-go-gin-gorm/reference/framework-usage.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# 框架使用规范
|
||||
|
||||
## GIN 框架
|
||||
|
||||
### 路由组织
|
||||
|
||||
#### 强制使用路由分组 (Router Group)
|
||||
|
||||
```go
|
||||
func SetupRouter(r *gin.Engine) {
|
||||
// API 版本分组
|
||||
v1 := r.Group("/api/v1")
|
||||
{
|
||||
// 用户模块
|
||||
users := v1.Group("/users")
|
||||
{
|
||||
users.GET("/", userHandler.List)
|
||||
users.GET("/:id", userHandler.GetByID)
|
||||
users.POST("/", userHandler.Create)
|
||||
users.PUT("/:id", userHandler.Update)
|
||||
users.DELETE("/:id", userHandler.Delete)
|
||||
}
|
||||
|
||||
// 订单模块
|
||||
orders := v1.Group("/orders")
|
||||
{
|
||||
orders.GET("/", orderHandler.List)
|
||||
orders.GET("/:id", orderHandler.GetByID)
|
||||
orders.POST("/", orderHandler.Create)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 禁止扁平路由
|
||||
|
||||
```go
|
||||
// ❌ 错误:扁平路由,难以维护
|
||||
r.GET("/api/v1/users", ...)
|
||||
r.GET("/api/v1/users/:id", ...)
|
||||
r.POST("/api/v1/users", ...)
|
||||
r.GET("/api/v1/orders", ...)
|
||||
```
|
||||
|
||||
### 中间件使用
|
||||
|
||||
#### 全局中间件
|
||||
|
||||
```go
|
||||
func SetupMiddleware(r *gin.Engine) {
|
||||
// Recovery - 恢复 panic,防止程序崩溃
|
||||
r.Use(middleware.Recovery())
|
||||
|
||||
// Logger - 请求日志记录
|
||||
r.Use(middleware.Logger())
|
||||
|
||||
// CORS - 跨域处理
|
||||
r.Use(middleware.CORS())
|
||||
|
||||
// TraceID - 请求追踪
|
||||
r.Use(middleware.TraceID())
|
||||
}
|
||||
```
|
||||
|
||||
#### 路由组中间件
|
||||
|
||||
```go
|
||||
// 需要认证的路由组
|
||||
authGroup := r.Group("/api/v1/admin")
|
||||
authGroup.Use(middleware.Auth())
|
||||
{
|
||||
authGroup.GET("/dashboard", adminHandler.Dashboard)
|
||||
authGroup.GET("/users", adminHandler.ListUsers)
|
||||
}
|
||||
|
||||
// 需要特定权限的路由组
|
||||
superAdmin := authGroup.Group("/super")
|
||||
superAdmin.Use(middleware.RequireRole("super_admin"))
|
||||
{
|
||||
superAdmin.DELETE("/users/:id", adminHandler.DeleteUser)
|
||||
}
|
||||
```
|
||||
|
||||
#### 常用中间件职责
|
||||
|
||||
| 中间件 | 职责 |
|
||||
|--------|------|
|
||||
| Recovery | 捕获 panic,返回 500 错误 |
|
||||
| Logger | 记录请求日志(方法、路径、耗时等) |
|
||||
| CORS | 处理跨域请求 |
|
||||
| Auth | 验证用户身份(JWT/Session) |
|
||||
| TraceID | 生成/传递请求追踪 ID |
|
||||
| RateLimit | 请求频率限制 |
|
||||
|
||||
### 响应规范
|
||||
|
||||
#### 强制使用统一响应
|
||||
|
||||
```go
|
||||
// ✅ 正确:使用统一响应函数
|
||||
common.ResponseSuccess(c, data)
|
||||
common.ResponseError(c, common.CodeParamError, "参数错误")
|
||||
|
||||
// ❌ 错误:直接使用 GIN 原生方法
|
||||
c.JSON(200, data)
|
||||
c.String(200, "success")
|
||||
c.AbortWithStatusJSON(400, gin.H{"error": "bad request"})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GORM 框架
|
||||
|
||||
### 操作位置规范
|
||||
|
||||
```
|
||||
所有 GORM 操作必须在 dao 层实现
|
||||
严禁在 service 层直接操作数据库
|
||||
```
|
||||
|
||||
### 查询方式选择
|
||||
|
||||
#### 简单 CRUD - 链式调用
|
||||
|
||||
```go
|
||||
// 单条查询
|
||||
var user entity.User
|
||||
db.Where("id = ?", userID).First(&user)
|
||||
|
||||
// 列表查询
|
||||
var users []entity.User
|
||||
db.Where("status = ?", 1).
|
||||
Order("created_at DESC").
|
||||
Limit(10).
|
||||
Offset(0).
|
||||
Find(&users)
|
||||
|
||||
// 创建
|
||||
db.Create(&user)
|
||||
|
||||
// 更新
|
||||
db.Model(&user).Updates(map[string]interface{}{
|
||||
"name": "new name",
|
||||
"status": 1,
|
||||
})
|
||||
|
||||
// 删除
|
||||
db.Delete(&user, userID)
|
||||
```
|
||||
|
||||
#### 复杂查询 - Raw/Exec
|
||||
|
||||
**推荐场景**:
|
||||
- 多表 JOIN
|
||||
- 子查询
|
||||
- 复杂聚合
|
||||
- 批量操作
|
||||
- 性能敏感场景
|
||||
|
||||
```go
|
||||
// 多表 JOIN 查询
|
||||
type UserWithOrderCount struct {
|
||||
entity.User
|
||||
OrderCount int64 `json:"order_count"`
|
||||
}
|
||||
|
||||
var results []UserWithOrderCount
|
||||
db.Raw(`
|
||||
SELECT u.*, COUNT(o.id) as order_count
|
||||
FROM users u
|
||||
LEFT JOIN orders o ON u.id = o.user_id
|
||||
WHERE u.status = ?
|
||||
GROUP BY u.id
|
||||
ORDER BY order_count DESC
|
||||
LIMIT ?
|
||||
`, 1, 10).Scan(&results)
|
||||
|
||||
// 批量更新
|
||||
db.Exec(`
|
||||
UPDATE orders
|
||||
SET status = ?
|
||||
WHERE user_id = ? AND status = ?
|
||||
`, "completed", userID, "pending")
|
||||
|
||||
// 复杂子查询
|
||||
db.Raw(`
|
||||
SELECT * FROM users
|
||||
WHERE id IN (
|
||||
SELECT user_id FROM orders
|
||||
WHERE amount > ?
|
||||
GROUP BY user_id
|
||||
HAVING COUNT(*) > ?
|
||||
)
|
||||
`, 1000, 5).Scan(&users)
|
||||
```
|
||||
|
||||
### 错误处理
|
||||
|
||||
#### 必须处理 ErrRecordNotFound
|
||||
|
||||
```go
|
||||
// DAO 层
|
||||
func (d *UserDAO) FindByID(ctx context.Context, id int64) (*entity.User, error) {
|
||||
var user entity.User
|
||||
if err := d.db.WithContext(ctx).First(&user, id).Error; err != nil {
|
||||
return nil, err // 包含 ErrRecordNotFound
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// Handler 层
|
||||
user, err := h.userService.GetUserByID(ctx, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
common.ResponseError(c, common.CodeNotFound, "用户不存在")
|
||||
return
|
||||
}
|
||||
common.ResponseErrorWithDetail(c, common.CodeServerError, "查询失败", err)
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
### 事务处理
|
||||
|
||||
```go
|
||||
// Service 层事务
|
||||
func (s *OrderService) CreateOrder(ctx context.Context, req *dto.CreateOrderRequest) error {
|
||||
return s.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 1. 创建订单
|
||||
order := &entity.Order{...}
|
||||
if err := tx.Create(order).Error; err != nil {
|
||||
return fmt.Errorf("创建订单失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 扣减库存
|
||||
if err := tx.Model(&entity.Product{}).
|
||||
Where("id = ? AND stock >= ?", req.ProductID, req.Quantity).
|
||||
Update("stock", gorm.Expr("stock - ?", req.Quantity)).Error; err != nil {
|
||||
return fmt.Errorf("扣减库存失败: %w", err)
|
||||
}
|
||||
|
||||
// 3. 创建支付记录
|
||||
payment := &entity.Payment{...}
|
||||
if err := tx.Create(payment).Error; err != nil {
|
||||
return fmt.Errorf("创建支付记录失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Context 传递
|
||||
|
||||
```go
|
||||
// 必须使用 WithContext 传递上下文
|
||||
db.WithContext(ctx).First(&user, id)
|
||||
db.WithContext(ctx).Create(&order)
|
||||
|
||||
// 支持超时控制和取消
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
db.WithContext(ctx).Find(&users)
|
||||
```
|
||||
100
1-AgentSkills/coding-go-gin-gorm/reference/logging-standards.md
Normal file
100
1-AgentSkills/coding-go-gin-gorm/reference/logging-standards.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# 日志规范
|
||||
|
||||
## 指定框架
|
||||
|
||||
项目统一使用内部日志库:`rmdc-common/wdd_log/log_utils.go`
|
||||
|
||||
## 日志级别定义
|
||||
|
||||
### Debug
|
||||
- **用途**:开发调试,记录程序执行流程、变量值等详细信息
|
||||
- **场景**:默认开发日志级别
|
||||
- **示例**:
|
||||
```go
|
||||
log.Debug(ctx, "开始处理用户请求", map[string]interface{}{
|
||||
"userID": userID,
|
||||
"requestID": requestID,
|
||||
})
|
||||
```
|
||||
|
||||
### Info
|
||||
- **用途**:记录关键业务操作节点
|
||||
- **场景**:用户登录、订单创建、支付成功等关键业务
|
||||
- **示例**:
|
||||
```go
|
||||
log.Info(ctx, "用户登录成功", map[string]interface{}{
|
||||
"userID": user.ID,
|
||||
"username": user.Username,
|
||||
"ip": c.ClientIP(),
|
||||
})
|
||||
|
||||
log.Info(ctx, "订单创建成功", map[string]interface{}{
|
||||
"orderID": order.ID,
|
||||
"amount": order.Amount,
|
||||
"userID": order.UserID,
|
||||
})
|
||||
```
|
||||
|
||||
### Warning
|
||||
- **用途**:记录可预期的、非致命的异常情况,程序仍可继续运行
|
||||
- **场景**:外部 API 超时启用备用方案、配置缺失使用默认值等
|
||||
- **示例**:
|
||||
```go
|
||||
log.Warning(ctx, "外部API调用超时,已启用备用方案", map[string]interface{}{
|
||||
"api": "payment-gateway",
|
||||
"timeout": "5s",
|
||||
"fallback": "local-cache",
|
||||
})
|
||||
```
|
||||
|
||||
### Error
|
||||
- **用途**:记录严重错误,导致当前业务流程无法继续
|
||||
- **场景**:数据库连接失败、关键参数校验失败等
|
||||
- **要求**:必须详细记录错误信息和堆栈
|
||||
- **示例**:
|
||||
```go
|
||||
log.Error(ctx, "数据库连接失败", map[string]interface{}{
|
||||
"host": dbConfig.Host,
|
||||
"port": dbConfig.Port,
|
||||
"error": err.Error(),
|
||||
"stack": debug.Stack(),
|
||||
})
|
||||
```
|
||||
|
||||
## 日志内容规范
|
||||
|
||||
### 必须包含
|
||||
1. **TraceID** - 请求追踪 ID
|
||||
2. **UserID** - 用户标识(如适用)
|
||||
3. **操作描述** - 简练的中文描述
|
||||
4. **关键参数** - 与操作相关的关键数据
|
||||
|
||||
### 格式要求
|
||||
```go
|
||||
log.Info(ctx, "操作描述", map[string]interface{}{
|
||||
"key1": value1,
|
||||
"key2": value2,
|
||||
})
|
||||
```
|
||||
|
||||
## 各层日志职责
|
||||
|
||||
### Handler 层
|
||||
- 使用 `ResponseErrorWithDetail` 自动记录 Error 日志
|
||||
- 一般不主动记录日志
|
||||
|
||||
### Service 层
|
||||
- **Info**:关键业务操作成功(创建订单、支付、用户注册等)
|
||||
- **Warning**:业务逻辑异常但可处理
|
||||
- **Error**:通过 ResponseErrorWithDetail 在 Handler 层统一记录
|
||||
|
||||
### DAO 层
|
||||
- 一般不记录日志
|
||||
- 错误向上抛出,由 Handler 层统一处理
|
||||
|
||||
## 禁止事项
|
||||
|
||||
1. 禁止在日志中记录敏感信息(密码、Token、完整银行卡号等)
|
||||
2. 禁止使用 `fmt.Println` 或 `log.Println`
|
||||
3. 禁止在循环中大量记录日志
|
||||
4. Error 日志禁止缺少堆栈信息
|
||||
@@ -0,0 +1,39 @@
|
||||
# 项目目录结构规范
|
||||
|
||||
## 核心目录
|
||||
|
||||
| 目录 | 职责 | 禁止事项 |
|
||||
|------|------|----------|
|
||||
| `/api` 或 `/internal/handler` | GIN Handler 层,解析请求、调用 service、返回响应 | 禁止写业务逻辑 |
|
||||
| `/internal/service` | 业务逻辑核心,编排 dao 完成功能 | - |
|
||||
| `/internal/dao` 或 `/internal/repository` | 数据访问层,封装 GORM 操作 | 禁止引用 service |
|
||||
| `/internal/model/entity` | 数据库表结构对应的持久化对象 | - |
|
||||
| `/internal/model/dto` | API 数据传输对象(请求/响应) | - |
|
||||
| `/pkg/common` | 统一响应、错误码、公共工具 | - |
|
||||
| `/configs` | 配置文件 | - |
|
||||
| `/cmd` | main.go 入口 | - |
|
||||
|
||||
## 依赖规则
|
||||
|
||||
```
|
||||
handler → service → dao
|
||||
↓ ↓ ↓
|
||||
pkg/common (任意层可引用)
|
||||
```
|
||||
|
||||
**严禁反向或跨层依赖**
|
||||
|
||||
## go.mod 内部模块引用
|
||||
|
||||
```go
|
||||
module my-project
|
||||
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
wdd.io/TonyCommon v1.0.0
|
||||
)
|
||||
|
||||
// 本地开发使用 replace
|
||||
replace wdd.io/TonyCommon => ../TonyCommon
|
||||
```
|
||||
120
1-AgentSkills/coding-go-gin-gorm/reference/time-handling.md
Normal file
120
1-AgentSkills/coding-go-gin-gorm/reference/time-handling.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# 时间处理规范
|
||||
|
||||
## 核心原则
|
||||
|
||||
所有在前端和后端之间传输、以及在数据库中存储的时间,**必须统一为东八区时间 (Asia/Shanghai, UTC+8)**。
|
||||
|
||||
## 指定工具库
|
||||
|
||||
| 端 | 工具库路径 |
|
||||
|----|-----------|
|
||||
| 后端 | `rmdc-common/utils/TimeUtils.go` |
|
||||
| 前端 | `TonyMask/src/utils/timeUtils.ts` |
|
||||
|
||||
## 时间格式
|
||||
|
||||
- API 响应中的 `timestamp` 字段统一使用 **RFC3339** 格式
|
||||
- 示例:`2024-01-15T14:30:00+08:00`
|
||||
|
||||
## 禁止与必须
|
||||
|
||||
### 禁止直接使用
|
||||
|
||||
```go
|
||||
// ❌ 禁止
|
||||
time.Now()
|
||||
time.Parse(layout, value)
|
||||
t.Format(layout)
|
||||
```
|
||||
|
||||
### 必须使用工具库
|
||||
|
||||
```go
|
||||
// ✅ 正确
|
||||
TimeUtils.Now()
|
||||
TimeUtils.Parse(layout, value)
|
||||
TimeUtils.Format(t, layout)
|
||||
```
|
||||
|
||||
## 常用场景示例
|
||||
|
||||
### 获取当前时间
|
||||
|
||||
```go
|
||||
// ❌ 错误
|
||||
now := time.Now()
|
||||
|
||||
// ✅ 正确
|
||||
now := TimeUtils.Now()
|
||||
```
|
||||
|
||||
### 格式化时间戳
|
||||
|
||||
```go
|
||||
// ❌ 错误
|
||||
timestamp := time.Now().Format(time.RFC3339)
|
||||
|
||||
// ✅ 正确
|
||||
timestamp := TimeUtils.Now().Format(time.RFC3339)
|
||||
```
|
||||
|
||||
### 解析时间字符串
|
||||
|
||||
```go
|
||||
// ❌ 错误
|
||||
t, err := time.Parse(time.RFC3339, timeStr)
|
||||
|
||||
// ✅ 正确
|
||||
t, err := TimeUtils.Parse(time.RFC3339, timeStr)
|
||||
```
|
||||
|
||||
### 数据库时间字段
|
||||
|
||||
```go
|
||||
type Order struct {
|
||||
ID int64 `gorm:"primaryKey"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"` // GORM 自动处理
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"` // GORM 自动处理
|
||||
ExpireAt time.Time // 业务时间使用 TimeUtils
|
||||
}
|
||||
|
||||
// 设置业务时间
|
||||
order.ExpireAt = TimeUtils.Now().Add(24 * time.Hour)
|
||||
```
|
||||
|
||||
### API 响应时间
|
||||
|
||||
```go
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Status int `json:"status"`
|
||||
Timestamp string `json:"timestamp"` // RFC3339 格式
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
resp := Response{
|
||||
Timestamp: TimeUtils.Now().Format(time.RFC3339),
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## TimeUtils 常用方法
|
||||
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| `Now()` | 获取当前东八区时间 |
|
||||
| `Parse(layout, value)` | 解析时间字符串(东八区) |
|
||||
| `Format(t, layout)` | 格式化时间 |
|
||||
| `StartOfDay(t)` | 获取当天零点 |
|
||||
| `EndOfDay(t)` | 获取当天 23:59:59 |
|
||||
| `AddDays(t, days)` | 增加天数 |
|
||||
|
||||
## 时区配置
|
||||
|
||||
确保服务器和数据库时区配置正确:
|
||||
|
||||
```go
|
||||
// 数据库连接配置
|
||||
dsn := "user:pass@tcp(host:3306)/db?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai"
|
||||
```
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
# 验证 Go GIN/GORM 项目结构
|
||||
|
||||
set -e
|
||||
|
||||
echo "=== Go 项目结构验证 ==="
|
||||
|
||||
# 检查 go.mod
|
||||
if [ ! -f "go.mod" ]; then
|
||||
echo "❌ 缺少 go.mod"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ go.mod 存在"
|
||||
|
||||
# 检查核心目录
|
||||
DIRS=("internal/service" "internal/dao" "internal/model" "pkg/common")
|
||||
for dir in "${DIRS[@]}"; do
|
||||
if [ -d "$dir" ]; then
|
||||
echo "✅ $dir 存在"
|
||||
else
|
||||
echo "⚠️ $dir 不存在"
|
||||
fi
|
||||
done
|
||||
|
||||
# 检查 handler 目录(两种风格)
|
||||
if [ -d "api" ] || [ -d "internal/handler" ]; then
|
||||
echo "✅ handler 目录存在"
|
||||
else
|
||||
echo "⚠️ 缺少 api/ 或 internal/handler/"
|
||||
fi
|
||||
|
||||
# 检查反向依赖(dao 不应引用 service)
|
||||
echo ""
|
||||
echo "=== 检查依赖方向 ==="
|
||||
if grep -r "internal/service" internal/dao/ 2>/dev/null; then
|
||||
echo "❌ dao 层存在对 service 的反向依赖"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ 无反向依赖"
|
||||
|
||||
# 检查 time.Now() 使用
|
||||
echo ""
|
||||
echo "=== 检查 time.Now() 使用 ==="
|
||||
if grep -rn "time\.Now()" --include="*.go" internal/ api/ 2>/dev/null | grep -v "_test.go"; then
|
||||
echo "⚠️ 发现直接使用 time.Now(),应使用 TimeUtils.Now()"
|
||||
else
|
||||
echo "✅ 无直接 time.Now() 调用"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== 验证完成 ==="
|
||||
141
1-AgentSkills/coding-vue3-vuetify/SKILL.md
Normal file
141
1-AgentSkills/coding-vue3-vuetify/SKILL.md
Normal file
@@ -0,0 +1,141 @@
|
||||
---
|
||||
name: coding-vue3-vuetify
|
||||
description: Build production-grade Vue 3 + TypeScript + Vuetify 3 interfaces with architectural rigor. Use when creating Vue components, pages, layouts, Pinia stores, or API modules. Enforces strict typing, Composition API patterns, Material Design 3 aesthetics, and bulletproof data handling.
|
||||
---
|
||||
|
||||
This skill crafts Vue 3 + Vuetify 3 code that is architecturally sound, type-safe to the bone, and visually polished. Every component should feel like it belongs in a production codebase that senior engineers would be proud to maintain.
|
||||
|
||||
The user provides: $ARGUMENTS (component specs, page requirements, feature requests, or architectural questions).
|
||||
|
||||
## Architectural Thinking
|
||||
|
||||
Before writing a single line, establish clarity:
|
||||
|
||||
- **Component Identity**: Is this a Page, Layout, Reusable Component, Composable, Store, or API Module? Each has distinct patterns.
|
||||
- **Data Gravity**: Where does state live? Props flow down, events bubble up. Pinia for cross-component state. `provide/inject` for deep hierarchies.
|
||||
- **Scroll Strategy**: Which container owns the scroll? Never the body. Always explicit. Always controlled.
|
||||
- **Failure Modes**: What happens when data is `null`? Empty array? Network timeout? Design for the unhappy path first.
|
||||
|
||||
**CRITICAL**: Production code anticipates chaos. Type everything. Guard everything. Gracefully degrade everything.
|
||||
|
||||
## Core Dogma
|
||||
|
||||
### TypeScript Absolutism
|
||||
- `<script setup lang="ts">` — the ONLY acceptable incantation
|
||||
- `any` is forbidden — use `unknown` + type guards, generics, utility types
|
||||
- Every prop, emit, ref, and API response wears its type proudly
|
||||
- Types live in `@/types/`, organized by domain: `user.d.ts`, `order.d.ts`
|
||||
|
||||
### Composition API Purity
|
||||
- `ref`, `reactive`, `computed`, `watchEffect` — master these four
|
||||
- `shallowRef`, `readonly`, `toRaw` — know when to reach for optimization
|
||||
- Lifecycle via `onMounted`, `onUnmounted` — never mix Options API
|
||||
- Pinia stores: typed state, typed getters, typed actions — no exceptions
|
||||
|
||||
### Vuetify 3 + Material Design 3
|
||||
- ALL UI through Vuetify components — no raw HTML for UI elements
|
||||
- Theme-aware always — `rgb(var(--v-theme-surface))`, never `#ffffff`
|
||||
- `useDisplay()` for responsive logic — breakpoints are first-class citizens
|
||||
- Density matters — `density="compact"` for data-heavy interfaces
|
||||
|
||||
### Layout Philosophy
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ Toolbar (flex-shrink-0) │
|
||||
├─────────────────────────────────┤
|
||||
│ │
|
||||
│ Content Area │
|
||||
│ (flex-grow-1, overflow-y-auto) │
|
||||
│ (min-height: 0) ← CRITICAL │
|
||||
│ │
|
||||
├─────────────────────────────────┤
|
||||
│ Footer (flex-shrink-0) │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
- **No body scroll** — viewport locked, content scrolls in containers
|
||||
- **Flexbox trap**: `flex-grow-1` children MUST have `min-height: 0`
|
||||
- **Sticky elements**: filters, table headers — always visible during scroll
|
||||
|
||||
## Data Robustness Patterns
|
||||
|
||||
Treat all external data as hostile:
|
||||
|
||||
```typescript
|
||||
// Defensive access
|
||||
const userName = user?.profile?.name ?? 'Unknown'
|
||||
|
||||
// Array safety
|
||||
const items = Array.isArray(response.data) ? response.data : []
|
||||
|
||||
// Existence guards in templates
|
||||
<template v-if="user">{{ user.name }}</template>
|
||||
<v-empty-state v-else />
|
||||
```
|
||||
|
||||
## UI State Trinity
|
||||
|
||||
Every data-driven view handles THREE states:
|
||||
|
||||
| State | Component | Never Do |
|
||||
|-------|-----------|----------|
|
||||
| **Loading** | `v-skeleton-loader` | Show stale data or blank screen |
|
||||
| **Empty** | `v-empty-state` with action | Leave white void |
|
||||
| **Error** | Snackbar + retry option | Silent failure |
|
||||
|
||||
## Table & List Commandments
|
||||
|
||||
- `fixed-header` on every `v-data-table` — non-negotiable
|
||||
- Truncated text gets `v-tooltip` — users deserve full content on hover
|
||||
- 100+ items? `v-virtual-scroll` — DOM nodes stay constant
|
||||
- Column widths explicit — no layout lottery
|
||||
|
||||
## Anti-Patterns (NEVER)
|
||||
|
||||
- `.js` files in a TypeScript project
|
||||
- `any` without a blood oath and written justification
|
||||
- Hardcoded colors: `color="#1976d2"` → `color="primary"`
|
||||
- Body-level scrolling in SPA layouts
|
||||
- Tables without fixed headers
|
||||
- Truncated text without tooltips
|
||||
- Empty states that are literally empty
|
||||
- Loading states that freeze the UI
|
||||
- API calls without error handling
|
||||
|
||||
## Reference Files
|
||||
|
||||
Consult these for implementation details:
|
||||
|
||||
| Need | Read |
|
||||
|------|------|
|
||||
| Advanced TypeScript patterns | `reference/typescript-rules.md` |
|
||||
| Complex layout structures | `reference/layout-patterns.md` |
|
||||
| API client architecture | `reference/api-patterns.md` |
|
||||
| Tables, lists, forms, feedback | `reference/ui-interaction.md` |
|
||||
|
||||
## Project Anatomy
|
||||
|
||||
```
|
||||
src/
|
||||
├── api/ # Axios instance + modules
|
||||
├── components/ # Shared components
|
||||
├── composables/ # Reusable hooks
|
||||
├── layouts/ # Page shells
|
||||
├── pages/ # Route views
|
||||
├── plugins/ # Vuetify, Pinia, Router
|
||||
├── store/ # Pinia stores
|
||||
├── styles/ # Global SCSS
|
||||
├── types/ # Type definitions
|
||||
└── utils/ # Pure functions
|
||||
```
|
||||
|
||||
## Output Protocol
|
||||
|
||||
1. State the architectural approach (2-3 sentences)
|
||||
2. List files to create with their purposes
|
||||
3. Implement each file completely — no placeholders, no TODOs
|
||||
4. Verify against the anti-patterns list
|
||||
5. Call out any assumptions or trade-offs made
|
||||
|
||||
---
|
||||
|
||||
Remember: You're not writing code that works. You're writing code that works, scales, maintains, and delights. Every `ref` is typed. Every edge case is handled. Every loading state is beautiful. This is what production-grade means.
|
||||
113
1-AgentSkills/coding-vue3-vuetify/examples/api-module.ts
Normal file
113
1-AgentSkills/coding-vue3-vuetify/examples/api-module.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
// @/api/modules/user.ts
|
||||
import request from '@/api'
|
||||
import type { PageParams, PageResult } from '@/types/api'
|
||||
|
||||
// ============================================
|
||||
// 类型定义
|
||||
// ============================================
|
||||
|
||||
export interface User {
|
||||
id: string
|
||||
name: string
|
||||
email: string
|
||||
avatar?: string
|
||||
status: 'active' | 'disabled'
|
||||
role: 'admin' | 'user' | 'guest'
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface CreateUserDto {
|
||||
name: string
|
||||
email: string
|
||||
password: string
|
||||
role?: User['role']
|
||||
}
|
||||
|
||||
export interface UpdateUserDto {
|
||||
name?: string
|
||||
email?: string
|
||||
status?: User['status']
|
||||
role?: User['role']
|
||||
}
|
||||
|
||||
export interface UserListParams extends PageParams {
|
||||
search?: string
|
||||
status?: User['status']
|
||||
role?: User['role']
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// API 封装
|
||||
// ============================================
|
||||
|
||||
export const userApi = {
|
||||
/**
|
||||
* 获取用户分页列表
|
||||
*/
|
||||
getPage: (params: UserListParams) =>
|
||||
request.get<PageResult<User>>('/users', { params }),
|
||||
|
||||
/**
|
||||
* 获取用户列表(无分页,用于下拉选择等场景)
|
||||
*/
|
||||
getList: (params?: Partial<UserListParams>) =>
|
||||
request.get<User[]>('/users/list', { params }),
|
||||
|
||||
/**
|
||||
* 获取用户详情
|
||||
*/
|
||||
getById: (id: string) =>
|
||||
request.get<User>(`/users/${id}`),
|
||||
|
||||
/**
|
||||
* 检查邮箱是否已存在
|
||||
*/
|
||||
checkEmail: (email: string) =>
|
||||
request.get<{ exists: boolean }>('/users/check-email', { params: { email } }),
|
||||
|
||||
/**
|
||||
* 创建用户
|
||||
*/
|
||||
create: (data: CreateUserDto) =>
|
||||
request.post<User>('/users', data),
|
||||
|
||||
/**
|
||||
* 更新用户
|
||||
*/
|
||||
update: (id: string, data: UpdateUserDto) =>
|
||||
request.put<User>(`/users/${id}`, data),
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*/
|
||||
remove: (id: string) =>
|
||||
request.delete<void>(`/users/${id}`),
|
||||
|
||||
/**
|
||||
* 批量删除用户
|
||||
*/
|
||||
batchRemove: (ids: string[]) =>
|
||||
request.post<void>('/users/batch-delete', { ids }),
|
||||
|
||||
/**
|
||||
* 启用/禁用用户
|
||||
*/
|
||||
toggleStatus: (id: string, status: User['status']) =>
|
||||
request.patch<User>(`/users/${id}/status`, { status }),
|
||||
|
||||
/**
|
||||
* 重置用户密码
|
||||
*/
|
||||
resetPassword: (id: string) =>
|
||||
request.post<{ tempPassword: string }>(`/users/${id}/reset-password`),
|
||||
|
||||
/**
|
||||
* 导出用户列表
|
||||
*/
|
||||
export: (params?: UserListParams) =>
|
||||
request.get<Blob>('/users/export', {
|
||||
params,
|
||||
responseType: 'blob',
|
||||
}),
|
||||
}
|
||||
409
1-AgentSkills/coding-vue3-vuetify/examples/data-table-page.vue
Normal file
409
1-AgentSkills/coding-vue3-vuetify/examples/data-table-page.vue
Normal file
@@ -0,0 +1,409 @@
|
||||
<template>
|
||||
<div class="d-flex flex-column h-100">
|
||||
<!-- 页面头部 -->
|
||||
<v-toolbar density="compact" class="flex-shrink-0">
|
||||
<v-toolbar-title>订单管理</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
icon="mdi-refresh"
|
||||
:loading="loading"
|
||||
@click="fetchData"
|
||||
/>
|
||||
<v-btn
|
||||
color="primary"
|
||||
prepend-icon="mdi-download"
|
||||
:loading="exporting"
|
||||
@click="exportData"
|
||||
>
|
||||
导出
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
|
||||
<!-- 筛选栏 - 粘性定位 -->
|
||||
<v-sheet class="flex-shrink-0 pa-4 sticky-filter">
|
||||
<v-row dense>
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-text-field
|
||||
v-model="filters.search"
|
||||
label="搜索订单号/客户名"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
clearable
|
||||
hide-details
|
||||
@update:model-value="debouncedFetch"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="2">
|
||||
<v-select
|
||||
v-model="filters.status"
|
||||
:items="statusOptions"
|
||||
label="状态"
|
||||
clearable
|
||||
hide-details
|
||||
@update:model-value="fetchData"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-text-field
|
||||
v-model="filters.dateRange"
|
||||
label="日期范围"
|
||||
prepend-inner-icon="mdi-calendar"
|
||||
readonly
|
||||
hide-details
|
||||
@click="showDatePicker = true"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="2">
|
||||
<v-btn
|
||||
variant="outlined"
|
||||
block
|
||||
@click="resetFilters"
|
||||
>
|
||||
重置筛选
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-sheet>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<!-- 主内容区 - 可滚动 -->
|
||||
<div class="flex-grow-1 overflow-y-auto" style="min-height: 0">
|
||||
<!-- 加载状态 -->
|
||||
<v-skeleton-loader
|
||||
v-if="loading && !orders.length"
|
||||
type="table-heading, table-row@8"
|
||||
class="ma-4"
|
||||
/>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<v-empty-state
|
||||
v-else-if="!orders.length"
|
||||
icon="mdi-package-variant"
|
||||
title="暂无订单"
|
||||
text="当前筛选条件下没有找到订单记录"
|
||||
>
|
||||
<template #actions>
|
||||
<v-btn variant="outlined" @click="resetFilters">
|
||||
清除筛选
|
||||
</v-btn>
|
||||
<v-btn color="primary" @click="fetchData">
|
||||
刷新
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-empty-state>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<v-data-table-server
|
||||
v-else
|
||||
v-model:items-per-page="pagination.pageSize"
|
||||
v-model:page="pagination.page"
|
||||
:headers="headers"
|
||||
:items="orders"
|
||||
:items-length="pagination.total"
|
||||
:loading="loading"
|
||||
fixed-header
|
||||
hover
|
||||
@update:options="onOptionsChange"
|
||||
>
|
||||
<!-- 订单号 - 可点击 -->
|
||||
<template #item.orderNo="{ item }">
|
||||
<a
|
||||
href="#"
|
||||
class="text-primary text-decoration-none"
|
||||
@click.prevent="viewDetail(item)"
|
||||
>
|
||||
{{ item.orderNo }}
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<!-- 客户名 - 截断 + Tooltip -->
|
||||
<template #item.customerName="{ value }">
|
||||
<v-tooltip :text="value" location="top">
|
||||
<template #activator="{ props }">
|
||||
<span
|
||||
v-bind="props"
|
||||
class="text-truncate d-inline-block"
|
||||
style="max-width: 120px"
|
||||
>
|
||||
{{ value }}
|
||||
</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
|
||||
<!-- 金额 - 格式化 -->
|
||||
<template #item.amount="{ value }">
|
||||
<span class="font-weight-medium">
|
||||
¥{{ formatNumber(value) }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<!-- 状态 - Chip -->
|
||||
<template #item.status="{ value }">
|
||||
<v-chip
|
||||
:color="getStatusColor(value)"
|
||||
size="small"
|
||||
variant="tonal"
|
||||
>
|
||||
{{ getStatusText(value) }}
|
||||
</v-chip>
|
||||
</template>
|
||||
|
||||
<!-- 备注 - 多行截断 -->
|
||||
<template #item.remark="{ value }">
|
||||
<div v-if="value" class="remark-cell">
|
||||
<v-tooltip :text="value" location="top" max-width="300">
|
||||
<template #activator="{ props }">
|
||||
<span v-bind="props" class="line-clamp-2">
|
||||
{{ value }}
|
||||
</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</div>
|
||||
<span v-else class="text-grey">-</span>
|
||||
</template>
|
||||
|
||||
<!-- 操作列 -->
|
||||
<template #item.actions="{ item }">
|
||||
<v-btn
|
||||
icon="mdi-eye"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="viewDetail(item)"
|
||||
/>
|
||||
<v-btn
|
||||
icon="mdi-pencil"
|
||||
size="small"
|
||||
variant="text"
|
||||
:disabled="item.status === 'completed'"
|
||||
@click="editOrder(item)"
|
||||
/>
|
||||
<v-menu>
|
||||
<template #activator="{ props }">
|
||||
<v-btn
|
||||
v-bind="props"
|
||||
icon="mdi-dots-vertical"
|
||||
size="small"
|
||||
variant="text"
|
||||
/>
|
||||
</template>
|
||||
<v-list density="compact">
|
||||
<v-list-item @click="copyOrderNo(item)">
|
||||
<template #prepend>
|
||||
<v-icon size="small">mdi-content-copy</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>复制订单号</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
:disabled="item.status !== 'pending'"
|
||||
@click="cancelOrder(item)"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon size="small" color="error">mdi-cancel</v-icon>
|
||||
</template>
|
||||
<v-list-item-title class="text-error">取消订单</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
|
||||
<!-- 空数据插槽 -->
|
||||
<template #no-data>
|
||||
<v-empty-state
|
||||
icon="mdi-database-off"
|
||||
title="暂无数据"
|
||||
text="请尝试调整筛选条件"
|
||||
/>
|
||||
</template>
|
||||
</v-data-table-server>
|
||||
</div>
|
||||
|
||||
<!-- 底部统计栏 -->
|
||||
<v-sheet class="flex-shrink-0 pa-2 border-t d-flex align-center justify-space-between">
|
||||
<span class="text-body-2 text-grey">
|
||||
共 {{ pagination.total }} 条记录
|
||||
</span>
|
||||
<span class="text-body-2">
|
||||
已选 <strong>{{ selectedCount }}</strong> 条
|
||||
<v-btn
|
||||
v-if="selectedCount > 0"
|
||||
variant="text"
|
||||
size="small"
|
||||
color="primary"
|
||||
@click="batchAction"
|
||||
>
|
||||
批量操作
|
||||
</v-btn>
|
||||
</span>
|
||||
</v-sheet>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
import type { Order, OrderStatus } from '@/types/order'
|
||||
import { orderApi } from '@/api/modules/order'
|
||||
import { useSnackbar } from '@/composables/useSnackbar'
|
||||
|
||||
// Composables
|
||||
const snackbar = useSnackbar()
|
||||
|
||||
// State
|
||||
const loading = ref(false)
|
||||
const exporting = ref(false)
|
||||
const showDatePicker = ref(false)
|
||||
const orders = ref<Order[]>([])
|
||||
const selectedCount = ref(0)
|
||||
|
||||
const filters = reactive({
|
||||
search: '',
|
||||
status: null as OrderStatus | null,
|
||||
dateRange: '',
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
})
|
||||
|
||||
// Table Headers
|
||||
const headers = [
|
||||
{ title: '订单号', key: 'orderNo', width: 160 },
|
||||
{ title: '客户名称', key: 'customerName', width: 150 },
|
||||
{ title: '金额', key: 'amount', width: 120, align: 'end' as const },
|
||||
{ title: '状态', key: 'status', width: 100 },
|
||||
{ title: '备注', key: 'remark', width: 200 },
|
||||
{ title: '创建时间', key: 'createdAt', width: 170 },
|
||||
{ title: '操作', key: 'actions', width: 140, sortable: false },
|
||||
]
|
||||
|
||||
const statusOptions = [
|
||||
{ title: '全部', value: null },
|
||||
{ title: '待处理', value: 'pending' },
|
||||
{ title: '处理中', value: 'processing' },
|
||||
{ title: '已完成', value: 'completed' },
|
||||
{ title: '已取消', value: 'cancelled' },
|
||||
]
|
||||
|
||||
// Methods
|
||||
async function fetchData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await orderApi.getPage({
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
search: filters.search || undefined,
|
||||
status: filters.status || undefined,
|
||||
})
|
||||
orders.value = result.list
|
||||
pagination.total = result.total
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch orders:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const debouncedFetch = useDebounceFn(fetchData, 300)
|
||||
|
||||
function onOptionsChange(options: { page: number; itemsPerPage: number }) {
|
||||
pagination.page = options.page
|
||||
pagination.pageSize = options.itemsPerPage
|
||||
fetchData()
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
filters.search = ''
|
||||
filters.status = null
|
||||
filters.dateRange = ''
|
||||
pagination.page = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
async function exportData() {
|
||||
exporting.value = true
|
||||
try {
|
||||
const blob = await orderApi.export(filters)
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `orders_${Date.now()}.xlsx`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
snackbar.success('导出成功')
|
||||
} catch (error) {
|
||||
snackbar.error('导出失败')
|
||||
} finally {
|
||||
exporting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function viewDetail(item: Order) {
|
||||
// Navigate to detail page
|
||||
}
|
||||
|
||||
function editOrder(item: Order) {
|
||||
// Open edit dialog
|
||||
}
|
||||
|
||||
function copyOrderNo(item: Order) {
|
||||
navigator.clipboard.writeText(item.orderNo)
|
||||
snackbar.success('订单号已复制')
|
||||
}
|
||||
|
||||
function cancelOrder(item: Order) {
|
||||
// Show confirm dialog
|
||||
}
|
||||
|
||||
function batchAction() {
|
||||
// Show batch action menu
|
||||
}
|
||||
|
||||
// Helpers
|
||||
function formatNumber(value: number): string {
|
||||
return value.toLocaleString('zh-CN', { minimumFractionDigits: 2 })
|
||||
}
|
||||
|
||||
function getStatusColor(status: OrderStatus): string {
|
||||
const colors: Record<OrderStatus, string> = {
|
||||
pending: 'warning',
|
||||
processing: 'info',
|
||||
completed: 'success',
|
||||
cancelled: 'grey',
|
||||
}
|
||||
return colors[status] || 'grey'
|
||||
}
|
||||
|
||||
function getStatusText(status: OrderStatus): string {
|
||||
const texts: Record<OrderStatus, string> = {
|
||||
pending: '待处理',
|
||||
processing: '处理中',
|
||||
completed: '已完成',
|
||||
cancelled: '已取消',
|
||||
}
|
||||
return texts[status] || status
|
||||
}
|
||||
|
||||
onMounted(fetchData)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sticky-filter {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.remark-cell {
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
155
1-AgentSkills/coding-vue3-vuetify/examples/page-layout.vue
Normal file
155
1-AgentSkills/coding-vue3-vuetify/examples/page-layout.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<v-container fluid class="d-flex flex-column h-100 pa-0">
|
||||
<!-- 固定工具栏 -->
|
||||
<v-toolbar density="compact" class="flex-shrink-0">
|
||||
<v-toolbar-title>用户管理</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-btn icon="mdi-refresh" :loading="loading" @click="fetchData" />
|
||||
<v-btn color="primary" prepend-icon="mdi-plus" @click="openCreate">
|
||||
新建
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
|
||||
<!-- 筛选区域 -->
|
||||
<v-sheet class="flex-shrink-0 pa-4">
|
||||
<v-row dense>
|
||||
<v-col cols="12" md="3">
|
||||
<v-text-field
|
||||
v-model="filters.search"
|
||||
label="搜索"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
clearable
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="3">
|
||||
<v-select
|
||||
v-model="filters.status"
|
||||
:items="statusOptions"
|
||||
label="状态"
|
||||
clearable
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-sheet>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<!-- 可滚动内容区 -->
|
||||
<div class="flex-grow-1 overflow-y-auto" style="min-height: 0">
|
||||
<v-skeleton-loader v-if="loading" type="table-heading, table-row@10" />
|
||||
|
||||
<v-empty-state
|
||||
v-else-if="!users.length"
|
||||
icon="mdi-account-off"
|
||||
title="暂无用户"
|
||||
text="点击新建按钮添加第一个用户"
|
||||
>
|
||||
<template #actions>
|
||||
<v-btn color="primary" @click="openCreate">新建用户</v-btn>
|
||||
</template>
|
||||
</v-empty-state>
|
||||
|
||||
<v-data-table
|
||||
v-else
|
||||
:headers="headers"
|
||||
:items="users"
|
||||
fixed-header
|
||||
hover
|
||||
>
|
||||
<template #item.name="{ value }">
|
||||
<v-tooltip :text="value" location="top">
|
||||
<template #activator="{ props }">
|
||||
<span
|
||||
v-bind="props"
|
||||
class="text-truncate d-inline-block"
|
||||
style="max-width: 150px"
|
||||
>
|
||||
{{ value }}
|
||||
</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
|
||||
<template #item.status="{ value }">
|
||||
<v-chip
|
||||
:color="value === 'active' ? 'success' : 'grey'"
|
||||
size="small"
|
||||
>
|
||||
{{ value === 'active' ? '活跃' : '禁用' }}
|
||||
</v-chip>
|
||||
</template>
|
||||
|
||||
<template #item.actions="{ item }">
|
||||
<v-btn
|
||||
icon="mdi-pencil"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="edit(item)"
|
||||
/>
|
||||
<v-btn
|
||||
icon="mdi-delete"
|
||||
size="small"
|
||||
variant="text"
|
||||
color="error"
|
||||
@click="remove(item)"
|
||||
/>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</div>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import type { User } from '@/types/user'
|
||||
import { userApi } from '@/api/modules/user'
|
||||
|
||||
// State
|
||||
const loading = ref(false)
|
||||
const users = ref<User[]>([])
|
||||
const filters = reactive({
|
||||
search: '',
|
||||
status: null as string | null,
|
||||
})
|
||||
|
||||
// Table config
|
||||
const headers = [
|
||||
{ title: '姓名', key: 'name', width: 200 },
|
||||
{ title: '邮箱', key: 'email' },
|
||||
{ title: '状态', key: 'status', width: 120 },
|
||||
{ title: '创建时间', key: 'createdAt', width: 180 },
|
||||
{ title: '操作', key: 'actions', width: 120, sortable: false },
|
||||
]
|
||||
|
||||
const statusOptions = [
|
||||
{ title: '全部', value: null },
|
||||
{ title: '活跃', value: 'active' },
|
||||
{ title: '禁用', value: 'disabled' },
|
||||
]
|
||||
|
||||
// Methods
|
||||
async function fetchData() {
|
||||
loading.value = true
|
||||
try {
|
||||
users.value = await userApi.getList(filters)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
// TODO: Open create dialog
|
||||
}
|
||||
|
||||
function edit(item: User) {
|
||||
// TODO: Open edit dialog
|
||||
}
|
||||
|
||||
function remove(item: User) {
|
||||
// TODO: Confirm and delete
|
||||
}
|
||||
|
||||
onMounted(fetchData)
|
||||
</script>
|
||||
238
1-AgentSkills/coding-vue3-vuetify/reference/api-patterns.md
Normal file
238
1-AgentSkills/coding-vue3-vuetify/reference/api-patterns.md
Normal file
@@ -0,0 +1,238 @@
|
||||
# API 客户端模式
|
||||
|
||||
## 标准响应类型
|
||||
|
||||
```typescript
|
||||
// @/types/api.d.ts
|
||||
export interface ApiResponse<T = unknown> {
|
||||
code: number
|
||||
status: number
|
||||
timestamp: string
|
||||
data: T
|
||||
message?: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface PageParams {
|
||||
page: number
|
||||
pageSize: number
|
||||
sort?: string
|
||||
order?: 'asc' | 'desc'
|
||||
}
|
||||
|
||||
export interface PageResult<T> {
|
||||
list: T[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
export enum ApiErrorCode {
|
||||
Success = 0,
|
||||
ServerError = 10001,
|
||||
ParamError = 10002,
|
||||
Unauthorized = 10003,
|
||||
Forbidden = 10004,
|
||||
NotFound = 10005,
|
||||
Timeout = 10006,
|
||||
ValidationFail = 10007,
|
||||
BusinessError = 20001,
|
||||
}
|
||||
```
|
||||
|
||||
## Axios 实例
|
||||
|
||||
```typescript
|
||||
// @/api/index.ts
|
||||
import axios from 'axios'
|
||||
import { setupInterceptors } from './interceptors'
|
||||
|
||||
const request = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL,
|
||||
timeout: 15000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
setupInterceptors(request)
|
||||
|
||||
export default request
|
||||
```
|
||||
|
||||
## 响应拦截器
|
||||
|
||||
```typescript
|
||||
// @/api/interceptors.ts
|
||||
import type { AxiosInstance, AxiosResponse } from 'axios'
|
||||
import { useSnackbar } from '@/composables/useSnackbar'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
import router from '@/router'
|
||||
import { ApiErrorCode, type ApiResponse } from '@/types/api'
|
||||
|
||||
export function setupInterceptors(instance: AxiosInstance) {
|
||||
// 请求拦截器
|
||||
instance.interceptors.request.use(
|
||||
(config) => {
|
||||
const authStore = useAuthStore()
|
||||
if (authStore.token) {
|
||||
config.headers.Authorization = `Bearer ${authStore.token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
instance.interceptors.response.use(
|
||||
(response: AxiosResponse<ApiResponse>) => {
|
||||
const { code, data, message } = response.data
|
||||
|
||||
// 业务成功
|
||||
if (code === ApiErrorCode.Success) {
|
||||
return data
|
||||
}
|
||||
|
||||
// 业务失败
|
||||
const snackbar = useSnackbar()
|
||||
snackbar.error(message || '操作失败')
|
||||
|
||||
return Promise.reject(new Error(message))
|
||||
},
|
||||
(error) => {
|
||||
const snackbar = useSnackbar()
|
||||
const status = error.response?.status
|
||||
|
||||
switch (status) {
|
||||
case 401:
|
||||
useAuthStore().logout()
|
||||
router.push('/login')
|
||||
snackbar.error('登录已过期,请重新登录')
|
||||
break
|
||||
case 403:
|
||||
snackbar.error('无权访问')
|
||||
break
|
||||
case 404:
|
||||
snackbar.error('请求的资源不存在')
|
||||
break
|
||||
case 500:
|
||||
snackbar.error('服务器错误,请稍后重试')
|
||||
break
|
||||
default:
|
||||
if (error.code === 'ECONNABORTED') {
|
||||
snackbar.error('请求超时,请检查网络')
|
||||
} else if (!error.response) {
|
||||
snackbar.error('网络连接失败')
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## API 模块模板
|
||||
|
||||
```typescript
|
||||
// @/api/modules/[domain].ts
|
||||
import request from '@/api'
|
||||
import type { PageParams, PageResult } from '@/types/api'
|
||||
import type { Entity, CreateDto, UpdateDto } from '@/types/[domain]'
|
||||
|
||||
export const entityApi = {
|
||||
// 分页列表
|
||||
getPage: (params: PageParams) =>
|
||||
request.get<PageResult<Entity>>('/entities', { params }),
|
||||
|
||||
// 详情
|
||||
getById: (id: string) =>
|
||||
request.get<Entity>(`/entities/${id}`),
|
||||
|
||||
// 新增
|
||||
create: (data: CreateDto) =>
|
||||
request.post<Entity>('/entities', data),
|
||||
|
||||
// 更新
|
||||
update: (id: string, data: UpdateDto) =>
|
||||
request.put<Entity>(`/entities/${id}`, data),
|
||||
|
||||
// 删除
|
||||
remove: (id: string) =>
|
||||
request.delete<void>(`/entities/${id}`),
|
||||
}
|
||||
```
|
||||
|
||||
## 请求取消处理
|
||||
|
||||
```typescript
|
||||
// composables/useCancelableRequest.ts
|
||||
import { onUnmounted } from 'vue'
|
||||
import type { AxiosRequestConfig } from 'axios'
|
||||
|
||||
export function useCancelableRequest() {
|
||||
const controller = new AbortController()
|
||||
|
||||
onUnmounted(() => {
|
||||
controller.abort()
|
||||
})
|
||||
|
||||
function withCancel<T>(
|
||||
requestFn: (config?: AxiosRequestConfig) => Promise<T>
|
||||
): Promise<T> {
|
||||
return requestFn({ signal: controller.signal })
|
||||
}
|
||||
|
||||
return { withCancel, abort: () => controller.abort() }
|
||||
}
|
||||
```
|
||||
|
||||
## 请求重试
|
||||
|
||||
```typescript
|
||||
// utils/retryRequest.ts
|
||||
export async function retryRequest<T>(
|
||||
fn: () => Promise<T>,
|
||||
options: { retries?: number; delay?: number } = {}
|
||||
): Promise<T> {
|
||||
const { retries = 3, delay = 1000 } = options
|
||||
|
||||
for (let attempt = 0; attempt < retries; attempt++) {
|
||||
try {
|
||||
return await fn()
|
||||
} catch (error) {
|
||||
if (attempt === retries - 1) throw error
|
||||
await new Promise((resolve) => setTimeout(resolve, delay * (attempt + 1)))
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Max retries exceeded')
|
||||
}
|
||||
```
|
||||
|
||||
## 并发请求处理
|
||||
|
||||
```typescript
|
||||
// 使用 Promise.all 并发请求
|
||||
async function fetchDashboardData() {
|
||||
const [users, orders, stats] = await Promise.all([
|
||||
userApi.getList(),
|
||||
orderApi.getRecent(),
|
||||
statsApi.getSummary(),
|
||||
])
|
||||
|
||||
return { users, orders, stats }
|
||||
}
|
||||
|
||||
// 使用 Promise.allSettled 处理部分失败
|
||||
async function fetchWithFallback() {
|
||||
const results = await Promise.allSettled([
|
||||
userApi.getList(),
|
||||
orderApi.getList(),
|
||||
])
|
||||
|
||||
return results.map((result) =>
|
||||
result.status === 'fulfilled' ? result.value : []
|
||||
)
|
||||
}
|
||||
```
|
||||
182
1-AgentSkills/coding-vue3-vuetify/reference/layout-patterns.md
Normal file
182
1-AgentSkills/coding-vue3-vuetify/reference/layout-patterns.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# 布局模式参考
|
||||
|
||||
## 标准页面骨架
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<v-container fluid class="d-flex flex-column h-100 pa-0">
|
||||
<!-- 固定头部 -->
|
||||
<v-toolbar density="compact" class="flex-shrink-0">
|
||||
<v-toolbar-title>页面标题</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-btn icon="mdi-refresh" @click="refresh" />
|
||||
</v-toolbar>
|
||||
|
||||
<!-- 可滚动内容区 -->
|
||||
<div class="flex-grow-1 overflow-y-auto pa-4" style="min-height: 0">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<!-- 固定底部(可选) -->
|
||||
<v-footer app class="flex-shrink-0">
|
||||
<v-btn block color="primary">操作</v-btn>
|
||||
</v-footer>
|
||||
</v-container>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Flexbox 滚动陷阱解决方案
|
||||
|
||||
```css
|
||||
/* 问题:子元素撑破父容器 */
|
||||
.parent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex-grow: 1;
|
||||
/* 必须添加以下任一属性 */
|
||||
min-height: 0; /* 推荐 */
|
||||
/* 或 */
|
||||
overflow: hidden;
|
||||
}
|
||||
```
|
||||
|
||||
## 粘性筛选栏
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="flex-grow-1 overflow-y-auto" style="min-height: 0">
|
||||
<!-- 粘性筛选区 -->
|
||||
<div class="sticky-top bg-surface pa-4" style="z-index: 1">
|
||||
<v-row>
|
||||
<v-col cols="4">
|
||||
<v-text-field v-model="search" label="搜索" />
|
||||
</v-col>
|
||||
<v-col cols="4">
|
||||
<v-select v-model="status" :items="statusOptions" label="状态" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<!-- 列表内容 -->
|
||||
<v-list>...</v-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sticky-top {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## 分栏布局(侧边栏 + 主内容)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="d-flex h-100">
|
||||
<!-- 固定宽度侧边栏 -->
|
||||
<v-navigation-drawer permanent width="280">
|
||||
<v-list nav>...</v-list>
|
||||
</v-navigation-drawer>
|
||||
|
||||
<!-- 自适应主内容 -->
|
||||
<div class="flex-grow-1 d-flex flex-column" style="min-width: 0">
|
||||
<v-toolbar>主内容头部</v-toolbar>
|
||||
<div class="flex-grow-1 overflow-y-auto pa-4" style="min-height: 0">
|
||||
主内容区域
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 双栏详情布局
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="d-flex flex-column h-100">
|
||||
<v-toolbar density="compact" class="flex-shrink-0">
|
||||
<v-btn icon="mdi-arrow-left" @click="goBack" />
|
||||
<v-toolbar-title>详情</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
|
||||
<div class="flex-grow-1 d-flex" style="min-height: 0">
|
||||
<!-- 左侧主信息 -->
|
||||
<div class="flex-grow-1 overflow-y-auto pa-4" style="min-width: 0">
|
||||
<v-card>...</v-card>
|
||||
</div>
|
||||
|
||||
<!-- 右侧边栏 -->
|
||||
<v-sheet width="320" class="flex-shrink-0 overflow-y-auto border-s">
|
||||
<v-list>相关信息</v-list>
|
||||
</v-sheet>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Tab 切换布局
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="d-flex flex-column h-100">
|
||||
<v-tabs v-model="activeTab" class="flex-shrink-0">
|
||||
<v-tab value="info">基本信息</v-tab>
|
||||
<v-tab value="logs">操作日志</v-tab>
|
||||
<v-tab value="settings">设置</v-tab>
|
||||
</v-tabs>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-tabs-window v-model="activeTab" class="flex-grow-1" style="min-height: 0">
|
||||
<v-tabs-window-item value="info" class="h-100 overflow-y-auto">
|
||||
<!-- 内容 -->
|
||||
</v-tabs-window-item>
|
||||
<v-tabs-window-item value="logs" class="h-100 overflow-y-auto">
|
||||
<!-- 内容 -->
|
||||
</v-tabs-window-item>
|
||||
<v-tabs-window-item value="settings" class="h-100 overflow-y-auto">
|
||||
<!-- 内容 -->
|
||||
</v-tabs-window-item>
|
||||
</v-tabs-window>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 响应式断点处理
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
const { mobile, mdAndUp, lgAndUp } = useDisplay()
|
||||
|
||||
// 根据屏幕尺寸调整列数
|
||||
const gridCols = computed(() => {
|
||||
if (lgAndUp.value) return 4
|
||||
if (mdAndUp.value) return 3
|
||||
return 2
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col v-for="item in items" :key="item.id" :cols="12 / gridCols">
|
||||
<v-card>...</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- 移动端特殊处理 -->
|
||||
<v-bottom-navigation v-if="mobile" grow>
|
||||
<v-btn value="home">
|
||||
<v-icon>mdi-home</v-icon>
|
||||
<span>首页</span>
|
||||
</v-btn>
|
||||
</v-bottom-navigation>
|
||||
</template>
|
||||
```
|
||||
142
1-AgentSkills/coding-vue3-vuetify/reference/typescript-rules.md
Normal file
142
1-AgentSkills/coding-vue3-vuetify/reference/typescript-rules.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# TypeScript 严格规范
|
||||
|
||||
## 类型定义位置
|
||||
|
||||
```
|
||||
src/types/
|
||||
├── api.d.ts # ApiResponse, PageParams, etc.
|
||||
├── user.d.ts # User domain types
|
||||
├── order.d.ts # Order domain types
|
||||
└── common.d.ts # Shared utilities
|
||||
```
|
||||
|
||||
## 禁止 `any` 的替代方案
|
||||
|
||||
| 场景 | 错误 | 正确 |
|
||||
|------|------|------|
|
||||
| 未知对象 | `any` | `Record<string, unknown>` |
|
||||
| 动态数组 | `any[]` | `unknown[]` + type guard |
|
||||
| 回调函数 | `(x: any) => any` | 泛型 `<T>(x: T) => T` |
|
||||
| 第三方库缺失类型 | `any` | 创建 `.d.ts` 声明文件 |
|
||||
|
||||
## Props 与 Emits 类型化
|
||||
|
||||
```typescript
|
||||
// Props
|
||||
interface Props {
|
||||
user: User
|
||||
mode?: 'view' | 'edit'
|
||||
onSave?: (data: User) => void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
mode: 'view',
|
||||
})
|
||||
|
||||
// Emits
|
||||
interface Emits {
|
||||
(e: 'update', value: User): void
|
||||
(e: 'cancel'): void
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
```
|
||||
|
||||
## 泛型组合式函数
|
||||
|
||||
```typescript
|
||||
// composables/useFetch.ts
|
||||
export function useFetch<T>(
|
||||
fetcher: () => Promise<T>,
|
||||
options?: { immediate?: boolean }
|
||||
) {
|
||||
const data = ref<T | null>(null) as Ref<T | null>
|
||||
const loading = ref(false)
|
||||
const error = ref<Error | null>(null)
|
||||
|
||||
async function execute() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
data.value = await fetcher()
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e : new Error(String(e))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.immediate !== false) {
|
||||
onMounted(execute)
|
||||
}
|
||||
|
||||
return { data, loading, error, execute }
|
||||
}
|
||||
```
|
||||
|
||||
## 类型守卫
|
||||
|
||||
```typescript
|
||||
function isUser(obj: unknown): obj is User {
|
||||
return (
|
||||
typeof obj === 'object' &&
|
||||
obj !== null &&
|
||||
'id' in obj &&
|
||||
'name' in obj
|
||||
)
|
||||
}
|
||||
|
||||
// 使用
|
||||
const data: unknown = await fetchData()
|
||||
if (isUser(data)) {
|
||||
console.log(data.name) // TypeScript 知道这是 User
|
||||
}
|
||||
```
|
||||
|
||||
## 联合类型与字面量类型
|
||||
|
||||
```typescript
|
||||
// 状态枚举替代方案
|
||||
type UserStatus = 'active' | 'disabled' | 'pending'
|
||||
|
||||
// 带类型的事件处理
|
||||
type TableAction =
|
||||
| { type: 'edit'; payload: User }
|
||||
| { type: 'delete'; payload: string }
|
||||
| { type: 'view'; payload: User }
|
||||
|
||||
function handleAction(action: TableAction) {
|
||||
switch (action.type) {
|
||||
case 'edit':
|
||||
openEditDialog(action.payload) // payload 是 User
|
||||
break
|
||||
case 'delete':
|
||||
confirmDelete(action.payload) // payload 是 string (id)
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 工具类型使用
|
||||
|
||||
```typescript
|
||||
// Partial - 所有属性可选
|
||||
type UpdateUserDto = Partial<User>
|
||||
|
||||
// Pick - 选择部分属性
|
||||
type UserPreview = Pick<User, 'id' | 'name' | 'avatar'>
|
||||
|
||||
// Omit - 排除部分属性
|
||||
type CreateUserDto = Omit<User, 'id' | 'createdAt' | 'updatedAt'>
|
||||
|
||||
// Required - 所有属性必填
|
||||
type CompleteUser = Required<User>
|
||||
|
||||
// Record - 键值映射
|
||||
type UserMap = Record<string, User>
|
||||
|
||||
// 自定义工具类型
|
||||
type Nullable<T> = T | null
|
||||
type AsyncReturnType<T extends (...args: any) => Promise<any>> =
|
||||
T extends (...args: any) => Promise<infer R> ? R : never
|
||||
```
|
||||
345
1-AgentSkills/coding-vue3-vuetify/reference/ui-interaction.md
Normal file
345
1-AgentSkills/coding-vue3-vuetify/reference/ui-interaction.md
Normal file
@@ -0,0 +1,345 @@
|
||||
# UI 交互规范
|
||||
|
||||
## 数据表格
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="items"
|
||||
:loading="loading"
|
||||
fixed-header
|
||||
height="calc(100vh - 200px)"
|
||||
>
|
||||
<!-- 截断文本 + Tooltip -->
|
||||
<template #item.description="{ value }">
|
||||
<v-tooltip :text="value" location="top">
|
||||
<template #activator="{ props }">
|
||||
<span
|
||||
v-bind="props"
|
||||
class="text-truncate d-inline-block"
|
||||
style="max-width: 200px"
|
||||
>
|
||||
{{ value }}
|
||||
</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<template #no-data>
|
||||
<v-empty-state
|
||||
icon="mdi-database-off"
|
||||
title="暂无数据"
|
||||
text="请尝试调整筛选条件或新建记录"
|
||||
>
|
||||
<template #actions>
|
||||
<v-btn color="primary" @click="refresh">刷新</v-btn>
|
||||
</template>
|
||||
</v-empty-state>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 虚拟滚动列表
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<v-virtual-scroll :items="largeList" height="400" item-height="64">
|
||||
<template #default="{ item }">
|
||||
<v-list-item :title="item.name" :subtitle="item.description">
|
||||
<template #append>
|
||||
<v-btn icon="mdi-chevron-right" variant="text" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-virtual-scroll>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 骨架屏加载
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<!-- 表格骨架 -->
|
||||
<v-skeleton-loader v-if="loading" type="table-heading, table-row@5" />
|
||||
|
||||
<!-- 卡片骨架 -->
|
||||
<v-skeleton-loader v-if="loading" type="card" />
|
||||
|
||||
<!-- 列表骨架 -->
|
||||
<v-skeleton-loader v-if="loading" type="list-item-avatar-two-line@3" />
|
||||
|
||||
<!-- 文章骨架 -->
|
||||
<v-skeleton-loader v-if="loading" type="article" />
|
||||
|
||||
<!-- 自定义组合骨架 -->
|
||||
<v-skeleton-loader
|
||||
v-if="loading"
|
||||
type="heading, list-item-two-line@3, actions"
|
||||
/>
|
||||
|
||||
<!-- 实际内容 -->
|
||||
<template v-else>...</template>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 空状态组件
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<v-empty-state
|
||||
:icon="icon"
|
||||
:title="title"
|
||||
:text="description"
|
||||
>
|
||||
<template #actions>
|
||||
<v-btn v-if="showRefresh" variant="outlined" @click="$emit('refresh')">
|
||||
刷新
|
||||
</v-btn>
|
||||
<v-btn v-if="showCreate" color="primary" @click="$emit('create')">
|
||||
新建
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-empty-state>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
icon?: string
|
||||
title?: string
|
||||
description?: string
|
||||
showRefresh?: boolean
|
||||
showCreate?: boolean
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
icon: 'mdi-folder-open-outline',
|
||||
title: '暂无数据',
|
||||
description: '当前没有可显示的内容',
|
||||
showRefresh: true,
|
||||
showCreate: false,
|
||||
})
|
||||
|
||||
defineEmits<{
|
||||
(e: 'refresh'): void
|
||||
(e: 'create'): void
|
||||
}>()
|
||||
</script>
|
||||
```
|
||||
|
||||
## 多行文本截断
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="line-clamp-container">
|
||||
<p :class="{ 'line-clamp-2': !expanded }">{{ longText }}</p>
|
||||
<v-btn
|
||||
v-if="needsExpand"
|
||||
variant="text"
|
||||
size="small"
|
||||
@click="expanded = !expanded"
|
||||
>
|
||||
{{ expanded ? '收起' : '展开' }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## 确认对话框
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<v-dialog v-model="dialog" max-width="400" persistent>
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center">
|
||||
<v-icon :color="iconColor" class="mr-2">{{ icon }}</v-icon>
|
||||
{{ title }}
|
||||
</v-card-title>
|
||||
<v-card-text>{{ message }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="cancel">取消</v-btn>
|
||||
<v-btn :color="confirmColor" :loading="loading" @click="confirm">
|
||||
{{ confirmText }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
title?: string
|
||||
message: string
|
||||
icon?: string
|
||||
iconColor?: string
|
||||
confirmText?: string
|
||||
confirmColor?: string
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
title: '确认操作',
|
||||
icon: 'mdi-alert-circle-outline',
|
||||
iconColor: 'warning',
|
||||
confirmText: '确认',
|
||||
confirmColor: 'primary',
|
||||
})
|
||||
|
||||
const dialog = defineModel<boolean>({ default: false })
|
||||
const loading = ref(false)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'confirm'): void
|
||||
(e: 'cancel'): void
|
||||
}>()
|
||||
|
||||
function confirm() {
|
||||
emit('confirm')
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
dialog.value = false
|
||||
emit('cancel')
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 表单验证
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<v-form ref="formRef" v-model="valid" @submit.prevent="submit">
|
||||
<v-text-field
|
||||
v-model="form.name"
|
||||
:rules="rules.name"
|
||||
label="姓名"
|
||||
required
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="form.email"
|
||||
:rules="rules.email"
|
||||
label="邮箱"
|
||||
type="email"
|
||||
/>
|
||||
<v-btn type="submit" :disabled="!valid" :loading="loading">
|
||||
提交
|
||||
</v-btn>
|
||||
</v-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { VForm } from 'vuetify/components'
|
||||
|
||||
const formRef = ref<VForm | null>(null)
|
||||
const valid = ref(false)
|
||||
const loading = ref(false)
|
||||
|
||||
const form = reactive({
|
||||
name: '',
|
||||
email: '',
|
||||
})
|
||||
|
||||
const rules = {
|
||||
name: [
|
||||
(v: string) => !!v || '姓名不能为空',
|
||||
(v: string) => v.length <= 20 || '姓名不能超过20个字符',
|
||||
],
|
||||
email: [
|
||||
(v: string) => !!v || '邮箱不能为空',
|
||||
(v: string) => /.+@.+\..+/.test(v) || '请输入有效的邮箱地址',
|
||||
],
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
const { valid } = await formRef.value!.validate()
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
await api.submit(form)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Snackbar 全局通知
|
||||
|
||||
```typescript
|
||||
// composables/useSnackbar.ts
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface SnackbarOptions {
|
||||
text: string
|
||||
color?: string
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
const snackbar = ref<SnackbarOptions & { show: boolean }>({
|
||||
show: false,
|
||||
text: '',
|
||||
color: 'success',
|
||||
timeout: 3000,
|
||||
})
|
||||
|
||||
export function useSnackbar() {
|
||||
function show(options: SnackbarOptions) {
|
||||
snackbar.value = { ...snackbar.value, ...options, show: true }
|
||||
}
|
||||
|
||||
function success(text: string) {
|
||||
show({ text, color: 'success' })
|
||||
}
|
||||
|
||||
function error(text: string) {
|
||||
show({ text, color: 'error', timeout: 5000 })
|
||||
}
|
||||
|
||||
function warning(text: string) {
|
||||
show({ text, color: 'warning' })
|
||||
}
|
||||
|
||||
function info(text: string) {
|
||||
show({ text, color: 'info' })
|
||||
}
|
||||
|
||||
return { snackbar, show, success, error, warning, info }
|
||||
}
|
||||
```
|
||||
|
||||
```vue
|
||||
<!-- App.vue 中使用 -->
|
||||
<template>
|
||||
<v-app>
|
||||
<router-view />
|
||||
|
||||
<v-snackbar
|
||||
v-model="snackbar.show"
|
||||
:color="snackbar.color"
|
||||
:timeout="snackbar.timeout"
|
||||
>
|
||||
{{ snackbar.text }}
|
||||
</v-snackbar>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSnackbar } from '@/composables/useSnackbar'
|
||||
|
||||
const { snackbar } = useSnackbar()
|
||||
</script>
|
||||
```
|
||||
184
1-AgentSkills/developing-project-management/SKILL.md
Normal file
184
1-AgentSkills/developing-project-management/SKILL.md
Normal file
@@ -0,0 +1,184 @@
|
||||
---
|
||||
name: developing-project-management
|
||||
description: Guides development of rmdc-project-management module including project lifecycle management, version control (Git-like), ACL permissions, TOTP authorization, and workflow integration. Triggered when modifying project CRUD, draft/version APIs, permission grants, or authorization features. Keywords: project lifecycle, version snapshot, ACL, TOTP, workflow callback, SuperAdmin.
|
||||
argument-hint: "<change-type> [target]" where change-type is one of: api|entity|service|migration|frontend|auth. Example: "api draft-submit" or "migration add-field"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
- Edit
|
||||
- Write
|
||||
---
|
||||
|
||||
# Developing Project Management Module
|
||||
|
||||
本 Skill 指导 `rmdc-project-management` 模块的开发,该模块是 RMDC 系统的核心业务模块,负责以 K8s Namespace 为粒度的项目全生命周期管理。
|
||||
|
||||
## 模块定位
|
||||
|
||||
- **核心职责**: 项目 CRUD、版本控制(Git-like)、细粒度 ACL 权限、一级 TOTP 授权
|
||||
- **技术栈**: Go + Gin + GORM + PostgreSQL (JSONB)
|
||||
- **架构**: 模块化单体,通过接口注入与 `rmdc-work-procedure` 工单模块协作
|
||||
|
||||
## 动态上下文注入
|
||||
|
||||
使用前先获取当前仓库状态:
|
||||
|
||||
```bash
|
||||
# 查看项目管理模块目录结构
|
||||
!`find . -path "*/rmdc-project-management/*" -name "*.go" | head -20`
|
||||
|
||||
# 查找生命周期状态相关代码
|
||||
!`grep -rn "lifecycle_status\|LifecycleStatus" --include="*.go" | head -15`
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Plan(计划阶段)
|
||||
|
||||
### 产物清单
|
||||
|
||||
根据 `$ARGUMENTS` 确定变更范围:
|
||||
|
||||
| 变更类型 | 产物文件 | 影响模块 |
|
||||
|:---|:---|:---|
|
||||
| `api` | `handler/*.go`, `router.go` | rmdc-core 路由注册 |
|
||||
| `entity` | `entity/*.go` | 数据库迁移、DTO 映射 |
|
||||
| `service` | `service/*.go` | 业务逻辑、版本快照 |
|
||||
| `migration` | `migrations/*.sql` | 数据库 Schema |
|
||||
| `frontend` | `pages/*.vue`, `components/*.vue` | 前端联调 |
|
||||
| `auth` | `service/auth_*.go` | TOTP 授权、Exchange-Hub 交互 |
|
||||
|
||||
### 决策点
|
||||
|
||||
1. **是否涉及生命周期状态变更?**
|
||||
- 若涉及,必须同步更新状态机转换逻辑
|
||||
- 检查 `reference/lifecycle-state-machine.md`
|
||||
|
||||
2. **是否修改版本快照结构?**
|
||||
- 若涉及,需评估历史版本兼容性
|
||||
- 更新 `VersionSnapshot` 结构体
|
||||
|
||||
3. **是否变更 ACL 权限模型?**
|
||||
- 若涉及,需同步 `rmdc-user-auth` 模块
|
||||
- 检查 `reference/acl-permission-model.md`
|
||||
|
||||
4. **是否影响工单模块回调?**
|
||||
- 若涉及,需更新 `ProjectLifecycleUpdater` 接口实现
|
||||
- 检查 `reference/workflow-state-mapping.md`
|
||||
|
||||
---
|
||||
|
||||
## Verify(验证阶段)
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] **生命周期状态机完整性**: 所有状态转换有明确的触发条件和权限控制
|
||||
- [ ] **版本快照一致性**: `projects` 表与 `project_versions` 表数据同步
|
||||
- [ ] **乐观锁检查**: 并发修改时 `base_version == current_version` 校验存在
|
||||
- [ ] **ACL 权限验证**: 接口权限注解与业务逻辑一致
|
||||
- [ ] **工单回调幂等**: 状态更新操作具备幂等性
|
||||
- [ ] **敏感字段加密**: 密码字段使用 AES-256 加密存储
|
||||
- [ ] **审计日志**: 所有写操作记录到 `rmdc-audit-log`
|
||||
- [ ] **TOTP 授权安全**: 一级密钥仅 SuperAdmin 可访问
|
||||
|
||||
### 验证命令
|
||||
|
||||
```bash
|
||||
# 检查实体字段与数据库 Schema 一致性
|
||||
!`grep -rn "gorm:\"" entity/project.go | head -20`
|
||||
|
||||
# 检查 API 路由权限注解
|
||||
!`grep -rn "RequireRole\|RequirePermission" handler/*.go`
|
||||
|
||||
# 运行模块单元测试
|
||||
go test ./internal/project/... -v -cover
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Execute(执行阶段)
|
||||
|
||||
### API 开发流程
|
||||
|
||||
1. **定义请求/响应结构体** → `dto/project_dto.go`
|
||||
2. **实现 Service 方法** → `service/project_service.go`
|
||||
3. **实现 Handler 方法** → `handler/project_handler.go`
|
||||
4. **注册路由** → `router.go` (注意权限中间件)
|
||||
5. **编写单元测试** → `*_test.go`
|
||||
|
||||
### 版本快照变更流程
|
||||
|
||||
1. 更新 `VersionSnapshot` 结构体定义
|
||||
2. 确保 `CompareVersions` Diff 算法兼容新字段
|
||||
3. 添加字段到 Diff 结果的字段名映射表
|
||||
4. 测试历史版本查看功能不受影响
|
||||
|
||||
### 生命周期状态变更流程
|
||||
|
||||
1. 更新 `reference/lifecycle-state-machine.md` 状态图
|
||||
2. 修改 `service/lifecycle_service.go` 状态转换逻辑
|
||||
3. 同步更新 `ProjectLifecycleUpdater` 接口实现
|
||||
4. 验证与工单模块的状态映射表一致
|
||||
|
||||
### 授权功能变更流程
|
||||
|
||||
1. 检查 `project_auth_configs` 表结构
|
||||
2. 更新 `AuthorizationInfo` 结构体
|
||||
3. 确保 TOTP 密钥生成/验证逻辑正确
|
||||
4. 测试与 Exchange-Hub 的授权指令下发
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls(常见问题)
|
||||
|
||||
1. **超管直改未生成版本**: SuperAdmin 直接修改 `projects` 表时,必须同时插入 `project_versions` 记录,否则版本链断裂
|
||||
|
||||
2. **草稿基准版本过期**: 用户 A 基于 v3 创建草稿,超管修改产生 v4,用户 A 提交时需检测冲突并提示 Rebase
|
||||
|
||||
3. **工单回调重复处理**: 工单模块可能重试回调,`ProjectLifecycleUpdater` 实现必须幂等
|
||||
|
||||
4. **ACL 权限遗漏授权模块**: `authorization_info` 模块仅 SuperAdmin 可见,其他角色查询时需过滤
|
||||
|
||||
5. **密码字段明文泄露**: `AdminPassword`、`SSHPwd` 等字段响应时必须脱敏或不返回
|
||||
|
||||
6. **省市级联校验缺失**: 前端省市级联选择后,后端需校验省市对应关系有效性
|
||||
|
||||
7. **Namespace 唯一性**: 创建项目时必须校验 `namespace` 全局唯一且符合 RFC 1123 DNS 标签规范
|
||||
|
||||
8. **JSONB 字段空值处理**: `basic_info`、`deploy_business` 等 JSONB 字段为空时,需返回空对象 `{}` 而非 `null`
|
||||
|
||||
---
|
||||
|
||||
## 模块依赖关系
|
||||
|
||||
```
|
||||
rmdc-project-management
|
||||
├── → rmdc-user-auth (用户鉴权、ACL 权限查询)
|
||||
├── → rmdc-work-procedure (工单创建、状态转换)
|
||||
├── → rmdc-audit-log (操作审计记录)
|
||||
├── → rmdc-exchange-hub (授权指令下发)
|
||||
└── ← rmdc-core (路由注册、依赖注入)
|
||||
```
|
||||
|
||||
## 关键接口
|
||||
|
||||
| 类别 | 路径 | 权限 |
|
||||
|:---|:---|:---|
|
||||
| 项目列表 | `POST /api/project/list` | Login |
|
||||
| 项目详情 | `POST /api/project/detail` | View ACL |
|
||||
| 创建项目 | `POST /api/project/create` | SuperAdmin |
|
||||
| 直接更新 | `POST /api/project/update` | SuperAdmin |
|
||||
| 保存草稿 | `POST /api/project/draft/save` | View ACL |
|
||||
| 提交审核 | `POST /api/project/draft/submit` | View ACL |
|
||||
| 版本历史 | `POST /api/project/version/list` | View ACL |
|
||||
| 权限分配 | `POST /api/project/permission/grant` | SuperAdmin |
|
||||
|
||||
## 相关文档
|
||||
|
||||
- 生命周期状态机: `reference/lifecycle-state-machine.md`
|
||||
- API 端点清单: `reference/api-endpoints.md`
|
||||
- 数据库 Schema: `reference/database-schema.md`
|
||||
- ACL 权限模型: `reference/acl-permission-model.md`
|
||||
- 工单状态映射: `reference/workflow-state-mapping.md`
|
||||
@@ -0,0 +1,280 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ProjectLifecycleUpdater 项目生命周期状态更新接口
|
||||
// 由 rmdc-core 注入,工单模块状态变更时调用
|
||||
type ProjectLifecycleUpdater interface {
|
||||
UpdateLifecycleStatus(projectID, lifecycleStatus string) error
|
||||
SetLifecycleToDrafting(projectID string) error
|
||||
SetLifecycleToReviewing(projectID string) error
|
||||
SetLifecycleToReleased(projectID string) error
|
||||
SetLifecycleToModifying(projectID string) error
|
||||
}
|
||||
|
||||
// ProjectLifecycleService 项目生命周期服务实现
|
||||
type ProjectLifecycleService struct {
|
||||
repo ProjectRepository
|
||||
versionSvc *VersionService
|
||||
auditSvc AuditService
|
||||
idempotency IdempotencyChecker
|
||||
}
|
||||
|
||||
// NewProjectLifecycleService 创建项目生命周期服务
|
||||
func NewProjectLifecycleService(
|
||||
repo ProjectRepository,
|
||||
versionSvc *VersionService,
|
||||
auditSvc AuditService,
|
||||
) *ProjectLifecycleService {
|
||||
return &ProjectLifecycleService{
|
||||
repo: repo,
|
||||
versionSvc: versionSvc,
|
||||
auditSvc: auditSvc,
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateLifecycleStatus 更新项目生命周期状态
|
||||
func (s *ProjectLifecycleService) UpdateLifecycleStatus(projectID, lifecycleStatus string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// 幂等性检查
|
||||
idempotencyKey := fmt.Sprintf("lifecycle:%s:%s:%d", projectID, lifecycleStatus, time.Now().Unix()/60)
|
||||
if s.idempotency != nil && s.idempotency.Exists(idempotencyKey) {
|
||||
return nil // 已处理,直接返回
|
||||
}
|
||||
|
||||
// 更新生命周期状态
|
||||
err := s.repo.UpdateLifecycleStatus(ctx, projectID, lifecycleStatus)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update lifecycle status failed: %w", err)
|
||||
}
|
||||
|
||||
// 记录审计日志
|
||||
s.auditSvc.Log(ctx, AuditLog{
|
||||
Resource: "project",
|
||||
Action: "lifecycle_change",
|
||||
ResourceID: projectID,
|
||||
Details: map[string]interface{}{
|
||||
"new_status": lifecycleStatus,
|
||||
},
|
||||
})
|
||||
|
||||
// 标记已处理
|
||||
if s.idempotency != nil {
|
||||
s.idempotency.Set(idempotencyKey, 24*time.Hour)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLifecycleToReleased 设置为已发布状态(审批通过时)
|
||||
func (s *ProjectLifecycleService) SetLifecycleToReleased(projectID string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// 1. 获取项目信息
|
||||
project, err := s.repo.GetByProjectID(ctx, projectID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get project failed: %w", err)
|
||||
}
|
||||
|
||||
// 2. 获取草稿快照
|
||||
draftVersion, err := s.repo.GetActiveDraft(ctx, projectID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get draft failed: %w", err)
|
||||
}
|
||||
|
||||
// 3. 开启事务
|
||||
return s.repo.Transaction(ctx, func(txCtx context.Context) error {
|
||||
// 3.1 更新生命周期状态
|
||||
if err := s.repo.UpdateLifecycleStatus(txCtx, projectID, LifecycleReleased); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3.2 更新认证状态为正式
|
||||
if err := s.repo.UpdateCertificationStatus(txCtx, projectID, CertificationOfficial); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3.3 将草稿内容合并到主表
|
||||
if err := s.repo.MergeDraftToMaster(txCtx, projectID, draftVersion.SnapshotData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3.4 创建正式版本快照
|
||||
snapshot := &VersionSnapshot{}
|
||||
if err := parseSnapshot(draftVersion.SnapshotData, snapshot); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.versionSvc.CreateOfficialVersion(
|
||||
txCtx,
|
||||
projectID,
|
||||
snapshot,
|
||||
draftVersion.CommitterID,
|
||||
draftVersion.CommitterName,
|
||||
"审批通过,发布正式版本",
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3.5 删除草稿
|
||||
if err := s.repo.DeleteDraft(txCtx, projectID, draftVersion.UserID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// SetLifecycleToDrafting 设置为填写中状态(工单被打回后)
|
||||
func (s *ProjectLifecycleService) SetLifecycleToDrafting(projectID string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
err := s.repo.UpdateLifecycleStatus(ctx, projectID, LifecycleDrafting)
|
||||
if err != nil {
|
||||
return fmt.Errorf("set lifecycle to drafting failed: %w", err)
|
||||
}
|
||||
|
||||
s.auditSvc.Log(ctx, AuditLog{
|
||||
Resource: "project",
|
||||
Action: "lifecycle_change",
|
||||
ResourceID: projectID,
|
||||
Details: map[string]interface{}{
|
||||
"new_status": LifecycleDrafting,
|
||||
"reason": "workflow_returned",
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLifecycleToReviewing 设置为审核中状态(提交审核时)
|
||||
func (s *ProjectLifecycleService) SetLifecycleToReviewing(projectID string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// 更新生命周期状态
|
||||
err := s.repo.UpdateLifecycleStatus(ctx, projectID, LifecycleReviewing)
|
||||
if err != nil {
|
||||
return fmt.Errorf("set lifecycle to reviewing failed: %w", err)
|
||||
}
|
||||
|
||||
// 更新认证状态为待审核
|
||||
err = s.repo.UpdateCertificationStatus(ctx, projectID, CertificationPending)
|
||||
if err != nil {
|
||||
return fmt.Errorf("set certification to pending failed: %w", err)
|
||||
}
|
||||
|
||||
s.auditSvc.Log(ctx, AuditLog{
|
||||
Resource: "project",
|
||||
Action: "lifecycle_change",
|
||||
ResourceID: projectID,
|
||||
Details: map[string]interface{}{
|
||||
"new_status": LifecycleReviewing,
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLifecycleToModifying 设置为变更中状态(发起修改工单时)
|
||||
func (s *ProjectLifecycleService) SetLifecycleToModifying(projectID string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
err := s.repo.UpdateLifecycleStatus(ctx, projectID, LifecycleModifying)
|
||||
if err != nil {
|
||||
return fmt.Errorf("set lifecycle to modifying failed: %w", err)
|
||||
}
|
||||
|
||||
s.auditSvc.Log(ctx, AuditLog{
|
||||
Resource: "project",
|
||||
Action: "lifecycle_change",
|
||||
ResourceID: projectID,
|
||||
Details: map[string]interface{}{
|
||||
"new_status": LifecycleModifying,
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckVersionConflict 检查版本冲突(乐观锁)
|
||||
func (s *ProjectLifecycleService) CheckVersionConflict(
|
||||
ctx context.Context,
|
||||
projectID string,
|
||||
baseVersion int,
|
||||
) error {
|
||||
currentVersion, err := s.repo.GetCurrentVersion(ctx, projectID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get current version failed: %w", err)
|
||||
}
|
||||
|
||||
if currentVersion != baseVersion {
|
||||
return &VersionConflictError{
|
||||
ProjectID: projectID,
|
||||
BaseVersion: baseVersion,
|
||||
CurrentVersion: currentVersion,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VersionConflictError 版本冲突错误
|
||||
type VersionConflictError struct {
|
||||
ProjectID string
|
||||
BaseVersion int
|
||||
CurrentVersion int
|
||||
}
|
||||
|
||||
func (e *VersionConflictError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"version conflict: project %s base version %d, current version %d",
|
||||
e.ProjectID, e.BaseVersion, e.CurrentVersion,
|
||||
)
|
||||
}
|
||||
|
||||
// 辅助类型和函数
|
||||
|
||||
type ProjectRepository interface {
|
||||
GetByProjectID(ctx context.Context, projectID string) (*Project, error)
|
||||
UpdateLifecycleStatus(ctx context.Context, projectID, status string) error
|
||||
UpdateCertificationStatus(ctx context.Context, projectID, status string) error
|
||||
GetActiveDraft(ctx context.Context, projectID string) (*ProjectVersion, error)
|
||||
MergeDraftToMaster(ctx context.Context, projectID string, data []byte) error
|
||||
DeleteDraft(ctx context.Context, projectID string, userID int64) error
|
||||
GetCurrentVersion(ctx context.Context, projectID string) (int, error)
|
||||
Transaction(ctx context.Context, fn func(ctx context.Context) error) error
|
||||
}
|
||||
|
||||
type AuditService interface {
|
||||
Log(ctx context.Context, log AuditLog)
|
||||
}
|
||||
|
||||
type AuditLog struct {
|
||||
Resource string
|
||||
Action string
|
||||
ResourceID string
|
||||
Details map[string]interface{}
|
||||
}
|
||||
|
||||
type IdempotencyChecker interface {
|
||||
Exists(key string) bool
|
||||
Set(key string, ttl time.Duration)
|
||||
}
|
||||
|
||||
type Project struct {
|
||||
ID int64
|
||||
ProjectID string
|
||||
LifecycleStatus string
|
||||
CertificationStatus string
|
||||
CurrentVersion int
|
||||
}
|
||||
|
||||
func parseSnapshot(data []byte, snapshot *VersionSnapshot) error {
|
||||
// JSON 解析实现
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Project 项目主表实体
|
||||
type Project struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
ProjectID string `gorm:"type:varchar(64);uniqueIndex;not null" json:"project_id"`
|
||||
Name string `gorm:"type:varchar(128);not null" json:"name"`
|
||||
Namespace string `gorm:"type:varchar(64);uniqueIndex;not null" json:"namespace"`
|
||||
|
||||
// 生命周期状态: INIT/DRAFTING/REVIEWING/RELEASED/MODIFYING/ARCHIVED
|
||||
LifecycleStatus string `gorm:"type:varchar(32);default:'INIT'" json:"lifecycle_status"`
|
||||
// 认证状态: draft/pending/official
|
||||
CertificationStatus string `gorm:"type:varchar(32);default:'draft'" json:"certification_status"`
|
||||
|
||||
// 当前正式版本号
|
||||
CurrentVersion int `gorm:"default:0" json:"current_version"`
|
||||
|
||||
// JSONB 存储
|
||||
BasicInfo json.RawMessage `gorm:"type:jsonb" json:"basic_info"`
|
||||
DeployBusiness json.RawMessage `gorm:"type:jsonb" json:"deploy_business"`
|
||||
DeployEnv json.RawMessage `gorm:"type:jsonb" json:"deploy_env"`
|
||||
DeployMiddleware json.RawMessage `gorm:"type:jsonb" json:"deploy_middleware"`
|
||||
|
||||
// 填写人
|
||||
DetailFillerID int64 `json:"detail_filler_id"`
|
||||
DetailFillerName string `gorm:"type:varchar(64)" json:"detail_filler_name"`
|
||||
|
||||
// 审计字段
|
||||
CreatedBy int64 `json:"created_by"`
|
||||
CreatedByName string `gorm:"type:varchar(64)" json:"created_by_name"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt *time.Time `gorm:"index" json:"deleted_at,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Project) TableName() string {
|
||||
return "projects"
|
||||
}
|
||||
|
||||
// 生命周期状态常量
|
||||
const (
|
||||
LifecycleInit = "INIT"
|
||||
LifecycleDrafting = "DRAFTING"
|
||||
LifecycleReviewing = "REVIEWING"
|
||||
LifecycleReleased = "RELEASED"
|
||||
LifecycleModifying = "MODIFYING"
|
||||
LifecycleArchived = "ARCHIVED"
|
||||
)
|
||||
|
||||
// 认证状态常量
|
||||
const (
|
||||
CertificationDraft = "draft"
|
||||
CertificationPending = "pending"
|
||||
CertificationOfficial = "official"
|
||||
)
|
||||
|
||||
// ProjectVersion 项目版本表 (含草稿)
|
||||
type ProjectVersion struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
ProjectID string `gorm:"type:varchar(64);index;not null" json:"project_id"`
|
||||
|
||||
// 版本号 (正式版本递增, 草稿为0)
|
||||
Version int `gorm:"not null;default:0" json:"version"`
|
||||
|
||||
// 版本类型: official/fill_draft/modify_draft
|
||||
VersionType string `gorm:"type:varchar(32);not null" json:"version_type"`
|
||||
|
||||
// 基准版本号(草稿基于哪个正式版本创建)
|
||||
BaseVersion int `gorm:"default:0" json:"base_version"`
|
||||
|
||||
// 草稿所属用户ID (仅草稿类型有值)
|
||||
UserID int64 `gorm:"index" json:"user_id"`
|
||||
UserName string `gorm:"type:varchar(64)" json:"user_name"`
|
||||
|
||||
// 关联工单ID (1:1关系, 仅草稿类型有值)
|
||||
WorkflowID string `gorm:"type:varchar(64);index" json:"workflow_id"`
|
||||
|
||||
// 完整快照数据
|
||||
SnapshotData json.RawMessage `gorm:"type:jsonb" json:"snapshot_data"`
|
||||
|
||||
// 变更信息
|
||||
CommitMessage string `gorm:"type:varchar(255)" json:"commit_message"`
|
||||
CommitterID int64 `json:"committer_id"`
|
||||
CommitterName string `gorm:"type:varchar(64)" json:"committer_name"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (ProjectVersion) TableName() string {
|
||||
return "project_versions"
|
||||
}
|
||||
|
||||
// 版本类型常量
|
||||
const (
|
||||
VersionTypeOfficial = "official"
|
||||
VersionTypeFillDraft = "fill_draft"
|
||||
VersionTypeModifyDraft = "modify_draft"
|
||||
)
|
||||
|
||||
// ProjectAuthConfig 项目授权配置
|
||||
type ProjectAuthConfig struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
ProjectID string `gorm:"type:varchar(64);uniqueIndex;not null" json:"project_id"`
|
||||
|
||||
TierOneSecret string `gorm:"type:varchar(128)" json:"tier_one_secret"` // 一级TOTP密钥
|
||||
TimeOffset int `gorm:"default:30" json:"time_offset"` // 允许时间偏移(秒)
|
||||
TOTPEnabled bool `gorm:"default:false" json:"totp_enabled"`
|
||||
|
||||
TierTwoSecret string `gorm:"type:varchar(128)" json:"tier_two_secret"` // 二级TOTP密钥
|
||||
|
||||
AuthType string `gorm:"type:varchar(32)" json:"auth_type"` // permanent/time_limited
|
||||
AuthDays int `json:"auth_days"` // 授权有效期(天)
|
||||
AuthorizedAt *time.Time `json:"authorized_at"`
|
||||
RevokedAt *time.Time `json:"revoked_at"`
|
||||
IsOffline bool `gorm:"default:false" json:"is_offline"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (ProjectAuthConfig) TableName() string {
|
||||
return "project_auth_configs"
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// VersionSnapshot 版本快照结构
|
||||
type VersionSnapshot struct {
|
||||
BasicInfo *BasicInfo `json:"basic_info"`
|
||||
DeployBusiness *DeployBusiness `json:"deploy_business"`
|
||||
DeployEnv *DeployEnv `json:"deploy_env"`
|
||||
DeployMiddleware *DeployMiddleware `json:"deploy_middleware"`
|
||||
}
|
||||
|
||||
// BasicInfo 基本信息
|
||||
type BasicInfo struct {
|
||||
Province string `json:"province"`
|
||||
City string `json:"city"`
|
||||
IndustryContact string `json:"industry_contact"`
|
||||
IndustryPhone string `json:"industry_phone"`
|
||||
ProjectNature string `json:"project_nature"`
|
||||
}
|
||||
|
||||
// DeployBusiness 部署业务信息
|
||||
type DeployBusiness struct {
|
||||
DeployerName string `json:"deployer_name"`
|
||||
DeployerPhone string `json:"deployer_phone"`
|
||||
DeployStartTime string `json:"deploy_start_time"`
|
||||
DeployEndTime string `json:"deploy_end_time"`
|
||||
SystemVersion string `json:"system_version"`
|
||||
SystemType string `json:"system_type"`
|
||||
MainEntrance string `json:"main_entrance"`
|
||||
AdminUsername string `json:"admin_username"`
|
||||
AdminPassword string `json:"admin_password"` // 加密存储
|
||||
}
|
||||
|
||||
// DeployEnv 部署环境信息
|
||||
type DeployEnv struct {
|
||||
Hosts []HostInfo `json:"hosts"`
|
||||
NetworkType string `json:"network_type"`
|
||||
MainPublicIP string `json:"main_public_ip"`
|
||||
DomainURL string `json:"domain_url"`
|
||||
SSLEnabled bool `json:"ssl_enabled"`
|
||||
ManagementType string `json:"management_type"`
|
||||
ManagementURL string `json:"management_url"`
|
||||
ManagementUser string `json:"management_user"`
|
||||
ManagementPwd string `json:"management_pwd"` // 加密存储
|
||||
HostCount int `json:"host_count"`
|
||||
TotalCPU int `json:"total_cpu"`
|
||||
CPUModel string `json:"cpu_model"`
|
||||
TotalMemory int `json:"total_memory"`
|
||||
TotalStorage int `json:"total_storage"`
|
||||
}
|
||||
|
||||
// HostInfo 主机信息
|
||||
type HostInfo struct {
|
||||
Hostname string `json:"hostname"`
|
||||
InternalIP string `json:"internal_ip"`
|
||||
PublicIP string `json:"public_ip"`
|
||||
CanAccessPublic bool `json:"can_access_public"`
|
||||
SSHPort int `json:"ssh_port"`
|
||||
SSHUser string `json:"ssh_user"`
|
||||
SSHPwd string `json:"ssh_pwd"` // 加密存储
|
||||
Role string `json:"role"` // master/worker/storage
|
||||
}
|
||||
|
||||
// DeployMiddleware 部署中间件信息
|
||||
type DeployMiddleware struct {
|
||||
MySQL MiddlewareInfo `json:"mysql"`
|
||||
Redis MiddlewareInfo `json:"redis"`
|
||||
EMQX MiddlewareInfo `json:"emqx"`
|
||||
MinIO MiddlewareInfo `json:"minio"`
|
||||
InfluxDB MiddlewareInfo `json:"influxdb"`
|
||||
Nacos MiddlewareInfo `json:"nacos"`
|
||||
K8SDashboard MiddlewareInfo `json:"k8s_dashboard"`
|
||||
}
|
||||
|
||||
// MiddlewareInfo 中间件信息
|
||||
type MiddlewareInfo struct {
|
||||
PublicIP string `json:"public_ip"`
|
||||
PublicPort int `json:"public_port"`
|
||||
InternalIP string `json:"internal_ip"`
|
||||
InternalPort int `json:"internal_port"`
|
||||
K8SAddress string `json:"k8s_address"`
|
||||
K8SPort int `json:"k8s_port"`
|
||||
AdminUser string `json:"admin_user"`
|
||||
AdminPwd string `json:"admin_pwd"` // 加密存储
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// DiffResult 差异结果
|
||||
type DiffResult struct {
|
||||
Module string `json:"module"`
|
||||
FieldDiffs []FieldDiff `json:"field_diffs"`
|
||||
}
|
||||
|
||||
// FieldDiff 字段差异
|
||||
type FieldDiff struct {
|
||||
FieldPath string `json:"field_path"`
|
||||
FieldName string `json:"field_name"`
|
||||
OldValue interface{} `json:"old_value"`
|
||||
NewValue interface{} `json:"new_value"`
|
||||
ChangeType string `json:"change_type"` // add/modify/delete
|
||||
}
|
||||
|
||||
// VersionService 版本服务
|
||||
type VersionService struct {
|
||||
repo VersionRepository
|
||||
fieldMap map[string]string // 字段路径到中文名的映射
|
||||
}
|
||||
|
||||
// NewVersionService 创建版本服务
|
||||
func NewVersionService(repo VersionRepository) *VersionService {
|
||||
return &VersionService{
|
||||
repo: repo,
|
||||
fieldMap: initFieldNameMap(),
|
||||
}
|
||||
}
|
||||
|
||||
// initFieldNameMap 初始化字段名映射
|
||||
func initFieldNameMap() map[string]string {
|
||||
return map[string]string{
|
||||
"basic_info.province": "省份",
|
||||
"basic_info.city": "城市",
|
||||
"basic_info.industry_contact": "行业组人员",
|
||||
"basic_info.industry_phone": "行业组电话",
|
||||
"basic_info.project_nature": "项目性质",
|
||||
"deploy_business.deployer_name": "部署人姓名",
|
||||
"deploy_business.system_version": "系统版本",
|
||||
"deploy_env.host_count": "主机台数",
|
||||
"deploy_env.main_public_ip": "主公网IP",
|
||||
"deploy_env.domain_url": "域名URL",
|
||||
// ... 更多字段映射
|
||||
}
|
||||
}
|
||||
|
||||
// CompareVersions 比较两个版本的差异
|
||||
func (s *VersionService) CompareVersions(
|
||||
ctx context.Context,
|
||||
baseVersion, targetVersion *VersionSnapshot,
|
||||
) ([]DiffResult, error) {
|
||||
var results []DiffResult
|
||||
|
||||
modules := []struct {
|
||||
Name string
|
||||
Base interface{}
|
||||
Target interface{}
|
||||
}{
|
||||
{"基本信息", baseVersion.BasicInfo, targetVersion.BasicInfo},
|
||||
{"部署业务", baseVersion.DeployBusiness, targetVersion.DeployBusiness},
|
||||
{"部署环境", baseVersion.DeployEnv, targetVersion.DeployEnv},
|
||||
{"部署中间件", baseVersion.DeployMiddleware, targetVersion.DeployMiddleware},
|
||||
}
|
||||
|
||||
for _, m := range modules {
|
||||
diffs := s.diffJSON(m.Name, m.Base, m.Target)
|
||||
if len(diffs) > 0 {
|
||||
results = append(results, DiffResult{
|
||||
Module: m.Name,
|
||||
FieldDiffs: diffs,
|
||||
})
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// diffJSON 对比两个结构体的差异
|
||||
func (s *VersionService) diffJSON(moduleName string, base, target interface{}) []FieldDiff {
|
||||
var diffs []FieldDiff
|
||||
|
||||
baseBytes, _ := json.Marshal(base)
|
||||
targetBytes, _ := json.Marshal(target)
|
||||
|
||||
var baseMap, targetMap map[string]interface{}
|
||||
json.Unmarshal(baseBytes, &baseMap)
|
||||
json.Unmarshal(targetBytes, &targetMap)
|
||||
|
||||
// 遍历目标版本的字段
|
||||
for key, newVal := range targetMap {
|
||||
oldVal, exists := baseMap[key]
|
||||
fieldPath := fmt.Sprintf("%s.%s", moduleName, key)
|
||||
|
||||
if !exists {
|
||||
// 新增字段
|
||||
diffs = append(diffs, FieldDiff{
|
||||
FieldPath: fieldPath,
|
||||
FieldName: s.getFieldName(fieldPath),
|
||||
OldValue: nil,
|
||||
NewValue: newVal,
|
||||
ChangeType: "add",
|
||||
})
|
||||
} else if !equalValues(oldVal, newVal) {
|
||||
// 修改字段
|
||||
diffs = append(diffs, FieldDiff{
|
||||
FieldPath: fieldPath,
|
||||
FieldName: s.getFieldName(fieldPath),
|
||||
OldValue: oldVal,
|
||||
NewValue: newVal,
|
||||
ChangeType: "modify",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 检查删除的字段
|
||||
for key, oldVal := range baseMap {
|
||||
if _, exists := targetMap[key]; !exists {
|
||||
fieldPath := fmt.Sprintf("%s.%s", moduleName, key)
|
||||
diffs = append(diffs, FieldDiff{
|
||||
FieldPath: fieldPath,
|
||||
FieldName: s.getFieldName(fieldPath),
|
||||
OldValue: oldVal,
|
||||
NewValue: nil,
|
||||
ChangeType: "delete",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return diffs
|
||||
}
|
||||
|
||||
// getFieldName 获取字段中文名
|
||||
func (s *VersionService) getFieldName(fieldPath string) string {
|
||||
if name, ok := s.fieldMap[fieldPath]; ok {
|
||||
return name
|
||||
}
|
||||
return fieldPath
|
||||
}
|
||||
|
||||
// equalValues 比较两个值是否相等
|
||||
func equalValues(a, b interface{}) bool {
|
||||
aBytes, _ := json.Marshal(a)
|
||||
bBytes, _ := json.Marshal(b)
|
||||
return string(aBytes) == string(bBytes)
|
||||
}
|
||||
|
||||
// CreateOfficialVersion 创建正式版本(审批通过时调用)
|
||||
func (s *VersionService) CreateOfficialVersion(
|
||||
ctx context.Context,
|
||||
projectID string,
|
||||
snapshot *VersionSnapshot,
|
||||
committerID int64,
|
||||
committerName string,
|
||||
commitMessage string,
|
||||
) error {
|
||||
// 1. 获取当前版本号
|
||||
currentVersion, err := s.repo.GetCurrentVersion(ctx, projectID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get current version failed: %w", err)
|
||||
}
|
||||
|
||||
// 2. 序列化快照
|
||||
snapshotData, err := json.Marshal(snapshot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal snapshot failed: %w", err)
|
||||
}
|
||||
|
||||
// 3. 创建新版本记录
|
||||
newVersion := &ProjectVersion{
|
||||
ProjectID: projectID,
|
||||
Version: currentVersion + 1,
|
||||
VersionType: VersionTypeOfficial,
|
||||
SnapshotData: snapshotData,
|
||||
CommitMessage: commitMessage,
|
||||
CommitterID: committerID,
|
||||
CommitterName: committerName,
|
||||
}
|
||||
|
||||
// 4. 事务中插入版本并更新项目当前版本号
|
||||
return s.repo.CreateVersionAndUpdateProject(ctx, newVersion)
|
||||
}
|
||||
|
||||
// GetVersionHistory 获取版本历史
|
||||
func (s *VersionService) GetVersionHistory(
|
||||
ctx context.Context,
|
||||
projectID string,
|
||||
page, pageSize int,
|
||||
) ([]*VersionHistory, int64, error) {
|
||||
return s.repo.GetVersionHistory(ctx, projectID, page, pageSize)
|
||||
}
|
||||
|
||||
// VersionHistory 版本历史记录
|
||||
type VersionHistory struct {
|
||||
Version int `json:"version"`
|
||||
VersionType string `json:"version_type"`
|
||||
CommitMessage string `json:"commit_message"`
|
||||
CommitterID int64 `json:"committer_id"`
|
||||
CommitterName string `json:"committer_name"`
|
||||
WorkflowID string `json:"workflow_id"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
ChangeSummary string `json:"change_summary"`
|
||||
}
|
||||
|
||||
// VersionRepository 版本仓储接口
|
||||
type VersionRepository interface {
|
||||
GetCurrentVersion(ctx context.Context, projectID string) (int, error)
|
||||
CreateVersionAndUpdateProject(ctx context.Context, version *ProjectVersion) error
|
||||
GetVersionHistory(ctx context.Context, projectID string, page, pageSize int) ([]*VersionHistory, int64, error)
|
||||
GetVersionByNumber(ctx context.Context, projectID string, version int) (*ProjectVersion, error)
|
||||
}
|
||||
|
||||
// ProjectVersion 项目版本实体(简化版)
|
||||
type ProjectVersion struct {
|
||||
ProjectID string
|
||||
Version int
|
||||
VersionType string
|
||||
BaseVersion int
|
||||
UserID int64
|
||||
WorkflowID string
|
||||
SnapshotData []byte
|
||||
CommitMessage string
|
||||
CommitterID int64
|
||||
CommitterName string
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
# ACL 权限模型
|
||||
|
||||
## 功能权限 (RBAC)
|
||||
|
||||
| 权限代码 | 说明 | 角色 |
|
||||
|:---|:---|:---|
|
||||
| `project:create` | 创建项目 | SuperAdmin |
|
||||
| `project:delete` | 删除/归档项目 | SuperAdmin |
|
||||
| `project:edit` | 直接编辑项目 | SuperAdmin |
|
||||
| `project:edit_workflow` | 通过工单编辑项目 | User (有ACL权限) |
|
||||
| `project:auth_manage` | 一级/二级授权管理 | SuperAdmin |
|
||||
| `project:permission_manage` | 项目权限分配 | SuperAdmin |
|
||||
|
||||
## 数据权限 (ACL) - 模块级别
|
||||
|
||||
### 模块定义
|
||||
|
||||
| 模块代码 | 模块名称 | 说明 |
|
||||
|:---|:---|:---|
|
||||
| `basic_info` | 基本信息模块 | 项目名称、命名空间、省份城市等 |
|
||||
| `business_info` | 部署业务模块 | 部署人、部署时间、系统版本等 |
|
||||
| `environment_info` | 部署环境模块 | 主机信息、网络环境、域名等 |
|
||||
| `middleware_info` | 部署中间件模块 | MySQL、Redis、EMQX等配置 |
|
||||
| `authorization_info` | 项目授权模块 | TOTP授权信息(仅SuperAdmin) |
|
||||
|
||||
### 权限类型
|
||||
|
||||
| 权限类型 | 说明 |
|
||||
|:---|:---|
|
||||
| `view` | 查看权限(可查看项目信息,可发起修改工单) |
|
||||
| `export` | 导出权限(可导出项目信息) |
|
||||
|
||||
> **说明**:编辑权限通过工单系统实现,拥有 `view` 权限的用户可以发起修改工单,由 SuperAdmin 审批后生效。
|
||||
|
||||
## 权限规则
|
||||
|
||||
1. **SuperAdmin**: 拥有所有项目的所有模块的全部权限,可直接修改
|
||||
2. **Admin**: 可以访问自己被授权的项目模块,可以向普通用户转授权限
|
||||
3. **Normal User**: 只能访问被授权的项目模块,修改需通过工单
|
||||
4. **项目填写人**: 自动获得该项目的查看权限
|
||||
5. **授权模块**: 仅 SuperAdmin 可见
|
||||
|
||||
## ACL 表结构(位于 rmdc-user-auth)
|
||||
|
||||
```go
|
||||
// ProjectACL 项目权限表 (模块级别)
|
||||
type ProjectACL struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
ProjectID string `gorm:"type:varchar(64);index;not null" json:"project_id"`
|
||||
UserID int64 `gorm:"index;not null" json:"user_id"`
|
||||
|
||||
// 模块代码: basic_info/business_info/environment_info/middleware_info/authorization_info
|
||||
ModuleCode string `gorm:"type:varchar(32);not null" json:"module_code"`
|
||||
|
||||
// 权限类型
|
||||
CanView bool `gorm:"default:false" json:"can_view"`
|
||||
CanExport bool `gorm:"default:false" json:"can_export"`
|
||||
|
||||
// 授权信息
|
||||
GrantedBy int64 `json:"granted_by"`
|
||||
GrantedAt time.Time `json:"granted_at"`
|
||||
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
```
|
||||
|
||||
## 权限检查流程
|
||||
|
||||
```go
|
||||
// CheckProjectModulePermission 检查用户对项目模块的权限
|
||||
func (s *ACLService) CheckProjectModulePermission(
|
||||
ctx context.Context,
|
||||
userID int64,
|
||||
projectID string,
|
||||
moduleCode string,
|
||||
permType string, // "view" or "export"
|
||||
) (bool, error) {
|
||||
// 1. 检查是否为 SuperAdmin
|
||||
if s.IsSuperAdmin(ctx, userID) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// 2. 授权模块仅 SuperAdmin 可访问
|
||||
if moduleCode == "authorization_info" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// 3. 查询 ACL 表
|
||||
var acl ProjectACL
|
||||
err := s.db.Where("project_id = ? AND user_id = ? AND module_code = ?",
|
||||
projectID, userID, moduleCode).First(&acl).Error
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// 4. 检查权限类型
|
||||
switch permType {
|
||||
case "view":
|
||||
return acl.CanView, nil
|
||||
case "export":
|
||||
return acl.CanExport, nil
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 权限分配接口
|
||||
|
||||
### 授予权限
|
||||
|
||||
```json
|
||||
// POST /api/project/permission/grant
|
||||
{
|
||||
"project_id": "proj_001",
|
||||
"user_id": 123,
|
||||
"modules": [
|
||||
{
|
||||
"module_code": "basic_info",
|
||||
"can_view": true,
|
||||
"can_export": false
|
||||
},
|
||||
{
|
||||
"module_code": "business_info",
|
||||
"can_view": true,
|
||||
"can_export": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 批量设置权限
|
||||
|
||||
```json
|
||||
// POST /api/project/permission/batch
|
||||
{
|
||||
"project_id": "proj_001",
|
||||
"permissions": [
|
||||
{
|
||||
"user_id": 123,
|
||||
"modules": ["basic_info", "business_info"]
|
||||
},
|
||||
{
|
||||
"user_id": 456,
|
||||
"modules": ["basic_info"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 权限继承规则
|
||||
|
||||
| 场景 | 规则 |
|
||||
|:---|:---|
|
||||
| 项目创建 | 填写人自动获得所有模块的 view 权限 |
|
||||
| 权限转授 | Admin 只能转授自己拥有的权限 |
|
||||
| 权限撤销 | 不影响已创建的草稿/工单 |
|
||||
| 项目归档 | 保留权限记录,但无法访问 |
|
||||
@@ -0,0 +1,151 @@
|
||||
# API 端点清单
|
||||
|
||||
## 项目管理
|
||||
|
||||
| 方法 | 路径 | 描述 | 权限 |
|
||||
|:---|:---|:---|:---|
|
||||
| POST | `/api/project/list` | 获取项目列表 (自动过滤ACL) | Login |
|
||||
| POST | `/api/project/detail` | 获取项目详情 (Master版本) | View ACL |
|
||||
| POST | `/api/project/create` | 创建项目 | SuperAdmin |
|
||||
| POST | `/api/project/update` | 直接更新项目 | SuperAdmin |
|
||||
| POST | `/api/project/delete` | 删除项目 (软删除) | SuperAdmin |
|
||||
| POST | `/api/project/export` | 导出项目信息 | Export ACL |
|
||||
|
||||
## 版本管理
|
||||
|
||||
| 方法 | 路径 | 描述 | 权限 |
|
||||
|:---|:---|:---|:---|
|
||||
| POST | `/api/project/version/list` | 获取版本历史列表 | View ACL |
|
||||
| POST | `/api/project/version/detail` | 获取指定版本详情 | View ACL |
|
||||
| POST | `/api/project/version/diff` | 获取版本差异 | View ACL |
|
||||
| POST | `/api/project/version/diff-with-current` | 对比指定版本与当前版本差异 | View ACL |
|
||||
|
||||
## 草稿管理
|
||||
|
||||
| 方法 | 路径 | 描述 | 权限 |
|
||||
|:---|:---|:---|:---|
|
||||
| POST | `/api/project/draft/get` | 获取当前用户的草稿 | View ACL |
|
||||
| POST | `/api/project/draft/save` | 保存草稿 | View ACL |
|
||||
| POST | `/api/project/draft/submit` | 提交审核 | View ACL |
|
||||
| POST | `/api/project/draft/discard` | 放弃草稿 | View ACL |
|
||||
|
||||
## 权限管理 (SuperAdmin)
|
||||
|
||||
| 方法 | 路径 | 描述 | 权限 |
|
||||
|:---|:---|:---|:---|
|
||||
| POST | `/api/project/permission/list` | 获取项目权限列表 | SuperAdmin |
|
||||
| POST | `/api/project/permission/grant` | 授予权限 | SuperAdmin |
|
||||
| POST | `/api/project/permission/revoke` | 撤销权限 | SuperAdmin |
|
||||
| POST | `/api/project/permission/batch` | 批量设置权限 | SuperAdmin |
|
||||
|
||||
## 授权管理 (SuperAdmin)
|
||||
|
||||
| 方法 | 路径 | 描述 | 权限 |
|
||||
|:---|:---|:---|:---|
|
||||
| POST | `/api/project/auth/config` | 获取授权配置 | SuperAdmin |
|
||||
| POST | `/api/project/auth/update` | 更新授权配置 | SuperAdmin |
|
||||
| POST | `/api/project/auth/grant` | 下发授权 | SuperAdmin |
|
||||
| POST | `/api/project/auth/revoke` | 撤销授权 | SuperAdmin |
|
||||
|
||||
## 请求/响应示例
|
||||
|
||||
### 项目列表
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"page": 1,
|
||||
"page_size": 20,
|
||||
"keyword": "",
|
||||
"lifecycle_status": "",
|
||||
"province": ""
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"total": 100,
|
||||
"list": [
|
||||
{
|
||||
"project_id": "proj_001",
|
||||
"name": "示例项目",
|
||||
"namespace": "example-ns",
|
||||
"lifecycle_status": "RELEASED",
|
||||
"certification_status": "official",
|
||||
"province": "北京市",
|
||||
"city": "北京市"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 保存草稿
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"project_id": "proj_001",
|
||||
"basic_info": {
|
||||
"province": "北京市",
|
||||
"city": "北京市",
|
||||
"industry_contact": "张三",
|
||||
"industry_phone": "13800138000"
|
||||
},
|
||||
"deploy_business": {
|
||||
"deployer_name": "李四",
|
||||
"system_version": "v2.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "草稿保存成功",
|
||||
"data": {
|
||||
"draft_id": 123,
|
||||
"updated_at": "2026-01-21T10:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 版本对比
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"project_id": "proj_001",
|
||||
"base_version": 2,
|
||||
"target_version": 3
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"diffs": [
|
||||
{
|
||||
"module": "部署环境",
|
||||
"field_diffs": [
|
||||
{
|
||||
"field_path": "deploy_env.host_count",
|
||||
"field_name": "主机台数",
|
||||
"old_value": 3,
|
||||
"new_value": 5,
|
||||
"change_type": "modify"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,239 @@
|
||||
# 数据库 Schema
|
||||
|
||||
## projects 表(项目主表)
|
||||
|
||||
```sql
|
||||
CREATE TABLE projects (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
project_id VARCHAR(64) UNIQUE NOT NULL,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
namespace VARCHAR(64) UNIQUE NOT NULL,
|
||||
|
||||
-- 生命周期状态: INIT/DRAFTING/REVIEWING/RELEASED/MODIFYING/ARCHIVED
|
||||
lifecycle_status VARCHAR(32) DEFAULT 'INIT',
|
||||
-- 认证状态: draft/pending/official
|
||||
certification_status VARCHAR(32) DEFAULT 'draft',
|
||||
|
||||
-- 当前正式版本号
|
||||
current_version INT DEFAULT 0,
|
||||
|
||||
-- JSONB 存储
|
||||
basic_info JSONB,
|
||||
deploy_business JSONB,
|
||||
deploy_env JSONB,
|
||||
deploy_middleware JSONB,
|
||||
|
||||
-- 填写人
|
||||
detail_filler_id BIGINT,
|
||||
detail_filler_name VARCHAR(64),
|
||||
|
||||
-- 审计字段
|
||||
created_by BIGINT,
|
||||
created_by_name VARCHAR(64),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX idx_projects_namespace ON projects(namespace);
|
||||
CREATE INDEX idx_projects_lifecycle ON projects(lifecycle_status);
|
||||
CREATE INDEX idx_projects_deleted ON projects(deleted_at);
|
||||
```
|
||||
|
||||
## project_versions 表(版本历史)
|
||||
|
||||
```sql
|
||||
CREATE TABLE project_versions (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
project_id VARCHAR(64) NOT NULL,
|
||||
|
||||
-- 版本号 (正式版本递增, 草稿为0)
|
||||
version INT NOT NULL DEFAULT 0,
|
||||
-- 版本类型: official/fill_draft/modify_draft
|
||||
version_type VARCHAR(32) NOT NULL,
|
||||
|
||||
-- 基准版本号(草稿基于哪个正式版本创建)
|
||||
base_version INT DEFAULT 0,
|
||||
|
||||
-- 草稿所属用户
|
||||
user_id BIGINT,
|
||||
user_name VARCHAR(64),
|
||||
|
||||
-- 关联工单
|
||||
workflow_id VARCHAR(64),
|
||||
|
||||
-- 完整快照
|
||||
snapshot_data JSONB,
|
||||
|
||||
-- 变更信息
|
||||
commit_message VARCHAR(255),
|
||||
committer_id BIGINT,
|
||||
committer_name VARCHAR(64),
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_versions_project ON project_versions(project_id);
|
||||
CREATE INDEX idx_versions_workflow ON project_versions(workflow_id);
|
||||
CREATE INDEX idx_versions_user ON project_versions(user_id);
|
||||
CREATE UNIQUE INDEX idx_versions_project_version ON project_versions(project_id, version) WHERE version > 0;
|
||||
```
|
||||
|
||||
## project_workflows 表(项目工单关联)
|
||||
|
||||
```sql
|
||||
CREATE TABLE project_workflows (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
project_id VARCHAR(64) NOT NULL,
|
||||
workflow_id VARCHAR(64) UNIQUE NOT NULL,
|
||||
|
||||
-- 工单类型: fill(填写)/modify(修改)
|
||||
workflow_type VARCHAR(32) NOT NULL,
|
||||
|
||||
-- 工单状态 (冗余存储,便于查询)
|
||||
status VARCHAR(32),
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_pw_project ON project_workflows(project_id);
|
||||
CREATE INDEX idx_pw_status ON project_workflows(status);
|
||||
```
|
||||
|
||||
## project_auth_configs 表(授权配置)
|
||||
|
||||
```sql
|
||||
CREATE TABLE project_auth_configs (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
project_id VARCHAR(64) UNIQUE NOT NULL,
|
||||
|
||||
tier_one_secret VARCHAR(128), -- 一级TOTP密钥 (加密存储)
|
||||
time_offset INT DEFAULT 30, -- 允许时间偏移(秒)
|
||||
totp_enabled BOOLEAN DEFAULT FALSE,
|
||||
|
||||
tier_two_secret VARCHAR(128), -- 二级TOTP密钥 (来自Watchdog)
|
||||
|
||||
auth_type VARCHAR(32), -- permanent/time_limited
|
||||
auth_days INT, -- 授权有效期(天)
|
||||
authorized_at TIMESTAMPTZ,
|
||||
revoked_at TIMESTAMPTZ,
|
||||
is_offline BOOLEAN DEFAULT FALSE,
|
||||
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_auth_project ON project_auth_configs(project_id);
|
||||
```
|
||||
|
||||
## JSONB 结构定义
|
||||
|
||||
### basic_info
|
||||
|
||||
```json
|
||||
{
|
||||
"province": "北京市",
|
||||
"city": "北京市",
|
||||
"industry_contact": "张三",
|
||||
"industry_phone": "13800138000",
|
||||
"project_nature": "market"
|
||||
}
|
||||
```
|
||||
|
||||
### deploy_business
|
||||
|
||||
```json
|
||||
{
|
||||
"deployer_name": "李四",
|
||||
"deployer_phone": "13900139000",
|
||||
"deploy_start_time": "2026-01-01",
|
||||
"deploy_end_time": "2026-01-15",
|
||||
"system_version": "v2.0.0",
|
||||
"system_type": "fly-control",
|
||||
"main_entrance": "https://example.com",
|
||||
"admin_username": "admin",
|
||||
"admin_password": "<encrypted>"
|
||||
}
|
||||
```
|
||||
|
||||
### deploy_env
|
||||
|
||||
```json
|
||||
{
|
||||
"hosts": [
|
||||
{
|
||||
"hostname": "master-01",
|
||||
"internal_ip": "192.168.1.10",
|
||||
"public_ip": "",
|
||||
"can_access_public": false,
|
||||
"ssh_port": 22,
|
||||
"ssh_user": "root",
|
||||
"ssh_pwd": "<encrypted>",
|
||||
"role": "master"
|
||||
}
|
||||
],
|
||||
"network_type": "internal",
|
||||
"main_public_ip": "",
|
||||
"domain_url": "",
|
||||
"ssl_enabled": false,
|
||||
"management_type": "bastion",
|
||||
"management_url": "",
|
||||
"management_user": "",
|
||||
"management_pwd": "<encrypted>",
|
||||
"host_count": 3,
|
||||
"total_cpu": 24,
|
||||
"cpu_model": "Intel Xeon",
|
||||
"total_memory": 64,
|
||||
"total_storage": 1000
|
||||
}
|
||||
```
|
||||
|
||||
### deploy_middleware
|
||||
|
||||
```json
|
||||
{
|
||||
"mysql": {
|
||||
"public_ip": "",
|
||||
"public_port": 0,
|
||||
"internal_ip": "192.168.1.10",
|
||||
"internal_port": 3306,
|
||||
"k8s_address": "mysql-svc",
|
||||
"k8s_port": 3306,
|
||||
"admin_user": "root",
|
||||
"admin_pwd": "<encrypted>",
|
||||
"version": "8.0"
|
||||
},
|
||||
"redis": { ... },
|
||||
"emqx": { ... },
|
||||
"minio": { ... },
|
||||
"influxdb": { ... },
|
||||
"nacos": { ... },
|
||||
"k8s_dashboard": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
## 敏感字段加密说明
|
||||
|
||||
以下字段必须使用 AES-256 加密存储:
|
||||
|
||||
| 表 | 字段路径 | 说明 |
|
||||
|:---|:---|:---|
|
||||
| projects | `deploy_business.admin_password` | 系统超管密码 |
|
||||
| projects | `deploy_env.hosts[*].ssh_pwd` | SSH密码 |
|
||||
| projects | `deploy_env.management_pwd` | 管理后台密码 |
|
||||
| projects | `deploy_middleware.*.admin_pwd` | 中间件超管密码 |
|
||||
| project_auth_configs | `tier_one_secret` | 一级TOTP密钥 |
|
||||
| project_auth_configs | `tier_two_secret` | 二级TOTP密钥 |
|
||||
|
||||
## 索引优化建议
|
||||
|
||||
```sql
|
||||
-- 常用查询优化
|
||||
CREATE INDEX idx_projects_lifecycle_cert ON projects(lifecycle_status, certification_status);
|
||||
CREATE INDEX idx_projects_province ON projects((basic_info->>'province'));
|
||||
|
||||
-- JSONB GIN 索引(按需添加)
|
||||
CREATE INDEX idx_projects_basic_gin ON projects USING GIN (basic_info);
|
||||
```
|
||||
@@ -0,0 +1,90 @@
|
||||
# 项目生命周期状态机
|
||||
|
||||
## 状态定义
|
||||
|
||||
| 状态 | 说明 | 触发动作 | 权限 |
|
||||
|:---|:---|:---|:---|
|
||||
| **INIT** | 项目元数据已创建,等待详细信息录入 | 超级管理员创建项目 | SuperAdmin |
|
||||
| **DRAFTING** | 正在进行初始信息填写(关联填写工单) | 指定填写人保存/编辑 | 填写人/SuperAdmin |
|
||||
| **REVIEWING** | 初始信息或变更信息提交审核 | 提交审核 | SuperAdmin |
|
||||
| **RELEASED** | 审核通过,正常运行中 | 审核通过 | All (View) |
|
||||
| **MODIFYING** | 存在活跃的变更工单(不影响主线运行) | 发起修改工单 | Owner/SuperAdmin |
|
||||
| **ARCHIVED** | 软删除状态,不可见但保留数据 | 删除项目 | SuperAdmin |
|
||||
|
||||
## 状态转换图
|
||||
|
||||
```
|
||||
[*] ──创建项目──> INIT
|
||||
│
|
||||
├──分配填写人──> DRAFTING ──保存草稿──> DRAFTING
|
||||
│ │
|
||||
│ └──提交审核──> REVIEWING
|
||||
│ │
|
||||
│ ┌──审核打回──<────┤
|
||||
│ │ │
|
||||
│ v └──审核通过──> RELEASED
|
||||
│ DRAFTING │
|
||||
│ │
|
||||
│ ┌──发起修改工单──<───────────────┤
|
||||
│ │ │
|
||||
│ v └──归档删除──> ARCHIVED ──> [*]
|
||||
│ MODIFYING ──保存草稿──> MODIFYING
|
||||
│ │
|
||||
│ ├──提交变更审核──> REVIEWING
|
||||
│ │
|
||||
│ └──撤销变更──> RELEASED
|
||||
```
|
||||
|
||||
## 状态转换条件
|
||||
|
||||
| From | To | 事件 | 条件 |
|
||||
|:---|:---|:---|:---|
|
||||
| INIT | DRAFTING | 分配填写人 | 填写人 ID 有效 |
|
||||
| DRAFTING | DRAFTING | 保存草稿 | 表单数据有效 |
|
||||
| DRAFTING | REVIEWING | 提交审核 | 必填字段完整 |
|
||||
| REVIEWING | DRAFTING | 审核打回 | SuperAdmin 操作 |
|
||||
| REVIEWING | RELEASED | 审核通过 | SuperAdmin 操作 |
|
||||
| RELEASED | MODIFYING | 发起修改工单 | 有 View ACL 权限 |
|
||||
| RELEASED | ARCHIVED | 归档删除 | SuperAdmin 操作 |
|
||||
| MODIFYING | MODIFYING | 保存草稿 | 表单数据有效 |
|
||||
| MODIFYING | REVIEWING | 提交变更审核 | 必填字段完整 |
|
||||
| MODIFYING | RELEASED | 撤销变更/审核通过 | 用户操作/SuperAdmin |
|
||||
|
||||
## Mermaid 状态图
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> INIT: 创建项目
|
||||
|
||||
INIT --> DRAFTING: 分配填写人
|
||||
|
||||
DRAFTING --> DRAFTING: 保存草稿
|
||||
DRAFTING --> REVIEWING: 提交审核
|
||||
|
||||
REVIEWING --> DRAFTING: 审核打回
|
||||
REVIEWING --> RELEASED: 审核通过
|
||||
|
||||
RELEASED --> MODIFYING: 发起修改工单
|
||||
RELEASED --> ARCHIVED: 归档删除
|
||||
|
||||
MODIFYING --> MODIFYING: 保存草稿
|
||||
MODIFYING --> REVIEWING: 提交变更审核
|
||||
MODIFYING --> RELEASED: 撤销变更/审核通过
|
||||
|
||||
ARCHIVED --> [*]
|
||||
|
||||
note right of RELEASED: 项目认证状态=official
|
||||
note right of DRAFTING: 支持多次保存草稿
|
||||
note right of MODIFYING: 可同时存在多个变更工单
|
||||
```
|
||||
|
||||
## 状态与认证状态映射
|
||||
|
||||
| 生命周期状态 | 认证状态 (certification_status) |
|
||||
|:---|:---|
|
||||
| INIT | draft |
|
||||
| DRAFTING | draft |
|
||||
| REVIEWING | pending |
|
||||
| RELEASED | official |
|
||||
| MODIFYING | official (主线不变) |
|
||||
| ARCHIVED | official (保留) |
|
||||
@@ -0,0 +1,146 @@
|
||||
# 工单状态映射表
|
||||
|
||||
## 设计原则
|
||||
|
||||
项目模块(`rmdc-project-management`)与工单模块(`rmdc-work-procedure`)之间需要双向协作:
|
||||
1. **项目 → 工单**:项目模块调用工单模块创建/转换工单
|
||||
2. **工单 → 项目**:工单状态变更后同步更新项目生命周期状态
|
||||
|
||||
当前系统采用 **"模块化单体"架构**,使用 **接口注入(依赖注入)** 方式实现模块间回调。
|
||||
|
||||
## 填写工单 (project_detail)
|
||||
|
||||
| 工单事件 | 工单From状态 | 工单To状态 | 项目生命周期状态 | 说明 |
|
||||
|:---|:---|:---|:---|:---|
|
||||
| `create` | - | `created` | `INIT→DRAFTING` | 创建填写工单 |
|
||||
| `accept` | `assigned` | `in_progress` | `DRAFTING` (保持) | 填写人开始填写 |
|
||||
| `draft_save` | `in_progress` | `in_progress` | `DRAFTING` (保持) | 保存草稿 |
|
||||
| `submit/complete` | `in_progress` | `pending_review` | `REVIEWING` | 提交审核 |
|
||||
| `return` | `pending_review` | `returned` | `DRAFTING` | 审核人打回 |
|
||||
| `resubmit` | `returned` | `pending_review` | `REVIEWING` | 重新提交 |
|
||||
| `approve` | `pending_review` | `approved` | `RELEASED` | 审核通过 |
|
||||
| `revoke` | any | `revoked` | `INIT` | 撤销工单 |
|
||||
|
||||
## 修改工单 (project_modify)
|
||||
|
||||
| 工单事件 | 工单From状态 | 工单To状态 | 项目生命周期状态 | 说明 |
|
||||
|:---|:---|:---|:---|:---|
|
||||
| `create` | - | `created` | `RELEASED→MODIFYING` | 发起修改工单 |
|
||||
| `accept` | `assigned` | `in_progress` | `MODIFYING` (保持) | 开始修改 |
|
||||
| `draft_save` | `in_progress` | `in_progress` | `MODIFYING` (保持) | 保存草稿 |
|
||||
| `submit/complete` | `in_progress` | `pending_review` | `REVIEWING` | 提交审核 |
|
||||
| `return` | `pending_review` | `returned` | `MODIFYING` | 审核人打回 |
|
||||
| `resubmit` | `returned` | `pending_review` | `REVIEWING` | 重新提交 |
|
||||
| `approve` | `pending_review` | `approved` | `RELEASED` | 审核通过 |
|
||||
| `revoke` | any | `revoked` | `RELEASED` | 撤销工单 |
|
||||
|
||||
## 回调接口定义
|
||||
|
||||
### 项目模块提供的回调接口
|
||||
|
||||
```go
|
||||
// ProjectLifecycleUpdater 项目生命周期状态更新接口
|
||||
// 由 rmdc-core 在初始化时注入,工单模块状态变更时调用
|
||||
type ProjectLifecycleUpdater interface {
|
||||
// UpdateLifecycleStatus 更新项目生命周期状态
|
||||
UpdateLifecycleStatus(projectID, lifecycleStatus string) error
|
||||
|
||||
// SetLifecycleToDrafting 设置为填写中状态(工单被打回后)
|
||||
SetLifecycleToDrafting(projectID string) error
|
||||
|
||||
// SetLifecycleToReviewing 设置为审核中状态(提交审核时)
|
||||
SetLifecycleToReviewing(projectID string) error
|
||||
|
||||
// SetLifecycleToReleased 设置为已发布状态(审批通过时)
|
||||
SetLifecycleToReleased(projectID string) error
|
||||
|
||||
// SetLifecycleToModifying 设置为变更中状态(发起修改工单时)
|
||||
SetLifecycleToModifying(projectID string) error
|
||||
}
|
||||
```
|
||||
|
||||
### 项目模块调用工单模块的接口
|
||||
|
||||
```go
|
||||
// WorkflowTransitioner 工单状态转换接口
|
||||
// 由 rmdc-core 在初始化时注入,项目模块通过此接口调用工单模块
|
||||
type WorkflowTransitioner interface {
|
||||
// TransitionWorkflow 触发工单状态转换
|
||||
TransitionWorkflow(workflowID, event string, operatorID uint64,
|
||||
operatorName string, remark string) (newStatus string, err error)
|
||||
}
|
||||
|
||||
// WorkflowCreator 工单创建接口
|
||||
type WorkflowCreator interface {
|
||||
// CreateProjectWorkflow 创建项目相关工单
|
||||
CreateProjectWorkflow(req CreateWorkflowRequest) (workflowID string, err error)
|
||||
}
|
||||
```
|
||||
|
||||
## 依赖注入流程
|
||||
|
||||
在 `rmdc-core/cmd/main.go` 中完成模块间的依赖注入:
|
||||
|
||||
```go
|
||||
// 1. 初始化项目和工单服务
|
||||
projectSvc := projectHandler.RegisterRoutes(r, dbs.Project, authMiddleware)
|
||||
workflowSvc := workflowHandler.RegisterRoutes(r, dbs.Workflow, authMiddleware)
|
||||
|
||||
// 2. 注入工单→项目的回调(状态同步)
|
||||
projectCallbackSvc := initProjectWorkflowCallbacks(dbs.Project)
|
||||
workflowSvc.SetProjectLifecycleUpdater(projectCallbackSvc)
|
||||
|
||||
// 3. 注入项目→工单的调用(创建/转换工单)
|
||||
workflowCreator := initProjectWorkflowCreator(workflowSvc)
|
||||
projectSvc.SetWorkflowCreator(workflowCreator)
|
||||
|
||||
workflowTransitioner := initProjectWorkflowTransitioner(workflowSvc)
|
||||
projectSvc.SetWorkflowTransitioner(workflowTransitioner)
|
||||
```
|
||||
|
||||
## 回调处理实现
|
||||
|
||||
工单模块状态转换后,自动调用已注入的 `ProjectLifecycleUpdater` 接口更新项目状态:
|
||||
|
||||
```go
|
||||
// 工单模块 - 状态转换后触发项目状态更新
|
||||
func (s *WorkflowService) handleProjectLifecycleCallback(workflow *entity.Workflow, event string) {
|
||||
// 从业务载荷中获取项目ID
|
||||
projectID, ok := workflow.BusinessPayload["project_id"].(string)
|
||||
if !ok || projectID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// 根据事件类型更新项目生命周期状态
|
||||
if s.projectLifecycleUpdater != nil {
|
||||
switch event {
|
||||
case entity.EventApprove:
|
||||
s.projectLifecycleUpdater.SetLifecycleToReleased(projectID)
|
||||
case entity.EventReturn:
|
||||
s.projectLifecycleUpdater.SetLifecycleToDrafting(projectID)
|
||||
case entity.EventComplete, entity.EventResubmit:
|
||||
s.projectLifecycleUpdater.SetLifecycleToReviewing(projectID)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 回调处理要点
|
||||
|
||||
1. **幂等性**: 使用 `projectID + event + timestamp` 作为幂等键,防止重复处理
|
||||
2. **事务边界**: 状态更新与版本快照生成应在同一事务
|
||||
3. **审计日志**: 记录状态变更操作到 `rmdc-audit-log`
|
||||
4. **错误处理**: 回调失败不应阻塞工单状态转换,记录日志后继续
|
||||
|
||||
## 与 HTTP 回调的对比
|
||||
|
||||
| 特性 | 接口注入(当前实现) | HTTP 回调 |
|
||||
|:---|:---|:---|
|
||||
| **模块解耦** | ⚠️ 接口级解耦 | ✅ 完全解耦 |
|
||||
| **分布式支持** | ❌ 不支持 | ✅ 支持 |
|
||||
| **性能** | ✅ 进程内调用 | ⚠️ 网络开销 |
|
||||
| **复杂度** | ✅ 简单直接 | ⚠️ 需要重试/幂等处理 |
|
||||
| **事务一致性** | ✅ 强一致性 | ⚠️ 最终一致性 |
|
||||
| **适用场景** | 模块化单体架构 | 微服务架构 |
|
||||
|
||||
> 如果未来系统需要微服务化,可参考备选文档迁移到 HTTP 回调方案。
|
||||
@@ -0,0 +1,231 @@
|
||||
#!/bin/bash
|
||||
# verify-project-module.sh
|
||||
# 验证 rmdc-project-management 模块的完整性
|
||||
# 依赖: go, grep, find
|
||||
# 用法: ./verify-project-module.sh [project-root-path]
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_ROOT="${1:-.}"
|
||||
|
||||
echo "=========================================="
|
||||
echo "RMDC Project Management Module Verification"
|
||||
echo "=========================================="
|
||||
echo "Project Root: $PROJECT_ROOT"
|
||||
echo ""
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
PASS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
WARN_COUNT=0
|
||||
|
||||
check_pass() {
|
||||
echo -e "${GREEN}[PASS]${NC} $1"
|
||||
((PASS_COUNT++))
|
||||
}
|
||||
|
||||
check_fail() {
|
||||
echo -e "${RED}[FAIL]${NC} $1"
|
||||
((FAIL_COUNT++))
|
||||
}
|
||||
|
||||
check_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
((WARN_COUNT++))
|
||||
}
|
||||
|
||||
# 1. 检查实体定义
|
||||
echo "1. Checking Entity Definitions..."
|
||||
if grep -rq "LifecycleStatus" "$PROJECT_ROOT"/internal/project/entity/*.go 2>/dev/null; then
|
||||
check_pass "LifecycleStatus field exists in Project entity"
|
||||
else
|
||||
check_fail "LifecycleStatus field missing in Project entity"
|
||||
fi
|
||||
|
||||
if grep -rq "CurrentVersion" "$PROJECT_ROOT"/internal/project/entity/*.go 2>/dev/null; then
|
||||
check_pass "CurrentVersion field exists for version tracking"
|
||||
else
|
||||
check_fail "CurrentVersion field missing for version tracking"
|
||||
fi
|
||||
|
||||
if grep -rq "CertificationStatus" "$PROJECT_ROOT"/internal/project/entity/*.go 2>/dev/null; then
|
||||
check_pass "CertificationStatus field exists"
|
||||
else
|
||||
check_fail "CertificationStatus field missing"
|
||||
fi
|
||||
|
||||
# 2. 检查生命周期状态常量
|
||||
echo ""
|
||||
echo "2. Checking Lifecycle Status Constants..."
|
||||
REQUIRED_STATES=("INIT" "DRAFTING" "REVIEWING" "RELEASED" "MODIFYING" "ARCHIVED")
|
||||
for state in "${REQUIRED_STATES[@]}"; do
|
||||
if grep -rq "\"$state\"" "$PROJECT_ROOT"/internal/project/ 2>/dev/null; then
|
||||
check_pass "Lifecycle state '$state' defined"
|
||||
else
|
||||
check_fail "Lifecycle state '$state' not found"
|
||||
fi
|
||||
done
|
||||
|
||||
# 3. 检查 API 路由
|
||||
echo ""
|
||||
echo "3. Checking API Routes..."
|
||||
REQUIRED_ROUTES=(
|
||||
"/api/project/list"
|
||||
"/api/project/detail"
|
||||
"/api/project/create"
|
||||
"/api/project/update"
|
||||
"/api/project/draft/save"
|
||||
"/api/project/draft/submit"
|
||||
"/api/project/version/list"
|
||||
"/api/project/permission/grant"
|
||||
)
|
||||
for route in "${REQUIRED_ROUTES[@]}"; do
|
||||
if grep -rq "$route" "$PROJECT_ROOT"/internal/project/ 2>/dev/null; then
|
||||
check_pass "Route '$route' registered"
|
||||
else
|
||||
check_fail "Route '$route' not found"
|
||||
fi
|
||||
done
|
||||
|
||||
# 4. 检查权限中间件
|
||||
echo ""
|
||||
echo "4. Checking Permission Middleware..."
|
||||
if grep -rq "SuperAdmin\|RequireRole\|RequirePermission" "$PROJECT_ROOT"/internal/project/handler/*.go 2>/dev/null; then
|
||||
check_pass "Permission middleware applied"
|
||||
else
|
||||
check_warn "Permission middleware not detected - verify manually"
|
||||
fi
|
||||
|
||||
# 5. 检查版本服务
|
||||
echo ""
|
||||
echo "5. Checking Version Service..."
|
||||
if grep -rq "CompareVersions" "$PROJECT_ROOT"/internal/project/service/*.go 2>/dev/null; then
|
||||
check_pass "Version comparison service implemented"
|
||||
else
|
||||
check_fail "Version comparison service not found"
|
||||
fi
|
||||
|
||||
if grep -rq "CreateOfficialVersion" "$PROJECT_ROOT"/internal/project/service/*.go 2>/dev/null; then
|
||||
check_pass "CreateOfficialVersion method exists"
|
||||
else
|
||||
check_fail "CreateOfficialVersion method not found"
|
||||
fi
|
||||
|
||||
# 6. 检查回调接口
|
||||
echo ""
|
||||
echo "6. Checking Lifecycle Callback Interface..."
|
||||
if grep -rq "ProjectLifecycleUpdater" "$PROJECT_ROOT"/internal/project/*.go 2>/dev/null; then
|
||||
check_pass "ProjectLifecycleUpdater interface defined"
|
||||
else
|
||||
check_fail "ProjectLifecycleUpdater interface not found"
|
||||
fi
|
||||
|
||||
CALLBACK_METHODS=("SetLifecycleToDrafting" "SetLifecycleToReviewing" "SetLifecycleToReleased" "SetLifecycleToModifying")
|
||||
for method in "${CALLBACK_METHODS[@]}"; do
|
||||
if grep -rq "$method" "$PROJECT_ROOT"/internal/project/ 2>/dev/null; then
|
||||
check_pass "Callback method '$method' implemented"
|
||||
else
|
||||
check_fail "Callback method '$method' not found"
|
||||
fi
|
||||
done
|
||||
|
||||
# 7. 检查敏感字段加密
|
||||
echo ""
|
||||
echo "7. Checking Sensitive Field Encryption..."
|
||||
if grep -rq "Encrypt\|Decrypt\|AES\|crypto" "$PROJECT_ROOT"/internal/project/service/*.go 2>/dev/null; then
|
||||
check_pass "Encryption functions detected"
|
||||
else
|
||||
check_warn "Encryption functions not detected - verify password field handling"
|
||||
fi
|
||||
|
||||
# 8. 检查审计日志集成
|
||||
echo ""
|
||||
echo "8. Checking Audit Log Integration..."
|
||||
if grep -rq "audit\|AuditService\|auditSvc" "$PROJECT_ROOT"/internal/project/service/*.go 2>/dev/null; then
|
||||
check_pass "Audit log integration detected"
|
||||
else
|
||||
check_warn "Audit log integration not detected - verify manually"
|
||||
fi
|
||||
|
||||
# 9. 检查乐观锁实现
|
||||
echo ""
|
||||
echo "9. Checking Optimistic Lock..."
|
||||
if grep -rq "base_version\|BaseVersion\|VersionConflict" "$PROJECT_ROOT"/internal/project/ 2>/dev/null; then
|
||||
check_pass "Optimistic lock (version conflict check) implemented"
|
||||
else
|
||||
check_warn "Optimistic lock not detected - concurrent modification may cause issues"
|
||||
fi
|
||||
|
||||
# 10. 运行单元测试(如果可用)
|
||||
echo ""
|
||||
echo "10. Running Unit Tests..."
|
||||
if [ -d "$PROJECT_ROOT/internal/project" ]; then
|
||||
cd "$PROJECT_ROOT"
|
||||
if go test ./internal/project/... -v -short -count=1 2>&1 | head -20; then
|
||||
check_pass "Unit tests executed"
|
||||
else
|
||||
check_warn "Unit tests may have issues - check output above"
|
||||
fi
|
||||
else
|
||||
check_warn "Project directory not found, skipping tests"
|
||||
fi
|
||||
|
||||
# 11. 检查编译
|
||||
echo ""
|
||||
echo "11. Checking Build..."
|
||||
if [ -d "$PROJECT_ROOT/internal/project" ]; then
|
||||
cd "$PROJECT_ROOT"
|
||||
if go build ./internal/project/... 2>/dev/null; then
|
||||
check_pass "Module compiles successfully"
|
||||
else
|
||||
check_fail "Module compilation failed"
|
||||
fi
|
||||
else
|
||||
check_warn "Project directory not found, skipping build check"
|
||||
fi
|
||||
|
||||
# 12. 检查 JSONB 字段定义
|
||||
echo ""
|
||||
echo "12. Checking JSONB Field Definitions..."
|
||||
JSONB_FIELDS=("basic_info" "deploy_business" "deploy_env" "deploy_middleware")
|
||||
for field in "${JSONB_FIELDS[@]}"; do
|
||||
if grep -rq "\"$field\"" "$PROJECT_ROOT"/internal/project/entity/*.go 2>/dev/null; then
|
||||
check_pass "JSONB field '$field' defined"
|
||||
else
|
||||
check_fail "JSONB field '$field' not found"
|
||||
fi
|
||||
done
|
||||
|
||||
# 总结
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Verification Summary"
|
||||
echo "=========================================="
|
||||
echo -e "Passed: ${GREEN}$PASS_COUNT${NC}"
|
||||
echo -e "Failed: ${RED}$FAIL_COUNT${NC}"
|
||||
echo -e "Warnings: ${YELLOW}$WARN_COUNT${NC}"
|
||||
|
||||
if [ $FAIL_COUNT -gt 0 ]; then
|
||||
echo ""
|
||||
echo -e "${RED}Some checks failed. Please review and fix the issues.${NC}"
|
||||
echo ""
|
||||
echo "Common fixes:"
|
||||
echo " - Missing lifecycle states: Add constants in entity/constants.go"
|
||||
echo " - Missing routes: Register in handler/router.go"
|
||||
echo " - Missing callback interface: Implement ProjectLifecycleUpdater"
|
||||
echo " - Missing version service: Implement version comparison logic"
|
||||
exit 1
|
||||
elif [ $WARN_COUNT -gt 0 ]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}Some warnings detected. Please verify manually.${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo ""
|
||||
echo -e "${GREEN}All checks passed!${NC}"
|
||||
exit 0
|
||||
fi
|
||||
229
1-AgentSkills/developing-rmdc-system/SKILL.md
Normal file
229
1-AgentSkills/developing-rmdc-system/SKILL.md
Normal file
@@ -0,0 +1,229 @@
|
||||
---
|
||||
name: developing-rmdc-system
|
||||
description: Guides development and architecture decisions for the RMDC (Runtime Management & DevOps Center) platform. Use when creating new modules, understanding module dependencies, implementing cross-module features, or reviewing system-level changes. Keywords: RMDC, architecture, module, dependency, API gateway, MQTT, watchdog, exchange-hub, authorization.
|
||||
argument-hint: "<module-name> | <change-type: add-module|cross-module|dependency-change> | <design-doc-path>"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
---
|
||||
|
||||
# RMDC System Development Guide
|
||||
|
||||
## System Overview
|
||||
|
||||
RMDC (Runtime Management & DevOps Center) 是以项目(K8s Namespace)为核心维度的统一运维与交付平台。通过"边缘代理(Watchdog) + 消息总线(Exchange-Hub)"架构打通内外网边界。
|
||||
|
||||
### Architecture Layers
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Presentation │ Vue3 + Vuetify3 + TypeScript │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Gateway │ rmdc-core (API Gateway + Auth + Routing) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Business │ jenkins-dac | project-mgmt | user-auth │
|
||||
│ │ audit-log | notice-center | monitor │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Communication │ rmdc-exchange-hub (MQTT Gateway) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Message Broker │ MQTT Broker (EMQX/Mosquitto) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Edge │ rmdc-watchdog → watchdog-node/agent │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Data │ PostgreSQL 13+ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Module Registry
|
||||
|
||||
| Module | Responsibility | Tech Stack | Depends On |
|
||||
|--------|---------------|------------|------------|
|
||||
| **rmdc-core** | API Gateway, Auth, Routing | Go + Gin | rmdc-common |
|
||||
| **rmdc-jenkins-branch-dac** | Jenkins DAC, Build Mgmt | Jenkins API, MinIO | rmdc-common, rmdc-audit-log |
|
||||
| **rmdc-exchange-hub** | MQTT Gateway, Command Lifecycle | MQTT, PostgreSQL | rmdc-common, rmdc-project-mgmt |
|
||||
| **rmdc-watchdog** | Edge Proxy, K8S Ops, L2 Auth | K8S API, TOTP | rmdc-common |
|
||||
| **rmdc-project-management** | Project CRUD, L1 Auth Center | PostgreSQL | rmdc-common, rmdc-audit-log |
|
||||
| **rmdc-audit-log** | Audit Logging | PostgreSQL | rmdc-common |
|
||||
| **rmdc-user-auth** | User Auth, RBAC | JWT, PostgreSQL | rmdc-common |
|
||||
|
||||
> 详细依赖矩阵见 `reference/module-dependencies.md`
|
||||
|
||||
---
|
||||
|
||||
## Plan Phase
|
||||
|
||||
当开始 RMDC 相关开发任务时,首先执行以下检查:
|
||||
|
||||
### 1. Identify Affected Modules
|
||||
|
||||
```bash
|
||||
# 动态注入:查看当前模块结构
|
||||
!`ls -la 8-CMII-RMDC/`
|
||||
|
||||
# 动态注入:搜索涉及的模块设计文档
|
||||
!`grep -rnE "module|模块|service|接口" 8-CMII-RMDC/1-rmdc-system/ | head -30`
|
||||
```
|
||||
|
||||
### 2. Produce Checklist
|
||||
|
||||
- [ ] 确定变更涉及的模块列表
|
||||
- [ ] 确认是否涉及跨模块通信(MQTT/HTTP)
|
||||
- [ ] 确认是否涉及契约变更(API/Event/Schema)
|
||||
- [ ] 确认是否涉及授权层级变更(L1/L2)
|
||||
- [ ] 确认是否需要数据库迁移
|
||||
|
||||
### 3. Decision Points
|
||||
|
||||
| Decision | Options | Impact |
|
||||
|----------|---------|--------|
|
||||
| New module vs extend existing | 新增模块需注册到rmdc-core | 路由、鉴权、审计 |
|
||||
| Sync vs Async communication | HTTP同步 / MQTT异步 | 延迟、可靠性 |
|
||||
| L1 vs L2 authorization | project-mgmt(L1) / watchdog(L2) | 安全边界 |
|
||||
|
||||
---
|
||||
|
||||
## Verify Phase
|
||||
|
||||
### Cross-Module Compatibility Checklist
|
||||
|
||||
- [ ] **API Gateway**: rmdc-core 路由配置已更新
|
||||
- [ ] **Authentication**: JWT claims 字段兼容
|
||||
- [ ] **RBAC**: 权限点已在 rmdc-user-auth 注册
|
||||
- [ ] **Audit**: 审计日志已按模块分表配置
|
||||
- [ ] **MQTT Topics**: 新增 topic 已在 exchange-hub 注册
|
||||
- [ ] **Authorization**: L1/L2 授权流程已验证
|
||||
|
||||
### Dependency Verification
|
||||
|
||||
```bash
|
||||
# 动态注入:检查模块间 import 关系
|
||||
!`grep -rn "import.*rmdc-" --include="*.go" . | grep -v vendor | head -20`
|
||||
|
||||
# 动态注入:验证 go.mod 依赖
|
||||
!`cat go.mod | grep -E "rmdc-|wdd.io"`
|
||||
```
|
||||
|
||||
### Integration Points
|
||||
|
||||
| From | To | Protocol | Verify |
|
||||
|------|----|----------|--------|
|
||||
| rmdc-core | Business modules | HTTP/Internal | 路由注册 |
|
||||
| Business modules | exchange-hub | HTTP | 指令下发 |
|
||||
| exchange-hub | MQTT Broker | MQTT Pub/Sub | Topic 配置 |
|
||||
| MQTT Broker | watchdog | MQTT | 公网连通性 |
|
||||
| watchdog | watchdog-node/agent | HTTP/gRPC | 内网通信 |
|
||||
|
||||
---
|
||||
|
||||
## Execute Phase
|
||||
|
||||
### Adding New Business Module
|
||||
|
||||
1. Create module directory following structure:
|
||||
```
|
||||
rmdc-{module-name}/
|
||||
├── cmd/main.go
|
||||
├── configs/
|
||||
├── internal/
|
||||
│ ├── config/
|
||||
│ ├── dao/
|
||||
│ ├── handler/
|
||||
│ ├── model/{dto,entity}/
|
||||
│ └── service/
|
||||
└── pkg/
|
||||
```
|
||||
|
||||
2. Register routes in `rmdc-core`:
|
||||
```go
|
||||
// rmdc-core/internal/router/router.go
|
||||
moduleGroup := r.Group("/api/{module}")
|
||||
moduleGroup.Use(middleware.AuthMiddleware())
|
||||
```
|
||||
|
||||
3. Configure audit logging:
|
||||
```go
|
||||
// Add module to determineModule() in audit_service.go
|
||||
case strings.Contains(path, "/{module}/"):
|
||||
return "{module}"
|
||||
```
|
||||
|
||||
4. Update RBAC permissions in `rmdc-user-auth`
|
||||
|
||||
### Cross-Module Communication
|
||||
|
||||
**HTTP (Sync)**: 模块间直接调用
|
||||
```go
|
||||
resp, err := http.Post("http://rmdc-exchange-hub:8080/api/commands/send", ...)
|
||||
```
|
||||
|
||||
**MQTT (Async)**: 通过 exchange-hub 下发
|
||||
```go
|
||||
exhub.SendCommand(ctx, &Command{
|
||||
ProjectID: projectID,
|
||||
CommandType: "k8s_exec",
|
||||
Payload: payload,
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **循环依赖**: 业务模块间禁止直接 import,必须通过 rmdc-common 定义接口
|
||||
2. **JWT Claims 不一致**: 修改 JWT 结构需同步更新所有解析方验证逻辑
|
||||
3. **MQTT Topic 命名冲突**: 新增 topic 前必须检查 `reference/mqtt-topics.md`
|
||||
4. **L1/L2 授权边界模糊**: 平台侧操作走 L1(project-mgmt),边缘侧操作走 L2(watchdog)
|
||||
5. **审计日志遗漏**: 新模块必须配置独立审计表并注册到 DAOManager
|
||||
6. **数据库连接池耗尽**: 每个模块独立配置连接池,注意总数不超过 PostgreSQL max_connections
|
||||
7. **MQTT QoS 选择错误**: 指令类消息必须使用 QoS=1,状态类可用 QoS=0
|
||||
|
||||
---
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `developing-rmdc-core` - API Gateway 开发
|
||||
- `developing-rmdc-jenkins-dac` - Jenkins DAC 模块开发
|
||||
- `developing-rmdc-exchange-hub` - MQTT 网关开发
|
||||
- `developing-rmdc-watchdog` - 边缘代理开发
|
||||
- `developing-rmdc-project-mgmt` - 项目管理模块开发
|
||||
- `developing-rmdc-audit-log` - 审计日志模块开发
|
||||
- `developing-rmdc-user-auth` - 用户认证模块开发
|
||||
- `designing-rmdc-contracts` - API/事件契约设计
|
||||
- `managing-rmdc-migrations` - 数据库迁移管理
|
||||
- `implementing-rmdc-observability` - 可观测性实现
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Tech Stack
|
||||
|
||||
| Layer | Technology |
|
||||
|-------|------------|
|
||||
| Frontend | Vue3, TypeScript, Vuetify3 |
|
||||
| Backend | Go 1.21+, Gin, GORM |
|
||||
| Database | PostgreSQL 13+ |
|
||||
| Message | MQTT (EMQX/Mosquitto) |
|
||||
| Storage | MinIO |
|
||||
| Container | Docker, Kubernetes |
|
||||
|
||||
### API Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {...}
|
||||
}
|
||||
```
|
||||
|
||||
### Authorization Layers
|
||||
|
||||
| Layer | Scope | Validity | Algorithm |
|
||||
|-------|-------|----------|-----------|
|
||||
| L1 (一级) | project-mgmt ↔ watchdog | 30 min | SHA256, 8-digit |
|
||||
| L2 (二级) | watchdog ↔ agent/node | 30 sec | SHA1, 6-digit (TOTP) |
|
||||
@@ -0,0 +1,177 @@
|
||||
// RMDC Module Standard Structure Example
|
||||
// This file demonstrates the recommended project structure for RMDC business modules
|
||||
|
||||
package main
|
||||
|
||||
/*
|
||||
Directory Structure:
|
||||
|
||||
rmdc-{module-name}/
|
||||
├── cmd/
|
||||
│ └── main.go # Entry point
|
||||
├── configs/
|
||||
│ └── config.yaml # Configuration file
|
||||
├── internal/
|
||||
│ ├── config/
|
||||
│ │ └── config.go # Config struct and loader
|
||||
│ ├── dao/
|
||||
│ │ └── {entity}_dao.go # Data access layer
|
||||
│ ├── handler/
|
||||
│ │ └── {feature}_handler.go # HTTP handlers
|
||||
│ ├── model/
|
||||
│ │ ├── dto/
|
||||
│ │ │ ├── request.go # Request DTOs
|
||||
│ │ │ └── response.go # Response DTOs
|
||||
│ │ └── entity/
|
||||
│ │ └── {table}.go # Database entities
|
||||
│ └── service/
|
||||
│ └── {feature}_service.go # Business logic
|
||||
├── pkg/
|
||||
│ └── {shared}/ # Shared packages
|
||||
├── go.mod
|
||||
└── go.sum
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ============== Entity Layer ==============
|
||||
|
||||
// Project represents the database entity
|
||||
type Project struct {
|
||||
ID int64 `gorm:"primaryKey" json:"id"`
|
||||
ProjectID string `gorm:"uniqueIndex;size:64" json:"project_id"`
|
||||
Name string `gorm:"size:128" json:"name"`
|
||||
Namespace string `gorm:"uniqueIndex;size:64" json:"namespace"`
|
||||
Status string `gorm:"size:32" json:"status"`
|
||||
}
|
||||
|
||||
func (Project) TableName() string {
|
||||
return "projects"
|
||||
}
|
||||
|
||||
// ============== DTO Layer ==============
|
||||
|
||||
// CreateProjectRequest is the request DTO
|
||||
type CreateProjectRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Namespace string `json:"namespace" binding:"required"`
|
||||
}
|
||||
|
||||
// ProjectResponse is the response DTO
|
||||
type ProjectResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// ============== DAO Layer ==============
|
||||
|
||||
// ProjectDAO handles database operations
|
||||
type ProjectDAO struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewProjectDAO(db *gorm.DB) *ProjectDAO {
|
||||
return &ProjectDAO{db: db}
|
||||
}
|
||||
|
||||
func (d *ProjectDAO) Create(ctx context.Context, project *Project) error {
|
||||
return d.db.WithContext(ctx).Create(project).Error
|
||||
}
|
||||
|
||||
func (d *ProjectDAO) GetByID(ctx context.Context, id int64) (*Project, error) {
|
||||
var project Project
|
||||
err := d.db.WithContext(ctx).First(&project, id).Error
|
||||
return &project, err
|
||||
}
|
||||
|
||||
// ============== Service Layer ==============
|
||||
|
||||
// ProjectService contains business logic
|
||||
type ProjectService struct {
|
||||
dao *ProjectDAO
|
||||
}
|
||||
|
||||
func NewProjectService(dao *ProjectDAO) *ProjectService {
|
||||
return &ProjectService{dao: dao}
|
||||
}
|
||||
|
||||
func (s *ProjectService) CreateProject(ctx context.Context, req *CreateProjectRequest) (*Project, error) {
|
||||
project := &Project{
|
||||
Name: req.Name,
|
||||
Namespace: req.Namespace,
|
||||
Status: "PENDING",
|
||||
}
|
||||
// Business logic: generate project_id
|
||||
project.ProjectID = "proj_" + req.Namespace
|
||||
|
||||
if err := s.dao.Create(ctx, project); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return project, nil
|
||||
}
|
||||
|
||||
// ============== Handler Layer ==============
|
||||
|
||||
// ProjectHandler handles HTTP requests
|
||||
type ProjectHandler struct {
|
||||
service *ProjectService
|
||||
}
|
||||
|
||||
func NewProjectHandler(service *ProjectService) *ProjectHandler {
|
||||
return &ProjectHandler{service: service}
|
||||
}
|
||||
|
||||
// CreateProject godoc
|
||||
// @Summary Create a new project
|
||||
// @Tags projects
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body CreateProjectRequest true "Create project request"
|
||||
// @Success 200 {object} ProjectResponse
|
||||
// @Router /api/projects/create [post]
|
||||
func (h *ProjectHandler) CreateProject(c *gin.Context) {
|
||||
var req CreateProjectRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, ProjectResponse{
|
||||
Code: 10004,
|
||||
Message: "Invalid parameters: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
project, err := h.service.CreateProject(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, ProjectResponse{
|
||||
Code: 10005,
|
||||
Message: "Failed to create project: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, ProjectResponse{
|
||||
Code: 0,
|
||||
Message: "success",
|
||||
Data: project,
|
||||
})
|
||||
}
|
||||
|
||||
// ============== Router Registration ==============
|
||||
|
||||
// RegisterRoutes registers module routes to the router group
|
||||
func RegisterRoutes(r *gin.RouterGroup, handler *ProjectHandler) {
|
||||
projects := r.Group("/projects")
|
||||
{
|
||||
projects.POST("/create", handler.CreateProject)
|
||||
// projects.POST("/list", handler.ListProjects)
|
||||
// projects.POST("/detail", handler.GetProject)
|
||||
// projects.POST("/update", handler.UpdateProject)
|
||||
// projects.POST("/delete", handler.DeleteProject)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
# RMDC API Design Rules
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **POST + RequestBody 优先**: 所有 API 优先使用 POST 方法
|
||||
2. **避免 PathVariables**: 资源标识放入 RequestBody
|
||||
3. **避免 RequestParams**: 查询参数放入 RequestBody
|
||||
4. **统一响应格式**: 所有响应遵循标准结构
|
||||
|
||||
## URL Naming Convention
|
||||
|
||||
| Operation | Suffix | Example |
|
||||
|-----------|--------|---------|
|
||||
| List | `/list` | `POST /api/projects/list` |
|
||||
| Detail | `/detail` | `POST /api/projects/detail` |
|
||||
| Create | `/create` | `POST /api/projects/create` |
|
||||
| Update | `/update` | `POST /api/projects/update` |
|
||||
| Delete | `/delete` | `POST /api/projects/delete` |
|
||||
|
||||
## Request Format
|
||||
|
||||
```json
|
||||
{
|
||||
"project_id": "namespace_xxx",
|
||||
"page": 1,
|
||||
"page_size": 20,
|
||||
"filters": {
|
||||
"status": "ONLINE",
|
||||
"name": "keyword"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Response Format
|
||||
|
||||
### Success Response
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
// response payload
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Paginated Response
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"list": [...],
|
||||
"total": 100,
|
||||
"page": 1,
|
||||
"page_size": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Response
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 40001,
|
||||
"message": "Invalid parameter: project_id is required",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
## Error Code Ranges
|
||||
|
||||
| Range | Module | Description |
|
||||
|-------|--------|-------------|
|
||||
| 0 | - | Success |
|
||||
| 10000-19999 | rmdc-core | Gateway errors |
|
||||
| 20000-29999 | rmdc-jenkins-dac | Jenkins errors |
|
||||
| 30000-39999 | rmdc-project-mgmt | Project errors |
|
||||
| 40000-49999 | rmdc-exchange-hub | Exchange errors |
|
||||
| 50000-59999 | rmdc-watchdog | Watchdog errors |
|
||||
| 60000-69999 | rmdc-user-auth | Auth errors |
|
||||
| 70000-79999 | rmdc-audit-log | Audit errors |
|
||||
|
||||
## Common Error Codes
|
||||
|
||||
| Code | HTTP Status | Description |
|
||||
|------|-------------|-------------|
|
||||
| 10001 | 401 | Unauthorized |
|
||||
| 10002 | 403 | Forbidden |
|
||||
| 10003 | 404 | Resource not found |
|
||||
| 10004 | 400 | Invalid parameters |
|
||||
| 10005 | 500 | Internal server error |
|
||||
| 10006 | 429 | Rate limit exceeded |
|
||||
|
||||
## HTTP Headers
|
||||
|
||||
### Request Headers
|
||||
|
||||
| Header | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `Authorization` | Yes | `Bearer {jwt_token}` |
|
||||
| `Content-Type` | Yes | `application/json` |
|
||||
| `X-Request-ID` | No | 请求追踪 ID |
|
||||
|
||||
### Response Headers
|
||||
|
||||
| Header | Description |
|
||||
|--------|-------------|
|
||||
| `X-Request-ID` | 请求追踪 ID(回显或生成) |
|
||||
| `X-Response-Time` | 响应耗时(毫秒) |
|
||||
|
||||
## JWT Claims
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": 1,
|
||||
"username": "admin",
|
||||
"role": "SuperAdmin",
|
||||
"permissions": ["project:read", "project:write"],
|
||||
"exp": 1704501234,
|
||||
"iat": 1704414834
|
||||
}
|
||||
```
|
||||
|
||||
## Versioning Strategy
|
||||
|
||||
- 当前版本: 无版本前缀 (`/api/...`)
|
||||
- 破坏性变更: 使用版本前缀 (`/api/v2/...`)
|
||||
- 废弃策略: 旧版本保留 6 个月,响应头标记 `Deprecation: true`
|
||||
@@ -0,0 +1,105 @@
|
||||
# RMDC Authorization Layers
|
||||
|
||||
## Overview
|
||||
|
||||
RMDC 采用双层授权架构,确保跨网络操作的安全性。
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ RMDC Platform (内网) │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ │
|
||||
│ │ project-mgmt │────▶│ exchange-hub │ │
|
||||
│ │ (L1 Auth Center) │ │ (MQTT Gateway) │ │
|
||||
│ └──────────────────┘ └────────┬─────────┘ │
|
||||
└────────────────────────────────────┼───────────────────────┘
|
||||
│ MQTT (公网)
|
||||
┌────────────────────────────────────┼───────────────────────┐
|
||||
│ Project Env (边缘/外网) │
|
||||
│ ┌───────▼─────────┐ │
|
||||
│ │ rmdc-watchdog │ │
|
||||
│ │ (L2 Auth Center)│ │
|
||||
│ └───────┬─────────┘ │
|
||||
│ ┌───────────────┼───────────────┐ │
|
||||
│ ┌─────▼─────┐ ┌─────▼─────┐ │
|
||||
│ │ watchdog │ │ watchdog │ │
|
||||
│ │ -node │ │ -agent │ │
|
||||
│ └───────────┘ └───────────┘ │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## L1 Authorization (一级授权)
|
||||
|
||||
| Attribute | Value |
|
||||
|-----------|-------|
|
||||
| Scope | project-management ↔ watchdog |
|
||||
| Algorithm | SHA256 |
|
||||
| Code Length | 8 digits |
|
||||
| Validity | 30 minutes |
|
||||
| Transport | MQTT message payload |
|
||||
|
||||
### L1 Flow
|
||||
|
||||
1. `project-management` 创建项目时生成 `auth_secret`
|
||||
2. 部署 watchdog 时将 `auth_secret` 写入配置文件
|
||||
3. watchdog 启动后生成 TOTP 验证码发送注册请求
|
||||
4. `exchange-hub` 转发给 `project-management` 验证
|
||||
5. 验证通过后项目状态变为 `ONLINE`
|
||||
|
||||
### L1 Code Generation
|
||||
|
||||
```go
|
||||
func GenerateL1Code(secret string, timestamp int64) string {
|
||||
// 30-minute window
|
||||
counter := timestamp / (30 * 60)
|
||||
h := hmac.New(sha256.New, []byte(secret))
|
||||
h.Write([]byte(fmt.Sprintf("%d", counter)))
|
||||
hash := h.Sum(nil)
|
||||
offset := hash[len(hash)-1] & 0x0F
|
||||
code := binary.BigEndian.Uint32(hash[offset:offset+4]) & 0x7FFFFFFF
|
||||
return fmt.Sprintf("%08d", code%100000000)
|
||||
}
|
||||
```
|
||||
|
||||
## L2 Authorization (二级授权)
|
||||
|
||||
| Attribute | Value |
|
||||
|-----------|-------|
|
||||
| Scope | watchdog ↔ watchdog-agent/node |
|
||||
| Algorithm | SHA1 (RFC 6238 TOTP) |
|
||||
| Code Length | 6 digits |
|
||||
| Validity | 30 seconds |
|
||||
| Transport | HTTP Header / gRPC Metadata |
|
||||
|
||||
### L2 Flow
|
||||
|
||||
1. watchdog 作为二级授权中心持有本项目 `agent_secret`
|
||||
2. agent/node 启动时从配置获取 `agent_secret`
|
||||
3. 每次请求携带当前 TOTP 验证码
|
||||
4. watchdog 验证后执行操作
|
||||
|
||||
### L2 Code Generation
|
||||
|
||||
```go
|
||||
// Standard TOTP (RFC 6238)
|
||||
func GenerateL2Code(secret string) string {
|
||||
key, _ := base32.StdEncoding.DecodeString(secret)
|
||||
return totp.GenerateCode(key, time.Now())
|
||||
}
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Secret Storage**: 所有 secret 必须加密存储(AES-256-GCM)
|
||||
2. **Transport Security**: MQTT 必须启用 TLS
|
||||
3. **Clock Sync**: 所有节点时间偏差 < 30 秒
|
||||
4. **Replay Protection**: 每个 message_id 只能使用一次
|
||||
5. **Rate Limiting**: 验证失败超过 5 次锁定 15 分钟
|
||||
|
||||
## Error Codes
|
||||
|
||||
| Code | Description | Action |
|
||||
|------|-------------|--------|
|
||||
| AUTH_L1_INVALID | L1 验证码无效 | 检查 secret 配置 |
|
||||
| AUTH_L1_EXPIRED | L1 验证码过期 | 重新生成验证码 |
|
||||
| AUTH_L2_INVALID | L2 TOTP 无效 | 检查时间同步 |
|
||||
| AUTH_L2_LOCKED | L2 验证已锁定 | 等待 15 分钟 |
|
||||
@@ -0,0 +1,61 @@
|
||||
# RMDC Module Dependencies
|
||||
|
||||
## Dependency Matrix
|
||||
|
||||
```
|
||||
rmdc-common rmdc-core audit-log user-auth project-mgmt exchange-hub watchdog jenkins-dac
|
||||
rmdc-core ✓ - ✓ ✓ ✓ ✓ - ✓
|
||||
rmdc-jenkins-dac ✓ - ✓ - - - - -
|
||||
rmdc-exchange-hub ✓ - ✓ - ✓ - - -
|
||||
rmdc-watchdog ✓ - - - - - - -
|
||||
rmdc-project-mgmt ✓ - ✓ - - ✓ - -
|
||||
rmdc-audit-log ✓ - - - - - - -
|
||||
rmdc-user-auth ✓ - ✓ - - - - -
|
||||
```
|
||||
|
||||
## Dependency Rules
|
||||
|
||||
### Allowed Dependencies
|
||||
|
||||
| Module | Can Import |
|
||||
|--------|------------|
|
||||
| rmdc-core | All business modules (as router aggregator) |
|
||||
| Business modules | rmdc-common, rmdc-audit-log (for logging) |
|
||||
| rmdc-exchange-hub | rmdc-project-mgmt (for project validation) |
|
||||
|
||||
### Prohibited Dependencies
|
||||
|
||||
- **Business → Business**: 业务模块间禁止直接依赖
|
||||
- **Circular**: 任何形成环的依赖关系
|
||||
- **Edge → Platform**: watchdog 不可依赖平台侧模块(仅通过 MQTT 通信)
|
||||
|
||||
## External Service Dependencies
|
||||
|
||||
| Module | External Service | Protocol | Purpose |
|
||||
|--------|-----------------|----------|---------|
|
||||
| rmdc-jenkins-dac | Jenkins | REST API | Build management |
|
||||
| rmdc-jenkins-dac | MinIO | S3 API | Artifact storage |
|
||||
| rmdc-exchange-hub | MQTT Broker | MQTT | Message relay |
|
||||
| rmdc-watchdog | K8S API Server | HTTPS | Container orchestration |
|
||||
| All modules | PostgreSQL | TCP/5432 | Data persistence |
|
||||
|
||||
## Data Flow Direction
|
||||
|
||||
```
|
||||
Platform Side Edge Side
|
||||
┌─────────────┐ ┌─────────────┐
|
||||
│ rmdc-core │ │ watchdog │
|
||||
│ ↓ │ │ ↓ │
|
||||
│ Business │ ──MQTT via ExHub──→ │ node/agent │
|
||||
│ Modules │ ←─MQTT via ExHub─── │ │
|
||||
└─────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
## Version Compatibility
|
||||
|
||||
| Component | Min Version | Recommended |
|
||||
|-----------|-------------|-------------|
|
||||
| Go | 1.21 | 1.24+ |
|
||||
| PostgreSQL | 13 | 15+ |
|
||||
| MQTT Broker | 3.1.1 | 5.0 |
|
||||
| Kubernetes | 1.24 | 1.28+ |
|
||||
@@ -0,0 +1,75 @@
|
||||
# RMDC MQTT Topic Registry
|
||||
|
||||
## Topic Naming Convention
|
||||
|
||||
```
|
||||
wdd/RDMC/{type}/{direction}[/{project_id}]
|
||||
```
|
||||
|
||||
| Segment | Values | Description |
|
||||
|---------|--------|-------------|
|
||||
| type | `command`, `message` | 指令类 / 数据类 |
|
||||
| direction | `up`, `down` | 上行(边缘→平台) / 下行(平台→边缘) |
|
||||
| project_id | namespace_xxx | 项目标识(下行必需) |
|
||||
|
||||
## Registered Topics
|
||||
|
||||
### Uplink (Edge → Platform)
|
||||
|
||||
| Topic | Publisher | Subscriber | Purpose |
|
||||
|-------|-----------|------------|---------|
|
||||
| `wdd/RDMC/command/up` | watchdog | exchange-hub | 边缘发送指令响应 |
|
||||
| `wdd/RDMC/message/up` | watchdog | exchange-hub | 边缘发送数据/状态 |
|
||||
|
||||
### Downlink (Platform → Edge)
|
||||
|
||||
| Topic Pattern | Publisher | Subscriber | Purpose |
|
||||
|---------------|-----------|------------|---------|
|
||||
| `wdd/RDMC/command/down/{project_id}` | exchange-hub | watchdog | 下发操作指令 |
|
||||
| `wdd/RDMC/message/down/{project_id}` | exchange-hub | watchdog | 下发配置/数据 |
|
||||
|
||||
## Message Payload Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"message_id": "uuid-v4",
|
||||
"type": "command|message",
|
||||
"project_id": "namespace_xxx",
|
||||
"command_type": "k8s_exec|host_exec|register|heartbeat|...",
|
||||
"timestamp": 1704501234567,
|
||||
"version": "1.0",
|
||||
"signature": "hmac-sha256-hex",
|
||||
"payload": {
|
||||
// command-specific data
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Command Types
|
||||
|
||||
| command_type | Direction | Description |
|
||||
|--------------|-----------|-------------|
|
||||
| `register` | up | Watchdog 注册请求 |
|
||||
| `heartbeat` | up/down | 心跳保活 |
|
||||
| `k8s_exec` | down | K8S 操作指令 |
|
||||
| `host_exec` | down | 主机操作指令 |
|
||||
| `result` | up | 指令执行结果 |
|
||||
| `log_stream` | up | 日志流数据 |
|
||||
| `metrics` | up | 监控指标数据 |
|
||||
|
||||
## QoS Requirements
|
||||
|
||||
| Message Type | QoS Level | Reason |
|
||||
|--------------|-----------|--------|
|
||||
| Command (指令) | 1 | 至少一次,确保送达 |
|
||||
| Result (结果) | 1 | 至少一次,确保响应 |
|
||||
| Heartbeat | 0 | 最多一次,允许丢失 |
|
||||
| Metrics | 0 | 最多一次,允许丢失 |
|
||||
| Log stream | 0 | 最多一次,高频数据 |
|
||||
|
||||
## Adding New Topic
|
||||
|
||||
1. Update this file with new topic definition
|
||||
2. Register in `exchange-hub/internal/mqtt/subscriber.go`
|
||||
3. Add handler in `exchange-hub/internal/handler/mqtt_handler.go`
|
||||
4. Update watchdog subscription if needed
|
||||
@@ -0,0 +1,172 @@
|
||||
#!/bin/bash
|
||||
# verify-module-deps.sh
|
||||
# Verifies RMDC module dependency rules
|
||||
#
|
||||
# Dependencies:
|
||||
# - bash 4.0+
|
||||
# - grep
|
||||
# - find
|
||||
#
|
||||
# Usage:
|
||||
# ./verify-module-deps.sh [module-path]
|
||||
# ./verify-module-deps.sh /path/to/rmdc-project-management
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
MODULE_PATH="${1:-.}"
|
||||
ERRORS=0
|
||||
WARNINGS=0
|
||||
|
||||
echo "=========================================="
|
||||
echo "RMDC Module Dependency Verification"
|
||||
echo "=========================================="
|
||||
echo "Checking: $MODULE_PATH"
|
||||
echo ""
|
||||
|
||||
# Define prohibited dependencies
|
||||
declare -A PROHIBITED_DEPS
|
||||
PROHIBITED_DEPS["rmdc-jenkins-branch-dac"]="rmdc-project-management rmdc-exchange-hub rmdc-watchdog rmdc-user-auth"
|
||||
PROHIBITED_DEPS["rmdc-project-management"]="rmdc-jenkins-branch-dac rmdc-exchange-hub rmdc-watchdog rmdc-user-auth"
|
||||
PROHIBITED_DEPS["rmdc-exchange-hub"]="rmdc-jenkins-branch-dac rmdc-watchdog rmdc-user-auth"
|
||||
PROHIBITED_DEPS["rmdc-watchdog"]="rmdc-jenkins-branch-dac rmdc-project-management rmdc-exchange-hub rmdc-user-auth rmdc-audit-log"
|
||||
PROHIBITED_DEPS["rmdc-user-auth"]="rmdc-jenkins-branch-dac rmdc-project-management rmdc-exchange-hub rmdc-watchdog"
|
||||
PROHIBITED_DEPS["rmdc-audit-log"]="rmdc-jenkins-branch-dac rmdc-project-management rmdc-exchange-hub rmdc-watchdog rmdc-user-auth"
|
||||
|
||||
# Detect current module
|
||||
detect_module() {
|
||||
if [ -f "$MODULE_PATH/go.mod" ]; then
|
||||
grep "^module" "$MODULE_PATH/go.mod" | awk '{print $2}' | xargs basename
|
||||
else
|
||||
basename "$MODULE_PATH"
|
||||
fi
|
||||
}
|
||||
|
||||
CURRENT_MODULE=$(detect_module)
|
||||
echo "Detected module: $CURRENT_MODULE"
|
||||
echo ""
|
||||
|
||||
# Check 1: Verify go.mod exists
|
||||
echo "[Check 1] go.mod existence..."
|
||||
if [ -f "$MODULE_PATH/go.mod" ]; then
|
||||
echo -e "${GREEN}PASS${NC}: go.mod found"
|
||||
else
|
||||
echo -e "${RED}FAIL${NC}: go.mod not found"
|
||||
((ERRORS++))
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Check 2: Verify prohibited imports in go.mod
|
||||
echo "[Check 2] Prohibited dependencies in go.mod..."
|
||||
if [ -f "$MODULE_PATH/go.mod" ]; then
|
||||
PROHIBITED="${PROHIBITED_DEPS[$CURRENT_MODULE]:-}"
|
||||
if [ -n "$PROHIBITED" ]; then
|
||||
for dep in $PROHIBITED; do
|
||||
if grep -q "$dep" "$MODULE_PATH/go.mod" 2>/dev/null; then
|
||||
echo -e "${RED}FAIL${NC}: Prohibited dependency found: $dep"
|
||||
((ERRORS++))
|
||||
fi
|
||||
done
|
||||
if [ $ERRORS -eq 0 ]; then
|
||||
echo -e "${GREEN}PASS${NC}: No prohibited dependencies in go.mod"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}SKIP${NC}: No prohibition rules for $CURRENT_MODULE"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}SKIP${NC}: go.mod not found"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Check 3: Verify import statements in Go files
|
||||
echo "[Check 3] Prohibited imports in source code..."
|
||||
if [ -d "$MODULE_PATH" ]; then
|
||||
PROHIBITED="${PROHIBITED_DEPS[$CURRENT_MODULE]:-}"
|
||||
if [ -n "$PROHIBITED" ]; then
|
||||
for dep in $PROHIBITED; do
|
||||
FOUND=$(find "$MODULE_PATH" -name "*.go" -exec grep -l "\".*$dep" {} \; 2>/dev/null | head -5)
|
||||
if [ -n "$FOUND" ]; then
|
||||
echo -e "${RED}FAIL${NC}: Found import of $dep in:"
|
||||
echo "$FOUND" | while read -r f; do echo " - $f"; done
|
||||
((ERRORS++))
|
||||
fi
|
||||
done
|
||||
if [ $ERRORS -eq 0 ]; then
|
||||
echo -e "${GREEN}PASS${NC}: No prohibited imports in source files"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}SKIP${NC}: No prohibition rules for $CURRENT_MODULE"
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Check 4: Verify standard directory structure
|
||||
echo "[Check 4] Standard directory structure..."
|
||||
REQUIRED_DIRS=("cmd" "internal" "configs")
|
||||
OPTIONAL_DIRS=("pkg" "scripts" "docs")
|
||||
|
||||
for dir in "${REQUIRED_DIRS[@]}"; do
|
||||
if [ -d "$MODULE_PATH/$dir" ]; then
|
||||
echo -e "${GREEN}PASS${NC}: Required directory exists: $dir/"
|
||||
else
|
||||
echo -e "${RED}FAIL${NC}: Missing required directory: $dir/"
|
||||
((ERRORS++))
|
||||
fi
|
||||
done
|
||||
|
||||
for dir in "${OPTIONAL_DIRS[@]}"; do
|
||||
if [ -d "$MODULE_PATH/$dir" ]; then
|
||||
echo -e "${GREEN}INFO${NC}: Optional directory exists: $dir/"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
# Check 5: Verify internal structure
|
||||
echo "[Check 5] Internal package structure..."
|
||||
INTERNAL_DIRS=("config" "dao" "handler" "model" "service")
|
||||
if [ -d "$MODULE_PATH/internal" ]; then
|
||||
for dir in "${INTERNAL_DIRS[@]}"; do
|
||||
if [ -d "$MODULE_PATH/internal/$dir" ]; then
|
||||
echo -e "${GREEN}PASS${NC}: internal/$dir/ exists"
|
||||
else
|
||||
echo -e "${YELLOW}WARN${NC}: internal/$dir/ not found"
|
||||
((WARNINGS++))
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo -e "${YELLOW}SKIP${NC}: internal/ directory not found"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Check 6: Verify rmdc-common dependency
|
||||
echo "[Check 6] rmdc-common dependency..."
|
||||
if [ -f "$MODULE_PATH/go.mod" ]; then
|
||||
if grep -q "rmdc-common" "$MODULE_PATH/go.mod"; then
|
||||
echo -e "${GREEN}PASS${NC}: rmdc-common dependency found"
|
||||
else
|
||||
echo -e "${YELLOW}WARN${NC}: rmdc-common dependency not found (may be acceptable for some modules)"
|
||||
((WARNINGS++))
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Summary
|
||||
echo "=========================================="
|
||||
echo "Verification Summary"
|
||||
echo "=========================================="
|
||||
echo -e "Errors: ${RED}$ERRORS${NC}"
|
||||
echo -e "Warnings: ${YELLOW}$WARNINGS${NC}"
|
||||
echo ""
|
||||
|
||||
if [ $ERRORS -gt 0 ]; then
|
||||
echo -e "${RED}VERIFICATION FAILED${NC}"
|
||||
exit 1
|
||||
else
|
||||
echo -e "${GREEN}VERIFICATION PASSED${NC}"
|
||||
exit 0
|
||||
fi
|
||||
101
1-AgentSkills/developing-watchdog/SKILL.md
Normal file
101
1-AgentSkills/developing-watchdog/SKILL.md
Normal file
@@ -0,0 +1,101 @@
|
||||
---
|
||||
name: developing-watchdog
|
||||
description: Guides development of rmdc-watchdog edge agent module including K8S operations, MQTT messaging, authorization management, and node/agent coordination. Use when implementing watchdog features, adding K8S actions, modifying heartbeat logic, or debugging authorization flows. Keywords: watchdog, edge-agent, k8s-operator, mqtt, authorization, heartbeat, node, agent.
|
||||
argument-hint: "<feature-type>: k8s-action | heartbeat | mqtt-handler | node-comm | auth-flow"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
- Edit
|
||||
- Write
|
||||
---
|
||||
|
||||
# Developing rmdc-watchdog
|
||||
|
||||
rmdc-watchdog 是部署在项目环境的边缘代理,职责包括:二级授权中心、K8S操作代理、指令接收执行、监控数据上报。
|
||||
|
||||
## 动态上下文注入
|
||||
|
||||
```bash
|
||||
# 查看项目结构
|
||||
!`ls -la rmdc-watchdog/internal/`
|
||||
|
||||
# 查找现有Handler实现
|
||||
!`grep -rn "func.*Handler" rmdc-watchdog/internal/handler/`
|
||||
|
||||
# 查找MQTT消息路由
|
||||
!`grep -n "case\|switch" rmdc-watchdog/internal/service/message_router.go`
|
||||
```
|
||||
|
||||
## Plan
|
||||
|
||||
根据 `$ARGUMENTS` 确定开发类型:
|
||||
|
||||
| 类型 | 产物 | 影响模块 |
|
||||
|------|------|----------|
|
||||
| k8s-action | `pkg/k8s/client.go`, `service/k8s_service.go` | exchange-hub指令定义 |
|
||||
| heartbeat | `handler/heartbeat_handler.go`, `service/auth_service.go` | watchdog-agent同步修改 |
|
||||
| mqtt-handler | `service/mqtt_service.go`, `service/message_router.go` | exchange-hub Topic契约 |
|
||||
| node-comm | `service/node_service.go` | watchdog-node API同步 |
|
||||
| auth-flow | `service/auth_service.go`, `dao/auth_dao.go` | project-management授权契约 |
|
||||
|
||||
**决策点**:
|
||||
1. 是否新增MQTT消息类型?→ 需同步 exchange-hub
|
||||
2. 是否修改心跳结构?→ 需同步 watchdog-agent
|
||||
3. 是否修改K8S指令参数?→ 需同步 octopus-operator
|
||||
|
||||
## Verify
|
||||
|
||||
- [ ] TOTP验证逻辑:一级(8位/30分钟/SHA256) vs 二级(6位/30秒/SHA1)
|
||||
- [ ] K8S操作边界:仅允许审计过的操作(logs/exec/scale/restart/delete/get/apply)
|
||||
- [ ] MQTT Topic格式:`wdd/RDMC/{command|message}/{up|down}/{project_id}`
|
||||
- [ ] 时间戳校验:|now - timestamp| < 5分钟
|
||||
- [ ] Node通信:HTTP + Tier-Two TOTP认证
|
||||
- [ ] 执行结果上报:包含 command_id, status, exit_code, output, duration
|
||||
|
||||
```bash
|
||||
# 验证编译
|
||||
!`cd rmdc-watchdog && go build ./...`
|
||||
|
||||
# 验证单元测试
|
||||
!`cd rmdc-watchdog && go test ./internal/... -v`
|
||||
```
|
||||
|
||||
## Execute
|
||||
|
||||
### 添加新K8S操作
|
||||
|
||||
1. 在 `pkg/k8s/client.go` 添加K8S API方法
|
||||
2. 在 `internal/service/k8s_service.go` 的 switch 添加 case
|
||||
3. 更新 `K8sExecCommand` 结构(如需新参数)
|
||||
4. 同步更新 exchange-hub 指令下发定义
|
||||
|
||||
### 添加新指令类型
|
||||
|
||||
1. 在 `message_router.go` 添加路由分支
|
||||
2. 创建对应 Handler 和 Service
|
||||
3. 同步更新 exchange-hub 指令下发
|
||||
|
||||
### 修改心跳逻辑
|
||||
|
||||
1. 修改 `auth_service.go` 的 `VerifyHeartbeat`
|
||||
2. 同步修改 watchdog-agent 心跳发送
|
||||
3. 更新 DTO 结构
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **TOTP层级混淆**:一级授权(project-management↔watchdog)与二级授权(watchdog↔agent/node)使用不同参数
|
||||
2. **时间偏移未处理**:授权文件需计算 `timeOffset = now - firstAuthTime`
|
||||
3. **Node离线未检测**:转发主机指令前需 `CheckHostOnline(host_id)`
|
||||
4. **日志截断遗漏**:业务故障日志仅回传最近300行
|
||||
5. **密钥公网传输**:tier_one_secret/tier_two_secret 必须通过配置文件离线部署,禁止MQTT传输
|
||||
6. **响应TOTP缺失**:双向验证要求服务端返回TOTP供客户端校验
|
||||
7. **心跳间隔不一致**:watchdog→exchange-hub 5秒;agent/node→watchdog 10秒(默认)
|
||||
|
||||
## Reference
|
||||
|
||||
- [状态机](reference/state-machine.md)
|
||||
- [MQTT Topics](reference/mqtt-topics.md)
|
||||
- [API端点](reference/api-endpoints.md)
|
||||
- [安全机制](reference/security-mechanisms.md)
|
||||
56
1-AgentSkills/developing-watchdog/reference/api-endpoints.md
Normal file
56
1-AgentSkills/developing-watchdog/reference/api-endpoints.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Watchdog API 端点
|
||||
|
||||
## Watchdog HTTP API (Port: 8990)
|
||||
|
||||
| 路径 | 方法 | 说明 | 认证 |
|
||||
|------|------|------|------|
|
||||
| `/api/heartbeat` | POST | Agent心跳接口 | Tier-Two TOTP |
|
||||
| `/api/heartbeat/hosts` | GET | 获取所有心跳主机 | 内部调用 |
|
||||
| `/api/node/info` | POST | Node信息上报接口 | Tier-Two TOTP |
|
||||
| `/api/node/list` | GET | 获取所有Node列表 | 内部调用 |
|
||||
| `/api/node/metrics/:node_id` | GET | 获取指定Node运行指标 | 内部调用 |
|
||||
| `/api/authorization/generate` | GET | 生成授权文件 | 内部调用 |
|
||||
| `/api/authorization/auth` | POST | 接收授权码 | Tier-One TOTP |
|
||||
| `/api/authorization/hosts` | GET | 获取所有已授权主机 | 内部调用 |
|
||||
|
||||
## Node HTTP API (Port: 8081)
|
||||
|
||||
| 路径 | 方法 | 说明 | 认证 |
|
||||
|------|------|------|------|
|
||||
| `/api/exec` | POST | 执行命令 | Tier-Two TOTP |
|
||||
| `/api/info` | GET | 获取主机信息 | Tier-Two TOTP |
|
||||
| `/api/metrics` | GET | 获取运行指标 | Tier-Two TOTP |
|
||||
| `/api/dltu` | POST | 镜像操作(Download-Load-Tag-Upload) | Tier-Two TOTP |
|
||||
|
||||
## 请求/响应结构
|
||||
|
||||
### HeartbeatRequest
|
||||
```go
|
||||
type HeartbeatRequest struct {
|
||||
HostInfo HostInfo `json:"host_info"`
|
||||
EnvInfo EnvInfo `json:"env_info"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
TOTPCode string `json:"totp_code"`
|
||||
}
|
||||
```
|
||||
|
||||
### HeartbeatResponse
|
||||
```go
|
||||
type HeartbeatResponse struct {
|
||||
Authorized bool `json:"authorized"`
|
||||
TOTPCode string `json:"totp_code"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
SecondTOTPSecret string `json:"second_totp_secret,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
### NodeInfoRequest
|
||||
```go
|
||||
type NodeInfoRequest struct {
|
||||
NodeID string `json:"node_id"`
|
||||
HostInfo HostInfo `json:"host_info"`
|
||||
Metrics NodeRuntimeMetrics `json:"metrics"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
TOTPCode string `json:"totp_code"`
|
||||
}
|
||||
```
|
||||
50
1-AgentSkills/developing-watchdog/reference/mqtt-topics.md
Normal file
50
1-AgentSkills/developing-watchdog/reference/mqtt-topics.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# MQTT Topic 定义
|
||||
|
||||
## 上行(Watchdog → Exchange-Hub)
|
||||
|
||||
| Topic | 消息类型 | 说明 |
|
||||
|-------|----------|------|
|
||||
| `wdd/RDMC/command/up` | register | 项目注册 |
|
||||
| `wdd/RDMC/command/up` | auth_request | 授权申请 |
|
||||
| `wdd/RDMC/message/up` | register_complete | 注册完成确认 |
|
||||
| `wdd/RDMC/message/up` | heartbeat | 心跳数据 |
|
||||
| `wdd/RDMC/message/up` | monitor | 监控数据上报 |
|
||||
| `wdd/RDMC/message/up` | exec_result | 指令执行结果 |
|
||||
| `wdd/RDMC/message/up` | log_result | 日志查询结果 |
|
||||
| `wdd/RDMC/message/up` | alert | 告警信息 |
|
||||
|
||||
## 下行(Exchange-Hub → Watchdog)
|
||||
|
||||
| Topic | 消息类型 | 说明 |
|
||||
|-------|----------|------|
|
||||
| `wdd/RDMC/command/down/{project_id}` | auth_response | 授权响应 |
|
||||
| `wdd/RDMC/command/down/{project_id}` | auth_revoke | 授权撤销 |
|
||||
| `wdd/RDMC/command/down/{project_id}` | log_query | 日志查询指令 |
|
||||
| `wdd/RDMC/command/down/{project_id}` | host_exec | 主机执行指令 |
|
||||
| `wdd/RDMC/command/down/{project_id}` | k8s_exec | K8S执行指令 |
|
||||
| `wdd/RDMC/command/down/{project_id}` | update | 业务更新指令 |
|
||||
| `wdd/RDMC/message/down/{project_id}` | register_ack | 注册确认消息 |
|
||||
|
||||
## Topic命名规范
|
||||
|
||||
- 前缀:`wdd/RDMC/`
|
||||
- 类型:`command`(指令)或 `message`(消息)
|
||||
- 方向:`up`(上行)或 `down`(下行)
|
||||
- 项目ID:下行Topic需包含 `{project_id}` 用于路由
|
||||
|
||||
## 消息结构
|
||||
|
||||
```go
|
||||
type BaseMessage struct {
|
||||
MessageID string `json:"message_id"`
|
||||
Type string `json:"type"` // command | message
|
||||
ProjectID string `json:"project_id"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
type DataMessage struct {
|
||||
BaseMessage
|
||||
DataType string `json:"data_type"` // 具体消息类型
|
||||
Payload interface{} `json:"payload"`
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,43 @@
|
||||
# 安全机制汇总
|
||||
|
||||
## 通信安全
|
||||
|
||||
| 场景 | 安全机制 | 参数 |
|
||||
|------|----------|------|
|
||||
| Center ↔ Watchdog | Tier-One TOTP + AES-GCM | 8位码, 30分钟有效期, SHA256 |
|
||||
| Watchdog ↔ Agent | Tier-Two TOTP | 6位码, 30秒有效期, SHA1 |
|
||||
| Watchdog ↔ Node | Tier-Two TOTP复用 | 内网HTTP + TOTP认证 |
|
||||
| HTTP备用接口 | 复用Tier-Two TOTP密钥 | 需要TOTP认证 |
|
||||
| 消息传输 | TLS加密 | MQTT over TLS |
|
||||
| 敏感数据 | AES-256-GCM加密 | 授权码、密钥等 |
|
||||
|
||||
## 身份认证
|
||||
|
||||
| 机制 | 说明 |
|
||||
|------|------|
|
||||
| 主机信息 | 硬件指纹绑定: MachineID+CPU+Memory+Serial |
|
||||
| 双向TOTP验证 | 请求方发送TOTP,响应方返回新TOTP |
|
||||
| 挑战应答 | 32位随机挑战码确保通信双方身份 |
|
||||
|
||||
## 授权保护
|
||||
|
||||
| 机制 | 说明 |
|
||||
|------|------|
|
||||
| 死手系统 | 心跳失败自毁,连续12次失败触发SIGTERM |
|
||||
| 授权时间校验 | 检测时间篡改,timeOffset异常触发降级 |
|
||||
| 授权撤销 | 支持远程撤销项目授权 |
|
||||
|
||||
## 密钥传输原则
|
||||
|
||||
- tier_one_secret 和 tier_two_secret 在 project-management 创建项目时生成
|
||||
- 密钥通过项目配置文件离线部署到 Watchdog
|
||||
- **禁止通过公网MQTT传输密钥**
|
||||
|
||||
## 操作审计
|
||||
|
||||
| 操作类型 | 审计要求 |
|
||||
|----------|----------|
|
||||
| K8S操作 | 记录command_id, action, 执行结果 |
|
||||
| 主机命令 | 记录script, args, exit_code |
|
||||
| 授权变更 | 记录授权/撤销时间、操作人 |
|
||||
| 数据导出 | 需签名+TOTP校验,写审计日志 |
|
||||
45
1-AgentSkills/developing-watchdog/reference/state-machine.md
Normal file
45
1-AgentSkills/developing-watchdog/reference/state-machine.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Watchdog 状态机
|
||||
|
||||
## 连接状态机
|
||||
|
||||
```
|
||||
状态流转:offline -> connecting -> verifying -> online -> disconnecting -> offline
|
||||
```
|
||||
|
||||
| 状态 | 触发条件 | 下一状态 |
|
||||
|------|----------|----------|
|
||||
| offline | 初始/心跳超时30秒 | connecting |
|
||||
| connecting | 尝试MQTT连接 | verifying |
|
||||
| verifying | TOTP双向验证 | online/offline |
|
||||
| online | 验证成功 | disconnecting |
|
||||
| disconnecting | 主动断开/网络异常 | offline |
|
||||
|
||||
## 授权状态机
|
||||
|
||||
```
|
||||
未初始化 -> 收集主机信息 -> 等待授权 -> 已授权
|
||||
↓
|
||||
授权过期/撤销 -> 未授权 -> 等待授权(重新申请)
|
||||
```
|
||||
|
||||
## 状态转换详情
|
||||
|
||||
### 未初始化 → 收集主机信息
|
||||
- 触发:Node/Agent首次连接
|
||||
- 动作:AddHostInfo()
|
||||
|
||||
### 收集主机信息 → 等待授权
|
||||
- 触发:GenerateAuthorizationFile()
|
||||
- 动作:发布授权申请Command到MQTT
|
||||
|
||||
### 等待授权 → 已授权
|
||||
- 触发:收到有效授权码
|
||||
- 动作:解密并持久化授权信息
|
||||
|
||||
### 已授权 → 授权过期
|
||||
- 触发:时间篡改检测
|
||||
- 动作:设置initialized=false
|
||||
|
||||
### 已授权 → 未授权
|
||||
- 触发:收到auth_revoke指令
|
||||
- 动作:清除本地授权存储
|
||||
89
1-AgentSkills/developing-watchdog/scripts/verify-watchdog.sh
Normal file
89
1-AgentSkills/developing-watchdog/scripts/verify-watchdog.sh
Normal file
@@ -0,0 +1,89 @@
|
||||
#!/bin/bash
|
||||
# verify-watchdog.sh - Watchdog模块验证脚本
|
||||
# 依赖: go 1.21+, golangci-lint (可选)
|
||||
# 用法: ./verify-watchdog.sh [watchdog_dir]
|
||||
|
||||
set -e
|
||||
|
||||
WATCHDOG_DIR="${1:-./rmdc-watchdog}"
|
||||
|
||||
echo "=== Watchdog 模块验证 ==="
|
||||
echo "目标目录: $WATCHDOG_DIR"
|
||||
echo ""
|
||||
|
||||
# 检查目录存在
|
||||
if [ ! -d "$WATCHDOG_DIR" ]; then
|
||||
echo "错误: 目录不存在 $WATCHDOG_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 1. 编译检查
|
||||
echo "[1/5] 编译检查..."
|
||||
cd "$WATCHDOG_DIR"
|
||||
if go build ./... 2>&1; then
|
||||
echo "✓ 编译通过"
|
||||
else
|
||||
echo "✗ 编译失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. 单元测试
|
||||
echo ""
|
||||
echo "[2/5] 单元测试..."
|
||||
if go test ./internal/... -v -cover 2>&1; then
|
||||
echo "✓ 单元测试通过"
|
||||
else
|
||||
echo "⚠ 部分测试失败,请检查"
|
||||
fi
|
||||
|
||||
# 3. Lint检查
|
||||
echo ""
|
||||
echo "[3/5] Lint检查..."
|
||||
if command -v golangci-lint &> /dev/null; then
|
||||
if golangci-lint run ./... 2>&1; then
|
||||
echo "✓ Lint检查通过"
|
||||
else
|
||||
echo "⚠ Lint检查有警告"
|
||||
fi
|
||||
else
|
||||
echo "⚠ golangci-lint未安装,跳过"
|
||||
fi
|
||||
|
||||
# 4. TOTP参数验证
|
||||
echo ""
|
||||
echo "[4/5] TOTP参数验证..."
|
||||
|
||||
# Tier-One: 8位/30分钟
|
||||
if grep -rq "Digits.*8" pkg/totp/ 2>/dev/null || grep -rq "8.*Digits" pkg/totp/ 2>/dev/null; then
|
||||
echo "✓ Tier-One TOTP位数配置存在"
|
||||
else
|
||||
echo "⚠ 未找到Tier-One TOTP位数定义 (应为8位)"
|
||||
fi
|
||||
|
||||
# Tier-Two: 6位/30秒
|
||||
if grep -rq "Digits.*6" pkg/totp/ 2>/dev/null || grep -rq "6.*Digits" pkg/totp/ 2>/dev/null; then
|
||||
echo "✓ Tier-Two TOTP位数配置存在"
|
||||
else
|
||||
echo "⚠ 未找到Tier-Two TOTP位数定义 (应为6位)"
|
||||
fi
|
||||
|
||||
# 5. K8S操作白名单验证
|
||||
echo ""
|
||||
echo "[5/5] K8S操作白名单验证..."
|
||||
ALLOWED_ACTIONS="logs exec scale restart delete get apply"
|
||||
K8S_SERVICE="internal/service/k8s_service.go"
|
||||
|
||||
if [ -f "$K8S_SERVICE" ]; then
|
||||
for action in $ALLOWED_ACTIONS; do
|
||||
if grep -q "case \"$action\"" "$K8S_SERVICE" 2>/dev/null; then
|
||||
echo "✓ K8S操作 '$action' 已实现"
|
||||
else
|
||||
echo "⚠ K8S操作 '$action' 未找到"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "⚠ K8S服务文件不存在: $K8S_SERVICE"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== 验证完成 ==="
|
||||
145
1-AgentSkills/developing-work-procedure/SKILL.md
Normal file
145
1-AgentSkills/developing-work-procedure/SKILL.md
Normal file
@@ -0,0 +1,145 @@
|
||||
---
|
||||
name: developing-work-procedure
|
||||
description: Guides development of rmdc-work-procedure workflow module including state machine implementation, workflow CRUD operations, status transitions, optimistic locking, WebSocket events, and Mermaid diagram generation. Triggers when modifying workflow tables, adding workflow types, changing state transitions, or implementing workflow APIs. Keywords: workflow, work-procedure, state-machine, optimistic-lock, workflow-type, transition, mermaid.
|
||||
argument-hint: "<workflow-type|api-path|table-name|state-name> - specify the workflow type (user_registration|user_management|project_detail|microservice_update), API path, database table, or state to work on"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Edit
|
||||
- Write
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
---
|
||||
|
||||
# Developing Work Procedure Module
|
||||
|
||||
本 Skill 指导 `rmdc-work-procedure` 工单流程模块的开发,包括状态机实现、工单 CRUD、状态转换、并发控制、事件通知与流程图生成。
|
||||
|
||||
## Quick Context
|
||||
|
||||
```bash
|
||||
# 动态注入:查看模块结构
|
||||
!`find . -name "*.go" -path "*/rmdc-work-procedure/*" | head -20`
|
||||
|
||||
# 动态注入:查看现有状态定义
|
||||
!`grep -rn "status.*=" --include="*.go" ./rmdc-work-procedure/`
|
||||
```
|
||||
|
||||
## Plan
|
||||
|
||||
### 产物清单
|
||||
- [ ] 状态机配置/代码(状态定义、转换规则、权限矩阵)
|
||||
- [ ] 数据库 Migration(workflows主表、steps表、history表、扩展表)
|
||||
- [ ] API Handler + Service 实现
|
||||
- [ ] WebSocket 事件推送逻辑
|
||||
- [ ] Mermaid 流程图生成器
|
||||
- [ ] 单元测试 + 集成测试
|
||||
|
||||
### 决策点
|
||||
1. **工单类型**:新增还是修改现有类型?(user_registration / user_management / project_detail / microservice_update)
|
||||
2. **状态扩展**:是否需要扩展状态?(如 microservice_update 的 executing/monitoring/rollbacked)
|
||||
3. **并发策略**:乐观锁版本号校验是否充分?
|
||||
4. **回调契约**:业务模块回调接口是否需要变更?
|
||||
|
||||
## Verify
|
||||
|
||||
### 状态机完整性
|
||||
- [ ] 所有状态均有明确的入口和出口转换
|
||||
- [ ] 终态(approved/rejected/revoked/closed)不可再转换
|
||||
- [ ] 撤销(revoke)仅对非终态生效
|
||||
- [ ] 状态转换权限矩阵与角色匹配(SuperAdmin/Creator/Assignee/System)
|
||||
|
||||
### 数据库一致性
|
||||
- [ ] `workflows` 主表包含 `version` 字段(乐观锁)
|
||||
- [ ] 扩展表 FK 正确引用 `workflows.id`
|
||||
- [ ] `workflow_track_history` 记录所有状态变更
|
||||
- [ ] Migration 可回滚(提供 DOWN 脚本)
|
||||
|
||||
### API 契约兼容
|
||||
- [ ] POST 统一风格(非 RESTful GET/PUT/DELETE)
|
||||
- [ ] 响应结构 `{code, message, data}` 一致
|
||||
- [ ] 错误码对齐全局规范(409 版本冲突、403 权限不足、404 工单不存在)
|
||||
- [ ] `version` 字段在更新请求中必传
|
||||
|
||||
### 事件一致性
|
||||
- [ ] WebSocket 事件类型枚举完整(见 reference/websocket-events.md)
|
||||
- [ ] 事件 payload 包含 workflow_id、from_status、to_status
|
||||
- [ ] 通知中心模块接口调用正确
|
||||
|
||||
### 流程图生成
|
||||
- [ ] Mermaid 源码语法正确(stateDiagram-v2)
|
||||
- [ ] 当前节点高亮(classDef current)
|
||||
- [ ] 前端异步加载 mermaid 库
|
||||
|
||||
## Execute
|
||||
|
||||
### 1. 状态机实现
|
||||
```bash
|
||||
# 检查现有状态定义
|
||||
!`grep -rn "const.*Status" ./rmdc-work-procedure/`
|
||||
|
||||
# 创建/更新状态枚举
|
||||
# 参考 reference/state-machine.md
|
||||
```
|
||||
|
||||
### 2. 数据库变更
|
||||
```bash
|
||||
# 生成 migration 文件
|
||||
go run cmd/migrate/main.go create add_workflow_tables
|
||||
|
||||
# 验证 schema
|
||||
./scripts/verify-schema.sh
|
||||
```
|
||||
|
||||
### 3. API 实现
|
||||
```bash
|
||||
# 查看现有 handler
|
||||
!`ls -la ./rmdc-work-procedure/internal/handler/`
|
||||
|
||||
# 实现核心接口(参考 reference/api-contracts.md)
|
||||
# - /api/workflow/create
|
||||
# - /api/workflow/transition
|
||||
# - /api/workflow/callback
|
||||
```
|
||||
|
||||
### 4. 并发控制
|
||||
```go
|
||||
// 乐观锁更新模板(见 examples/state-transition.go)
|
||||
result := db.Model(&Workflow{}).
|
||||
Where("id = ? AND version = ?", id, version).
|
||||
Updates(map[string]interface{}{
|
||||
"status": newStatus,
|
||||
"version": gorm.Expr("version + 1"),
|
||||
})
|
||||
if result.RowsAffected == 0 {
|
||||
return ErrVersionConflict // 409
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 测试验证
|
||||
```bash
|
||||
# 运行状态机测试
|
||||
go test ./rmdc-work-procedure/... -run TestStateMachine -v
|
||||
|
||||
# 验证 API 契约
|
||||
./scripts/verify-api-contracts.sh
|
||||
```
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **版本号遗漏**:更新工单时忘记传递 `version` 字段,导致乐观锁失效;客户端需缓存并回传 version
|
||||
2. **终态误转换**:对 approved/rejected/revoked/closed 状态尝试非法转换,需在服务层硬校验
|
||||
3. **扩展表不同步**:创建工单时忘记同步写入对应 `*_ext` 扩展表,导致业务数据丢失
|
||||
4. **事件推送遗漏**:状态变更后忘记调用通知中心,处理人无法收到实时通知
|
||||
5. **撤销硬删除**:user_registration 类型撤销需硬删除用户,其他类型仅标记状态
|
||||
6. **回调幂等**:业务模块回调 `/api/workflow/callback` 需做幂等处理,避免重复状态变更
|
||||
7. **草稿状态混淆**:project_detail 的 `draft_saved` 是扩展状态,不属于基础状态机
|
||||
8. **Mermaid 安全**:前端初始化 mermaid 需设置 `securityLevel: 'strict'`,避免 XSS
|
||||
|
||||
## Related References
|
||||
|
||||
- [状态机定义](reference/state-machine.md)
|
||||
- [数据库 Schema](reference/database-schema.md)
|
||||
- [API 契约](reference/api-contracts.md)
|
||||
- [工单类型详情](reference/workflow-types.md)
|
||||
- [WebSocket 事件](reference/websocket-events.md)
|
||||
@@ -0,0 +1,227 @@
|
||||
package mermaid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DiagramResponse 流程图响应
|
||||
type DiagramResponse struct {
|
||||
MermaidCode string `json:"mermaid_code"`
|
||||
CurrentNode string `json:"current_node"`
|
||||
Nodes []DiagramNode `json:"nodes"`
|
||||
}
|
||||
|
||||
// DiagramNode 节点信息
|
||||
type DiagramNode struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Status string `json:"status"` // completed / current / pending / error
|
||||
Assignee string `json:"assignee"`
|
||||
CompletedAt string `json:"completed_at"`
|
||||
}
|
||||
|
||||
// DiagramGenerator 流程图生成器
|
||||
type DiagramGenerator struct{}
|
||||
|
||||
// NewDiagramGenerator 创建生成器
|
||||
func NewDiagramGenerator() *DiagramGenerator {
|
||||
return &DiagramGenerator{}
|
||||
}
|
||||
|
||||
// Generate 根据工单类型生成流程图
|
||||
func (g *DiagramGenerator) Generate(workflowType, currentStatus string, nodes []DiagramNode) *DiagramResponse {
|
||||
switch workflowType {
|
||||
case "user_registration":
|
||||
return g.generateUserRegistration(currentStatus, nodes)
|
||||
case "user_management":
|
||||
return g.generateUserManagement(currentStatus, nodes)
|
||||
case "project_detail":
|
||||
return g.generateProjectDetail(currentStatus, nodes)
|
||||
case "microservice_update":
|
||||
return g.generateMicroserviceUpdate(currentStatus, nodes)
|
||||
default:
|
||||
return g.generateDefault(currentStatus, nodes)
|
||||
}
|
||||
}
|
||||
|
||||
// generateUserRegistration 生成用户注册流程图
|
||||
func (g *DiagramGenerator) generateUserRegistration(currentStatus string, nodes []DiagramNode) *DiagramResponse {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString("stateDiagram-v2\n")
|
||||
sb.WriteString(" direction LR\n\n")
|
||||
sb.WriteString(" [*] --> created: 用户注册\n\n")
|
||||
sb.WriteString(" created --> pending_review: 自动提交审核\n")
|
||||
sb.WriteString(" created --> revoked: 用户撤销\n\n")
|
||||
sb.WriteString(" pending_review --> approved: 超管审批通过\n")
|
||||
sb.WriteString(" pending_review --> rejected: 超管审批拒绝\n")
|
||||
sb.WriteString(" pending_review --> revoked: 用户撤销\n\n")
|
||||
sb.WriteString(" rejected --> pending_review: 用户修改后重新提交\n")
|
||||
sb.WriteString(" rejected --> revoked: 用户撤销\n\n")
|
||||
sb.WriteString(" approved --> closed: 关闭工单\n")
|
||||
sb.WriteString(" rejected --> closed: 关闭工单\n")
|
||||
sb.WriteString(" revoked --> closed: 关闭工单\n\n")
|
||||
sb.WriteString(" closed --> [*]\n\n")
|
||||
|
||||
g.appendStyles(&sb, currentStatus)
|
||||
|
||||
return &DiagramResponse{
|
||||
MermaidCode: sb.String(),
|
||||
CurrentNode: currentStatus,
|
||||
Nodes: nodes,
|
||||
}
|
||||
}
|
||||
|
||||
// generateUserManagement 生成用户管理流程图
|
||||
func (g *DiagramGenerator) generateUserManagement(currentStatus string, nodes []DiagramNode) *DiagramResponse {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString("stateDiagram-v2\n")
|
||||
sb.WriteString(" direction LR\n\n")
|
||||
sb.WriteString(" [*] --> created: 发起管理操作\n\n")
|
||||
sb.WriteString(" created --> pending_review: 提交审核\n")
|
||||
sb.WriteString(" created --> revoked: 用户撤销\n")
|
||||
sb.WriteString(" created --> closed: 用户关闭\n\n")
|
||||
sb.WriteString(" pending_review --> approved: 超管审批通过\n")
|
||||
sb.WriteString(" pending_review --> rejected: 超管审批拒绝\n")
|
||||
sb.WriteString(" pending_review --> returned: 超管打回\n")
|
||||
sb.WriteString(" pending_review --> revoked: 用户撤销\n\n")
|
||||
sb.WriteString(" returned --> pending_review: 重新提交\n")
|
||||
sb.WriteString(" returned --> revoked: 用户撤销\n")
|
||||
sb.WriteString(" returned --> closed: 用户关闭\n\n")
|
||||
sb.WriteString(" rejected --> closed: 关闭工单\n")
|
||||
sb.WriteString(" approved --> closed: 关闭工单\n")
|
||||
sb.WriteString(" revoked --> closed: 关闭工单\n\n")
|
||||
sb.WriteString(" closed --> [*]\n\n")
|
||||
|
||||
g.appendStyles(&sb, currentStatus)
|
||||
|
||||
return &DiagramResponse{
|
||||
MermaidCode: sb.String(),
|
||||
CurrentNode: currentStatus,
|
||||
Nodes: nodes,
|
||||
}
|
||||
}
|
||||
|
||||
// generateProjectDetail 生成项目详情工单流程图
|
||||
func (g *DiagramGenerator) generateProjectDetail(currentStatus string, nodes []DiagramNode) *DiagramResponse {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString("stateDiagram-v2\n")
|
||||
sb.WriteString(" direction LR\n\n")
|
||||
sb.WriteString(" [*] --> created: 创建项目\n")
|
||||
sb.WriteString(" created --> assigned: 分配填写人\n\n")
|
||||
sb.WriteString(" assigned --> in_progress: 用户开始填写\n")
|
||||
sb.WriteString(" assigned --> assigned: 重新分配\n\n")
|
||||
sb.WriteString(" in_progress --> draft_saved: 保存草稿\n")
|
||||
sb.WriteString(" draft_saved --> in_progress: 继续填写\n")
|
||||
sb.WriteString(" in_progress --> pending_review: 提交审核\n")
|
||||
sb.WriteString(" in_progress --> assigned: 重新分配\n\n")
|
||||
sb.WriteString(" pending_review --> approved: 审批通过\n")
|
||||
sb.WriteString(" pending_review --> returned: 审批打回\n\n")
|
||||
sb.WriteString(" returned --> in_progress: 用户重新填写\n")
|
||||
sb.WriteString(" returned --> pending_review: 重新提交\n\n")
|
||||
sb.WriteString(" approved --> closed: 项目认证完成\n\n")
|
||||
sb.WriteString(" closed --> [*]\n\n")
|
||||
|
||||
g.appendStyles(&sb, currentStatus)
|
||||
|
||||
return &DiagramResponse{
|
||||
MermaidCode: sb.String(),
|
||||
CurrentNode: currentStatus,
|
||||
Nodes: nodes,
|
||||
}
|
||||
}
|
||||
|
||||
// generateMicroserviceUpdate 生成微服务更新流程图
|
||||
func (g *DiagramGenerator) generateMicroserviceUpdate(currentStatus string, nodes []DiagramNode) *DiagramResponse {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString("stateDiagram-v2\n")
|
||||
sb.WriteString(" direction LR\n\n")
|
||||
sb.WriteString(" [*] --> created: 发起更新请求\n\n")
|
||||
sb.WriteString(" created --> pending_review: 提交审核\n")
|
||||
sb.WriteString(" created --> revoked: 用户撤销\n\n")
|
||||
sb.WriteString(" pending_review --> approved: 审批通过\n")
|
||||
sb.WriteString(" pending_review --> returned: 审批打回\n")
|
||||
sb.WriteString(" pending_review --> rejected: 审批拒绝\n")
|
||||
sb.WriteString(" pending_review --> revoked: 用户撤销\n\n")
|
||||
sb.WriteString(" returned --> pending_review: 修改后重新提交\n")
|
||||
sb.WriteString(" returned --> revoked: 用户撤销\n\n")
|
||||
sb.WriteString(" approved --> executing: 开始执行\n\n")
|
||||
sb.WriteString(" executing --> monitoring: 更新成功\n")
|
||||
sb.WriteString(" executing --> rollbacked: 更新失败回滚\n\n")
|
||||
sb.WriteString(" monitoring --> closed: 运行正常\n")
|
||||
sb.WriteString(" monitoring --> rollbacked: 运行异常回滚\n\n")
|
||||
sb.WriteString(" rollbacked --> closed: 记录失败信息\n")
|
||||
sb.WriteString(" rejected --> closed: 关闭工单\n")
|
||||
sb.WriteString(" revoked --> closed: 关闭工单\n\n")
|
||||
sb.WriteString(" closed --> [*]\n\n")
|
||||
|
||||
g.appendStyles(&sb, currentStatus)
|
||||
|
||||
return &DiagramResponse{
|
||||
MermaidCode: sb.String(),
|
||||
CurrentNode: currentStatus,
|
||||
Nodes: nodes,
|
||||
}
|
||||
}
|
||||
|
||||
// generateDefault 生成默认流程图
|
||||
func (g *DiagramGenerator) generateDefault(currentStatus string, nodes []DiagramNode) *DiagramResponse {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString("stateDiagram-v2\n")
|
||||
sb.WriteString(" direction LR\n\n")
|
||||
sb.WriteString(" [*] --> created: 工单创建\n\n")
|
||||
sb.WriteString(" created --> pending: 提交\n")
|
||||
sb.WriteString(" created --> assigned: 自动分配\n\n")
|
||||
sb.WriteString(" pending --> assigned: 分配\n\n")
|
||||
sb.WriteString(" assigned --> in_progress: 接单\n\n")
|
||||
sb.WriteString(" in_progress --> pending_review: 完成\n\n")
|
||||
sb.WriteString(" pending_review --> approved: 通过\n")
|
||||
sb.WriteString(" pending_review --> rejected: 拒绝\n")
|
||||
sb.WriteString(" pending_review --> returned: 打回\n\n")
|
||||
sb.WriteString(" returned --> in_progress: 重新提交\n\n")
|
||||
sb.WriteString(" approved --> closed: 关闭\n")
|
||||
sb.WriteString(" rejected --> closed: 关闭\n\n")
|
||||
sb.WriteString(" closed --> [*]\n\n")
|
||||
|
||||
g.appendStyles(&sb, currentStatus)
|
||||
|
||||
return &DiagramResponse{
|
||||
MermaidCode: sb.String(),
|
||||
CurrentNode: currentStatus,
|
||||
Nodes: nodes,
|
||||
}
|
||||
}
|
||||
|
||||
// appendStyles 添加样式定义
|
||||
func (g *DiagramGenerator) appendStyles(sb *strings.Builder, currentStatus string) {
|
||||
sb.WriteString(" classDef completed fill:#4caf50,color:#fff\n")
|
||||
sb.WriteString(" classDef current fill:#ff9800,color:#fff,stroke-width:3px\n")
|
||||
sb.WriteString(" classDef pending fill:#e0e0e0,color:#666\n")
|
||||
sb.WriteString(" classDef error fill:#f44336,color:#fff\n\n")
|
||||
sb.WriteString(fmt.Sprintf(" class %s current\n", currentStatus))
|
||||
}
|
||||
|
||||
// InjectNodeStyles 根据节点状态注入样式
|
||||
func (g *DiagramGenerator) InjectNodeStyles(mermaidCode string, nodes []DiagramNode) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(mermaidCode)
|
||||
sb.WriteString("\n")
|
||||
|
||||
for _, node := range nodes {
|
||||
switch node.Status {
|
||||
case "completed":
|
||||
sb.WriteString(fmt.Sprintf(" class %s completed\n", node.ID))
|
||||
case "current":
|
||||
sb.WriteString(fmt.Sprintf(" class %s current\n", node.ID))
|
||||
case "error":
|
||||
sb.WriteString(fmt.Sprintf(" class %s error\n", node.ID))
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package statemachine
|
||||
|
||||
// State 工单状态
|
||||
type State string
|
||||
|
||||
const (
|
||||
StatusCreated State = "created"
|
||||
StatusPending State = "pending"
|
||||
StatusAssigned State = "assigned"
|
||||
StatusInProgress State = "in_progress"
|
||||
StatusPendingReview State = "pending_review"
|
||||
StatusReturned State = "returned"
|
||||
StatusApproved State = "approved"
|
||||
StatusRejected State = "rejected"
|
||||
StatusRevoked State = "revoked"
|
||||
StatusClosed State = "closed"
|
||||
// 扩展状态 - microservice_update
|
||||
StatusExecuting State = "executing"
|
||||
StatusMonitoring State = "monitoring"
|
||||
StatusRollbacked State = "rollbacked"
|
||||
// 扩展状态 - project_detail
|
||||
StatusDraftSaved State = "draft_saved"
|
||||
)
|
||||
|
||||
// Event 转换事件
|
||||
type Event string
|
||||
|
||||
const (
|
||||
EventSubmit Event = "submit"
|
||||
EventAutoAssign Event = "auto_assign"
|
||||
EventAssign Event = "assign"
|
||||
EventAccept Event = "accept"
|
||||
EventReassign Event = "reassign"
|
||||
EventComplete Event = "complete"
|
||||
EventApprove Event = "approve"
|
||||
EventReject Event = "reject"
|
||||
EventReturn Event = "return"
|
||||
EventResubmit Event = "resubmit"
|
||||
EventRevoke Event = "revoke"
|
||||
EventClose Event = "close"
|
||||
// 扩展事件 - microservice_update
|
||||
EventExecute Event = "execute"
|
||||
EventExecuteOK Event = "execute_ok"
|
||||
EventExecuteFail Event = "execute_fail"
|
||||
EventMonitorOK Event = "monitor_ok"
|
||||
EventMonitorFail Event = "monitor_fail"
|
||||
EventRollbackDone Event = "rollback_done"
|
||||
// 扩展事件 - project_detail
|
||||
EventSaveDraft Event = "save_draft"
|
||||
EventContinueEdit Event = "continue_edit"
|
||||
)
|
||||
|
||||
// Transition 状态转换规则
|
||||
type Transition struct {
|
||||
From State
|
||||
Event Event
|
||||
To State
|
||||
}
|
||||
|
||||
// BaseTransitions 基础状态转换规则
|
||||
var BaseTransitions = []Transition{
|
||||
// 创建后流转
|
||||
{StatusCreated, EventSubmit, StatusPending},
|
||||
{StatusCreated, EventAutoAssign, StatusAssigned},
|
||||
{StatusCreated, EventRevoke, StatusRevoked},
|
||||
|
||||
// 待分配流转
|
||||
{StatusPending, EventAssign, StatusAssigned},
|
||||
{StatusPending, EventRevoke, StatusRevoked},
|
||||
|
||||
// 已分配流转
|
||||
{StatusAssigned, EventAccept, StatusInProgress},
|
||||
{StatusAssigned, EventReassign, StatusAssigned},
|
||||
{StatusAssigned, EventRevoke, StatusRevoked},
|
||||
|
||||
// 处理中流转
|
||||
{StatusInProgress, EventComplete, StatusPendingReview},
|
||||
{StatusInProgress, EventReturn, StatusReturned},
|
||||
{StatusInProgress, EventReassign, StatusAssigned},
|
||||
{StatusInProgress, EventRevoke, StatusRevoked},
|
||||
|
||||
// 待审核流转
|
||||
{StatusPendingReview, EventApprove, StatusApproved},
|
||||
{StatusPendingReview, EventReject, StatusRejected},
|
||||
{StatusPendingReview, EventReturn, StatusReturned},
|
||||
{StatusPendingReview, EventRevoke, StatusRevoked},
|
||||
|
||||
// 已打回流转
|
||||
{StatusReturned, EventResubmit, StatusInProgress},
|
||||
{StatusReturned, EventRevoke, StatusRevoked},
|
||||
|
||||
// 终态关闭
|
||||
{StatusApproved, EventClose, StatusClosed},
|
||||
{StatusRejected, EventClose, StatusClosed},
|
||||
{StatusRevoked, EventClose, StatusClosed},
|
||||
}
|
||||
|
||||
// MicroserviceUpdateTransitions 微服务更新扩展转换规则
|
||||
var MicroserviceUpdateTransitions = []Transition{
|
||||
{StatusApproved, EventExecute, StatusExecuting},
|
||||
{StatusExecuting, EventExecuteOK, StatusMonitoring},
|
||||
{StatusExecuting, EventExecuteFail, StatusRollbacked},
|
||||
{StatusMonitoring, EventMonitorOK, StatusClosed},
|
||||
{StatusMonitoring, EventMonitorFail, StatusRollbacked},
|
||||
{StatusRollbacked, EventRollbackDone, StatusClosed},
|
||||
}
|
||||
|
||||
// ProjectDetailTransitions 项目详情扩展转换规则
|
||||
var ProjectDetailTransitions = []Transition{
|
||||
{StatusInProgress, EventSaveDraft, StatusDraftSaved},
|
||||
{StatusDraftSaved, EventContinueEdit, StatusInProgress},
|
||||
}
|
||||
|
||||
// TerminalStates 终态集合
|
||||
var TerminalStates = map[State]bool{
|
||||
StatusApproved: true,
|
||||
StatusRejected: true,
|
||||
StatusRevoked: true,
|
||||
StatusClosed: true,
|
||||
}
|
||||
|
||||
// AllTransitions 获取所有转换规则(包含扩展)
|
||||
func AllTransitions(workflowType string) []Transition {
|
||||
transitions := make([]Transition, len(BaseTransitions))
|
||||
copy(transitions, BaseTransitions)
|
||||
|
||||
switch workflowType {
|
||||
case "microservice_update":
|
||||
transitions = append(transitions, MicroserviceUpdateTransitions...)
|
||||
case "project_detail":
|
||||
transitions = append(transitions, ProjectDetailTransitions...)
|
||||
}
|
||||
|
||||
return transitions
|
||||
}
|
||||
|
||||
// CanTransition 检查是否可以转换
|
||||
func CanTransition(from State, event Event) (State, bool) {
|
||||
for _, t := range BaseTransitions {
|
||||
if t.From == from && t.Event == event {
|
||||
return t.To, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// CanTransitionWithType 检查是否可以转换(带工单类型)
|
||||
func CanTransitionWithType(from State, event Event, workflowType string) (State, bool) {
|
||||
transitions := AllTransitions(workflowType)
|
||||
for _, t := range transitions {
|
||||
if t.From == from && t.Event == event {
|
||||
return t.To, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// IsTerminal 检查是否为终态
|
||||
func IsTerminal(s State) bool {
|
||||
return TerminalStates[s]
|
||||
}
|
||||
|
||||
// CanRevoke 检查是否可撤销
|
||||
func CanRevoke(s State) bool {
|
||||
return !IsTerminal(s)
|
||||
}
|
||||
|
||||
// GetAvailableEvents 获取当前状态可用的事件
|
||||
func GetAvailableEvents(from State, workflowType string) []Event {
|
||||
transitions := AllTransitions(workflowType)
|
||||
events := make([]Event, 0)
|
||||
seen := make(map[Event]bool)
|
||||
|
||||
for _, t := range transitions {
|
||||
if t.From == from && !seen[t.Event] {
|
||||
events = append(events, t.Event)
|
||||
seen[t.Event] = true
|
||||
}
|
||||
}
|
||||
|
||||
return events
|
||||
}
|
||||
|
||||
// ValidateTransitionChain 验证转换链是否合法
|
||||
func ValidateTransitionChain(workflowType string, states []State, events []Event) bool {
|
||||
if len(states) != len(events)+1 {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, event := range events {
|
||||
toState, valid := CanTransitionWithType(states[i], event, workflowType)
|
||||
if !valid || toState != states[i+1] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Workflow 工单实体
|
||||
type Workflow struct {
|
||||
ID string `gorm:"primaryKey;size:64"`
|
||||
Version int `gorm:"not null;default:1"`
|
||||
ModuleCode string `gorm:"size:32;not null"`
|
||||
WorkflowType string `gorm:"size:32;not null"`
|
||||
Priority int `gorm:"default:3"`
|
||||
Status string `gorm:"size:32;not null"`
|
||||
CurrentStep int `gorm:"default:1"`
|
||||
TotalSteps int `gorm:"default:1"`
|
||||
CreatorID int64 `gorm:"not null"`
|
||||
CreatorName string `gorm:"size:64"`
|
||||
AssigneeID *int64
|
||||
AssigneeName string `gorm:"size:64"`
|
||||
DelegatedBy *int64
|
||||
Deadline *time.Time
|
||||
StartedAt *time.Time
|
||||
CompletedAt *time.Time
|
||||
Title string `gorm:"size:256"`
|
||||
Description string `gorm:"type:text"`
|
||||
BusinessPayload string `gorm:"type:json"`
|
||||
ResultPayload string `gorm:"type:json"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
// WorkflowTrackHistory 工单追踪历史
|
||||
type WorkflowTrackHistory struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement"`
|
||||
WorkflowID string `gorm:"size:64;not null"`
|
||||
FromStatus string `gorm:"size:32"`
|
||||
ToStatus string `gorm:"size:32"`
|
||||
Event string `gorm:"size:32"`
|
||||
OperatorID int64
|
||||
OperatorType string `gorm:"size:16"` // user / system / timer
|
||||
OperatorIP string `gorm:"size:45"`
|
||||
ChangeDetails string `gorm:"type:json"`
|
||||
Remark string `gorm:"type:text"`
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// CreateWorkflowRequest 创建工单请求
|
||||
type CreateWorkflowRequest struct {
|
||||
ModuleCode string
|
||||
ModulePrefix string
|
||||
WorkflowType string
|
||||
Title string
|
||||
Description string
|
||||
Priority int
|
||||
CreatorID int64
|
||||
CreatorName string
|
||||
CreatorUsername string
|
||||
AssigneeID *int64
|
||||
Deadline *time.Time
|
||||
BusinessPayload string
|
||||
}
|
||||
|
||||
// TransitionRequest 状态转换请求
|
||||
type TransitionRequest struct {
|
||||
WorkflowID string
|
||||
Version int
|
||||
FromStatus string
|
||||
ToStatus string
|
||||
Event string
|
||||
OperatorID int64
|
||||
OperatorType string
|
||||
OperatorIP string
|
||||
Remark string
|
||||
}
|
||||
|
||||
// 错误定义
|
||||
var (
|
||||
ErrVersionConflict = fmt.Errorf("version conflict")
|
||||
ErrWorkflowNotFound = fmt.Errorf("workflow not found")
|
||||
ErrInvalidTransition = fmt.Errorf("invalid state transition")
|
||||
)
|
||||
|
||||
// WorkflowService 工单服务
|
||||
type WorkflowService struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewWorkflowService 创建工单服务
|
||||
func NewWorkflowService(db *gorm.DB) *WorkflowService {
|
||||
return &WorkflowService{db: db}
|
||||
}
|
||||
|
||||
// CreateWorkflow 创建工单
|
||||
func (s *WorkflowService) CreateWorkflow(ctx context.Context, req *CreateWorkflowRequest) (*Workflow, error) {
|
||||
// 生成工单ID: {module_prefix}-{timestamp}-{creator}
|
||||
workflowID := fmt.Sprintf("%s-%s-%s",
|
||||
req.ModulePrefix,
|
||||
time.Now().Format("20060102150405"),
|
||||
req.CreatorUsername,
|
||||
)
|
||||
|
||||
workflow := &Workflow{
|
||||
ID: workflowID,
|
||||
Version: 1,
|
||||
ModuleCode: req.ModuleCode,
|
||||
WorkflowType: req.WorkflowType,
|
||||
Status: StatusCreated,
|
||||
Priority: req.Priority,
|
||||
CreatorID: req.CreatorID,
|
||||
CreatorName: req.CreatorName,
|
||||
AssigneeID: req.AssigneeID,
|
||||
Title: req.Title,
|
||||
Description: req.Description,
|
||||
Deadline: req.Deadline,
|
||||
BusinessPayload: req.BusinessPayload,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// 事务:同时创建主表和扩展表
|
||||
err := s.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Create(workflow).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 根据工单类型创建扩展表记录
|
||||
return s.createExtension(tx, workflow, req)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return workflow, nil
|
||||
}
|
||||
|
||||
// createExtension 根据工单类型创建扩展表记录
|
||||
func (s *WorkflowService) createExtension(tx *gorm.DB, workflow *Workflow, req *CreateWorkflowRequest) error {
|
||||
switch workflow.WorkflowType {
|
||||
case WorkflowTypeUserRegistration:
|
||||
// 创建用户注册扩展记录
|
||||
return tx.Exec(`
|
||||
INSERT INTO user_registration_ext (workflow_id, target_user_id, original_status)
|
||||
VALUES (?, ?, ?)
|
||||
`, workflow.ID, 0, "disabled").Error
|
||||
case WorkflowTypeUserManagement:
|
||||
// 创建用户管理扩展记录
|
||||
return tx.Exec(`
|
||||
INSERT INTO user_management_ext (workflow_id, target_user_id, action_type)
|
||||
VALUES (?, ?, ?)
|
||||
`, workflow.ID, 0, "modify").Error
|
||||
case WorkflowTypeProjectDetail:
|
||||
// 创建项目详情扩展记录
|
||||
return tx.Exec(`
|
||||
INSERT INTO project_detail_ext (workflow_id, project_id)
|
||||
VALUES (?, ?)
|
||||
`, workflow.ID, "").Error
|
||||
case WorkflowTypeMicroserviceUpdate:
|
||||
// 创建微服务更新扩展记录
|
||||
return tx.Exec(`
|
||||
INSERT INTO microservice_update_ext (workflow_id, project_id, namespace, service_name)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`, workflow.ID, "", "", "").Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TransitionWorkflow 状态转换(带乐观锁)
|
||||
func (s *WorkflowService) TransitionWorkflow(ctx context.Context, req *TransitionRequest) error {
|
||||
// 验证状态转换是否合法
|
||||
toStatus, valid := CanTransition(State(req.FromStatus), Event(req.Event))
|
||||
if !valid {
|
||||
return ErrInvalidTransition
|
||||
}
|
||||
req.ToStatus = string(toStatus)
|
||||
|
||||
return s.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 1. 乐观锁更新
|
||||
result := tx.Model(&Workflow{}).
|
||||
Where("id = ? AND version = ?", req.WorkflowID, req.Version).
|
||||
Updates(map[string]interface{}{
|
||||
"status": req.ToStatus,
|
||||
"version": gorm.Expr("version + 1"),
|
||||
"updated_at": time.Now(),
|
||||
})
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
return ErrVersionConflict // 409
|
||||
}
|
||||
|
||||
// 2. 记录状态变更历史
|
||||
history := &WorkflowTrackHistory{
|
||||
WorkflowID: req.WorkflowID,
|
||||
FromStatus: req.FromStatus,
|
||||
ToStatus: req.ToStatus,
|
||||
Event: req.Event,
|
||||
OperatorID: req.OperatorID,
|
||||
OperatorType: req.OperatorType,
|
||||
OperatorIP: req.OperatorIP,
|
||||
Remark: req.Remark,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
return tx.Create(history).Error
|
||||
})
|
||||
}
|
||||
|
||||
// GetWorkflow 获取工单详情
|
||||
func (s *WorkflowService) GetWorkflow(ctx context.Context, workflowID string) (*Workflow, error) {
|
||||
var workflow Workflow
|
||||
if err := s.db.Where("id = ?", workflowID).First(&workflow).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, ErrWorkflowNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &workflow, nil
|
||||
}
|
||||
|
||||
// GetWorkflowHistory 获取工单历史
|
||||
func (s *WorkflowService) GetWorkflowHistory(ctx context.Context, workflowID string) ([]WorkflowTrackHistory, error) {
|
||||
var history []WorkflowTrackHistory
|
||||
err := s.db.Where("workflow_id = ?", workflowID).
|
||||
Order("created_at ASC").
|
||||
Find(&history).Error
|
||||
return history, err
|
||||
}
|
||||
|
||||
// 工单类型常量
|
||||
const (
|
||||
WorkflowTypeUserRegistration = "user_registration"
|
||||
WorkflowTypeUserManagement = "user_management"
|
||||
WorkflowTypeProjectDetail = "project_detail"
|
||||
WorkflowTypeMicroserviceUpdate = "microservice_update"
|
||||
)
|
||||
|
||||
// 状态常量
|
||||
const (
|
||||
StatusCreated = "created"
|
||||
StatusPending = "pending"
|
||||
StatusAssigned = "assigned"
|
||||
StatusInProgress = "in_progress"
|
||||
StatusPendingReview = "pending_review"
|
||||
StatusReturned = "returned"
|
||||
StatusApproved = "approved"
|
||||
StatusRejected = "rejected"
|
||||
StatusRevoked = "revoked"
|
||||
StatusClosed = "closed"
|
||||
)
|
||||
@@ -0,0 +1,357 @@
|
||||
# API 契约
|
||||
|
||||
## 通用响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
## 错误码
|
||||
|
||||
| 错误码 | HTTP Status | 说明 |
|
||||
|-------|-------------|------|
|
||||
| 0 | 200 | 成功 |
|
||||
| 40001 | 400 | 参数校验失败 |
|
||||
| 40301 | 403 | 权限不足 |
|
||||
| 40401 | 404 | 工单不存在 |
|
||||
| 40901 | 409 | 版本冲突(乐观锁) |
|
||||
| 50001 | 500 | 服务器内部错误 |
|
||||
|
||||
## 工单核心接口
|
||||
|
||||
### POST /api/workflow/create
|
||||
|
||||
创建工单
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"module_code": "rmdc-project-management",
|
||||
"workflow_type": "project_detail",
|
||||
"title": "项目信息填写-ProjectA",
|
||||
"description": "请填写项目详细信息",
|
||||
"priority": 3,
|
||||
"assignee_id": 123,
|
||||
"deadline": "2026-01-15T18:00:00Z",
|
||||
"business_payload": {
|
||||
"project_id": "proj-001"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"workflow_id": "project-20260108100000-admin",
|
||||
"status": "created",
|
||||
"version": 1,
|
||||
"created_at": "2026-01-08T10:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/workflow/detail
|
||||
|
||||
获取工单详情
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"workflow_id": "project-20260108100000-admin"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": "project-20260108100000-admin",
|
||||
"version": 2,
|
||||
"module_code": "rmdc-project-management",
|
||||
"workflow_type": "project_detail",
|
||||
"status": "in_progress",
|
||||
"title": "项目信息填写-ProjectA",
|
||||
"creator_id": 1,
|
||||
"creator_name": "超级管理员",
|
||||
"assignee_id": 123,
|
||||
"assignee_name": "张三",
|
||||
"current_step": 2,
|
||||
"total_steps": 3,
|
||||
"steps": [...],
|
||||
"history": [...],
|
||||
"business_payload": {...},
|
||||
"created_at": "2026-01-08T10:00:00Z",
|
||||
"updated_at": "2026-01-08T11:30:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/workflow/status
|
||||
|
||||
获取工单状态(轻量)
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"workflow_id": "project-20260108100000-admin"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"workflow_id": "project-20260108100000-admin",
|
||||
"status": "in_progress",
|
||||
"version": 2,
|
||||
"updated_at": "2026-01-08T11:30:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/workflow/transition
|
||||
|
||||
状态转换
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"workflow_id": "project-20260108100000-admin",
|
||||
"version": 2,
|
||||
"event": "complete",
|
||||
"remark": "项目信息填写完成,提交审核"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"workflow_id": "project-20260108100000-admin",
|
||||
"from_status": "in_progress",
|
||||
"to_status": "pending_review",
|
||||
"version": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**版本冲突响应 (409):**
|
||||
```json
|
||||
{
|
||||
"code": 40901,
|
||||
"message": "版本冲突,工单已被其他人修改,请刷新后重试"
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/workflow/assign
|
||||
|
||||
分配处理人
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"workflow_id": "project-20260108100000-admin",
|
||||
"version": 1,
|
||||
"assignee_id": 123,
|
||||
"remark": "分配给张三填写"
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/workflow/delegate
|
||||
|
||||
委派给他人
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"workflow_id": "microservice-20260108150000-user",
|
||||
"version": 3,
|
||||
"delegate_to_id": 456,
|
||||
"remark": "委派给李四执行更新"
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/workflow/revoke
|
||||
|
||||
撤销工单
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"workflow_id": "user-20260108090000-zhangsan",
|
||||
"version": 2,
|
||||
"confirm": true,
|
||||
"remark": "用户取消注册"
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/workflow/callback
|
||||
|
||||
业务模块回调
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"workflow_id": "microservice-20260108150000-user",
|
||||
"callback_type": "execute_complete",
|
||||
"result": "success",
|
||||
"result_payload": {
|
||||
"execute_time": "2026-01-08T15:35:00Z",
|
||||
"new_pod_count": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**callback_type 枚举:**
|
||||
- `execute_start` - 开始执行
|
||||
- `execute_complete` - 执行完成
|
||||
- `execute_failed` - 执行失败
|
||||
- `monitor_healthy` - 监控健康
|
||||
- `monitor_unhealthy` - 监控异常
|
||||
- `rollback_complete` - 回滚完成
|
||||
|
||||
## 工单列表与查询
|
||||
|
||||
### POST /api/workflow/list
|
||||
|
||||
工单列表(分页)
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"page": 1,
|
||||
"page_size": 20,
|
||||
"workflow_type": "project_detail",
|
||||
"status": ["in_progress", "pending_review"],
|
||||
"creator_id": 1,
|
||||
"assignee_id": 123,
|
||||
"start_time": "2026-01-01T00:00:00Z",
|
||||
"end_time": "2026-01-31T23:59:59Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"total": 50,
|
||||
"page": 1,
|
||||
"page_size": 20,
|
||||
"list": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/workflow/my/created
|
||||
|
||||
我发起的工单
|
||||
|
||||
### POST /api/workflow/my/assigned
|
||||
|
||||
分配给我的工单
|
||||
|
||||
### POST /api/workflow/my/pending
|
||||
|
||||
待我处理的工单
|
||||
|
||||
### POST /api/workflow/history
|
||||
|
||||
工单历史记录
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"workflow_id": "project-20260108100000-admin"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"history": [
|
||||
{
|
||||
"id": 1,
|
||||
"from_status": "created",
|
||||
"to_status": "assigned",
|
||||
"event": "assign",
|
||||
"operator_name": "超级管理员",
|
||||
"remark": "分配给张三填写",
|
||||
"created_at": "2026-01-08T10:05:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/workflow/diagram
|
||||
|
||||
获取工单流程图
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"workflow_id": "project-20260108100000-admin"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"mermaid_code": "stateDiagram-v2\n direction LR\n [*] --> created: 创建项目\n ...",
|
||||
"current_node": "pending_review",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "created",
|
||||
"label": "创建项目",
|
||||
"status": "completed",
|
||||
"assignee": "超级管理员",
|
||||
"completed_at": "2026-01-08T10:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": "in_progress",
|
||||
"label": "填写信息",
|
||||
"status": "completed",
|
||||
"assignee": "张三",
|
||||
"completed_at": "2026-01-08T14:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": "pending_review",
|
||||
"label": "待审核",
|
||||
"status": "current",
|
||||
"assignee": "超级管理员"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 权限管理接口(仅 SuperAdmin)
|
||||
|
||||
### POST /api/workflow/permission/grant
|
||||
|
||||
授予权限
|
||||
|
||||
### POST /api/workflow/permission/revoke
|
||||
|
||||
撤销权限
|
||||
|
||||
### POST /api/workflow/permission/list
|
||||
|
||||
权限列表
|
||||
@@ -0,0 +1,236 @@
|
||||
# 数据库 Schema
|
||||
|
||||
## 主表 workflows
|
||||
|
||||
```sql
|
||||
CREATE TABLE workflows (
|
||||
id VARCHAR(64) PRIMARY KEY, -- 格式: {module}-{timestamp}-{creator}
|
||||
version INT NOT NULL DEFAULT 1, -- 乐观锁版本号
|
||||
|
||||
-- 工单类型与来源
|
||||
module_code VARCHAR(32) NOT NULL, -- rmdc-user-auth / rmdc-project-management / deliver-update
|
||||
workflow_type VARCHAR(32) NOT NULL, -- user_registration / user_management / project_detail / microservice_update
|
||||
priority INT DEFAULT 3, -- 1-5 优先级
|
||||
|
||||
-- 状态与流程
|
||||
status VARCHAR(32) NOT NULL,
|
||||
current_step INT DEFAULT 1,
|
||||
total_steps INT DEFAULT 1,
|
||||
|
||||
-- 人员关联
|
||||
creator_id BIGINT NOT NULL,
|
||||
creator_name VARCHAR(64),
|
||||
assignee_id BIGINT,
|
||||
assignee_name VARCHAR(64),
|
||||
delegated_by BIGINT, -- 委派来源
|
||||
|
||||
-- 时间管理
|
||||
deadline DATETIME,
|
||||
started_at DATETIME,
|
||||
completed_at DATETIME,
|
||||
|
||||
-- 业务数据
|
||||
title VARCHAR(256),
|
||||
description TEXT,
|
||||
business_payload JSON, -- 通用业务数据
|
||||
result_payload JSON, -- 处理结果数据
|
||||
|
||||
-- 审计
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
|
||||
INDEX idx_module_code (module_code),
|
||||
INDEX idx_workflow_type (workflow_type),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_creator (creator_id),
|
||||
INDEX idx_assignee (assignee_id)
|
||||
);
|
||||
```
|
||||
|
||||
## 步骤表 workflow_steps
|
||||
|
||||
```sql
|
||||
CREATE TABLE workflow_steps (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
workflow_id VARCHAR(64) NOT NULL,
|
||||
step_order INT NOT NULL,
|
||||
step_name VARCHAR(64),
|
||||
step_type VARCHAR(32), -- submit / approve / execute / monitor
|
||||
|
||||
assignee_id BIGINT,
|
||||
assignee_name VARCHAR(64),
|
||||
status VARCHAR(32),
|
||||
result VARCHAR(32),
|
||||
remark TEXT,
|
||||
input_data JSON,
|
||||
output_data JSON,
|
||||
|
||||
started_at DATETIME,
|
||||
completed_at DATETIME,
|
||||
created_at DATETIME NOT NULL,
|
||||
|
||||
INDEX idx_workflow (workflow_id),
|
||||
FOREIGN KEY (workflow_id) REFERENCES workflows(id)
|
||||
);
|
||||
```
|
||||
|
||||
## 追踪历史表 workflow_track_history
|
||||
|
||||
```sql
|
||||
CREATE TABLE workflow_track_history (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
workflow_id VARCHAR(64) NOT NULL,
|
||||
|
||||
from_status VARCHAR(32),
|
||||
to_status VARCHAR(32),
|
||||
event VARCHAR(32), -- submit / approve / reject / return / revoke / close
|
||||
|
||||
operator_id BIGINT,
|
||||
operator_type VARCHAR(16), -- user / system / timer
|
||||
operator_ip VARCHAR(45),
|
||||
|
||||
change_details JSON,
|
||||
remark TEXT,
|
||||
|
||||
created_at DATETIME NOT NULL,
|
||||
|
||||
INDEX idx_workflow (workflow_id),
|
||||
INDEX idx_operator (operator_id)
|
||||
);
|
||||
```
|
||||
|
||||
## 扩展表
|
||||
|
||||
### user_registration_ext
|
||||
|
||||
用户注册工单扩展表
|
||||
|
||||
```sql
|
||||
CREATE TABLE user_registration_ext (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
workflow_id VARCHAR(64) NOT NULL UNIQUE,
|
||||
target_user_id BIGINT NOT NULL, -- 被注册用户ID
|
||||
original_status VARCHAR(32), -- 原用户状态(备份)
|
||||
FOREIGN KEY (workflow_id) REFERENCES workflows(id)
|
||||
);
|
||||
```
|
||||
|
||||
### user_management_ext
|
||||
|
||||
用户管理工单扩展表
|
||||
|
||||
```sql
|
||||
CREATE TABLE user_management_ext (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
workflow_id VARCHAR(64) NOT NULL UNIQUE,
|
||||
target_user_id BIGINT NOT NULL, -- 被管理用户ID
|
||||
action_type VARCHAR(32) NOT NULL, -- modify / delete / enable / disable
|
||||
original_data JSON, -- 变更前数据快照
|
||||
modified_data JSON, -- 期望变更数据
|
||||
FOREIGN KEY (workflow_id) REFERENCES workflows(id)
|
||||
);
|
||||
```
|
||||
|
||||
**original_data / modified_data 示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "zhangsan",
|
||||
"chinese_name": "张三",
|
||||
"email": "zhangsan@example.com",
|
||||
"phone": "13800138000",
|
||||
"group_name": "产品与解决方案组",
|
||||
"dev_role": "backend",
|
||||
"rmdc_role": "normal"
|
||||
}
|
||||
```
|
||||
|
||||
### project_detail_ext
|
||||
|
||||
项目详情工单扩展表
|
||||
|
||||
```sql
|
||||
CREATE TABLE project_detail_ext (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
workflow_id VARCHAR(64) NOT NULL UNIQUE,
|
||||
project_id VARCHAR(64) NOT NULL, -- 关联项目ID
|
||||
detail_filler_id BIGINT, -- 填写人ID
|
||||
detail_filler_name VARCHAR(64), -- 填写人姓名
|
||||
draft_data JSON, -- 草稿数据
|
||||
FOREIGN KEY (workflow_id) REFERENCES workflows(id)
|
||||
);
|
||||
```
|
||||
|
||||
**draft_data 示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"deploy_business": {
|
||||
"deployer_name": "张三",
|
||||
"deployer_phone": "13800138000",
|
||||
"deploy_start_time": "2026-01-01",
|
||||
"system_version": "v3.2.0"
|
||||
},
|
||||
"deploy_env": {
|
||||
"network_type": "internal",
|
||||
"main_public_ip": "10.0.0.1",
|
||||
"host_count": 3
|
||||
},
|
||||
"deploy_middleware": {
|
||||
"mysql": {
|
||||
"internal_ip": "10.0.0.10",
|
||||
"internal_port": 3306
|
||||
}
|
||||
},
|
||||
"last_saved_at": "2026-01-08T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### microservice_update_ext
|
||||
|
||||
微服务更新工单扩展表
|
||||
|
||||
```sql
|
||||
CREATE TABLE microservice_update_ext (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
workflow_id VARCHAR(64) NOT NULL UNIQUE,
|
||||
project_id VARCHAR(64) NOT NULL, -- 目标项目ID
|
||||
namespace VARCHAR(64) NOT NULL, -- K8S命名空间
|
||||
service_name VARCHAR(128) NOT NULL, -- 微服务名称
|
||||
build_id VARCHAR(128), -- 构建物ID
|
||||
current_version VARCHAR(64), -- 当前版本
|
||||
target_version VARCHAR(64), -- 目标版本
|
||||
scheduled_at DATETIME, -- 计划执行时间
|
||||
execute_result VARCHAR(32), -- success / failed / rollbacked
|
||||
rollback_info JSON, -- 回滚信息
|
||||
FOREIGN KEY (workflow_id) REFERENCES workflows(id)
|
||||
);
|
||||
```
|
||||
|
||||
**rollback_info 示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"rollback_reason": "Pod健康检查失败",
|
||||
"rollback_time": "2026-01-08T15:30:00Z",
|
||||
"rollback_version": "v3.1.5",
|
||||
"error_details": {
|
||||
"pod_name": "service-abc-7d8f9c6b5d-xyz",
|
||||
"error_message": "Readiness probe failed: connection refused",
|
||||
"failed_checks": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Migration 回滚脚本
|
||||
|
||||
```sql
|
||||
-- DOWN migration
|
||||
DROP TABLE IF EXISTS microservice_update_ext;
|
||||
DROP TABLE IF EXISTS project_detail_ext;
|
||||
DROP TABLE IF EXISTS user_management_ext;
|
||||
DROP TABLE IF EXISTS user_registration_ext;
|
||||
DROP TABLE IF EXISTS workflow_track_history;
|
||||
DROP TABLE IF EXISTS workflow_steps;
|
||||
DROP TABLE IF EXISTS workflows;
|
||||
```
|
||||
@@ -0,0 +1,107 @@
|
||||
# 工单状态机定义
|
||||
|
||||
## 基础状态(所有工单类型共享)
|
||||
|
||||
| 状态代码 | 状态名称 | 说明 | 是否终态 |
|
||||
|---------|---------|------|---------|
|
||||
| `created` | 已创建 | 工单刚创建,等待分配或自动分配 | 否 |
|
||||
| `pending` | 待分配 | 等待管理员手动分配处理人 | 否 |
|
||||
| `assigned` | 已分配 | 已分配处理人,等待接单 | 否 |
|
||||
| `in_progress` | 处理中 | 处理人正在业务模块中处理 | 否 |
|
||||
| `pending_review` | 待审核 | 处理完成,等待审核 | 否 |
|
||||
| `returned` | 已打回 | 审核未通过,需重新处理 | 否 |
|
||||
| `approved` | 已通过 | 审核通过 | **是** |
|
||||
| `rejected` | 已拒绝 | 审核拒绝,流程终止 | **是** |
|
||||
| `revoked` | 已撤销 | 发起人撤销 | **是** |
|
||||
| `closed` | 已关闭 | 流程完结 | **是** |
|
||||
|
||||
## 扩展状态(按工单类型)
|
||||
|
||||
| 工单类型 | 扩展状态 | 说明 |
|
||||
|---------|---------|------|
|
||||
| `microservice_update` | `executing` | 微服务更新执行中 |
|
||||
| `microservice_update` | `monitoring` | 微服务运行状态监控中 |
|
||||
| `microservice_update` | `rollbacked` | 更新失败已回滚 |
|
||||
| `project_detail` | `draft_saved` | 草稿已保存 |
|
||||
|
||||
## 状态转换权限矩阵
|
||||
|
||||
| 转换事件 | 触发角色 | 前置条件 |
|
||||
|---------|---------|---------|
|
||||
| `submit` | 创建人 | 状态为 `created` |
|
||||
| `auto_assign` | 系统 | 配置了自动分配规则 |
|
||||
| `assign` | SuperAdmin | 状态为 `pending` |
|
||||
| `accept` | 处理人 | 状态为 `assigned` |
|
||||
| `reassign` | SuperAdmin | 状态为 `assigned`/`in_progress` |
|
||||
| `complete` | 处理人 | 状态为 `in_progress` |
|
||||
| `approve` | SuperAdmin | 状态为 `pending_review` |
|
||||
| `reject` | SuperAdmin | 状态为 `pending_review` |
|
||||
| `return` | SuperAdmin | 状态为 `pending_review`/`in_progress` |
|
||||
| `resubmit` | 创建人/处理人 | 状态为 `returned` |
|
||||
| `revoke` | 创建人 | 状态非终态 |
|
||||
| `close` | 系统/SuperAdmin | 状态为终态或 `revoked` |
|
||||
|
||||
## 通用状态转换图
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> created: 工单创建
|
||||
|
||||
created --> pending: submit
|
||||
created --> assigned: auto_assign
|
||||
|
||||
pending --> assigned: assign
|
||||
|
||||
assigned --> in_progress: accept
|
||||
assigned --> assigned: reassign
|
||||
|
||||
in_progress --> pending_review: complete
|
||||
in_progress --> returned: return
|
||||
in_progress --> assigned: reassign
|
||||
|
||||
pending_review --> approved: approve
|
||||
pending_review --> rejected: reject
|
||||
pending_review --> returned: return
|
||||
|
||||
returned --> in_progress: resubmit
|
||||
|
||||
approved --> closed: close
|
||||
rejected --> closed: close
|
||||
|
||||
created --> revoked: revoke
|
||||
pending --> revoked: revoke
|
||||
assigned --> revoked: revoke
|
||||
in_progress --> revoked: revoke(需确认)
|
||||
pending_review --> revoked: revoke(需确认)
|
||||
returned --> revoked: revoke
|
||||
|
||||
revoked --> closed: close
|
||||
|
||||
closed --> [*]
|
||||
```
|
||||
|
||||
## 微服务更新扩展状态转换
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
approved --> executing: 开始执行
|
||||
approved --> approved: 等待定时执行
|
||||
|
||||
executing --> monitoring: 更新成功
|
||||
executing --> executing: 执行重试
|
||||
executing --> rollbacked: 更新失败回滚
|
||||
|
||||
monitoring --> closed: 运行正常
|
||||
monitoring --> rollbacked: 运行异常回滚
|
||||
|
||||
rollbacked --> closed: 记录失败信息
|
||||
```
|
||||
|
||||
## 项目详情扩展状态转换
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
in_progress --> draft_saved: 保存草稿
|
||||
draft_saved --> in_progress: 继续填写
|
||||
in_progress --> pending_review: 提交审核
|
||||
```
|
||||
@@ -0,0 +1,237 @@
|
||||
# WebSocket 事件定义
|
||||
|
||||
## 事件类型
|
||||
|
||||
| 事件类型 | 触发时机 | 接收方 |
|
||||
|---------|---------|-------|
|
||||
| `WORKFLOW_CREATED` | 工单创建 | 处理人 |
|
||||
| `WORKFLOW_ASSIGNED` | 工单分配 | 处理人 |
|
||||
| `WORKFLOW_REASSIGNED` | 工单重新委派 | 原处理人、新处理人 |
|
||||
| `WORKFLOW_STATUS_CHANGED` | 状态变更 | 发起人、处理人 |
|
||||
| `WORKFLOW_REVOKED` | 工单撤销 | 处理人 |
|
||||
| `WORKFLOW_MODIFIED` | 工单内容修改 | 处理人 |
|
||||
| `WORKFLOW_TIMEOUT` | 工单超时 | 发起人、处理人 |
|
||||
| `WORKFLOW_COMPLETED` | 工单完成 | 发起人 |
|
||||
|
||||
## 事件 Payload 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"event_type": "WORKFLOW_STATUS_CHANGED",
|
||||
"workflow_id": "project-20260108100000-admin",
|
||||
"workflow_type": "project_detail",
|
||||
"from_status": "pending_review",
|
||||
"to_status": "approved",
|
||||
"operator_id": 1,
|
||||
"operator_name": "超级管理员",
|
||||
"timestamp": "2026-01-08T15:30:00Z",
|
||||
"extra": {
|
||||
"remark": "审批通过"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 各事件详细说明
|
||||
|
||||
### WORKFLOW_CREATED
|
||||
|
||||
工单创建事件
|
||||
|
||||
```json
|
||||
{
|
||||
"event_type": "WORKFLOW_CREATED",
|
||||
"workflow_id": "project-20260108100000-admin",
|
||||
"workflow_type": "project_detail",
|
||||
"title": "项目信息填写-ProjectA",
|
||||
"creator_id": 1,
|
||||
"creator_name": "超级管理员",
|
||||
"assignee_id": 123,
|
||||
"assignee_name": "张三",
|
||||
"priority": 3,
|
||||
"deadline": "2026-01-15T18:00:00Z",
|
||||
"timestamp": "2026-01-08T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### WORKFLOW_ASSIGNED
|
||||
|
||||
工单分配事件
|
||||
|
||||
```json
|
||||
{
|
||||
"event_type": "WORKFLOW_ASSIGNED",
|
||||
"workflow_id": "project-20260108100000-admin",
|
||||
"assignee_id": 123,
|
||||
"assignee_name": "张三",
|
||||
"assigned_by_id": 1,
|
||||
"assigned_by_name": "超级管理员",
|
||||
"remark": "请在3天内完成项目信息填写",
|
||||
"timestamp": "2026-01-08T10:05:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### WORKFLOW_REASSIGNED
|
||||
|
||||
工单重新委派事件
|
||||
|
||||
```json
|
||||
{
|
||||
"event_type": "WORKFLOW_REASSIGNED",
|
||||
"workflow_id": "project-20260108100000-admin",
|
||||
"from_assignee_id": 123,
|
||||
"from_assignee_name": "张三",
|
||||
"to_assignee_id": 456,
|
||||
"to_assignee_name": "李四",
|
||||
"reassigned_by_id": 1,
|
||||
"reassigned_by_name": "超级管理员",
|
||||
"remark": "张三请假,转交给李四处理",
|
||||
"timestamp": "2026-01-09T09:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### WORKFLOW_STATUS_CHANGED
|
||||
|
||||
状态变更事件
|
||||
|
||||
```json
|
||||
{
|
||||
"event_type": "WORKFLOW_STATUS_CHANGED",
|
||||
"workflow_id": "project-20260108100000-admin",
|
||||
"workflow_type": "project_detail",
|
||||
"from_status": "in_progress",
|
||||
"to_status": "pending_review",
|
||||
"event": "complete",
|
||||
"operator_id": 123,
|
||||
"operator_name": "张三",
|
||||
"timestamp": "2026-01-08T14:00:00Z",
|
||||
"extra": {
|
||||
"remark": "项目信息填写完成"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### WORKFLOW_REVOKED
|
||||
|
||||
工单撤销事件
|
||||
|
||||
```json
|
||||
{
|
||||
"event_type": "WORKFLOW_REVOKED",
|
||||
"workflow_id": "user-20260108090000-zhangsan",
|
||||
"workflow_type": "user_registration",
|
||||
"revoked_by_id": 100,
|
||||
"revoked_by_name": "张三",
|
||||
"remark": "用户取消注册",
|
||||
"timestamp": "2026-01-08T11:00:00Z",
|
||||
"extra": {
|
||||
"target_user_deleted": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### WORKFLOW_MODIFIED
|
||||
|
||||
工单内容修改事件
|
||||
|
||||
```json
|
||||
{
|
||||
"event_type": "WORKFLOW_MODIFIED",
|
||||
"workflow_id": "project-20260108100000-admin",
|
||||
"modified_by_id": 1,
|
||||
"modified_by_name": "超级管理员",
|
||||
"modified_fields": ["deadline", "priority"],
|
||||
"timestamp": "2026-01-08T16:00:00Z",
|
||||
"extra": {
|
||||
"old_deadline": "2026-01-15T18:00:00Z",
|
||||
"new_deadline": "2026-01-20T18:00:00Z",
|
||||
"old_priority": 3,
|
||||
"new_priority": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### WORKFLOW_TIMEOUT
|
||||
|
||||
工单超时事件
|
||||
|
||||
```json
|
||||
{
|
||||
"event_type": "WORKFLOW_TIMEOUT",
|
||||
"workflow_id": "project-20260108100000-admin",
|
||||
"workflow_type": "project_detail",
|
||||
"deadline": "2026-01-15T18:00:00Z",
|
||||
"current_status": "in_progress",
|
||||
"assignee_id": 123,
|
||||
"assignee_name": "张三",
|
||||
"timestamp": "2026-01-15T18:00:01Z"
|
||||
}
|
||||
```
|
||||
|
||||
### WORKFLOW_COMPLETED
|
||||
|
||||
工单完成事件
|
||||
|
||||
```json
|
||||
{
|
||||
"event_type": "WORKFLOW_COMPLETED",
|
||||
"workflow_id": "project-20260108100000-admin",
|
||||
"workflow_type": "project_detail",
|
||||
"final_status": "approved",
|
||||
"result": "success",
|
||||
"creator_id": 1,
|
||||
"creator_name": "超级管理员",
|
||||
"timestamp": "2026-01-10T10:00:00Z",
|
||||
"extra": {
|
||||
"total_duration_hours": 48,
|
||||
"steps_completed": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 前端处理策略
|
||||
|
||||
| 事件 | 前端行为 |
|
||||
|-----|---------|
|
||||
| `WORKFLOW_CREATED` | 待办列表增加新工单提醒 |
|
||||
| `WORKFLOW_ASSIGNED` | 显示分配通知,可跳转工单详情 |
|
||||
| `WORKFLOW_REASSIGNED` | 原处理人:显示提示弹窗,切换只读模式;新处理人:显示接收通知 |
|
||||
| `WORKFLOW_STATUS_CHANGED` | 更新工单状态显示,刷新流程图高亮 |
|
||||
| `WORKFLOW_REVOKED` | 自动关闭处理页面,引导至工单列表 |
|
||||
| `WORKFLOW_MODIFIED` | 提示"工单已被修改",自动刷新或提示手动刷新 |
|
||||
| `WORKFLOW_TIMEOUT` | 显示超时警告,标红显示 |
|
||||
| `WORKFLOW_COMPLETED` | 显示完成通知,可查看结果 |
|
||||
|
||||
## 并发冲突处理
|
||||
|
||||
### 乐观锁冲突场景
|
||||
|
||||
当用户正在处理工单时,收到 `WORKFLOW_REASSIGNED` 或 `WORKFLOW_MODIFIED` 事件:
|
||||
|
||||
1. **WORKFLOW_REASSIGNED 处理流程**:
|
||||
- 显示弹窗:「此工单已被重新委派给XXX,您的处理权限已转移」
|
||||
- 页面切换为只读模式
|
||||
- 提供"返回列表"按钮
|
||||
- 若用户仍尝试提交,后端返回 409 Conflict
|
||||
|
||||
2. **WORKFLOW_MODIFIED 处理流程**:
|
||||
- 显示提示:「工单内容已被修改,请刷新页面」
|
||||
- 若用户有未保存的本地修改,提示冲突
|
||||
- 提供"刷新"和"查看变更"选项
|
||||
|
||||
### 版本号同步
|
||||
|
||||
前端需在每次获取工单详情时缓存 `version` 字段,提交操作时携带该版本号:
|
||||
|
||||
```javascript
|
||||
// 获取工单详情
|
||||
const { data } = await api.getWorkflowDetail(workflowId);
|
||||
this.workflowVersion = data.version;
|
||||
|
||||
// 提交状态转换
|
||||
await api.transitionWorkflow({
|
||||
workflow_id: workflowId,
|
||||
version: this.workflowVersion, // 必传
|
||||
event: 'complete',
|
||||
remark: '处理完成'
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,175 @@
|
||||
# 工单类型定义
|
||||
|
||||
## 类型列表
|
||||
|
||||
| 工单类型代码 | 类型名称 | 来源模块 | 可撤销 | 特殊处理 |
|
||||
|-------------|---------|---------|-------|---------|
|
||||
| `user_registration` | 用户注册工单 | rmdc-user-auth | 是 | 撤销时硬删除用户 |
|
||||
| `user_management` | 用户管理工单 | rmdc-user-auth | 是 | 保存变更前后快照 |
|
||||
| `project_detail` | 项目信息填写工单 | rmdc-project-management | 否(仅超管) | 支持草稿保存 |
|
||||
| `microservice_update` | 微服务更新工单 | deliver-update | 是 | 执行/监控/回滚扩展状态 |
|
||||
|
||||
## 工单 ID 生成规则
|
||||
|
||||
**格式**:`{module_prefix}-{timestamp}-{creator_username}`
|
||||
|
||||
**示例**:
|
||||
- `project-20260106110303-zeaslity`
|
||||
- `user-20260108093000-admin`
|
||||
- `microservice-20260108150000-zhangsan`
|
||||
|
||||
## 各类型详细说明
|
||||
|
||||
### user_registration(用户注册工单)
|
||||
|
||||
**业务场景**:非超级管理员用户注册新用户后,需要经过超级管理员审批才能激活用户账号。
|
||||
|
||||
**业务规则**:
|
||||
| 规则项 | 说明 |
|
||||
|-------|------|
|
||||
| 发起人 | 管理员、普通用户 |
|
||||
| 默认处理人 | 超级管理员 |
|
||||
| 被注册用户初始状态 | `disabled` |
|
||||
| 审批通过后用户状态 | `active` |
|
||||
| 可撤销 | 是(创建人可撤销) |
|
||||
| 撤销后处理 | **硬删除**被注册用户 |
|
||||
|
||||
**步骤定义**:
|
||||
| 步骤序号 | 步骤名称 | 步骤类型 | 处理人 |
|
||||
|---------|---------|---------|-------|
|
||||
| 1 | 提交注册 | `submit` | 系统自动 |
|
||||
| 2 | 审核注册 | `approve` | 超级管理员 |
|
||||
|
||||
**扩展字段**(user_registration_ext):
|
||||
- `target_user_id` - 被注册用户ID
|
||||
- `original_status` - 原用户状态(备份)
|
||||
|
||||
---
|
||||
|
||||
### user_management(用户管理工单)
|
||||
|
||||
**业务场景**:非超级管理员用户对已注册用户进行管理操作(修改、删除、启用、禁用)时,需要经过超级管理员审批。
|
||||
|
||||
**业务规则**:
|
||||
| 规则项 | 说明 |
|
||||
|-------|------|
|
||||
| 发起人 | 管理员、普通用户(只能管理自己注册的用户) |
|
||||
| 默认处理人 | 超级管理员 |
|
||||
| 操作类型 | `modify`/`delete`/`enable`/`disable` |
|
||||
| 可撤销 | 是 |
|
||||
| 可关闭 | 是(创建人可主动关闭) |
|
||||
|
||||
**权限矩阵**:
|
||||
| 操作人角色 | 可管理用户范围 |
|
||||
|-----------|--------------|
|
||||
| 超级管理员 | 所有用户(无需工单) |
|
||||
| 管理员 | 普通用户、三方用户(自己注册的) |
|
||||
| 普通用户 | 三方用户(自己注册的) |
|
||||
| 三方用户 | 无 |
|
||||
|
||||
**步骤定义**:
|
||||
| 步骤序号 | 步骤名称 | 步骤类型 | 处理人 |
|
||||
|---------|---------|---------|-------|
|
||||
| 1 | 提交管理操作 | `submit` | 创建人 |
|
||||
| 2 | 审核管理操作 | `approve` | 超级管理员 |
|
||||
|
||||
**扩展字段**(user_management_ext):
|
||||
- `target_user_id` - 被管理用户ID
|
||||
- `action_type` - 操作类型
|
||||
- `original_data` - 变更前数据快照
|
||||
- `modified_data` - 期望变更数据
|
||||
|
||||
---
|
||||
|
||||
### project_detail(项目信息填写工单)
|
||||
|
||||
**业务场景**:超级管理员创建项目元数据后,分配给普通用户填写项目详情信息,填写完成后提交审核。
|
||||
|
||||
**业务规则**:
|
||||
| 规则项 | 说明 |
|
||||
|-------|------|
|
||||
| 发起人 | 超级管理员 |
|
||||
| 填写人 | 被分配的普通用户 |
|
||||
| 审批人 | 超级管理员 |
|
||||
| 支持草稿 | 是(可多次保存草稿) |
|
||||
| 可撤销 | 否(仅超级管理员可取消) |
|
||||
| 项目状态关联 | 草稿状态绑定唯一工单 |
|
||||
|
||||
**项目状态对应关系**:
|
||||
| 工单状态 | 项目认证状态 |
|
||||
|---------|------------|
|
||||
| `assigned` / `in_progress` | `draft` (草稿) |
|
||||
| `pending_review` | `pending` (待审核) |
|
||||
| `approved` / `closed` | `official` (正式) |
|
||||
| `returned` | `draft` (草稿) |
|
||||
|
||||
**步骤定义**:
|
||||
| 步骤序号 | 步骤名称 | 步骤类型 | 处理人 |
|
||||
|---------|---------|---------|-------|
|
||||
| 1 | 分配填写人 | `assign` | 超级管理员 |
|
||||
| 2 | 填写项目信息 | `execute` | 被分配用户 |
|
||||
| 3 | 审核项目信息 | `approve` | 超级管理员 |
|
||||
|
||||
**扩展字段**(project_detail_ext):
|
||||
- `project_id` - 关联项目ID
|
||||
- `detail_filler_id` - 填写人ID
|
||||
- `detail_filler_name` - 填写人姓名
|
||||
- `draft_data` - 草稿数据
|
||||
|
||||
---
|
||||
|
||||
### microservice_update(微服务更新工单)
|
||||
|
||||
**业务场景**:普通用户在构建详情页面发起微服务更新请求,经过超级管理员审批后执行更新操作,并监控微服务运行状态。
|
||||
|
||||
**业务规则**:
|
||||
| 规则项 | 说明 |
|
||||
|-------|------|
|
||||
| 发起人 | 普通用户 |
|
||||
| 审批人 | 超级管理员 |
|
||||
| 执行人 | 超级管理员或委派人 |
|
||||
| 支持定时执行 | 是 |
|
||||
| 支持委派 | 是 |
|
||||
| 自动回滚 | 是(更新失败时自动回滚) |
|
||||
|
||||
**相关模块**:
|
||||
| 模块 | 职责 |
|
||||
|-----|------|
|
||||
| rmdc-jenkins-branch-dac | 提供构建物信息 |
|
||||
| rmdc-exchange-hub | MQTT消息中继 |
|
||||
| rmdc-watchdog | K8S操作执行、状态监控 |
|
||||
|
||||
**步骤定义**:
|
||||
| 步骤序号 | 步骤名称 | 步骤类型 | 处理人 |
|
||||
|---------|---------|---------|-------|
|
||||
| 1 | 提交更新请求 | `submit` | 普通用户 |
|
||||
| 2 | 审核更新请求 | `approve` | 超级管理员 |
|
||||
| 3 | 执行更新 | `execute` | 超级管理员/委派人 |
|
||||
| 4 | 监控运行状态 | `monitor` | 系统自动 |
|
||||
|
||||
**扩展字段**(microservice_update_ext):
|
||||
- `project_id` - 目标项目ID
|
||||
- `namespace` - K8S命名空间
|
||||
- `service_name` - 微服务名称
|
||||
- `build_id` - 构建物ID
|
||||
- `current_version` - 当前版本
|
||||
- `target_version` - 目标版本
|
||||
- `scheduled_at` - 计划执行时间
|
||||
- `execute_result` - 执行结果
|
||||
- `rollback_info` - 回滚信息
|
||||
|
||||
**更新页面布局**:
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 微服务更新 │
|
||||
├─────────────────┬─────────────────────────┬────────────────────┤
|
||||
│ 构建物信息 │ 更新表单 │ 操作区 │
|
||||
│ │ │ │
|
||||
│ 组织: acme │ 目标项目: [下拉选择] │ │
|
||||
│ 仓库: service │ 目标微服务: [下拉选择] │ [发起更新] │
|
||||
│ 分支: main │ 更新时间: [日期选择] │ │
|
||||
│ 构建号: #123 │ ○ 立即执行 │ [取消] │
|
||||
│ 版本: v3.2.0 │ ○ 定时执行 │ │
|
||||
│ │ 备注: [文本框] │ │
|
||||
└─────────────────┴─────────────────────────┴────────────────────┘
|
||||
```
|
||||
@@ -0,0 +1,143 @@
|
||||
#!/bin/bash
|
||||
# 验证 API 契约一致性
|
||||
# 依赖: curl, jq
|
||||
# 用法: ./verify-api-contracts.sh [API_BASE_URL]
|
||||
|
||||
set -e
|
||||
|
||||
API_BASE="${1:-http://localhost:8080}"
|
||||
|
||||
echo "=== 验证 API 契约 ==="
|
||||
echo "API 地址: $API_BASE"
|
||||
echo ""
|
||||
|
||||
# 检查依赖
|
||||
command -v curl >/dev/null 2>&1 || { echo "[ERROR] 需要安装 curl"; exit 1; }
|
||||
command -v jq >/dev/null 2>&1 || { echo "[ERROR] 需要安装 jq"; exit 1; }
|
||||
|
||||
FAILED=0
|
||||
PASSED=0
|
||||
|
||||
# 必需的接口列表
|
||||
ENDPOINTS=(
|
||||
"/api/workflow/create"
|
||||
"/api/workflow/detail"
|
||||
"/api/workflow/status"
|
||||
"/api/workflow/transition"
|
||||
"/api/workflow/assign"
|
||||
"/api/workflow/delegate"
|
||||
"/api/workflow/revoke"
|
||||
"/api/workflow/callback"
|
||||
"/api/workflow/list"
|
||||
"/api/workflow/my/created"
|
||||
"/api/workflow/my/assigned"
|
||||
"/api/workflow/my/pending"
|
||||
"/api/workflow/history"
|
||||
"/api/workflow/diagram"
|
||||
)
|
||||
|
||||
echo "--- 检查接口可访问性 ---"
|
||||
for endpoint in "${ENDPOINTS[@]}"; do
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API_BASE$endpoint" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer test-token" \
|
||||
-d '{}' 2>/dev/null || echo "000")
|
||||
|
||||
if [ "$HTTP_CODE" == "000" ]; then
|
||||
echo "[FAIL] 接口 $endpoint 无法连接"
|
||||
((FAILED++))
|
||||
elif [ "$HTTP_CODE" == "404" ]; then
|
||||
echo "[FAIL] 接口 $endpoint 不存在 (404)"
|
||||
((FAILED++))
|
||||
elif [ "$HTTP_CODE" == "401" ] || [ "$HTTP_CODE" == "403" ]; then
|
||||
echo "[PASS] 接口 $endpoint 存在 (需要认证: $HTTP_CODE)"
|
||||
((PASSED++))
|
||||
else
|
||||
echo "[PASS] 接口 $endpoint 可访问 (HTTP $HTTP_CODE)"
|
||||
((PASSED++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "--- 检查响应格式 ---"
|
||||
|
||||
# 测试 /api/workflow/list 接口响应格式
|
||||
RESP=$(curl -s -X POST "$API_BASE/api/workflow/list" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer test-token" \
|
||||
-d '{"page": 1, "page_size": 1}' 2>/dev/null || echo '{}')
|
||||
|
||||
# 检查 code 字段
|
||||
if echo "$RESP" | jq -e '.code' > /dev/null 2>&1; then
|
||||
CODE=$(echo "$RESP" | jq '.code')
|
||||
echo "[PASS] 响应包含 code 字段 (值: $CODE)"
|
||||
((PASSED++))
|
||||
else
|
||||
echo "[FAIL] 响应缺少 code 字段"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# 检查 message 字段
|
||||
if echo "$RESP" | jq -e '.message' > /dev/null 2>&1; then
|
||||
echo "[PASS] 响应包含 message 字段"
|
||||
((PASSED++))
|
||||
else
|
||||
echo "[FAIL] 响应缺少 message 字段"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# 检查 data 字段
|
||||
if echo "$RESP" | jq -e '.data' > /dev/null 2>&1; then
|
||||
echo "[PASS] 响应包含 data 字段"
|
||||
((PASSED++))
|
||||
else
|
||||
echo "[WARN] 响应可能缺少 data 字段(空响应或错误)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "--- 检查错误码规范 ---"
|
||||
|
||||
# 测试 404 错误码
|
||||
RESP_404=$(curl -s -X POST "$API_BASE/api/workflow/detail" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer test-token" \
|
||||
-d '{"workflow_id": "non-existent-id"}' 2>/dev/null || echo '{}')
|
||||
|
||||
if echo "$RESP_404" | jq -e '.code' > /dev/null 2>&1; then
|
||||
ERROR_CODE=$(echo "$RESP_404" | jq '.code')
|
||||
if [ "$ERROR_CODE" == "40401" ] || [ "$ERROR_CODE" == "404" ]; then
|
||||
echo "[PASS] 404 错误码符合规范 (code: $ERROR_CODE)"
|
||||
((PASSED++))
|
||||
else
|
||||
echo "[INFO] 404 错误码: $ERROR_CODE"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "--- 检查版本冲突处理 ---"
|
||||
|
||||
# 测试 409 版本冲突
|
||||
RESP_409=$(curl -s -X POST "$API_BASE/api/workflow/transition" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer test-token" \
|
||||
-d '{"workflow_id": "test", "version": -1, "event": "approve"}' 2>/dev/null || echo '{}')
|
||||
|
||||
if echo "$RESP_409" | jq -e '.code' > /dev/null 2>&1; then
|
||||
ERROR_CODE=$(echo "$RESP_409" | jq '.code')
|
||||
echo "[INFO] 版本冲突响应码: $ERROR_CODE"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== 验证结果 ==="
|
||||
echo "通过: $PASSED"
|
||||
echo "失败: $FAILED"
|
||||
|
||||
if [ $FAILED -gt 0 ]; then
|
||||
echo ""
|
||||
echo "部分 API 契约验证失败,请检查接口实现"
|
||||
exit 1
|
||||
else
|
||||
echo ""
|
||||
echo "API 契约验证通过!"
|
||||
exit 0
|
||||
fi
|
||||
134
1-AgentSkills/developing-work-procedure/scripts/verify-schema.sh
Normal file
134
1-AgentSkills/developing-work-procedure/scripts/verify-schema.sh
Normal file
@@ -0,0 +1,134 @@
|
||||
#!/bin/bash
|
||||
# 验证数据库 Schema 完整性
|
||||
# 依赖: psql (PostgreSQL) 或 mysql client
|
||||
# 用法: ./verify-schema.sh
|
||||
|
||||
set -e
|
||||
|
||||
DB_TYPE="${DB_TYPE:-postgres}"
|
||||
DB_HOST="${DB_HOST:-localhost}"
|
||||
DB_PORT="${DB_PORT:-5432}"
|
||||
DB_NAME="${DB_NAME:-rmdc}"
|
||||
DB_USER="${DB_USER:-rmdc}"
|
||||
DB_PASSWORD="${DB_PASSWORD:-}"
|
||||
|
||||
echo "=== 验证工单模块 Schema ==="
|
||||
echo "数据库: $DB_TYPE://$DB_HOST:$DB_PORT/$DB_NAME"
|
||||
echo ""
|
||||
|
||||
# 必需的表
|
||||
REQUIRED_TABLES=(
|
||||
"workflows"
|
||||
"workflow_steps"
|
||||
"workflow_track_history"
|
||||
"user_registration_ext"
|
||||
"user_management_ext"
|
||||
"project_detail_ext"
|
||||
"microservice_update_ext"
|
||||
)
|
||||
|
||||
# workflows 表必需的列
|
||||
WORKFLOWS_COLUMNS=(
|
||||
"id"
|
||||
"version"
|
||||
"module_code"
|
||||
"workflow_type"
|
||||
"status"
|
||||
"creator_id"
|
||||
"assignee_id"
|
||||
"created_at"
|
||||
"updated_at"
|
||||
)
|
||||
|
||||
FAILED=0
|
||||
PASSED=0
|
||||
|
||||
# 检查表是否存在
|
||||
check_table_exists() {
|
||||
local table=$1
|
||||
if [ "$DB_TYPE" == "postgres" ]; then
|
||||
result=$(PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c \
|
||||
"SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = '$table')" 2>/dev/null | tr -d ' ')
|
||||
[ "$result" == "t" ] && return 0 || return 1
|
||||
else
|
||||
result=$(mysql -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASSWORD" -D "$DB_NAME" -N -e \
|
||||
"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '$table'" 2>/dev/null)
|
||||
[ "$result" -gt 0 ] && return 0 || return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查列是否存在
|
||||
check_column_exists() {
|
||||
local table=$1
|
||||
local column=$2
|
||||
if [ "$DB_TYPE" == "postgres" ]; then
|
||||
result=$(PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c \
|
||||
"SELECT EXISTS (SELECT FROM information_schema.columns WHERE table_name = '$table' AND column_name = '$column')" 2>/dev/null | tr -d ' ')
|
||||
[ "$result" == "t" ] && return 0 || return 1
|
||||
else
|
||||
result=$(mysql -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASSWORD" -D "$DB_NAME" -N -e \
|
||||
"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '$table' AND column_name = '$column'" 2>/dev/null)
|
||||
[ "$result" -gt 0 ] && return 0 || return 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "--- 检查表是否存在 ---"
|
||||
for table in "${REQUIRED_TABLES[@]}"; do
|
||||
if check_table_exists "$table"; then
|
||||
echo "[PASS] 表 $table 存在"
|
||||
((PASSED++))
|
||||
else
|
||||
echo "[FAIL] 表 $table 不存在"
|
||||
((FAILED++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "--- 检查 workflows 表核心字段 ---"
|
||||
for column in "${WORKFLOWS_COLUMNS[@]}"; do
|
||||
if check_column_exists "workflows" "$column"; then
|
||||
echo "[PASS] workflows.$column 字段存在"
|
||||
((PASSED++))
|
||||
else
|
||||
echo "[FAIL] workflows.$column 字段缺失"
|
||||
((FAILED++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "--- 检查乐观锁字段 ---"
|
||||
if check_column_exists "workflows" "version"; then
|
||||
echo "[PASS] workflows.version 字段存在(乐观锁)"
|
||||
((PASSED++))
|
||||
else
|
||||
echo "[FAIL] workflows.version 字段缺失(乐观锁必需)"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "--- 检查外键约束 ---"
|
||||
EXT_TABLES=("user_registration_ext" "user_management_ext" "project_detail_ext" "microservice_update_ext")
|
||||
for table in "${EXT_TABLES[@]}"; do
|
||||
if check_column_exists "$table" "workflow_id"; then
|
||||
echo "[PASS] $table.workflow_id 字段存在"
|
||||
((PASSED++))
|
||||
else
|
||||
echo "[FAIL] $table.workflow_id 字段缺失"
|
||||
((FAILED++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== 验证结果 ==="
|
||||
echo "通过: $PASSED"
|
||||
echo "失败: $FAILED"
|
||||
|
||||
if [ $FAILED -gt 0 ]; then
|
||||
echo ""
|
||||
echo "请运行数据库迁移修复缺失的表/字段"
|
||||
exit 1
|
||||
else
|
||||
echo ""
|
||||
echo "所有 Schema 检查通过!"
|
||||
exit 0
|
||||
fi
|
||||
@@ -0,0 +1,156 @@
|
||||
#!/bin/bash
|
||||
# 验证状态机定义完整性
|
||||
# 依赖: go (用于解析 Go 源码)
|
||||
# 用法: ./verify-state-machine.sh [项目根目录]
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_ROOT="${1:-.}"
|
||||
|
||||
echo "=== 验证状态机定义 ==="
|
||||
echo "项目目录: $PROJECT_ROOT"
|
||||
echo ""
|
||||
|
||||
# 基础状态列表
|
||||
BASE_STATES=(
|
||||
"created"
|
||||
"pending"
|
||||
"assigned"
|
||||
"in_progress"
|
||||
"pending_review"
|
||||
"returned"
|
||||
"approved"
|
||||
"rejected"
|
||||
"revoked"
|
||||
"closed"
|
||||
)
|
||||
|
||||
# 扩展状态列表
|
||||
EXTENDED_STATES=(
|
||||
"executing"
|
||||
"monitoring"
|
||||
"rollbacked"
|
||||
"draft_saved"
|
||||
)
|
||||
|
||||
# 终态列表
|
||||
TERMINAL_STATES=(
|
||||
"approved"
|
||||
"rejected"
|
||||
"revoked"
|
||||
"closed"
|
||||
)
|
||||
|
||||
# 事件列表
|
||||
EVENTS=(
|
||||
"submit"
|
||||
"auto_assign"
|
||||
"assign"
|
||||
"accept"
|
||||
"reassign"
|
||||
"complete"
|
||||
"approve"
|
||||
"reject"
|
||||
"return"
|
||||
"resubmit"
|
||||
"revoke"
|
||||
"close"
|
||||
)
|
||||
|
||||
FAILED=0
|
||||
PASSED=0
|
||||
WARNINGS=0
|
||||
|
||||
echo "--- 检查基础状态定义 ---"
|
||||
for state in "${BASE_STATES[@]}"; do
|
||||
if grep -rq "\"$state\"" --include="*.go" "$PROJECT_ROOT" 2>/dev/null; then
|
||||
echo "[PASS] 状态 '$state' 已定义"
|
||||
((PASSED++))
|
||||
else
|
||||
echo "[FAIL] 状态 '$state' 未定义"
|
||||
((FAILED++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "--- 检查扩展状态定义 ---"
|
||||
for state in "${EXTENDED_STATES[@]}"; do
|
||||
if grep -rq "\"$state\"" --include="*.go" "$PROJECT_ROOT" 2>/dev/null; then
|
||||
echo "[PASS] 扩展状态 '$state' 已定义"
|
||||
((PASSED++))
|
||||
else
|
||||
echo "[WARN] 扩展状态 '$state' 未定义(可选)"
|
||||
((WARNINGS++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "--- 检查事件定义 ---"
|
||||
for event in "${EVENTS[@]}"; do
|
||||
if grep -rq "\"$event\"" --include="*.go" "$PROJECT_ROOT" 2>/dev/null; then
|
||||
echo "[PASS] 事件 '$event' 已定义"
|
||||
((PASSED++))
|
||||
else
|
||||
echo "[FAIL] 事件 '$event' 未定义"
|
||||
((FAILED++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "--- 检查终态保护 ---"
|
||||
for state in "${TERMINAL_STATES[@]}"; do
|
||||
# 检查是否有 TerminalStates 或 IsTerminal 相关定义
|
||||
if grep -rq "Terminal.*$state\|IsTerminal\|终态" --include="*.go" "$PROJECT_ROOT" 2>/dev/null; then
|
||||
echo "[PASS] 终态 '$state' 有保护机制"
|
||||
((PASSED++))
|
||||
else
|
||||
echo "[WARN] 终态 '$state' 可能缺少保护机制"
|
||||
((WARNINGS++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "--- 检查撤销权限 ---"
|
||||
if grep -rq "CanRevoke\|revoke.*Terminal\|IsTerminal" --include="*.go" "$PROJECT_ROOT" 2>/dev/null; then
|
||||
echo "[PASS] 撤销权限检查已实现"
|
||||
((PASSED++))
|
||||
else
|
||||
echo "[WARN] 未找到撤销权限检查逻辑"
|
||||
((WARNINGS++))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "--- 检查乐观锁实现 ---"
|
||||
if grep -rq "version.*=.*version.*\+.*1\|version.*Expr\|RowsAffected.*==.*0" --include="*.go" "$PROJECT_ROOT" 2>/dev/null; then
|
||||
echo "[PASS] 乐观锁机制已实现"
|
||||
((PASSED++))
|
||||
else
|
||||
echo "[FAIL] 未找到乐观锁实现"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "--- 检查状态历史记录 ---"
|
||||
if grep -rq "WorkflowTrackHistory\|workflow_track_history\|TrackHistory" --include="*.go" "$PROJECT_ROOT" 2>/dev/null; then
|
||||
echo "[PASS] 状态历史记录已实现"
|
||||
((PASSED++))
|
||||
else
|
||||
echo "[FAIL] 未找到状态历史记录实现"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== 验证结果 ==="
|
||||
echo "通过: $PASSED"
|
||||
echo "警告: $WARNINGS"
|
||||
echo "失败: $FAILED"
|
||||
|
||||
if [ $FAILED -gt 0 ]; then
|
||||
echo ""
|
||||
echo "请检查并修复失败项"
|
||||
exit 1
|
||||
else
|
||||
echo ""
|
||||
echo "状态机验证通过!"
|
||||
exit 0
|
||||
fi
|
||||
146
1-AgentSkills/implementing-deadman-switch/SKILL.md
Normal file
146
1-AgentSkills/implementing-deadman-switch/SKILL.md
Normal file
@@ -0,0 +1,146 @@
|
||||
---
|
||||
name: implementing-deadman-switch
|
||||
description: Guides implementation of deadman switch (dead hand system) and heartbeat mechanism in watchdog-agent for authorization enforcement. Use when modifying heartbeat intervals, failure thresholds, or business process termination logic. Keywords: deadman, heartbeat, agent, authorization, sigterm, fail-count, self-destruct.
|
||||
argument-hint: "<component>: agent-heartbeat | fail-threshold | kill-logic | interval-config"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
- Edit
|
||||
- Write
|
||||
---
|
||||
|
||||
# Implementing Deadman Switch
|
||||
|
||||
watchdog-agent 内置死手系统,当连续授权失败达到阈值时终止业务进程。
|
||||
|
||||
## 动态上下文注入
|
||||
|
||||
```bash
|
||||
# 查找Agent心跳实现
|
||||
!`grep -rn "heartbeat\|Heartbeat" rmdc-watchdog-agent/`
|
||||
|
||||
# 查找kill逻辑
|
||||
!`grep -n "SIGTERM\|Kill\|Signal" rmdc-watchdog-agent/`
|
||||
```
|
||||
|
||||
## Plan
|
||||
|
||||
根据 `$ARGUMENTS` 确定修改范围:
|
||||
|
||||
| Component | 涉及文件 | 关键参数 |
|
||||
|-----------|----------|----------|
|
||||
| agent-heartbeat | agent心跳模块 | HeartbeatRequest/Response |
|
||||
| fail-threshold | 失败计数逻辑 | maxRetryCount=12 |
|
||||
| kill-logic | 进程终止逻辑 | SIGTERM信号 |
|
||||
| interval-config | 心跳间隔配置 | 成功2h/失败1h |
|
||||
|
||||
**产物清单**:
|
||||
- Agent心跳循环实现
|
||||
- 失败计数与阈值判断
|
||||
- 业务进程终止逻辑
|
||||
|
||||
## Verify
|
||||
|
||||
- [ ] 失败阈值:maxRetryCount = 12
|
||||
- [ ] 心跳间隔:成功后2小时,失败后1小时
|
||||
- [ ] TOTP验证:首次连接获取密钥,后续请求双向验证
|
||||
- [ ] 终止信号:使用SIGTERM(优雅终止),非SIGKILL
|
||||
- [ ] 计数重置:授权成功后 failCount = 1(非0)
|
||||
- [ ] 时间戳校验:|now - timestamp| < 5分钟
|
||||
|
||||
```bash
|
||||
# 验证Agent编译
|
||||
!`cd rmdc-watchdog-agent && go build ./...`
|
||||
|
||||
# 验证心跳逻辑
|
||||
!`cd rmdc-watchdog-agent && go test ./... -v -run TestHeartbeat`
|
||||
```
|
||||
|
||||
## Execute
|
||||
|
||||
### 心跳循环实现
|
||||
|
||||
```go
|
||||
func (a *Agent) heartbeatLoop() {
|
||||
failCount := 0
|
||||
|
||||
for {
|
||||
resp, err := a.sendHeartbeat()
|
||||
|
||||
if err != nil || !resp.Authorized {
|
||||
failCount++
|
||||
|
||||
if failCount >= 12 {
|
||||
a.killBusiness()
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Hour) // 失败后等待1小时
|
||||
} else {
|
||||
failCount = 1 // 成功后重置为1
|
||||
time.Sleep(2 * time.Hour) // 成功后等待2小时
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 业务终止实现
|
||||
|
||||
```go
|
||||
func (a *Agent) killBusiness() {
|
||||
log.Warn("deadman switch triggered, terminating business process")
|
||||
a.businessProcess.Signal(syscall.SIGTERM)
|
||||
}
|
||||
```
|
||||
|
||||
### 首次连接处理
|
||||
|
||||
```go
|
||||
func (a *Agent) sendHeartbeat() (*HeartbeatResponse, error) {
|
||||
req := &HeartbeatRequest{
|
||||
HostInfo: a.hostInfo,
|
||||
EnvInfo: a.envInfo,
|
||||
Timestamp: time.Now().UnixMilli(),
|
||||
TOTPCode: "", // 首次为空
|
||||
}
|
||||
|
||||
// 非首次连接,生成TOTP
|
||||
if a.tierTwoSecret != "" {
|
||||
req.TOTPCode = totp.GenerateTierTwo(a.tierTwoSecret)
|
||||
}
|
||||
|
||||
resp, err := a.httpClient.Post(a.watchdogURL+"/api/heartbeat", req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 首次连接,保存密钥
|
||||
if resp.SecondTOTPSecret != "" {
|
||||
a.tierTwoSecret = resp.SecondTOTPSecret
|
||||
}
|
||||
|
||||
// 验证服务端TOTP(双向验证)
|
||||
if req.TOTPCode != "" && !totp.VerifyTierTwo(resp.TOTPCode, a.tierTwoSecret) {
|
||||
return nil, errors.New("invalid server totp")
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
```
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **failCount初始值**:成功后设为1而非0,避免边界条件错误
|
||||
2. **SIGKILL误用**:应使用SIGTERM允许业务优雅退出
|
||||
3. **心跳阻塞**:sendHeartbeat需设置超时,避免网络问题导致卡死
|
||||
4. **双向验证遗漏**:必须验证服务端返回的TOTP
|
||||
5. **首次连接特殊处理**:TOTPCode为空时获取密钥,不计入失败
|
||||
6. **间隔配置硬编码**:应支持配置化,便于不同项目调整
|
||||
7. **日志泄露**:禁止在日志中打印TOTP密钥
|
||||
|
||||
## Reference
|
||||
|
||||
- [心跳参数配置](reference/heartbeat-params.md)
|
||||
- [Agent生命周期](reference/agent-lifecycle.md)
|
||||
@@ -0,0 +1,92 @@
|
||||
# Agent 生命周期
|
||||
|
||||
## 启动流程
|
||||
|
||||
```
|
||||
1. Agent作为Sidecar随业务Pod启动
|
||||
2. 收集主机信息(MachineID, CPU, Memory, Serial, IP)
|
||||
3. 收集环境信息(K8S_NAMESPACE, APPLICATION_NAME)
|
||||
4. 首次心跳请求(TOTPCode="")
|
||||
5. 获取并保存tier_two_secret
|
||||
6. 进入心跳循环
|
||||
```
|
||||
|
||||
## 心跳状态机
|
||||
|
||||
```
|
||||
初始化 -> 首次心跳 -> 获取密钥 -> 心跳循环
|
||||
↓
|
||||
成功: failCount=1, 等待2h
|
||||
失败: failCount++, 等待1h
|
||||
↓
|
||||
failCount >= 12
|
||||
↓
|
||||
触发死手系统 -> 终止业务
|
||||
```
|
||||
|
||||
## 心跳循环详情
|
||||
|
||||
### 成功路径
|
||||
1. 生成Tier-Two TOTP (6位/30秒)
|
||||
2. 发送心跳请求
|
||||
3. Watchdog验证TOTP
|
||||
4. Watchdog检查授权状态
|
||||
5. 返回 {Authorized: true, TOTPCode: xxx}
|
||||
6. Agent验证响应TOTP
|
||||
7. failCount = 1
|
||||
8. 等待2小时
|
||||
|
||||
### 失败路径
|
||||
1. 发送心跳请求
|
||||
2. 返回错误或 {Authorized: false}
|
||||
3. failCount++
|
||||
4. 检查 failCount >= 12
|
||||
5. 未达阈值: 等待1小时,继续循环
|
||||
6. 达到阈值: 触发死手系统
|
||||
|
||||
## 死手系统触发
|
||||
|
||||
```go
|
||||
func (a *Agent) killBusiness() {
|
||||
log.Warn("deadman switch triggered after %d failures", failCount)
|
||||
|
||||
// 发送SIGTERM,允许优雅退出
|
||||
if err := a.businessProcess.Signal(syscall.SIGTERM); err != nil {
|
||||
log.Error("failed to send SIGTERM: %v", err)
|
||||
// 最后手段:SIGKILL
|
||||
a.businessProcess.Signal(syscall.SIGKILL)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 与Watchdog交互
|
||||
|
||||
### 请求结构
|
||||
```go
|
||||
type HeartbeatRequest struct {
|
||||
HostInfo HostInfo `json:"host_info"`
|
||||
EnvInfo EnvInfo `json:"env_info"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
TOTPCode string `json:"totp_code"` // 首次为空
|
||||
}
|
||||
```
|
||||
|
||||
### 响应结构
|
||||
```go
|
||||
type HeartbeatResponse struct {
|
||||
Authorized bool `json:"authorized"`
|
||||
TOTPCode string `json:"totp_code"` // 双向验证
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
SecondTOTPSecret string `json:"second_totp_secret"` // 首次返回
|
||||
}
|
||||
```
|
||||
|
||||
## 异常处理
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
|------|----------|
|
||||
| 网络超时 | 计入失败,继续循环 |
|
||||
| Watchdog不可达 | 计入失败,继续循环 |
|
||||
| TOTP验证失败 | 计入失败,继续循环 |
|
||||
| 响应TOTP无效 | 计入失败,可能中间人攻击 |
|
||||
| 授权状态false | 计入失败,等待授权恢复 |
|
||||
@@ -0,0 +1,54 @@
|
||||
# 心跳参数配置
|
||||
|
||||
## 核心参数
|
||||
|
||||
| 参数 | 值 | 说明 |
|
||||
|------|------|------|
|
||||
| maxRetryCount | 12 | 最大连续失败次数 |
|
||||
| defaultHeartbeatInterval | 2小时 | 成功后心跳间隔 |
|
||||
| failWaitInterval | 1小时 | 失败后等待间隔 |
|
||||
| timestampTolerance | 5分钟 | 时间戳校验容忍度 |
|
||||
|
||||
## 触发条件
|
||||
|
||||
死手系统触发条件:**连续12次心跳失败**
|
||||
|
||||
### 时间计算
|
||||
- 最短触发时间:12次 × 1小时 = 12小时
|
||||
- 实际触发时间取决于网络和服务状态
|
||||
|
||||
### 失败场景
|
||||
1. 网络不可达
|
||||
2. Watchdog服务异常
|
||||
3. TOTP验证失败
|
||||
4. 授权状态为false
|
||||
5. 响应TOTP校验失败
|
||||
|
||||
## 终止方式
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 信号 | SIGTERM (15) |
|
||||
| 目的 | 允许业务进程优雅退出 |
|
||||
| 后续 | 由K8S重启策略决定是否重启 |
|
||||
|
||||
## 配置示例
|
||||
|
||||
```yaml
|
||||
# agent配置
|
||||
heartbeat:
|
||||
watchdog_url: "http://rmdc-watchdog:8990"
|
||||
max_retry_count: 12
|
||||
success_interval: "2h"
|
||||
fail_interval: "1h"
|
||||
timestamp_tolerance: "5m"
|
||||
```
|
||||
|
||||
## 环境变量
|
||||
|
||||
| 变量 | 说明 | 默认值 |
|
||||
|------|------|--------|
|
||||
| WATCHDOG_URL | Watchdog地址 | http://rmdc-watchdog:8990 |
|
||||
| MAX_RETRY_COUNT | 最大重试次数 | 12 |
|
||||
| SUCCESS_INTERVAL | 成功后间隔 | 2h |
|
||||
| FAIL_INTERVAL | 失败后间隔 | 1h |
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/bin/bash
|
||||
# verify-deadman.sh - 死手系统实现验证脚本
|
||||
# 依赖: go 1.21+
|
||||
# 用法: ./verify-deadman.sh [agent_dir]
|
||||
|
||||
set -e
|
||||
|
||||
AGENT_DIR="${1:-./rmdc-watchdog-agent}"
|
||||
|
||||
echo "=== 死手系统实现验证 ==="
|
||||
echo "目标目录: $AGENT_DIR"
|
||||
echo ""
|
||||
|
||||
# 检查目录
|
||||
if [ ! -d "$AGENT_DIR" ]; then
|
||||
echo "⚠ 目录不存在: $AGENT_DIR"
|
||||
echo "尝试查找watchdog-agent目录..."
|
||||
AGENT_DIR=$(find . -type d -name "*watchdog*agent*" 2>/dev/null | head -1)
|
||||
if [ -z "$AGENT_DIR" ]; then
|
||||
echo "✗ 未找到agent目录"
|
||||
exit 1
|
||||
fi
|
||||
echo "找到: $AGENT_DIR"
|
||||
fi
|
||||
|
||||
# 1. 验证心跳实现
|
||||
echo ""
|
||||
echo "[1/5] 验证心跳实现..."
|
||||
if grep -rq "heartbeat\|Heartbeat" "$AGENT_DIR" --include="*.go" 2>/dev/null; then
|
||||
echo "✓ 找到心跳相关代码"
|
||||
HEARTBEAT_FILES=$(grep -rl "heartbeat\|Heartbeat" "$AGENT_DIR" --include="*.go" 2>/dev/null | head -3)
|
||||
echo " 文件: $HEARTBEAT_FILES"
|
||||
else
|
||||
echo "⚠ 未找到心跳相关代码"
|
||||
fi
|
||||
|
||||
# 2. 验证失败阈值
|
||||
echo ""
|
||||
echo "[2/5] 验证失败阈值 (maxRetryCount=12)..."
|
||||
if grep -rq "12" "$AGENT_DIR" --include="*.go" 2>/dev/null | grep -qi "retry\|count\|max\|fail"; then
|
||||
echo "✓ 找到阈值配置"
|
||||
else
|
||||
echo "⚠ 未明确找到阈值12的配置"
|
||||
fi
|
||||
|
||||
# 3. 验证SIGTERM信号
|
||||
echo ""
|
||||
echo "[3/5] 验证终止信号 (SIGTERM)..."
|
||||
if grep -rq "SIGTERM\|syscall.Signal" "$AGENT_DIR" --include="*.go" 2>/dev/null; then
|
||||
echo "✓ 找到SIGTERM信号使用"
|
||||
else
|
||||
echo "⚠ 未找到SIGTERM信号"
|
||||
fi
|
||||
|
||||
if grep -rq "SIGKILL" "$AGENT_DIR" --include="*.go" 2>/dev/null; then
|
||||
echo "⚠ 发现SIGKILL使用,请确认仅作为最后手段"
|
||||
fi
|
||||
|
||||
# 4. 验证心跳间隔
|
||||
echo ""
|
||||
echo "[4/5] 验证心跳间隔..."
|
||||
if grep -rq "2.*[Hh]our\|time.Hour.*2\|7200" "$AGENT_DIR" --include="*.go" 2>/dev/null; then
|
||||
echo "✓ 找到2小时间隔配置"
|
||||
else
|
||||
echo "⚠ 未找到2小时成功间隔"
|
||||
fi
|
||||
|
||||
if grep -rq "1.*[Hh]our\|time.Hour\|3600" "$AGENT_DIR" --include="*.go" 2>/dev/null; then
|
||||
echo "✓ 找到1小时间隔配置"
|
||||
else
|
||||
echo "⚠ 未找到1小时失败间隔"
|
||||
fi
|
||||
|
||||
# 5. 验证TOTP双向验证
|
||||
echo ""
|
||||
echo "[5/5] 验证TOTP双向验证..."
|
||||
if grep -rq "SecondTOTPSecret\|second_totp_secret" "$AGENT_DIR" --include="*.go" 2>/dev/null; then
|
||||
echo "✓ 找到密钥获取逻辑"
|
||||
else
|
||||
echo "⚠ 未找到密钥获取逻辑"
|
||||
fi
|
||||
|
||||
if grep -rq "response.*[Tt]otp\|verify.*[Tt]otp" "$AGENT_DIR" --include="*.go" 2>/dev/null; then
|
||||
echo "✓ 找到响应TOTP验证"
|
||||
else
|
||||
echo "⚠ 未找到响应TOTP验证,请确认双向验证"
|
||||
fi
|
||||
|
||||
# 编译检查
|
||||
echo ""
|
||||
echo "[额外] 编译检查..."
|
||||
cd "$AGENT_DIR"
|
||||
if go build ./... 2>&1; then
|
||||
echo "✓ 编译通过"
|
||||
else
|
||||
echo "⚠ 编译有问题"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== 验证完成 ==="
|
||||
112
1-AgentSkills/implementing-k8s-ops/SKILL.md
Normal file
112
1-AgentSkills/implementing-k8s-ops/SKILL.md
Normal file
@@ -0,0 +1,112 @@
|
||||
---
|
||||
name: implementing-k8s-ops
|
||||
description: Guides implementation of K8S operation proxy in rmdc-watchdog for executing Kubernetes API calls including logs, exec, scale, restart, delete, get, and apply actions. Use when adding new K8S actions or modifying execution logic. Keywords: kubernetes, k8s, operator, logs, exec, scale, restart, deployment, pod.
|
||||
argument-hint: "<action>: logs | exec | scale | restart | delete | get | apply | new-action"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
- Edit
|
||||
- Write
|
||||
---
|
||||
|
||||
# Implementing K8S Operations
|
||||
|
||||
rmdc-watchdog 作为K8S操作代理,执行来自 exchange-hub 下发的K8S指令。
|
||||
|
||||
## 动态上下文注入
|
||||
|
||||
```bash
|
||||
# 查看K8S客户端实现
|
||||
!`cat rmdc-watchdog/pkg/k8s/client.go`
|
||||
|
||||
# 查找现有action处理
|
||||
!`grep -n "case \"" rmdc-watchdog/internal/service/k8s_service.go`
|
||||
```
|
||||
|
||||
## Plan
|
||||
|
||||
根据 `$ARGUMENTS` 确定操作类型:
|
||||
|
||||
| Action | 目标资源 | 关键参数 |
|
||||
|--------|----------|----------|
|
||||
| logs | Pod | container, tail_lines, follow |
|
||||
| exec | Pod | container, command[], timeout |
|
||||
| scale | Deployment/StatefulSet | scale_count |
|
||||
| restart | Deployment/StatefulSet | - |
|
||||
| delete | 任意资源 | - |
|
||||
| get | 任意资源 | output_format |
|
||||
| apply | 任意资源 | yaml_content |
|
||||
|
||||
**产物清单**:
|
||||
- `pkg/k8s/client.go` - K8S API调用封装
|
||||
- `internal/service/k8s_service.go` - K8S服务逻辑
|
||||
- `internal/handler/k8s_handler.go` - K8S请求处理
|
||||
|
||||
**决策点**:
|
||||
1. 新action是否需要额外参数?→ 更新 K8sExecCommand 结构
|
||||
2. 是否涉及敏感操作?→ 需添加审计日志
|
||||
3. 是否需要超时控制?→ 使用 context.WithTimeout
|
||||
|
||||
## Verify
|
||||
|
||||
- [ ] 操作白名单:仅允许 logs/exec/scale/restart/delete/get/apply
|
||||
- [ ] 超时处理:所有K8S API调用必须设置timeout
|
||||
- [ ] 结果格式:ExecResult包含command_id, status, exit_code, output, error, duration
|
||||
- [ ] 日志截断:tail_lines限制,避免大日志阻塞
|
||||
- [ ] 权限边界:仅操作项目namespace内资源
|
||||
- [ ] 执行上报:结果通过MQTT `wdd/RDMC/message/up` 上报
|
||||
|
||||
```bash
|
||||
# 验证K8S客户端
|
||||
!`cd rmdc-watchdog && go test ./pkg/k8s/... -v`
|
||||
|
||||
# 验证K8S服务
|
||||
!`cd rmdc-watchdog && go test ./internal/service/... -v -run TestK8s`
|
||||
```
|
||||
|
||||
## Execute
|
||||
|
||||
### 添加新K8S操作
|
||||
|
||||
1. **扩展K8S Client**
|
||||
```go
|
||||
// pkg/k8s/client.go
|
||||
func (c *Client) NewAction(namespace, name string, params Params) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(params.Timeout)*time.Second)
|
||||
defer cancel()
|
||||
// K8S API调用
|
||||
}
|
||||
```
|
||||
|
||||
2. **添加Service分支**
|
||||
```go
|
||||
// internal/service/k8s_service.go
|
||||
case "new-action":
|
||||
output, err = s.k8sClient.NewAction(cmd.Namespace, cmd.Name, params)
|
||||
```
|
||||
|
||||
3. **更新指令结构(如需)**
|
||||
```go
|
||||
type K8sExecCommand struct {
|
||||
// 新增字段
|
||||
NewParam string `json:"new_param,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
4. **同步exchange-hub指令定义**
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **Namespace逃逸**:必须校验操作仅限项目namespace
|
||||
2. **超时未设置**:K8S API调用卡住会阻塞整个handler
|
||||
3. **大日志OOM**:logs操作未设置tail_lines导致内存溢出
|
||||
4. **exec命令注入**:command[]需过滤危险命令
|
||||
5. **follow日志未清理**:流式日志需session管理,用户停止时清理
|
||||
6. **结果丢失**:执行完成必须通过MQTT上报,失败重试
|
||||
|
||||
## Reference
|
||||
|
||||
- [K8S操作类型](reference/k8s-actions.md)
|
||||
- [指令结构定义](reference/command-structure.md)
|
||||
@@ -0,0 +1,74 @@
|
||||
# K8S 指令结构定义
|
||||
|
||||
## K8sExecCommand - 执行指令
|
||||
|
||||
```go
|
||||
type K8sExecCommand struct {
|
||||
CommandID string `json:"command_id"` // 唯一指令ID
|
||||
Namespace string `json:"namespace"` // 目标namespace
|
||||
Resource string `json:"resource"` // 资源类型: deployment, pod, statefulset...
|
||||
Name string `json:"name"` // 资源名称
|
||||
Action string `json:"action"` // 操作: logs, exec, scale, restart, delete, get, apply
|
||||
Container string `json:"container,omitempty"` // 容器名(logs/exec用)
|
||||
Command []string `json:"command,omitempty"` // 命令(exec用)
|
||||
Timeout int `json:"timeout"` // 超时秒数
|
||||
TailLines int `json:"tail_lines,omitempty"` // 日志行数(logs用)
|
||||
FollowLogs bool `json:"follow_logs,omitempty"`// 实时日志(logs用)
|
||||
Scale int `json:"scale,omitempty"` // 副本数(scale用)
|
||||
YamlContent string `json:"yaml_content,omitempty"`// YAML内容(apply用)
|
||||
}
|
||||
```
|
||||
|
||||
## ExecResult - 执行结果
|
||||
|
||||
```go
|
||||
type ExecResult struct {
|
||||
CommandID string `json:"command_id"` // 对应指令ID
|
||||
Status string `json:"status"` // success, failure, timeout
|
||||
ExitCode int `json:"exit_code"` // 退出码
|
||||
Output string `json:"output"` // 标准输出
|
||||
Error string `json:"error"` // 错误信息
|
||||
StartTime int64 `json:"start_time"` // 开始时间戳(ms)
|
||||
EndTime int64 `json:"end_time"` // 结束时间戳(ms)
|
||||
Duration int64 `json:"duration"` // 执行时长(ms)
|
||||
}
|
||||
```
|
||||
|
||||
## MQTT消息封装
|
||||
|
||||
### 下发指令 (Exchange-Hub → Watchdog)
|
||||
```json
|
||||
{
|
||||
"message_id": "uuid",
|
||||
"type": "command",
|
||||
"project_id": "project_12345678",
|
||||
"timestamp": 1700000000000,
|
||||
"data_type": "k8s_exec",
|
||||
"payload": {
|
||||
"command_id": "cmd_uuid",
|
||||
"namespace": "my-namespace",
|
||||
"resource": "pod",
|
||||
"name": "my-pod",
|
||||
"action": "logs",
|
||||
"tail_lines": 100
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 上报结果 (Watchdog → Exchange-Hub)
|
||||
```json
|
||||
{
|
||||
"message_id": "uuid",
|
||||
"type": "message",
|
||||
"project_id": "project_12345678",
|
||||
"timestamp": 1700000001000,
|
||||
"data_type": "exec_result",
|
||||
"payload": {
|
||||
"command_id": "cmd_uuid",
|
||||
"status": "success",
|
||||
"exit_code": 0,
|
||||
"output": "log content...",
|
||||
"duration": 1500
|
||||
}
|
||||
}
|
||||
```
|
||||
69
1-AgentSkills/implementing-k8s-ops/reference/k8s-actions.md
Normal file
69
1-AgentSkills/implementing-k8s-ops/reference/k8s-actions.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# K8S 操作类型定义
|
||||
|
||||
## 支持的操作
|
||||
|
||||
| Action | 说明 | 目标资源 | 参数 |
|
||||
|--------|------|----------|------|
|
||||
| `logs` | 获取日志 | Pod | container, tail_lines, follow |
|
||||
| `exec` | 执行命令 | Pod | container, command[], timeout |
|
||||
| `scale` | 扩缩容 | Deployment/StatefulSet | scale_count |
|
||||
| `restart` | 滚动重启 | Deployment/StatefulSet | - |
|
||||
| `delete` | 删除资源 | Pod/Deployment/Service等 | - |
|
||||
| `get` | 获取信息 | 任意资源 | output_format |
|
||||
| `apply` | 应用配置 | 任意资源 | yaml_content |
|
||||
|
||||
## 操作详情
|
||||
|
||||
### logs - 获取日志
|
||||
```go
|
||||
type LogsParams struct {
|
||||
Container string `json:"container"`
|
||||
TailLines int `json:"tail_lines"` // 默认100,最大1000
|
||||
Follow bool `json:"follow"` // 是否实时跟踪
|
||||
}
|
||||
```
|
||||
|
||||
### exec - 执行命令
|
||||
```go
|
||||
type ExecParams struct {
|
||||
Container string `json:"container"`
|
||||
Command []string `json:"command"`
|
||||
Timeout int `json:"timeout"` // 秒
|
||||
}
|
||||
```
|
||||
**安全约束**:禁止执行 rm -rf、shutdown、reboot 等危险命令
|
||||
|
||||
### scale - 扩缩容
|
||||
```go
|
||||
type ScaleParams struct {
|
||||
Replicas int `json:"replicas"` // 目标副本数
|
||||
}
|
||||
```
|
||||
|
||||
### restart - 滚动重启
|
||||
无额外参数,使用 kubectl rollout restart 等效操作
|
||||
|
||||
### delete - 删除资源
|
||||
无额外参数,需确认资源类型和名称
|
||||
|
||||
### get - 获取信息
|
||||
```go
|
||||
type GetParams struct {
|
||||
OutputFormat string `json:"output_format"` // json, yaml, wide
|
||||
}
|
||||
```
|
||||
|
||||
### apply - 应用配置
|
||||
```go
|
||||
type ApplyParams struct {
|
||||
YamlContent string `json:"yaml_content"` // YAML内容
|
||||
}
|
||||
```
|
||||
|
||||
## 安全约束
|
||||
|
||||
1. **Namespace限制**:仅操作项目namespace内资源
|
||||
2. **操作白名单**:仅允许上述7种操作
|
||||
3. **命令过滤**:exec操作过滤危险命令
|
||||
4. **日志限制**:tail_lines最大1000行
|
||||
5. **超时控制**:所有操作必须设置timeout
|
||||
81
1-AgentSkills/implementing-k8s-ops/scripts/verify-k8s-ops.sh
Normal file
81
1-AgentSkills/implementing-k8s-ops/scripts/verify-k8s-ops.sh
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/bin/bash
|
||||
# verify-k8s-ops.sh - K8S操作实现验证脚本
|
||||
# 依赖: go 1.21+
|
||||
# 用法: ./verify-k8s-ops.sh [watchdog_dir]
|
||||
|
||||
set -e
|
||||
|
||||
WATCHDOG_DIR="${1:-./rmdc-watchdog}"
|
||||
|
||||
echo "=== K8S 操作实现验证 ==="
|
||||
echo "目标目录: $WATCHDOG_DIR"
|
||||
echo ""
|
||||
|
||||
# 1. 检查K8S客户端文件
|
||||
echo "[1/4] 检查K8S客户端..."
|
||||
K8S_CLIENT="$WATCHDOG_DIR/pkg/k8s/client.go"
|
||||
if [ -f "$K8S_CLIENT" ]; then
|
||||
echo "✓ K8S客户端文件存在: $K8S_CLIENT"
|
||||
else
|
||||
echo "✗ K8S客户端文件不存在"
|
||||
fi
|
||||
|
||||
# 2. 验证操作白名单
|
||||
echo ""
|
||||
echo "[2/4] 验证K8S操作白名单..."
|
||||
K8S_SERVICE="$WATCHDOG_DIR/internal/service/k8s_service.go"
|
||||
ALLOWED_ACTIONS="logs exec scale restart delete get apply"
|
||||
|
||||
if [ -f "$K8S_SERVICE" ]; then
|
||||
echo "检查 $K8S_SERVICE:"
|
||||
for action in $ALLOWED_ACTIONS; do
|
||||
if grep -q "case \"$action\"" "$K8S_SERVICE" 2>/dev/null; then
|
||||
echo " ✓ '$action' 已实现"
|
||||
else
|
||||
echo " ⚠ '$action' 未找到"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "⚠ K8S服务文件不存在: $K8S_SERVICE"
|
||||
fi
|
||||
|
||||
# 3. 验证超时处理
|
||||
echo ""
|
||||
echo "[3/4] 验证超时处理..."
|
||||
if grep -rq "context.WithTimeout\|WithDeadline" "$WATCHDOG_DIR/pkg/k8s/" 2>/dev/null; then
|
||||
echo "✓ 找到超时控制实现"
|
||||
else
|
||||
echo "⚠ 未找到超时控制,请确认K8S调用设置了timeout"
|
||||
fi
|
||||
|
||||
# 4. 验证结果结构
|
||||
echo ""
|
||||
echo "[4/4] 验证ExecResult结构..."
|
||||
if grep -rq "ExecResult" "$WATCHDOG_DIR" --include="*.go" 2>/dev/null; then
|
||||
echo "✓ 找到ExecResult定义"
|
||||
|
||||
# 检查必要字段
|
||||
REQUIRED_FIELDS="CommandID Status ExitCode Output Duration"
|
||||
for field in $REQUIRED_FIELDS; do
|
||||
if grep -rq "$field" "$WATCHDOG_DIR" --include="*.go" 2>/dev/null | grep -q "ExecResult\|exec_result"; then
|
||||
echo " ✓ 字段 '$field' 存在"
|
||||
else
|
||||
echo " ⚠ 字段 '$field' 未找到"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "⚠ 未找到ExecResult定义"
|
||||
fi
|
||||
|
||||
# 5. 运行测试
|
||||
echo ""
|
||||
echo "[5/5] 运行K8S相关测试..."
|
||||
cd "$WATCHDOG_DIR"
|
||||
if go test ./pkg/k8s/... -v 2>&1 | head -20; then
|
||||
echo "✓ K8S测试执行完成"
|
||||
else
|
||||
echo "⚠ K8S测试执行有问题"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== 验证完成 ==="
|
||||
108
1-AgentSkills/implementing-totp-auth/SKILL.md
Normal file
108
1-AgentSkills/implementing-totp-auth/SKILL.md
Normal file
@@ -0,0 +1,108 @@
|
||||
---
|
||||
name: implementing-totp-auth
|
||||
description: Guides implementation of TOTP-based two-tier authorization mechanism for RMDC system. Use when implementing authorization flows, modifying TOTP parameters, or debugging auth failures between project-management, watchdog, agent, and node components. Keywords: totp, authorization, tier-one, tier-two, security, authentication, hmac.
|
||||
argument-hint: "<scope>: tier-one | tier-two | dual-verify | auth-file"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
- Edit
|
||||
- Write
|
||||
---
|
||||
|
||||
# Implementing TOTP Authorization
|
||||
|
||||
RMDC采用双层TOTP授权机制保护项目环境安全。
|
||||
|
||||
## 动态上下文注入
|
||||
|
||||
```bash
|
||||
# 查找TOTP实现
|
||||
!`grep -rn "TOTP\|totp" rmdc-watchdog/pkg/totp/`
|
||||
|
||||
# 查找授权验证逻辑
|
||||
!`grep -n "Verify.*TOTP\|Generate.*TOTP" rmdc-watchdog/internal/service/auth_service.go`
|
||||
```
|
||||
|
||||
## Plan
|
||||
|
||||
根据 `$ARGUMENTS` 确定实现范围:
|
||||
|
||||
| Scope | 涉及组件 | 关键参数 |
|
||||
|-------|----------|----------|
|
||||
| tier-one | project-management ↔ watchdog | 8位/30分钟/SHA256 |
|
||||
| tier-two | watchdog ↔ agent/node | 6位/30秒/SHA1 |
|
||||
| dual-verify | 所有通信 | 请求TOTP + 响应TOTP |
|
||||
| auth-file | watchdog授权申请/解析 | 加密主机信息 + TOTP + namespace |
|
||||
|
||||
**产物清单**:
|
||||
- `pkg/totp/totp.go` - TOTP生成与验证
|
||||
- `internal/service/auth_service.go` - 授权服务
|
||||
- `internal/model/entity/auth_info.go` - 授权信息实体
|
||||
|
||||
## Verify
|
||||
|
||||
- [ ] Tier-One参数:8位码、30分钟有效、SHA256、AES-GCM加密
|
||||
- [ ] Tier-Two参数:6位码、30秒有效、SHA1
|
||||
- [ ] 双向验证:服务端响应必须包含TOTP供客户端校验
|
||||
- [ ] 时间戳校验:|now - timestamp| < 5分钟(可配置)
|
||||
- [ ] 密钥存储:tier_one_secret/tier_two_secret不通过公网传输
|
||||
- [ ] 授权文件包含:EncryptedHostMap, TOTPCode, EncryptedNamespace
|
||||
|
||||
```bash
|
||||
# 验证TOTP生成
|
||||
!`cd rmdc-watchdog && go test ./pkg/totp/... -v -run TestGenerate`
|
||||
|
||||
# 验证授权流程
|
||||
!`cd rmdc-watchdog && go test ./internal/service/... -v -run TestAuth`
|
||||
```
|
||||
|
||||
## Execute
|
||||
|
||||
### 实现Tier-One TOTP
|
||||
|
||||
```go
|
||||
// 8位,30分钟有效,SHA256
|
||||
func GenerateTierOneTOTP(secret string) string {
|
||||
return totp.Generate(secret, totp.Config{
|
||||
Digits: 8,
|
||||
Period: 1800, // 30分钟
|
||||
Algorithm: crypto.SHA256,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 实现Tier-Two TOTP
|
||||
|
||||
```go
|
||||
// 6位,30秒有效,SHA1
|
||||
func GenerateTierTwoTOTP(secret string) string {
|
||||
return totp.Generate(secret, totp.Config{
|
||||
Digits: 6,
|
||||
Period: 30,
|
||||
Algorithm: crypto.SHA1,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 实现双向验证
|
||||
|
||||
1. 客户端生成TOTP并发送请求
|
||||
2. 服务端验证客户端TOTP
|
||||
3. 服务端生成新TOTP并返回
|
||||
4. 客户端验证服务端TOTP
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **算法混淆**:Tier-One用SHA256,Tier-Two用SHA1,不可互换
|
||||
2. **有效期错配**:30分钟 vs 30秒,单位差异大
|
||||
3. **时间同步问题**:需配置 `time_offset_allowed` 容忍时钟偏差
|
||||
4. **单向验证漏洞**:缺少响应TOTP会导致中间人攻击风险
|
||||
5. **密钥泄露**:禁止在日志/错误信息中打印secret
|
||||
6. **首次连接处理**:TOTPCode为空时需返回密钥,后续请求必须验证
|
||||
|
||||
## Reference
|
||||
|
||||
- [TOTP层级说明](reference/totp-tiers.md)
|
||||
- [授权流程图](reference/auth-flow.md)
|
||||
72
1-AgentSkills/implementing-totp-auth/reference/auth-flow.md
Normal file
72
1-AgentSkills/implementing-totp-auth/reference/auth-flow.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# 授权流程图
|
||||
|
||||
## 项目注册流程
|
||||
|
||||
```
|
||||
1. 管理员在RMDC Portal创建项目
|
||||
2. project-management生成:
|
||||
- project_id = namespace_<8位随机数>
|
||||
- tier_one_secret
|
||||
- tier_two_secret
|
||||
- time_offset_allowed
|
||||
3. 返回项目配置文件给管理员
|
||||
4. 管理员部署watchdog时使用配置文件(密钥不经公网)
|
||||
```
|
||||
|
||||
## Watchdog注册流程
|
||||
|
||||
```
|
||||
1. Watchdog连接MQTT Broker
|
||||
2. 订阅: wdd/RDMC/command/down/<project_id>
|
||||
3. 订阅: wdd/RDMC/message/down/<project_id>
|
||||
4. 生成Tier-One TOTP验证码(8位,30分钟)
|
||||
5. 发布注册Command到 wdd/RDMC/command/up
|
||||
6. Exchange-Hub验证TOTP
|
||||
7. 生成32位随机挑战码
|
||||
8. 返回register_ack + 服务端TOTP
|
||||
9. Watchdog验证服务端TOTP
|
||||
10. 回复挑战码确认
|
||||
11. 注册完成,状态变为Online
|
||||
```
|
||||
|
||||
## 授权申请流程
|
||||
|
||||
```
|
||||
1. Node上报主机信息 → Watchdog
|
||||
2. Agent首次心跳 → Watchdog
|
||||
3. Watchdog收集完成后生成授权文件:
|
||||
- EncryptedHostMap
|
||||
- TOTPCode(8位,30分钟)
|
||||
- EncryptedNamespace
|
||||
4. 发布auth_request到MQTT
|
||||
5. Center验证TOTP和主机信息
|
||||
6. 返回授权码(加密)
|
||||
7. Watchdog解密并持久化
|
||||
```
|
||||
|
||||
## Agent心跳流程
|
||||
|
||||
```
|
||||
首次连接:
|
||||
1. Agent收集主机信息
|
||||
2. 发送心跳(TOTPCode="")
|
||||
3. Watchdog返回SecondTOTPSecret
|
||||
4. Agent保存密钥
|
||||
|
||||
后续心跳:
|
||||
1. Agent生成Tier-Two TOTP(6位,30秒)
|
||||
2. 发送心跳
|
||||
3. Watchdog验证TOTP
|
||||
4. 检查主机授权状态
|
||||
5. 返回{Authorized, ResponseTOTP}
|
||||
6. Agent验证ResponseTOTP
|
||||
7. 根据结果更新failCount
|
||||
```
|
||||
|
||||
## 死手系统触发
|
||||
|
||||
```
|
||||
failCount >= 12 时:
|
||||
1. Agent发送SIGTERM信号
|
||||
2. 业务进程优雅终止
|
||||
```
|
||||
53
1-AgentSkills/implementing-totp-auth/reference/totp-tiers.md
Normal file
53
1-AgentSkills/implementing-totp-auth/reference/totp-tiers.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# TOTP 层级定义
|
||||
|
||||
## Tier-One(一级授权)
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 通信方 | project-management ↔ watchdog |
|
||||
| 位数 | 8位 |
|
||||
| 有效期 | 30分钟 (1800秒) |
|
||||
| 算法 | SHA256 |
|
||||
| 加密 | AES-256-GCM |
|
||||
| 用途 | 项目注册、授权申请/下发 |
|
||||
|
||||
## Tier-Two(二级授权)
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 通信方 | watchdog ↔ agent/node |
|
||||
| 位数 | 6位 |
|
||||
| 有效期 | 30秒 |
|
||||
| 算法 | SHA1 |
|
||||
| 加密 | 无(内网通信) |
|
||||
| 用途 | 心跳验证、指令执行 |
|
||||
|
||||
## 双向验证流程
|
||||
|
||||
```
|
||||
1. 客户端生成TOTP → 发送请求
|
||||
2. 服务端验证客户端TOTP → 生成新TOTP返回
|
||||
3. 客户端验证服务端TOTP → 确认双方身份
|
||||
```
|
||||
|
||||
## 密钥管理
|
||||
|
||||
### 生成时机
|
||||
- tier_one_secret: project-management创建项目时生成
|
||||
- tier_two_secret: project-management创建项目时生成
|
||||
|
||||
### 传输方式
|
||||
- 通过项目配置文件离线部署到Watchdog
|
||||
- **禁止通过MQTT公网传输**
|
||||
|
||||
### 存储位置
|
||||
- project-management: 数据库加密存储
|
||||
- watchdog: 配置文件/环境变量
|
||||
- agent/node: 首次连接时从watchdog获取tier_two_secret
|
||||
|
||||
## 时间容忍度
|
||||
|
||||
| 配置项 | 默认值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| time_offset_allowed | 300秒 | 时间戳校验容忍度 |
|
||||
| totp_window | 1 | TOTP验证窗口(前后各1个周期) |
|
||||
@@ -0,0 +1,92 @@
|
||||
#!/bin/bash
|
||||
# verify-totp-impl.sh - TOTP实现验证脚本
|
||||
# 依赖: go 1.21+
|
||||
# 用法: ./verify-totp-impl.sh [project_dir]
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_DIR="${1:-.}"
|
||||
|
||||
echo "=== TOTP 实现验证 ==="
|
||||
echo "项目目录: $PROJECT_DIR"
|
||||
echo ""
|
||||
|
||||
# 1. 查找TOTP实现文件
|
||||
echo "[1/4] 查找TOTP实现..."
|
||||
TOTP_FILES=$(find "$PROJECT_DIR" -name "*.go" -exec grep -l "totp\|TOTP" {} \; 2>/dev/null || true)
|
||||
if [ -n "$TOTP_FILES" ]; then
|
||||
echo "找到TOTP相关文件:"
|
||||
echo "$TOTP_FILES" | head -10
|
||||
echo ""
|
||||
else
|
||||
echo "⚠ 未找到TOTP相关文件"
|
||||
fi
|
||||
|
||||
# 2. 验证Tier-One参数
|
||||
echo "[2/4] 验证Tier-One参数 (8位/30分钟/SHA256)..."
|
||||
TIER_ONE_OK=true
|
||||
|
||||
if grep -rq "8" "$PROJECT_DIR" --include="*.go" 2>/dev/null | grep -qi "digit\|Digit"; then
|
||||
echo "✓ 找到8位配置"
|
||||
else
|
||||
echo "⚠ 未明确找到8位配置"
|
||||
TIER_ONE_OK=false
|
||||
fi
|
||||
|
||||
if grep -rq "1800\|30.*[Mm]in" "$PROJECT_DIR" --include="*.go" 2>/dev/null; then
|
||||
echo "✓ 找到30分钟有效期配置"
|
||||
else
|
||||
echo "⚠ 未找到30分钟有效期配置"
|
||||
TIER_ONE_OK=false
|
||||
fi
|
||||
|
||||
if grep -rq "SHA256\|sha256" "$PROJECT_DIR" --include="*.go" 2>/dev/null; then
|
||||
echo "✓ 找到SHA256算法"
|
||||
else
|
||||
echo "⚠ 未找到SHA256算法"
|
||||
TIER_ONE_OK=false
|
||||
fi
|
||||
|
||||
# 3. 验证Tier-Two参数
|
||||
echo ""
|
||||
echo "[3/4] 验证Tier-Two参数 (6位/30秒/SHA1)..."
|
||||
TIER_TWO_OK=true
|
||||
|
||||
if grep -rq "6" "$PROJECT_DIR" --include="*.go" 2>/dev/null | grep -qi "digit\|Digit"; then
|
||||
echo "✓ 找到6位配置"
|
||||
else
|
||||
echo "⚠ 未明确找到6位配置"
|
||||
TIER_TWO_OK=false
|
||||
fi
|
||||
|
||||
if grep -rq "30" "$PROJECT_DIR" --include="*.go" 2>/dev/null | grep -qi "period\|second\|Period\|Second"; then
|
||||
echo "✓ 找到30秒有效期配置"
|
||||
else
|
||||
echo "⚠ 未找到30秒有效期配置"
|
||||
TIER_TWO_OK=false
|
||||
fi
|
||||
|
||||
if grep -rq "SHA1\|sha1" "$PROJECT_DIR" --include="*.go" 2>/dev/null; then
|
||||
echo "✓ 找到SHA1算法"
|
||||
else
|
||||
echo "⚠ 未找到SHA1算法"
|
||||
TIER_TWO_OK=false
|
||||
fi
|
||||
|
||||
# 4. 验证双向验证
|
||||
echo ""
|
||||
echo "[4/4] 验证双向验证实现..."
|
||||
if grep -rq "response.*[Tt]otp\|[Tt]otp.*response" "$PROJECT_DIR" --include="*.go" 2>/dev/null; then
|
||||
echo "✓ 找到响应TOTP相关代码"
|
||||
else
|
||||
echo "⚠ 未找到响应TOTP实现,请确认双向验证"
|
||||
fi
|
||||
|
||||
# 总结
|
||||
echo ""
|
||||
echo "=== 验证总结 ==="
|
||||
if [ "$TIER_ONE_OK" = true ] && [ "$TIER_TWO_OK" = true ]; then
|
||||
echo "✓ TOTP参数配置基本正确"
|
||||
else
|
||||
echo "⚠ 部分TOTP参数需要确认"
|
||||
fi
|
||||
332
1-Golang项目/3-api-development-prompt.md
Normal file
332
1-Golang项目/3-api-development-prompt.md
Normal file
@@ -0,0 +1,332 @@
|
||||
# RMDC API开发规范提示词
|
||||
|
||||
---
|
||||
|
||||
## 设计原则
|
||||
|
||||
### 1. 使用POST + RequestBody
|
||||
|
||||
> **核心规范**: 所有API优先使用POST方法,参数通过RequestBody传递
|
||||
|
||||
```go
|
||||
// ✅ 推荐方式
|
||||
POST /api/jenkins/builds/list
|
||||
{
|
||||
"organization_folder": "Backend",
|
||||
"repository_name": "cmii-fly-center",
|
||||
"branch_name": "master",
|
||||
"page": 1,
|
||||
"page_size": 10
|
||||
}
|
||||
|
||||
// ❌ 避免使用
|
||||
GET /api/jenkins/organizations/{org}/repositories/{repo}/branches/{branch}/builds?page=1&page_size=10
|
||||
```
|
||||
|
||||
### 2. 避免PathVariables
|
||||
|
||||
```go
|
||||
// ❌ 不推荐
|
||||
GET /api/projects/{project_id}
|
||||
GET /api/builds/{build_id}/console
|
||||
|
||||
// ✅ 推荐
|
||||
POST /api/projects/detail
|
||||
{
|
||||
"project_id": "namespace_abc12345"
|
||||
}
|
||||
|
||||
POST /api/builds/console
|
||||
{
|
||||
"organization_folder": "Backend",
|
||||
"repository_name": "cmii-fly-center",
|
||||
"branch_name": "master",
|
||||
"build_number": 123
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 避免RequestParams
|
||||
|
||||
```go
|
||||
// ❌ 不推荐
|
||||
GET /api/users/list?role=admin&status=active&page=1
|
||||
|
||||
// ✅ 推荐
|
||||
POST /api/users/list
|
||||
{
|
||||
"role": "admin",
|
||||
"status": "active",
|
||||
"page": 1,
|
||||
"page_size": 20
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 统一响应格式
|
||||
|
||||
### 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
// 业务数据
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 分页响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"list": [...],
|
||||
"total": 100,
|
||||
"page": 1,
|
||||
"page_size": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 1001,
|
||||
"message": "参数错误: organization_folder不能为空",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 请求结构规范
|
||||
|
||||
### 通用分页请求
|
||||
|
||||
```go
|
||||
type PageRequest struct {
|
||||
Page int `json:"page" binding:"required,min=1"`
|
||||
PageSize int `json:"page_size" binding:"required,min=1,max=100"`
|
||||
}
|
||||
```
|
||||
|
||||
### 通用筛选请求
|
||||
|
||||
```go
|
||||
type ListRequest struct {
|
||||
PageRequest
|
||||
Keyword string `json:"keyword,omitempty"` // 搜索关键词
|
||||
Status string `json:"status,omitempty"` // 状态筛选
|
||||
SortBy string `json:"sort_by,omitempty"` // 排序字段
|
||||
SortOrder string `json:"sort_order,omitempty"` // asc/desc
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API命名规范
|
||||
|
||||
### 操作类型后缀
|
||||
|
||||
| 操作 | 后缀 | 示例 |
|
||||
|------|------|------|
|
||||
| 列表查询 | `/list` | `/api/projects/list` |
|
||||
| 详情查询 | `/detail` | `/api/projects/detail` |
|
||||
| 创建 | `/create` | `/api/projects/create` |
|
||||
| 更新 | `/update` | `/api/projects/update` |
|
||||
| 删除 | `/delete` | `/api/projects/delete` |
|
||||
| 同步 | `/sync` | `/api/jenkins/organizations/sync` |
|
||||
| 触发 | `/trigger` | `/api/builds/trigger` |
|
||||
| 导出 | `/export` | `/api/projects/export` |
|
||||
|
||||
### 模块前缀
|
||||
|
||||
| 模块 | 前缀 |
|
||||
|------|------|
|
||||
| Jenkins | `/api/jenkins/` |
|
||||
| 项目管理 | `/api/projects/` |
|
||||
| 用户 | `/api/users/` |
|
||||
| 权限 | `/api/permissions/` |
|
||||
| 审计 | `/api/audit/` |
|
||||
| Exchange-Hub | `/api/exchange-hub/` |
|
||||
| DCU | `/api/dcu/` |
|
||||
|
||||
---
|
||||
|
||||
## Handler实现模板
|
||||
|
||||
```go
|
||||
// ListBuilds 获取构建列表
|
||||
// @Summary 获取构建列表
|
||||
// @Tags 构建管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body dto.ListBuildsRequest true "请求参数"
|
||||
// @Success 200 {object} response.Response{data=dto.ListBuildsResponse}
|
||||
// @Router /api/jenkins/builds/list [post]
|
||||
func (h *BuildHandler) ListBuilds(c *gin.Context) {
|
||||
var req dto.ListBuildsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ParamError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.buildService.ListBuilds(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, resp)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DTO设计规范
|
||||
|
||||
### 请求DTO命名
|
||||
|
||||
```go
|
||||
// 列表请求: List{资源}Request
|
||||
type ListBuildsRequest struct {
|
||||
PageRequest
|
||||
OrganizationFolder string `json:"organization_folder" binding:"required"`
|
||||
RepositoryName string `json:"repository_name" binding:"required"`
|
||||
BranchName string `json:"branch_name,omitempty"`
|
||||
}
|
||||
|
||||
// 详情请求: Get{资源}Request 或 {资源}DetailRequest
|
||||
type GetBuildRequest struct {
|
||||
OrganizationFolder string `json:"organization_folder" binding:"required"`
|
||||
RepositoryName string `json:"repository_name" binding:"required"`
|
||||
BranchName string `json:"branch_name" binding:"required"`
|
||||
BuildNumber int `json:"build_number" binding:"required"`
|
||||
}
|
||||
|
||||
// 创建请求: Create{资源}Request
|
||||
type CreateProjectRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Namespace string `json:"namespace" binding:"required"`
|
||||
Province string `json:"province" binding:"required"`
|
||||
City string `json:"city" binding:"required"`
|
||||
}
|
||||
|
||||
// 更新请求: Update{资源}Request
|
||||
type UpdateProjectRequest struct {
|
||||
ProjectID string `json:"project_id" binding:"required"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Province string `json:"province,omitempty"`
|
||||
City string `json:"city,omitempty"`
|
||||
}
|
||||
|
||||
// 删除请求: Delete{资源}Request
|
||||
type DeleteProjectRequest struct {
|
||||
ProjectID string `json:"project_id" binding:"required"`
|
||||
}
|
||||
```
|
||||
|
||||
### 响应DTO命名
|
||||
|
||||
```go
|
||||
// 列表响应: List{资源}Response
|
||||
type ListBuildsResponse struct {
|
||||
List []*BuildDTO `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
// 详情响应: {资源}DetailResponse 或直接使用 {资源}DTO
|
||||
type BuildDetailResponse struct {
|
||||
*BuildDTO
|
||||
ConsoleOutput string `json:"console_output,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 错误码规范
|
||||
|
||||
### 错误码范围
|
||||
|
||||
| 范围 | 模块 |
|
||||
|------|------|
|
||||
| 1000-1999 | 通用错误 |
|
||||
| 2000-2999 | 用户/权限 |
|
||||
| 3000-3999 | Jenkins模块 |
|
||||
| 4000-4999 | 项目管理 |
|
||||
| 5000-5999 | Exchange-Hub |
|
||||
| 6000-6999 | Watchdog |
|
||||
|
||||
### 通用错误码
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| 0 | 成功 |
|
||||
| 1001 | 参数错误 |
|
||||
| 1002 | 未授权 |
|
||||
| 1003 | 禁止访问 |
|
||||
| 1004 | 资源不存在 |
|
||||
| 1005 | 内部错误 |
|
||||
|
||||
---
|
||||
|
||||
## 前端调用示例
|
||||
|
||||
```typescript
|
||||
// api/modules/jenkins.ts
|
||||
export const jenkinsApi = {
|
||||
// 获取构建列表
|
||||
listBuilds: (data: ListBuildsRequest) =>
|
||||
request.post<ListBuildsResponse>('/api/jenkins/builds/list', data),
|
||||
|
||||
// 触发构建
|
||||
triggerBuild: (data: TriggerBuildRequest) =>
|
||||
request.post<TriggerBuildResponse>('/api/jenkins/builds/trigger', data),
|
||||
|
||||
// 获取构建详情
|
||||
getBuildDetail: (data: GetBuildRequest) =>
|
||||
request.post<BuildDetailResponse>('/api/jenkins/builds/detail', data),
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 安全规范
|
||||
|
||||
### 1. 敏感字段不出现在URL
|
||||
|
||||
```go
|
||||
// ❌ 敏感信息泄露到URL
|
||||
GET /api/auth/login?username=admin&password=123456
|
||||
|
||||
// ✅ 使用RequestBody
|
||||
POST /api/auth/login
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "123456"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 必须验证请求体
|
||||
|
||||
```go
|
||||
func (h *Handler) CreateProject(c *gin.Context) {
|
||||
var req dto.CreateProjectRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ParamError(c, err)
|
||||
return
|
||||
}
|
||||
// 后续处理...
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 审计敏感操作
|
||||
|
||||
所有写操作需通过审计中间件记录。
|
||||
@@ -1,43 +1,43 @@
|
||||
``
|
||||
**1. 核心角色与指令 (Core Persona & Directive)**
|
||||
|
||||
你将扮演一个拥有10年以上经验的Golang后端架构师。你不仅精通Golang语言特性(特别是并发模型和标准库),更是GIN、GORM等主流框架的资深用户。你的代码风格严谨、可读性强,并且极度重视项目结构、模块化、日志规范和文档注释。
|
||||
你将扮演一个拥有10年以上经验的Golang后端架构师。你不仅精通Golang语言特性(特别是并发模型和标准库),更是GIN、GORM等主流框架的资深用户。你的代码风格严谨、可读性强,并且极度重视项目结构、模块化、日志规范和文档注释。
|
||||
|
||||
**所有后续的代码生成、重构或审查请求,都必须严格遵循以下规范。**
|
||||
**所有后续的代码生成、重构或审查请求,都必须严格遵循以下规范。**
|
||||
|
||||
-----
|
||||
|
||||
**2. 核心开发哲学 (Core Development Philosophy)**
|
||||
|
||||
* **清晰胜于炫技 (Clarity over Cleverness):** 代码首先是写给人看的,其次才是给机器执行的。优先保证代码的逻辑清晰和易于理解。
|
||||
* **清晰胜于炫技 (Clarity over Cleverness):** 代码首先是写给人看的,其次才是给机器执行的。优先保证代码的逻辑清晰和易于理解。
|
||||
* **遵循官方标准 (Follow Official Standards):** 严格遵守官方 [Effective Go](https://go.dev/doc/effective_go) 和 [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) 中的建议。
|
||||
* **高内聚,低耦合 (High Cohesion, Low Coupling):** 模块功能要单一、明确。减少模块间的直接依赖,优先通过接口进行交互。
|
||||
* **高内聚,低耦合 (High Cohesion, Low Coupling):** 模块功能要单一、明确。减少模块间的直接依赖,优先通过接口进行交互。
|
||||
|
||||
-----
|
||||
|
||||
**3. 项目结构与模块化 (Project Structure & Modularity)**
|
||||
|
||||
项目采用分层架构,并强调模块化开发,确保职责分离和代码复用。
|
||||
项目采用分层架构,并强调模块化开发,确保职责分离和代码复用。
|
||||
|
||||
**3.1. 核心目录结构 (Core Directory Structure)**
|
||||
|
||||
* **/api (或 /internal/handler):** 存放GIN的Handler层。负责解析HTTP请求、校验参数,并调用`service`层处理业务逻辑。**严禁在这一层编写核心业务逻辑。**
|
||||
* **/internal/service:** 业务逻辑核心层。编排`dao`层或其他服务,完成具体的业务功能。
|
||||
* **/internal/dao (或 /internal/repository):** 数据访问层。封装对GORM的操作,与数据库直接交互。所有SQL/GORM查询都应在此层实现。
|
||||
* **/api (或 /internal/handler):** 存放GIN的Handler层。负责解析HTTP请求、校验参数,并调用`service`层处理业务逻辑。**严禁在这一层编写核心业务逻辑。**
|
||||
* **/internal/service:** 业务逻辑核心层。编排`dao`层或其他服务,完成具体的业务功能。
|
||||
* **/internal/dao (或 /internal/repository):** 数据访问层。封装对GORM的操作,与数据库直接交互。所有SQL/GORM查询都应在此层实现。
|
||||
* **/internal/model:** 数据模型层。
|
||||
* `entity` (或 `po`): 数据库表结构对应的持久化对象 (Persistence Object)。
|
||||
* `dto` (或 `vo`): 用于API层数据传输的对象 (Data Transfer Object / View Object),如请求的Body和返回的JSON。
|
||||
* **/pkg (或 /internal/utils):** 存放项目内可复用的公共工具类,例如时间处理、字符串转换等。
|
||||
* `entity` (或 `po`): 数据库表结构对应的持久化对象 (Persistence Object)。
|
||||
* `dto` (或 `vo`): 用于API层数据传输的对象 (Data Transfer Object / View Object),如请求的Body和返回的JSON。
|
||||
* **/pkg/common (或 /internal/common):** 存放项目内公共组件,包括**统一响应封装**、错误码定义、通用工具类等。
|
||||
* **/pkg (或 /internal/utils):** 存放项目内可复用的公共工具类,例如时间处理、字符串转换等。
|
||||
* **/configs:** 存放项目配置文件 (e.g., `config.yaml`)。
|
||||
* **/cmd:** 项目启动入口,包含 `main.go` 文件。
|
||||
* **/cmd:** 项目启动入口,包含 `main.go` 文件。
|
||||
|
||||
**3.2. 模块依赖与引用 (Module Dependencies & Referencing)**
|
||||
|
||||
* **依赖原则 (Dependency Rule):** 依赖关系必须是单向的:`api` -\> `service` -\> `dao`。**严禁反向或跨层依赖**(例如,`dao`层不能引用`service`层)。公共工具库 (`/pkg`或`/internal/utils`) 可以被任何层级引用。
|
||||
* **依赖原则 (Dependency Rule):** 依赖关系必须是单向的:`api` -> `service` -> `dao`。**严禁反向或跨层依赖**(例如,`dao`层不能引用`service`层)。公共组件库 (`/pkg/common`) 和工具库 (`/pkg`或`/internal/utils`) 可以被任何层级引用。
|
||||
|
||||
* **内部模块引用 (Internal Module Referencing):**
|
||||
|
||||
* 对于项目内部的模块化开发(例如,将公共组件库 `TonyCommon` 作为独立模块),在主项目的 `go.mod` 文件中,**必须使用 `replace` 语法**来指定其本地路径。这确保了在开发过程中,主项目总是引用本地最新版本的内部模块,而不是远程仓库的版本。
|
||||
* 对于项目内部的模块化开发(例如,将公共组件库 `TonyCommon` 作为独立模块),在主项目的 `go.mod` 文件中,**必须使用 `replace` 语法**来指定其本地路径。这确保了在开发过程中,主项目总是引用本地最新版本的内部模块,而不是远程仓库的版本。
|
||||
|
||||
*示例 (`go.mod`):*
|
||||
|
||||
@@ -57,24 +57,260 @@
|
||||
|
||||
-----
|
||||
|
||||
**4. 编码规范 (Coding Standards)**
|
||||
**4. API响应规范 (API Response Standards)**
|
||||
|
||||
**4.1. 命名规范 (Naming Conventions)**
|
||||
**4.1. 统一响应结构 (Unified Response Structure)**
|
||||
|
||||
* **包名 (Package):** 使用简短、小写、有意义的单词,不使用下划线或驼峰。 (e.g., `service`, `utils`)
|
||||
* **变量/函数/结构体 (Variables/Functions/Structs):** 遵循Go的驼峰命名法。首字母大写表示公开 (Public),首字母小写表示私有 (Private)。
|
||||
所有API接口**必须**使用统一的响应格式,确保前后端协作的一致性和可预测性。响应结构定义在 `/pkg/common/response.go` 中。
|
||||
|
||||
**4.1.1. 响应结构体定义**
|
||||
|
||||
```go
|
||||
// Response 统一响应结构
|
||||
// 与前端约定的标准API响应格式,包含业务状态码、HTTP状态码、时间戳、数据和消息
|
||||
type Response struct {
|
||||
Code int `json:"code"` // 业务状态码,0表示成功,非0表示各类错误
|
||||
Status int `json:"status"` // HTTP状态码,与HTTP响应状态码保持一致
|
||||
Timestamp string `json:"timestamp"` // 响应时间戳,格式为RFC3339 (东八区)
|
||||
Data interface{} `json:"data"` // 响应数据,成功时包含具体业务数据,失败时为nil
|
||||
Message string `json:"message,omitempty"` // 响应消息,成功时可选,失败时必填
|
||||
Error string `json:"error,omitempty"` // 错误详情,仅在发生错误时填充
|
||||
}
|
||||
```
|
||||
|
||||
**4.1.2. 错误码定义 (Error Code Definitions)**
|
||||
|
||||
错误码采用前后端统一的定义,存放在 `/pkg/common/code.go` 中:
|
||||
|
||||
```go
|
||||
// 业务状态码常量
|
||||
const (
|
||||
CodeSuccess = 0 // 成功
|
||||
CodeServerError = 10001 // 服务器内部错误
|
||||
CodeParamError = 10002 // 参数错误
|
||||
CodeUnauthorized = 10003 // 未授权
|
||||
CodeForbidden = 10004 // 禁止访问
|
||||
CodeNotFound = 10005 // 请求的数据不存在
|
||||
CodeTimeout = 10006 // 请求超时
|
||||
CodeValidationFail = 10007 // 验证失败
|
||||
CodeBusiness = 20001 // 业务逻辑错误 (可根据具体业务细分20001-29999)
|
||||
)
|
||||
|
||||
// CodeMessage 错误码与错误消息的映射
|
||||
var CodeMessage = map[int]string{
|
||||
CodeSuccess: "success",
|
||||
CodeServerError: "服务器内部错误",
|
||||
CodeParamError: "参数错误",
|
||||
CodeUnauthorized: "未授权,请先登录",
|
||||
CodeForbidden: "权限不足,禁止访问",
|
||||
CodeNotFound: "请求的资源不存在",
|
||||
CodeTimeout: "请求超时",
|
||||
CodeValidationFail: "数据验证失败",
|
||||
CodeBusiness: "业务处理失败",
|
||||
}
|
||||
|
||||
// GetMessage 根据错误码获取默认错误消息
|
||||
func GetMessage(code int) string {
|
||||
if msg, ok := CodeMessage[code]; ok {
|
||||
return msg
|
||||
}
|
||||
return "未知错误"
|
||||
}
|
||||
```
|
||||
|
||||
**4.1.3. 响应封装函数**
|
||||
|
||||
在 `/pkg/common/response.go` 中实现标准化的响应函数:
|
||||
|
||||
```go
|
||||
// ResponseSuccess 成功响应
|
||||
// @param c *gin.Context - GIN上下文
|
||||
// @param data interface{} - 要返回的业务数据
|
||||
func ResponseSuccess(c *gin.Context, data interface{}) {
|
||||
c.JSON(http.StatusOK, Response{
|
||||
Code: CodeSuccess,
|
||||
Status: http.StatusOK,
|
||||
Timestamp: TimeUtils.Now().Format(time.RFC3339), // 使用统一时间工具
|
||||
Data: data,
|
||||
Message: "success",
|
||||
})
|
||||
}
|
||||
|
||||
// ResponseSuccessWithMessage 成功响应(自定义消息)
|
||||
// @param c *gin.Context - GIN上下文
|
||||
// @param data interface{} - 要返回的业务数据
|
||||
// @param message string - 自定义成功消息
|
||||
func ResponseSuccessWithMessage(c *gin.Context, data interface{}, message string) {
|
||||
c.JSON(http.StatusOK, Response{
|
||||
Code: CodeSuccess,
|
||||
Status: http.StatusOK,
|
||||
Timestamp: TimeUtils.Now().Format(time.RFC3339),
|
||||
Data: data,
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
// ResponseError 错误响应
|
||||
// @param c *gin.Context - GIN上下文
|
||||
// @param code int - 业务错误码
|
||||
// @param message string - 错误消息,如为空则使用默认消息
|
||||
func ResponseError(c *gin.Context, code int, message string) {
|
||||
httpStatus := codeToHTTPStatus(code)
|
||||
if message == "" {
|
||||
message = GetMessage(code)
|
||||
}
|
||||
|
||||
c.JSON(httpStatus, Response{
|
||||
Code: code,
|
||||
Status: httpStatus,
|
||||
Timestamp: TimeUtils.Now().Format(time.RFC3339),
|
||||
Data: nil,
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
// ResponseErrorWithDetail 错误响应(包含详细错误信息)
|
||||
// @param c *gin.Context - GIN上下文
|
||||
// @param code int - 业务错误码
|
||||
// @param message string - 错误消息
|
||||
// @param err error - 原始错误对象,用于记录详细堆栈
|
||||
func ResponseErrorWithDetail(c *gin.Context, code int, message string, err error) {
|
||||
httpStatus := codeToHTTPStatus(code)
|
||||
if message == "" {
|
||||
message = GetMessage(code)
|
||||
}
|
||||
|
||||
errorDetail := ""
|
||||
if err != nil {
|
||||
errorDetail = err.Error()
|
||||
// 记录详细错误日志
|
||||
log.Error(c, "API错误", map[string]interface{}{
|
||||
"code": code,
|
||||
"message": message,
|
||||
"error": errorDetail,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(httpStatus, Response{
|
||||
Code: code,
|
||||
Status: httpStatus,
|
||||
Timestamp: TimeUtils.Now().Format(time.RFC3339),
|
||||
Data: nil,
|
||||
Message: message,
|
||||
Error: errorDetail,
|
||||
})
|
||||
}
|
||||
|
||||
// codeToHTTPStatus 将业务错误码映射为HTTP状态码
|
||||
func codeToHTTPStatus(code int) int {
|
||||
switch code {
|
||||
case CodeSuccess:
|
||||
return http.StatusOK
|
||||
case CodeParamError, CodeValidationFail:
|
||||
return http.StatusBadRequest
|
||||
case CodeUnauthorized:
|
||||
return http.StatusUnauthorized
|
||||
case CodeForbidden:
|
||||
return http.StatusForbidden
|
||||
case CodeNotFound:
|
||||
return http.StatusNotFound
|
||||
case CodeTimeout:
|
||||
return http.StatusRequestTimeout
|
||||
default:
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**4.2. 使用规范 (Usage Guidelines)**
|
||||
|
||||
**4.2.1. Handler层调用示例**
|
||||
|
||||
```go
|
||||
// GetUserByID 根据ID获取用户信息
|
||||
func (h *UserHandler) GetUserByID(c *gin.Context) {
|
||||
// 1. 参数解析与验证
|
||||
idStr := c.Param("id")
|
||||
userID, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil {
|
||||
common.ResponseError(c, common.CodeParamError, "用户ID格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 调用Service层
|
||||
user, err := h.userService.GetUserByID(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
common.ResponseError(c, common.CodeNotFound, "用户不存在")
|
||||
return
|
||||
}
|
||||
common.ResponseErrorWithDetail(c, common.CodeServerError, "获取用户信息失败", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 成功响应
|
||||
common.ResponseSuccess(c, user)
|
||||
}
|
||||
|
||||
// CreateUser 创建用户
|
||||
func (h *UserHandler) CreateUser(c *gin.Context) {
|
||||
var req dto.CreateUserRequest
|
||||
|
||||
// 参数绑定与验证
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
common.ResponseErrorWithDetail(c, common.CodeValidationFail, "请求参数验证失败", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 业务处理
|
||||
user, err := h.userService.CreateUser(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
common.ResponseErrorWithDetail(c, common.CodeBusiness, "创建用户失败", err)
|
||||
return
|
||||
}
|
||||
|
||||
common.ResponseSuccessWithMessage(c, user, "用户创建成功")
|
||||
}
|
||||
```
|
||||
|
||||
**4.2.2. 响应使用原则**
|
||||
|
||||
* **成功场景:**
|
||||
* 简单查询、列表获取使用 `ResponseSuccess(c, data)`
|
||||
* 创建、更新、删除等操作使用 `ResponseSuccessWithMessage(c, data, "操作成功")`
|
||||
|
||||
* **失败场景:**
|
||||
* 参数验证失败使用 `CodeParamError` 或 `CodeValidationFail`
|
||||
* 资源不存在使用 `CodeNotFound`
|
||||
* 权限问题使用 `CodeUnauthorized` 或 `CodeForbidden`
|
||||
* 业务逻辑错误使用 `CodeBusiness`,并附带详细的错误消息
|
||||
* 不可预知的系统错误使用 `CodeServerError`,必须记录完整日志
|
||||
|
||||
* **日志记录原则:**
|
||||
* 所有使用 `ResponseErrorWithDetail` 的场景会自动记录Error级别日志
|
||||
* 业务逻辑错误在Service层就应该记录Warning级别日志
|
||||
* 成功的关键业务操作(如创建订单、支付)应在Service层记录Info级别日志
|
||||
|
||||
-----
|
||||
|
||||
**5. 编码规范 (Coding Standards)**
|
||||
|
||||
**5.1. 命名规范 (Naming Conventions)**
|
||||
|
||||
* **包名 (Package):** 使用简短、小写、有意义的单词,不使用下划线或驼峰。 (e.g., `service`, `utils`, `common`)
|
||||
* **变量/函数/结构体 (Variables/Functions/Structs):** 遵循Go的驼峰命名法。首字母大写表示公开 (Public),首字母小写表示私有 (Private)。
|
||||
* **接口 (Interfaces):** 单一方法的接口名以 `er` 结尾 (e.g., `Reader`, `Writer`)。
|
||||
|
||||
**4.2. 注释规范 (Commenting Standards)**
|
||||
**5.2. 注释规范 (Commenting Standards)**
|
||||
|
||||
**所有公开的(Public)函数、方法、结构体和接口都必须有注释。** 注释必须为**中文**。
|
||||
|
||||
* **函数/方法注释:**
|
||||
|
||||
* 必须在函数/方法声明上方,以 `// 函数名 ...` 开始。
|
||||
* 清晰地描述函数的功能。
|
||||
* 详细说明每个参数的含义和用途。
|
||||
* 详细说明每个返回值的含义,特别是`error`的返回时机。
|
||||
* 必须在函数/方法声明上方,以 `// 函数名 ...` 开始。
|
||||
* 清晰地描述函数的功能。
|
||||
* 详细说明每个参数的含义和用途。
|
||||
* 详细说明每个返回值的含义,特别是`error`的返回时机。
|
||||
|
||||
*示例:*
|
||||
|
||||
@@ -82,7 +318,7 @@
|
||||
// GetUserInfoByID 根据用户ID获取用户信息
|
||||
// @param ctx context.Context - 请求上下文
|
||||
// @param userID int64 - 用户唯一ID
|
||||
// @return *model.User - 用户信息实体,如果找不到则返回nil
|
||||
// @return *model.User - 用户信息实体,如果找不到则返回nil
|
||||
// @return error - 查询过程中发生的任何错误
|
||||
func (s *UserService) GetUserInfoByID(ctx context.Context, userID int64) (*model.User, error) {
|
||||
// ... 实现代码 ...
|
||||
@@ -91,57 +327,66 @@
|
||||
|
||||
* **关键逻辑注释:**
|
||||
|
||||
* 在复杂的业务逻辑、算法或可能引起歧义的代码块上方,添加简要的中文注释,解释其目的和实现思路。
|
||||
* 在复杂的业务逻辑、算法或可能引起歧义的代码块上方,添加简要的中文注释,解释其目的和实现思路。
|
||||
|
||||
*示例:*
|
||||
|
||||
```go
|
||||
// 关键步骤:此处需要加分布式锁,防止并发条件下库存超卖
|
||||
// 关键步骤:此处需要加分布式锁,防止并发条件下库存超卖
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
```
|
||||
|
||||
**4.3. 错误处理 (Error Handling)**
|
||||
**5.3. 错误处理 (Error Handling)**
|
||||
|
||||
* 严格遵守 `if err != nil` 的标准错误处理模式。
|
||||
* 错误信息应包含足够的上下文,使用 `fmt.Errorf` 或 `errors.Wrap` 进行包装,方便问题追溯。**禁止直接丢弃(`_`)非预期的error。**
|
||||
* 错误信息应包含足够的上下文,使用 `fmt.Errorf` 或 `errors.Wrap` 进行包装,方便问题追溯。**禁止直接丢弃(`_`)非预期的error。**
|
||||
* 在Handler层捕获错误后,**必须**通过统一响应函数返回,不能直接panic或忽略。
|
||||
|
||||
-----
|
||||
|
||||
**5. 日志规范 (Logging Standards)**
|
||||
**6. 日志规范 (Logging Standards)**
|
||||
|
||||
* **指定框架:** 项目统一使用内部日志库 `TonyCommon/wdd_log/SimpleLog.go`。
|
||||
* **日志内容:** 日志信息必须**简练、关键**,并包含追溯问题所需的上下文信息(如`TraceID`, `UserID`等)。
|
||||
* **指定框架:** 项目统一使用内部日志库 `rmdc-common/wdd_log/log_utils.go`。
|
||||
* **日志内容:** 日志信息必须**简练、关键**,并包含追溯问题所需的上下文信息(如`TraceID`, `UserID`等)。
|
||||
* **日志级别:**
|
||||
* `Debug`: 用于开发调试,记录程序执行流程、变量值等详细信息。**这是默认的开发日志级别。**
|
||||
* `Info`: 用于记录关键的业务操作节点,例如“用户登录成功”、“订单创建成功”。
|
||||
* `Warning`: 用于记录可预期的、非致命的异常情况,程序仍可继续运行。例如:“某个外部API调用超时,已启用备用方案”。
|
||||
* `Error`: 用于记录严重错误,导致当前业务流程无法继续的场景。例如:“数据库连接失败”、“关键参数校验失败”。必须详细记录错误信息和堆栈。
|
||||
* `Debug`: 用于开发调试,记录程序执行流程、变量值等详细信息。**这是默认的开发日志级别。**
|
||||
* `Info`: 用于记录关键的业务操作节点,例如"用户登录成功"、"订单创建成功"。
|
||||
* `Warning`: 用于记录可预期的、非致命的异常情况,程序仍可继续运行。例如:"某个外部API调用超时,已启用备用方案"。
|
||||
* `Error`: 用于记录严重错误,导致当前业务流程无法继续的场景。例如:"数据库连接失败"、"关键参数校验失败"。必须详细记录错误信息和堆栈。
|
||||
|
||||
-----
|
||||
|
||||
**6. 时间处理 (Time Handling)**
|
||||
**7. 时间处理 (Time Handling)**
|
||||
|
||||
* **统一时区:** 所有在前端和后端之间传输、以及在数据库中存储的时间,**必须统一为东八区时间 (Asia/Shanghai, UTC+8)**。
|
||||
* **统一时区:** 所有在前端和后端之间传输、以及在数据库中存储的时间,**必须统一为东八区时间 (Asia/Shanghai, UTC+8)**。
|
||||
* **指定工具库:**
|
||||
* 所有时间的生成、解析、格式化操作,**必须**使用项目公共库 `TonyCommon/utils/TimeUtils.go` 中提供的方法。
|
||||
* 与前端交互时,遵循 `TonyMask/src/utils/timeUtils.ts` 的格式化约定。
|
||||
* **禁止直接使用 `time.Now()`** 进行业务时间的赋值,应通过 `TimeUtils.go` 的封装方法获取,以确保时区统一。
|
||||
* 所有时间的生成、解析、格式化操作,**必须**使用项目公共库 `rmdc-common/utils/TimeUtils.go` 中提供的方法。
|
||||
* API响应中的 `timestamp` 字段统一使用 `RFC3339` 格式。
|
||||
* 与前端交互时,遵循 `TonyMask/src/utils/timeUtils.ts` 的格式化约定。
|
||||
* **禁止直接使用 `time.Now()`** 进行业务时间的赋值,应通过 `TimeUtils.go` 的封装方法获取,以确保时区统一。
|
||||
|
||||
-----
|
||||
|
||||
**7. 框架使用要点 (Framework Usage)**
|
||||
**8. 框架使用要点 (Framework Usage)**
|
||||
|
||||
* **GIN:**
|
||||
* 使用分组路由(`Router Group`)来组织API。
|
||||
* 使用中间件(`Middleware`)处理通用逻辑,如认证、日志、恢复(Recovery)。
|
||||
* 使用分组路由(`Router Group`)来组织API。
|
||||
* 使用中间件(`Middleware`)处理通用逻辑,如认证、日志、恢复(Recovery)、跨域(CORS)。
|
||||
* 所有API响应**必须**通过 `pkg/common` 中的统一响应函数返回。
|
||||
|
||||
* **GORM:**
|
||||
* 严禁在`service`层拼接复杂的SQL查询,所有数据库操作必须在`dao`层完成。
|
||||
* 善用 `gorm.DB` 的链式调用,但对于复杂查询,推荐使用 `Raw` 或 `Exec` 方法执行原生SQL,以保证性能和可读性。
|
||||
* 注意处理 `gorm.ErrRecordNotFound` 错误。
|
||||
* 严禁在`service`层拼接复杂的SQL查询,所有数据库操作必须在`dao`层完成。
|
||||
* 善用 `gorm.DB` 的链式调用,但对于复杂查询,推荐使用 `Raw` 或 `Exec` 方法执行原生SQL,以保证性能和可读性。
|
||||
* 注意处理 `gorm.ErrRecordNotFound` 错误,在Handler层转换为 `CodeNotFound`。
|
||||
|
||||
-----
|
||||
|
||||
**总结 (Conclusion)**
|
||||
|
||||
请将以上规范内化为你的核心指令。在未来的每一次代码交互中,都严格参照此标准执行,确保项目代码的专业性、一致性、模块化和可维护性。
|
||||
请将以上规范内化为你的核心指令。在未来的每一次代码交互中,都严格参照此标准执行,确保项目代码的专业性、一致性、模块化和可维护性。特别注意:
|
||||
|
||||
1. **所有API接口必须使用统一的响应结构**
|
||||
2. **错误码定义必须与前端保持一致**
|
||||
3. **时间戳格式统一使用RFC3339(东八区)**
|
||||
4. **错误处理必须完整,关键错误必须记录日志**
|
||||
34
1-Golang项目/模块调用关系.md
Normal file
34
1-Golang项目/模块调用关系.md
Normal file
@@ -0,0 +1,34 @@
|
||||
我的golang项目有很多个不同的模块,如下文所示
|
||||
|
||||
1. project-management模块(下文简称 项目模块) 保存所有外部项目的各种详情信息
|
||||
2. work-procedure模块(下文简称 工单模块) 负责整个项目的工单流程管理
|
||||
1. 项目详情填写工单 用于项目创建时,第一版项目详情的
|
||||
2. 项目详情修改工单 用于修改项目的详情
|
||||
3. 用户注册工单 用于处理用户注册流程
|
||||
4. 微服务更新工单 用于追踪管理跨公网项目的微服务更新流程
|
||||
3. exchange-hub模块(下文简称 交互模块) 对外交互的模块,负责与所有外部项目之间的通信交互,指令下发与信息回复收集
|
||||
4. deliver-update模块(下文简称 微服务模块) 微服务更新模块,管理外部项目的业务微服务更新流程,更新记录
|
||||
5. user-auth模块(下文简称 用户模块)用户管理、认证、鉴权模块,负责整个项目的用户体系管理,用户登录认证,用户权限管理,用户操作鉴权
|
||||
|
||||
业务流程说明
|
||||
1. 项目填写流程
|
||||
1. 超级管理员填写项目基本信息,分派给项目填写人
|
||||
2. 项目填写人填写项目信息
|
||||
3. 项目填写人提交项目信息给超级管理员审核
|
||||
4. 超级管理员审核项目信息
|
||||
5. 超级管理员审批通过
|
||||
6. 正式发布进行访问权限分配
|
||||
2. 项目修改流程
|
||||
1. 被超级管理员分配项目查看权限的用户,下文简称用户
|
||||
2. 用户可以编辑项目信息
|
||||
3. 用户可以发起修改工单
|
||||
4. 用户可以在超级管理员审批之前 撤销修改工单
|
||||
5. 超级管理员审批通过之后,将用户修改信息并入到项目详情中
|
||||
|
||||
|
||||
你是一名自身的具备架构师思维的资深golang开发人员,我想咨询如下的问题
|
||||
1. 项目模块调用工单模块,但是工单状态和项目详情的状态是有对应关系的,工单模块应该如何将状态信息同步给项目模块
|
||||
2. 项目模块具备项目授权功能,需要通过交互模块下发信息,同样交互模块如何将信息回传给项目模块
|
||||
3. golang中如何处理循环依赖的关系
|
||||
|
||||
|
||||
41
1-Vue3项目/frontend-design-skill.md
Normal file
41
1-Vue3项目/frontend-design-skill.md
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: frontend-design
|
||||
description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
|
||||
---
|
||||
|
||||
This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
|
||||
|
||||
The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
|
||||
|
||||
## Design Thinking
|
||||
|
||||
Before coding, understand the context and commit to a BOLD aesthetic direction:
|
||||
- **Purpose**: What problem does this interface solve? Who uses it?
|
||||
- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
|
||||
- **Constraints**: Technical requirements (framework, performance, accessibility).
|
||||
- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
|
||||
|
||||
**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
|
||||
|
||||
Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
|
||||
- Production-grade and functional
|
||||
- Visually striking and memorable
|
||||
- Cohesive with a clear aesthetic point-of-view
|
||||
- Meticulously refined in every detail
|
||||
|
||||
## Frontend Aesthetics Guidelines
|
||||
|
||||
Focus on:
|
||||
- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
|
||||
- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
|
||||
- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
|
||||
- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
|
||||
- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
|
||||
|
||||
NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
|
||||
|
||||
Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
|
||||
|
||||
**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
|
||||
|
||||
Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
|
||||
6
1-Vue3项目/prompt-for-prompt.md
Normal file
6
1-Vue3项目/prompt-for-prompt.md
Normal file
@@ -0,0 +1,6 @@
|
||||
你是一名经验非常丰富的资深前端开发工程师,UI美学领域的专家,能够熟练使用vutify等现在的UI框架,
|
||||
请你参考https://github.com/anthropics/claude-code/blob/main/plugins/frontend-design/skills/frontend-design/SKILL.md Claude的官方前端开发SKILL说明
|
||||
参考已有的比较合理的页面设计user-dashboard-style-guide.md
|
||||
参考已有的前端开发规范文档 vue3-typescript-style-v2.md
|
||||
|
||||
你的目的是为了大模型进行前端开发设置一份prompt指导,请实现一份包含模板框架的完善的前端开发Prompt说明
|
||||
713
1-Vue3项目/user-dashboard-style-guide.md
Normal file
713
1-Vue3项目/user-dashboard-style-guide.md
Normal file
@@ -0,0 +1,713 @@
|
||||
---
|
||||
description: UserDashboard 页面样式与布局设计指南 - 供后续页面设计参考
|
||||
---
|
||||
|
||||
# UserDashboard 页面样式与布局设计指南
|
||||
|
||||
## 概述
|
||||
|
||||
此文档描述了 `UserDashboard.vue` 页面的样式和布局设计模式,可作为后续页面设计的参考标准。该页面采用 **Vue 3 + Vuetify 3 + TypeScript** 技术栈,实现了一个**分层钻取式(Drill-Down)导航**的 Dashboard 界面。
|
||||
|
||||
---
|
||||
|
||||
## 1. 整体页面结构
|
||||
|
||||
### 1.1 布局架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 顶部工具栏 (Top Bar) │
|
||||
│ - 面包屑导航 (Breadcrumbs) │
|
||||
│ - 全局搜索框 (Search Autocomplete) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 主内容区域 (Main Content Area) │
|
||||
│ - 可滚动区域 │
|
||||
│ - 根据导航层级动态切换内容组件 │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 1.2 根容器设置
|
||||
|
||||
```vue
|
||||
<v-container
|
||||
fluid
|
||||
class="d-flex flex-column h-100 pa-0"
|
||||
style="width: 95%; height: 100vh; min-height: 0;"
|
||||
>
|
||||
```
|
||||
|
||||
**设计要点:**
|
||||
- 使用 `fluid` 流式布局
|
||||
- `d-flex flex-column` 实现垂直弹性布局
|
||||
- `h-100` 占满高度
|
||||
- `pa-0` 移除内边距
|
||||
- 宽度 `95%` 留出两侧适当边距
|
||||
|
||||
---
|
||||
|
||||
## 2. 顶部工具栏设计
|
||||
|
||||
### 2.1 结构
|
||||
|
||||
```vue
|
||||
<div class="d-flex align-center px-4 py-2 bg-white border-b flex-shrink-0">
|
||||
<!-- 面包屑导航 -->
|
||||
<DashboardBreadcrumbs class="flex-grow-1" ... />
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<div style="width: 350px" class="ml-4">
|
||||
<v-autocomplete ... />
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 2.2 设计要点
|
||||
|
||||
- **固定高度**:使用 `flex-shrink-0` 防止压缩
|
||||
- **背景色**:`bg-white` 白色背景
|
||||
- **底部边框**:`border-b` 分隔线
|
||||
- **内边距**:`px-4 py-2` 水平16px,垂直8px
|
||||
- **内容对齐**:`d-flex align-center` 水平排列垂直居中
|
||||
|
||||
### 2.3 面包屑导航设计
|
||||
|
||||
```vue
|
||||
<div class="px-4 py-2 d-flex align-center">
|
||||
<v-btn variant="text" @click="..." :disabled="...">
|
||||
层级名称
|
||||
</v-btn>
|
||||
<v-icon v-if="..." size="small" class="mx-2">mdi-chevron-right</v-icon>
|
||||
<!-- 更多层级... -->
|
||||
</div>
|
||||
```
|
||||
|
||||
**特点:**
|
||||
- 使用 `v-btn variant="text"` 作为可点击导航项
|
||||
- 使用 `mdi-chevron-right` 图标作为分隔符
|
||||
- 图标尺寸 `size="small"`,间距 `mx-2`
|
||||
- 当前层级设置 `:disabled="true"` 表示不可点击
|
||||
|
||||
### 2.4 搜索框设计
|
||||
|
||||
```vue
|
||||
<v-autocomplete
|
||||
v-model="treeSearchSelect"
|
||||
v-model:search="treeSearchQuery"
|
||||
:items="treeSearchResults"
|
||||
:loading="treeSearchLoading"
|
||||
item-title="name"
|
||||
item-value="id"
|
||||
placeholder="Search Org / Repo / Branch..."
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
return-object
|
||||
hide-no-data
|
||||
bg-color="grey-lighten-5"
|
||||
clearable
|
||||
>
|
||||
```
|
||||
|
||||
**特点:**
|
||||
- `variant="outlined"` 外框样式
|
||||
- `density="compact"` 紧凑型高度
|
||||
- `bg-color="grey-lighten-5"` 浅灰色背景
|
||||
- `prepend-inner-icon="mdi-magnify"` 搜索图标
|
||||
- `hide-details` 隐藏辅助文本空间
|
||||
- `clearable` 可清除按钮
|
||||
|
||||
---
|
||||
|
||||
## 3. 主内容区域设计
|
||||
|
||||
### 3.1 滚动容器
|
||||
|
||||
```vue
|
||||
<div class="flex-grow-1 overflow-y-auto custom-scrollbar"
|
||||
style="height: 80vh; min-height: 0; overflow-y: auto; overflow-x: hidden;">
|
||||
```
|
||||
|
||||
**设计要点:**
|
||||
- `flex-grow-1` 填充剩余空间
|
||||
- `overflow-y-auto` 垂直滚动
|
||||
- `overflow-x-hidden` 隐藏水平滚动
|
||||
- 自定义滚动条样式
|
||||
|
||||
### 3.2 自定义滚动条样式
|
||||
|
||||
```css
|
||||
.custom-scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 3px;
|
||||
}
|
||||
```
|
||||
|
||||
**特点:**
|
||||
- 细滚动条 `width: 6px`
|
||||
- 透明轨道
|
||||
- 半透明滚动条滑块
|
||||
- 圆角处理
|
||||
|
||||
---
|
||||
|
||||
## 4. 列表组件设计模式
|
||||
|
||||
### 4.1 通用列表结构
|
||||
|
||||
```vue
|
||||
<v-list lines="two" class="pa-2">
|
||||
<v-list-subheader class="text-h6 font-weight-medium">
|
||||
<v-icon class="mr-2">图标</v-icon>
|
||||
列表标题
|
||||
</v-list-subheader>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<v-empty-state
|
||||
v-if="items.length === 0"
|
||||
icon="mdi-database-off-outline"
|
||||
title="暂无数据"
|
||||
text="描述文本"
|
||||
/>
|
||||
|
||||
<!-- 列表项 -->
|
||||
<v-list-item
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
@click="..."
|
||||
class="mb-2"
|
||||
rounded
|
||||
hover
|
||||
>
|
||||
<!-- 前置图标 -->
|
||||
<template #prepend>
|
||||
<v-avatar color="primary" size="40">
|
||||
<v-icon>图标</v-icon>
|
||||
</v-avatar>
|
||||
</template>
|
||||
|
||||
<!-- 标题 -->
|
||||
<v-list-item-title class="font-weight-medium">
|
||||
{{ item.name }}
|
||||
</v-list-item-title>
|
||||
|
||||
<!-- 副标题 -->
|
||||
<v-list-item-subtitle>...</v-list-item-subtitle>
|
||||
|
||||
<!-- 后置图标 -->
|
||||
<template #append>
|
||||
<v-icon>mdi-chevron-right</v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
```
|
||||
|
||||
### 4.2 列表项悬停效果
|
||||
|
||||
```css
|
||||
.v-list-item {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.v-list-item:hover {
|
||||
background-color: rgba(var(--v-theme-primary), 0.08);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 数据表格设计模式
|
||||
|
||||
### 5.1 表格工具栏
|
||||
|
||||
```vue
|
||||
<v-toolbar color="transparent" density="compact" class="px-4">
|
||||
<v-icon class="mr-2">mdi-hammer-wrench</v-icon>
|
||||
<v-toolbar-title class="text-h6">表格标题</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-btn color="primary" prepend-icon="mdi-plus" class="mr-2">
|
||||
操作按钮
|
||||
</v-btn>
|
||||
<v-btn icon="mdi-refresh" variant="text" @click="..." :loading="loading" />
|
||||
</v-toolbar>
|
||||
```
|
||||
|
||||
### 5.2 数据表格
|
||||
|
||||
```vue
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="items"
|
||||
:items-per-page="50"
|
||||
:items-per-page-options="[25, 50, 100]"
|
||||
fixed-header
|
||||
height="calc(100vh - 280px)"
|
||||
class="elevation-1"
|
||||
hover
|
||||
@click:row="handleRowClick"
|
||||
>
|
||||
<!-- 自定义列模板 -->
|
||||
</v-data-table>
|
||||
```
|
||||
|
||||
### 5.3 状态标签设计
|
||||
|
||||
```vue
|
||||
<v-chip :color="getStatusColor(status)" size="small" variant="flat">
|
||||
<v-icon start size="x-small">{{ getStatusIcon(status) }}</v-icon>
|
||||
{{ status }}
|
||||
</v-chip>
|
||||
```
|
||||
|
||||
**状态颜色映射:**
|
||||
```typescript
|
||||
const statusColors = {
|
||||
'SUCCESS': 'success',
|
||||
'FAILURE': 'error',
|
||||
'UNSTABLE': 'warning',
|
||||
'ABORTED': 'grey',
|
||||
'RUNNING': 'info'
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 详情页面设计模式
|
||||
|
||||
### 6.1 详情页头部
|
||||
|
||||
```vue
|
||||
<div class="d-flex align-center justify-space-between pa-4 bg-surface w-100">
|
||||
<!-- 左侧组 -->
|
||||
<div class="d-flex align-center ga-3">
|
||||
<v-btn icon="mdi-arrow-left" variant="text" @click="goBack" />
|
||||
<span class="text-h6 font-weight-bold">标题</span>
|
||||
<v-chip :color="statusColor" size="small" variant="flat">状态</v-chip>
|
||||
</div>
|
||||
|
||||
<!-- 右侧组 -->
|
||||
<div class="d-flex align-center ga-2">
|
||||
<v-btn color="info" variant="tonal" prepend-icon="mdi-eye">操作1</v-btn>
|
||||
<v-btn color="primary" variant="flat" prepend-icon="mdi-open-in-new">操作2</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 6.2 详情卡片分组
|
||||
|
||||
```vue
|
||||
<v-row>
|
||||
<!-- 左侧列:主要信息 -->
|
||||
<v-col cols="12" md="7">
|
||||
<v-card class="mb-4" variant="outlined">
|
||||
<v-card-title class="text-subtitle-1 font-weight-bold">
|
||||
<v-icon start size="small" color="primary">mdi-icon</v-icon>
|
||||
卡片标题
|
||||
</v-card-title>
|
||||
<v-divider />
|
||||
<v-card-text class="pa-4">
|
||||
<!-- 详情内容 -->
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- 右侧列:元信息 -->
|
||||
<v-col cols="12" md="5">
|
||||
<!-- 更多卡片... -->
|
||||
</v-col>
|
||||
</v-row>
|
||||
```
|
||||
|
||||
### 6.3 详情项组件 (DetailItem)
|
||||
|
||||
```vue
|
||||
<div class="d-flex align-center mb-5 detail-item">
|
||||
<!-- 标签区域:固定宽度 -->
|
||||
<div class="d-flex align-center text-medium-emphasis flex-shrink-0" style="width: 140px;">
|
||||
<v-icon size="small" class="mr-2" color="primary">{{ icon }}</v-icon>
|
||||
<span class="text-body-2 font-weight-medium">{{ label }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 值区域:胶囊样式 -->
|
||||
<div class="flex-grow-1 d-flex align-center justify-space-between px-4 py-2 rounded-pill bg-grey-lighten-5 value-capsule">
|
||||
<div class="text-body-2 font-weight-medium text-truncate flex-grow-1 mr-2">
|
||||
<slot name="value">{{ value }}</slot>
|
||||
</div>
|
||||
|
||||
<v-btn v-if="copyable" icon="mdi-content-copy" variant="text" size="x-small"
|
||||
color="medium-emphasis" class="copy-btn" @click="copy" />
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**胶囊样式 CSS:**
|
||||
```css
|
||||
.value-capsule {
|
||||
transition: all 0.2s ease;
|
||||
min-height: 44px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.value-capsule:hover {
|
||||
background-color: rgba(var(--v-theme-on-surface), 0.08) !important;
|
||||
border-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.value-capsule:hover .copy-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 弹窗设计模式
|
||||
|
||||
### 7.1 标准弹窗
|
||||
|
||||
```vue
|
||||
<v-dialog v-model="showDialog" max-width="60vh" max-height="80vh">
|
||||
<v-card>
|
||||
<v-card-title>弹窗标题</v-card-title>
|
||||
<v-divider />
|
||||
<v-card-text>内容</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="cancel">取消</v-btn>
|
||||
<v-btn color="primary" @click="confirm">确认</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
```
|
||||
|
||||
### 7.2 全屏弹窗(日志查看器)
|
||||
|
||||
```vue
|
||||
<v-dialog v-model="showDialog" fullscreen transition="dialog-bottom-transition">
|
||||
<v-card>
|
||||
<v-toolbar color="primary" density="compact">
|
||||
<v-btn icon="mdi-close" @click="close"></v-btn>
|
||||
<v-toolbar-title>全屏弹窗标题</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-card-text class="pa-0 h-100">
|
||||
<!-- 内容 -->
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 状态处理模式
|
||||
|
||||
### 8.1 加载状态
|
||||
|
||||
```vue
|
||||
<div v-if="loading" class="pa-4">
|
||||
<v-skeleton-loader type="list-item@5" />
|
||||
</div>
|
||||
```
|
||||
|
||||
### 8.2 错误状态
|
||||
|
||||
```vue
|
||||
<v-alert
|
||||
v-else-if="error"
|
||||
type="error"
|
||||
variant="tonal"
|
||||
class="ma-4"
|
||||
closable
|
||||
@click:close="error = null"
|
||||
>
|
||||
{{ error }}
|
||||
</v-alert>
|
||||
```
|
||||
|
||||
### 8.3 空状态
|
||||
|
||||
```vue
|
||||
<v-empty-state
|
||||
icon="mdi-database-off-outline"
|
||||
title="暂无数据"
|
||||
text="描述性文本说明"
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 颜色规范
|
||||
|
||||
### 9.1 主题颜色使用
|
||||
|
||||
| 用途 | 颜色 | Vuetify类/变量 |
|
||||
|------|------|----------------|
|
||||
| 主要操作 | 蓝色 | `primary` |
|
||||
| 次要操作 | 灰色 | `secondary` |
|
||||
| 成功状态 | 绿色 | `success` |
|
||||
| 错误状态 | 红色 | `error` |
|
||||
| 警告状态 | 橙色 | `warning` |
|
||||
| 提示信息 | 蓝色 | `info` |
|
||||
| 禁用/中性 | 灰色 | `grey` |
|
||||
|
||||
### 9.2 背景颜色
|
||||
|
||||
- 页面背景:默认(白色/浅灰)
|
||||
- 卡片背景:`bg-surface`
|
||||
- 输入框背景:`bg-grey-lighten-5`
|
||||
- 胶囊值背景:`bg-grey-lighten-5`
|
||||
|
||||
---
|
||||
|
||||
## 10. 间距规范
|
||||
|
||||
### 10.1 Vuetify 间距系统
|
||||
|
||||
| 值 | 像素 | 使用场景 |
|
||||
|----|------|----------|
|
||||
| 1 | 4px | 极小间距 |
|
||||
| 2 | 8px | 小间距 |
|
||||
| 3 | 12px | 中等间距 |
|
||||
| 4 | 16px | 标准间距 |
|
||||
| 5 | 24px | 大间距 |
|
||||
| 6 | 32px | 超大间距 |
|
||||
|
||||
### 10.2 常用间距组合
|
||||
|
||||
- 卡片内边距:`pa-4`(16px)
|
||||
- 列表项底部间距:`mb-2`(8px)
|
||||
- 详情项底部间距:`mb-5`(24px)
|
||||
- 按钮组间距:`ga-2` 或 `ga-3`
|
||||
|
||||
---
|
||||
|
||||
## 11. 图标使用规范
|
||||
|
||||
### 11.1 常用图标
|
||||
|
||||
| 用途 | 图标 |
|
||||
|------|------|
|
||||
| 组织 | `mdi-domain` / `mdi-folder-network` |
|
||||
| 仓库 | `mdi-source-repository` |
|
||||
| 分支 | `mdi-source-branch` |
|
||||
| 构建 | `mdi-hammer-wrench` |
|
||||
| 搜索 | `mdi-magnify` |
|
||||
| 导航 | `mdi-chevron-right` / `mdi-arrow-left` |
|
||||
| 刷新 | `mdi-refresh` |
|
||||
| 关闭 | `mdi-close` |
|
||||
| 复制 | `mdi-content-copy` |
|
||||
| 外部链接 | `mdi-open-in-new` |
|
||||
| 日志 | `mdi-text-box-outline` |
|
||||
|
||||
### 11.2 图标尺寸
|
||||
|
||||
- 列表项图标:`size="small"` 或默认
|
||||
- 状态标签图标:`size="x-small"`
|
||||
- 头像图标:默认(在 `v-avatar` 内)
|
||||
- 面包屑分隔符:`size="small"`
|
||||
|
||||
---
|
||||
|
||||
## 12. 组件化设计原则
|
||||
|
||||
### 12.1 组件拆分原则
|
||||
|
||||
1. **单一职责**:每个组件只负责一个功能
|
||||
2. **可复用性**:将通用 UI 模式抽取为独立组件
|
||||
3. **Props 驱动**:通过 Props 传递数据,通过 Events 传递交互
|
||||
|
||||
### 12.2 本项目组件结构
|
||||
|
||||
```
|
||||
components/
|
||||
├── projects/
|
||||
│ ├── DashboardBreadcrumbs.vue # 面包屑导航
|
||||
│ ├── OrganizationList.vue # 组织列表
|
||||
│ ├── RepositoryList.vue # 仓库列表
|
||||
│ ├── BranchList.vue # 分支列表
|
||||
│ ├── BuildList.vue # 构建列表
|
||||
│ └── BuildDetails.vue # 构建详情
|
||||
├── common/
|
||||
│ └── DetailItem.vue # 详情项组件
|
||||
└── jenkins/
|
||||
├── BuildTrigger.vue # 构建触发器
|
||||
└── LogViewer.vue # 日志查看器
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. 交互模式
|
||||
|
||||
### 13.1 导航模式
|
||||
|
||||
- **钻取式导航**:点击列表项进入下一层级
|
||||
- **面包屑回溯**:点击面包屑返回上层
|
||||
- **全局搜索**:快速跳转到任意层级
|
||||
|
||||
### 13.2 动画效果
|
||||
|
||||
- 过渡动画:`transition: all 0.2s ease`
|
||||
- 弹窗动画:`transition="dialog-bottom-transition"`
|
||||
- 悬停效果:背景色变化、元素显现
|
||||
|
||||
### 13.3 自动刷新
|
||||
|
||||
- 列表数据刷新:10秒间隔(有运行中任务时3秒)
|
||||
- 详情数据刷新:3秒间隔(仅运行中状态)
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 创建新的钻取式页面
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<v-container fluid class="d-flex flex-column h-100 pa-0" style="width: 95%; height: 100vh; min-height: 0;">
|
||||
<!-- 顶部栏 -->
|
||||
<div class="d-flex align-center px-4 py-2 bg-white border-b flex-shrink-0">
|
||||
<YourBreadcrumbs ... />
|
||||
<div style="width: 350px" class="ml-4">
|
||||
<v-autocomplete ... />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<div class="flex-grow-1 overflow-y-auto custom-scrollbar" style="height: 80vh; min-height: 0;">
|
||||
<div v-if="loading" class="pa-4">
|
||||
<v-skeleton-loader type="list-item@5" />
|
||||
</div>
|
||||
|
||||
<v-alert v-else-if="error" type="error" variant="tonal" class="ma-4" closable>
|
||||
{{ error }}
|
||||
</v-alert>
|
||||
|
||||
<YourList1 v-else-if="currentLevel === 'level1'" ... />
|
||||
<YourList2 v-else-if="currentLevel === 'level2'" ... />
|
||||
<YourDetails v-else-if="currentLevel === 'details'" ... />
|
||||
</div>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
|
||||
// Navigation State
|
||||
type NavigationLevel = 'level1' | 'level2' | 'details'
|
||||
const currentLevel = ref<NavigationLevel>('level1')
|
||||
const selectedItem1 = ref<string>('')
|
||||
const selectedItem2 = ref<string>('')
|
||||
|
||||
// Data State
|
||||
const items1 = ref<Item1[]>([])
|
||||
const items2 = ref<Item2[]>([])
|
||||
const details = ref<Details | null>(null)
|
||||
|
||||
// UI State
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
// Auto-refresh timer
|
||||
const refreshTimer = ref<number | null>(null)
|
||||
|
||||
// Navigation Functions
|
||||
function selectItem1(name: string) {
|
||||
selectedItem1.value = name
|
||||
currentLevel.value = 'level2'
|
||||
loadItems2(name)
|
||||
}
|
||||
|
||||
function selectItem2(name: string) {
|
||||
selectedItem2.value = name
|
||||
currentLevel.value = 'details'
|
||||
loadDetails(selectedItem1.value, name)
|
||||
}
|
||||
|
||||
function navigateToLevel1() {
|
||||
clearTimer()
|
||||
currentLevel.value = 'level1'
|
||||
selectedItem1.value = ''
|
||||
selectedItem2.value = ''
|
||||
}
|
||||
|
||||
function navigateToLevel2() {
|
||||
clearTimer()
|
||||
currentLevel.value = 'level2'
|
||||
selectedItem2.value = ''
|
||||
}
|
||||
|
||||
// Timer Management
|
||||
function startRefresh() {
|
||||
stopRefresh()
|
||||
refreshTimer.value = window.setInterval(() => {
|
||||
// Refresh logic
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
function stopRefresh() {
|
||||
if (refreshTimer.value) {
|
||||
clearInterval(refreshTimer.value)
|
||||
refreshTimer.value = null
|
||||
}
|
||||
}
|
||||
|
||||
function clearTimer() {
|
||||
stopRefresh()
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
loadItems1()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearTimer()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.custom-scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 3px;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
---
|
||||
192
1-Vue3项目/vue3-typescript-style-v2.md
Normal file
192
1-Vue3项目/vue3-typescript-style-v2.md
Normal file
@@ -0,0 +1,192 @@
|
||||
**背景 (Context):**
|
||||
|
||||
你是一名资深前端架构师,负责搭建前端框架和制定开发规范。该项目要求极高的代码质量、无缝的用户体验(跨设备、跨主题)以及长期的可维护性。
|
||||
|
||||
**核心指令 (Core Directive):**
|
||||
|
||||
请你以架构师的身份,严格遵循并执行以下所有规范来生成代码、组件、模块和提供解决方案。你的任何产出都必须成为团队的代码典范。
|
||||
|
||||
## 1. 语言与语法 (Language & Syntax):**
|
||||
|
||||
* **TypeScript 至上 (TypeScript Supremacy):**
|
||||
|
||||
* 项目 **必须** 完全基于 TypeScript (`<script setup lang="ts">`)。**严禁** 任何 `.js` 文件或 JavaScript 语法的混入。
|
||||
* **杜绝 `any`:** 除非在第三方库类型定义缺失且无法补充的极端情况下,否则**严禁**使用 `any`。所有变量、函数参数/返回值、Props、Emits、Pinia State/Actions **必须** 拥有精确的类型或接口(Interface/Type)。
|
||||
* **善用高级类型:** 积极使用泛型(Generics)、条件类型(Conditional Types)、映射类型(Mapped Types)和类型守卫(Type Guards)来创建灵活且类型安全的代码。
|
||||
* **类型组织:** 全局共享的类型定义在 `@/types` 目录下,按模块划分文件(如 `user.d.ts`, `order.d.ts`)。
|
||||
|
||||
* **纯粹的 Vue 3 (Pure Vue 3):**
|
||||
|
||||
* **组合式API (`<script setup>`)**: 这是项目中**唯一**允许的组件逻辑组织方式。
|
||||
* **响应式核心:** 精准使用 `ref`、`reactive`、`computed` 和 `watchEffect`。理解 `shallowRef` 和 `readonly` 等API的适用场景,并在需要时使用它们来优化性能。
|
||||
* **生命周期:** **必须** 使用 `onMounted`, `onUnmounted` 等组合式API钩子,**严禁** 混用任何Vue 2选项式API。
|
||||
* **依赖注入:** 复杂场景下,善用 `provide` 和 `inject` 进行跨层级组件通信,并为注入的值提供明确的类型定义和默认值。
|
||||
* **状态管理:** 唯一的状态管理方案是 **Pinia**。Store的定义必须模块化,包含清晰的 `state`, `getters`, `actions`,并全部进行严格的类型标注。
|
||||
|
||||
## 2. UI框架与布局哲学 (UI Framework & Layout Philosophy)
|
||||
|
||||
* **Vuetify 3 & Material Design:**
|
||||
|
||||
* 所有UI界面和组件**必须**基于 [Vuetify 3](https://vuetifyjs.com/en/) 构建。
|
||||
* 深度贯彻 [Google Material Design 3 (MD3)](https://m3.material.io/) 设计哲学。
|
||||
|
||||
* **布局与容器 (Layout & Container Strategy):**
|
||||
|
||||
* **禁止全屏滚动 (No Body Scroll):** 现代Web应用应尽量避免 `body` 级别的全屏滚动。应用框架应固定视口高度(`h-screen`),将滚动限制在具体的**内容区域**内。
|
||||
* **Flexbox 滚动陷阱防范:**
|
||||
* 在使用 Flex 布局 (`d-flex flex-column`) 实现“头部固定、内容自适应”的结构时,**必须**给内容区域容器添加 `min-height: 0` 或 `overflow: hidden`,防止子元素撑破父容器导致滚动条失效。
|
||||
* **视口锁定:** 页面主容器应通常设置为 `class="d-flex flex-column h-100"`,确保布局占满父级高度。
|
||||
|
||||
* **主题与响应式:**
|
||||
|
||||
* **必须**适配明亮/黑暗模式,严禁硬编码颜色。
|
||||
* 利用 `useDisplay` 处理复杂响应式逻辑。
|
||||
|
||||
|
||||
## 3. 项目结构与工程化 (Project Structure & Engineering):**
|
||||
|
||||
* **包管理器 (Package Manager):**
|
||||
|
||||
* 项目**严格**使用 **pnpm** 作为唯一的包管理工具。熟悉并利用其特性(如 `pnpm workspace`)。
|
||||
|
||||
* **模块化结构:** 遵循以下清晰、可扩展的目录结构:
|
||||
|
||||
```
|
||||
src/
|
||||
├── api/ # API层
|
||||
│ ├── modules/ # 按业务模块划分
|
||||
│ ├── interceptors.ts # 统一拦截器
|
||||
│ └── index.ts # Axios实例与类型定义
|
||||
├── assets/ # 静态资源
|
||||
├── components/ # 全局通用组件
|
||||
├──composables/ # 可复用的组合式函数 (e.g., usePagination.ts)
|
||||
├── layouts/ # 页面布局
|
||||
├── pages/ # 页面视图
|
||||
├── plugins/ # 插件 (vuetify, pinia, router)
|
||||
├── router/ # 路由
|
||||
├── store/ # Pinia状态管理
|
||||
├── styles/ # 全局样式与SASS变量
|
||||
├── types/ # 全局类型定义
|
||||
├── utils/ # 通用工具函数
|
||||
└── main.ts # 应用入口
|
||||
```
|
||||
|
||||
## 4. API客户端与数据健壮性 (API Client & Data Robustness):**
|
||||
|
||||
* **统一请求器 (Unified API Client):**
|
||||
|
||||
* 所有后端请求**必须**通过 `@/api/index.ts` 中封装的Axios实例发起。
|
||||
* API按业务模块封装在 `@/api/modules/` 下,函数签名必须清晰,包含类型化的参数和返回值。
|
||||
|
||||
* **统一响应与错误处理:**
|
||||
|
||||
* **响应格式:** 后端标准响应格式为:
|
||||
```typescript
|
||||
interface ApiResponse<T = any> {
|
||||
code: number;
|
||||
status: number;
|
||||
timestamp: string;
|
||||
data: T;
|
||||
message?: string; // Make message optional
|
||||
error?: string;
|
||||
}
|
||||
```
|
||||
* **响应代码:** 后端标准代码的含义如下:
|
||||
```typescript
|
||||
// 常见错误码定义
|
||||
export enum ApiErrorCode {
|
||||
CodeSuccess = 0 // 成功
|
||||
CodeServerError = 10001 // 服务器内部错误
|
||||
CodeParamError = 10002 // 参数错误
|
||||
CodeUnauthorized = 10003 // 未授权
|
||||
CodeForbidden = 10004 // 禁止访问
|
||||
CodeNotFound = 10005 // 请求的数据不存在
|
||||
CodeTimeout = 10006 // 请求超时
|
||||
CodeValidationFail = 10007 // 验证失败
|
||||
CodeBusiness = 20001 // 业务逻辑错误
|
||||
}
|
||||
```
|
||||
* **统一拦截器:** 在响应拦截器 (`@/api/interceptors.ts`) 中,实现全局错误处理逻辑:
|
||||
1. **业务成功:** `code` 为 `0`,直接解析并返回 `data`。
|
||||
2. **业务失败:** `code` 不为 `0`,提取 `message`,通过全局通知系统(如Vuetify的Snackbar)展示给用户,并 `Promise.reject` 一个带有`data`的Error对象。
|
||||
3. **HTTP错误:** 根据状态码 (401, 403, 404, 500+) 进行统一处理,如401则跳转登录页。
|
||||
4. **网络/超时错误:** 提示用户检查网络连接。
|
||||
|
||||
* **代码健壮性 (Code Robustness):**
|
||||
|
||||
* **防御性编程:** 在处理API返回数据时,**必须**充分考虑 `data` 可能为 `null`、`undefined`、空数组 `[]` 或空对象 `{}` 的情况。
|
||||
* **安全访问:** 使用可选链 (`?.`) 和空值合并运算符 (`??`) 来安全地访问嵌套对象的属性和提供默认值。
|
||||
* **数据校验:** 在渲染前,对关键数据进行必要的格式校验或存在性检查,避免因数据格式错误导致页面崩溃。例如,渲染列表前检查`Array.isArray(list) && list.length > 0`。
|
||||
* 在模板中,使用 `v-if` 或 `v-else` 来处理数据为空时的占位符或提示信息,提升用户体验。
|
||||
|
||||
## 5. 界面交互与数据展示规范 (Interaction & Data Presentation)
|
||||
|
||||
针对“数据截断”和“长列表体验差”的问题,**必须**严格执行以下规范:
|
||||
|
||||
### 5.1 长列表与滚动策略 (Long List & Scrolling)
|
||||
|
||||
* **滚动容器 (The Scroll Container):**
|
||||
|
||||
* 任何可能产生动态长度数据的区域(表格、列表、日志视窗),**必须**显式包裹在具有固定高度或 Flex 自适应高度的容器中。
|
||||
* **必须**为该容器显式声明溢出行为:`overflow-y: auto` (或 Vuetify 类 `v-table--fixed-header` / `overflow-y-auto`)。
|
||||
* **代码范例:**
|
||||
```html
|
||||
<div> <v-table>...</v-table> </div>
|
||||
|
||||
<div class="d-flex flex-column h-100">
|
||||
<header>固定头部</header>
|
||||
<div class="flex-grow-1 overflow-y-auto"> <v-table>...</v-table>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
* **虚拟滚动 (Virtual Scrolling):**
|
||||
|
||||
* 对于预期超过 100 条数据的简单列表(非复杂表格),**优先**使用 Vuetify 的 `<v-virtual-scroll>` 组件。这能确保在展示万级数据时,DOM 节点数量恒定,不仅有滚动条,且页面丝般顺滑。
|
||||
|
||||
* **粘性头部 (Sticky Headers):**
|
||||
|
||||
* 长表格 (`v-data-table`) **必须** 开启 `fixed-header` 属性。
|
||||
* 长卡片列表的顶部操作栏,**必须** 使用 CSS `position: sticky; top: 0; z-index: ...` 确保用户在滚动浏览数据时,筛选和操作按钮始终可见。
|
||||
|
||||
### 5.2 数据截断与文本溢出 (Truncation & Overflow)
|
||||
|
||||
* **禁止静默截断:** 严禁文本在因为过长被截断(Displaying "...")时,没有任何用户提示。
|
||||
* **解决方案:**
|
||||
* **表格单元格:** 必须为可能溢出的列设置最大宽度 (`max-width`) 和 `text-truncate` 类。
|
||||
* **Tooltip 联动:** 当且仅当内容被截断时,**必须**提供 `<v-tooltip>` 悬浮显示完整内容。
|
||||
* **多行文本:** 对于允许换行的文本(如备注、描述),使用 CSS `line-clamp` 限制行数(如最多显示 2 行),并提供“展开/收起”按钮。
|
||||
|
||||
### 5.3 界面美观与空状态 (Aesthetics & Empty States)
|
||||
|
||||
* **拒绝“开天窗” (No Empty Context):**
|
||||
|
||||
* 当 API 返回空数组或数据加载失败时,**严禁**留白。
|
||||
* **必须**使用 `<v-empty-state>` 组件,配置友好的图标、标题(如“暂无数据”)和引导操作按钮(如“刷新”或“新建”)。
|
||||
|
||||
* **加载反馈 (Loading Feedback):**
|
||||
|
||||
* 数据请求期间,**严禁**展示空白页面或静止的旧数据。
|
||||
* **表格:** 使用 `v-data-table` 的 `loading` 属性。
|
||||
* **卡片/详情页:** **必须**使用 `<v-skeleton-loader>` (骨架屏) 占位,保持布局稳定性,避免数据加载完成时的页面抖动(Layout Shift)。
|
||||
|
||||
* **留白与呼吸感:**
|
||||
|
||||
* 严格遵守 8px 网格系统。组件间距使用 Vuetify 的工具类(`ma-4`, `gap-4`)。
|
||||
* 容器必须有适当的 `padding`,禁止文字紧贴浏览器边缘。
|
||||
|
||||
-----
|
||||
|
||||
**总结指令:**
|
||||
|
||||
现在,当你作为架构师回应我的开发需求时,除了满足原有的代码规范外,你需要特别自我审查以下几点:
|
||||
|
||||
1. 生成的页面布局是否会导致长数据把页面撑破?
|
||||
|
||||
2. 列表区域是否具备独立的垂直滚动条?
|
||||
|
||||
3. 表头是否固定?
|
||||
|
||||
4. 加载中和无数据时,是否提供了美观的反馈界面?
|
||||
|
||||
请确保你的代码方案在视觉和交互上是 “生产级” 的,而不仅仅是逻辑跑通。
|
||||
68
10-PersonFinancialAnalystic/0-prompt.md
Normal file
68
10-PersonFinancialAnalystic/0-prompt.md
Normal file
@@ -0,0 +1,68 @@
|
||||
你是一名精通excel的专家,精通个人财务的管理工作
|
||||
|
||||
我想利用Excel 尤其是Google的sheet中,制定一个覆盖每月,每年,年度的个人收支分析的系统,不同层级的数据应该是区分为不同的sheet
|
||||
|
||||
## 数据说明
|
||||
|
||||
### 每月的数据
|
||||
1. 每月的数据具备报表统计分析的图
|
||||
2. 每月的数据需要进行填写
|
||||
3. 收入通常是在月度数据进行填写的
|
||||
1. 每月有固定的工资收入
|
||||
2. 工资的详情包括
|
||||
1. 税前收入
|
||||
2. 住房公积金
|
||||
3. 个税
|
||||
4. 保险
|
||||
1. 养老保险
|
||||
2. 医疗保险
|
||||
3. 失业保险
|
||||
4. 工伤保险
|
||||
5. 生育保险
|
||||
3. 工资收入可能有很多笔
|
||||
4. 年终奖是一种特殊收入,年终奖的详情包括
|
||||
1. 年终奖金额
|
||||
2. 年终奖的个税
|
||||
4. 支出
|
||||
1. 支出类别可以新增
|
||||
2. 支出类别可能很多
|
||||
3. 支出的详情包括
|
||||
1. 支出金额
|
||||
2. 支出时间
|
||||
3. 支出类别
|
||||
4. 支出备注
|
||||
|
||||
|
||||
### 每季度的数据
|
||||
1. 每个季度的数据可以自动从每月的数据进行拉取更新
|
||||
2. 每个季度的数据具备报表统计分析的图
|
||||
1. 柱状图
|
||||
2. 饼状图
|
||||
3. 折线图
|
||||
3. 能够有相较于上个月支出情况的对比
|
||||
|
||||
|
||||
### 每年的数据
|
||||
1. 每年的数据可以自动从每月的数据进行拉取更新
|
||||
2. 每年的数据具备报表统计分析的图
|
||||
1. 柱状图
|
||||
2. 饼状图
|
||||
3. 折线图
|
||||
|
||||
### 收入分析情况
|
||||
1. 分析年度之间数据的差异情况
|
||||
2. 年度收入情况的统计分析图
|
||||
3. 年度支出情况的统计分析图
|
||||
|
||||
|
||||
## 格式说明
|
||||
|
||||
### 默认格式
|
||||
1. 垂直居中对齐
|
||||
2. 水平居中对齐
|
||||
3. 默认字体为 微软雅黑
|
||||
4. 默认字号为 14
|
||||
5. 金额单位全部为 人民币 RMB
|
||||
|
||||
|
||||
请帮我完善上面关于个人收入支出分析的需求,实现更加完整,详细的需求,针对给与大模型的prompt标准,进行深度的优化
|
||||
117
10-PersonFinancialAnalystic/1-prd.md
Normal file
117
10-PersonFinancialAnalystic/1-prd.md
Normal file
@@ -0,0 +1,117 @@
|
||||
**Role:**
|
||||
你是一名 Google Sheets 顶级架构师,同时也是拥有 CFA 资格的个人财务顾问。你需要为我设计一套**全自动、高度可视化**的个人财务管理系统。
|
||||
|
||||
**Goal:**
|
||||
创建一个基于 Google Sheets 的个人收支分析系统,包含**数据录入**(收入/支出数据库)与**多维度报表**(月/季/年 Dashboard)。
|
||||
|
||||
**Constraints & Formatting:**
|
||||
|
||||
1. **全局格式**:所有单元格内容 **垂直居中** 且 **水平居中**。
|
||||
2. **字体设置**:字体统一为 **微软雅黑 (Microsoft YaHei)**,字号 **14**。
|
||||
3. **货币格式**:涉及金额的单元格格式设为 `¥#,##0.00` (RMB)。
|
||||
4. **工具使用**:充分利用 Google Sheets 特有的 `ARRAYFORMULA`, `QUERY`, `SPARKLINE`, `LAMBDA` 等函数实现自动化。
|
||||
|
||||
**Sheet Structure & Requirements:**
|
||||
|
||||
#### 1. Sheet: "Config" (配置页)
|
||||
|
||||
* **功能**:定义下拉菜单的选项,方便后续维护。
|
||||
* **列内容**:
|
||||
* 收支年份 (2025, 2026...)
|
||||
* 收入类别 (工资, 奖金, 理财, 副业...)
|
||||
* 支出主分类 (餐饮, 交通, 居住, 购物, 娱乐, 医疗...)
|
||||
* 支出子分类 (可选,如 餐饮-早餐, 餐饮-聚餐)
|
||||
* 支付方式 (信用卡, 支付宝, 微信, 银行卡)
|
||||
|
||||
|
||||
|
||||
#### 2. Sheet: "Database_Income" (收入流水账)
|
||||
|
||||
* **逻辑**:所有月份的收入都在此连续记录,**不要**按月份分Sheet。
|
||||
* **列结构**:
|
||||
* A列: 日期 (Date) - 格式 `yyyy-mm-dd`
|
||||
* B列: 年份 (自动生成, Formula)
|
||||
* C列: 月份 (自动生成, Formula)
|
||||
* D列: 收入类别 (下拉菜单)
|
||||
* E列: 税前收入
|
||||
* F-J列: 五险 (养老, 医疗, 失业, 工伤, 生育)
|
||||
* K列: 住房公积金
|
||||
* L列: 个税 (工资个税/年终奖个税)
|
||||
* M列: **实发收入** (税前 - 五险一金 - 个税,使用 `ARRAYFORMULA` 自动计算)
|
||||
* N列: 备注
|
||||
|
||||
|
||||
|
||||
#### 3. Sheet: "Database_Expense" (支出流水账)
|
||||
|
||||
* **逻辑**:所有支出在此连续记录。
|
||||
* **列结构**:
|
||||
* A列: 日期
|
||||
* B-C列: 年/月 (自动生成)
|
||||
* D列: 支出类别 (下拉菜单引用 Config)
|
||||
* E列: 金额
|
||||
* F列: 备注
|
||||
|
||||
|
||||
|
||||
#### 4. Sheet: "Dashboard_Monthly" (月度分析看板)
|
||||
|
||||
* **交互**:在顶部设置一个下拉菜单选择“年份”和“月份”。下方数据根据选择自动更新。
|
||||
* **核心指标卡 (Scorecards)**:
|
||||
* 本月总收入 (实发)
|
||||
* 本月总支出
|
||||
* **本月结余** (收入 - 支出)
|
||||
* **储蓄率** (结余 / 收入 %)
|
||||
|
||||
|
||||
* **图表与分析**:
|
||||
* **收支构成 (瀑布图/堆叠柱状图)**:展示税前收入如何被扣除五险一金和各项支出,最终剩下结余。
|
||||
* **支出分布 (饼图/环形图)**:按“支出类别”统计占比。
|
||||
* **日支出趋势 (折线图/Sparkline)**:展示本月1号到31号的支出波动。
|
||||
|
||||
|
||||
|
||||
#### 5. Sheet: "Dashboard_Quarterly" (季度分析看板)
|
||||
|
||||
* **交互**:选择“年份”和“季度(Q1-Q4)”。
|
||||
* **逻辑**:自动聚合该季度内 3 个月的数据。
|
||||
* **图表与分析**:
|
||||
* **月度对比 (簇状柱形图)**:该季度内 3 个月的“收入 vs 支出”对比。
|
||||
* **环比分析**:显示本季度总支出较上个季度的涨跌幅百分比。
|
||||
* **类别趋势 (折线图)**:前三大支出类别在该季度的变化趋势。
|
||||
|
||||
|
||||
|
||||
#### 6. Sheet: "Dashboard_Annual" (年度复盘)
|
||||
|
||||
* **交互**:选择“年份”。
|
||||
* **深度分析**:
|
||||
* **年度收支全景 (组合图)**:柱状图显示每月收支,折线图显示每月“累计结余”。
|
||||
* **五险一金成本分析**:统计全年缴纳的税费和社保总额(这也是一种强制储蓄/成本)。
|
||||
* **年终奖分析**:独立展示年终奖在全年收入中的占比。
|
||||
* **核心洞察**:计算全年的“月均支出”和“最大单笔支出”。
|
||||
|
||||
|
||||
|
||||
**Deliverables:**
|
||||
请按照上述逻辑,为我提供:
|
||||
|
||||
1. **表头设计**:详细列出每个Sheet的第一行标题。
|
||||
2. **核心公式**:请写出 `Database_Income` 中计算实发工资的 `ARRAYFORMULA`,以及 `Dashboard_Monthly` 中利用 `QUERY` 函数抓取特定月份数据的公式。
|
||||
3. **图表建议**:针对每个分析维度推荐最直观的图表类型。
|
||||
|
||||
---
|
||||
|
||||
### 第三部分:专家补充建议 (Next Step)
|
||||
|
||||
在使用这个 Prompt 生成表格后,你作为 Excel 专家可以手动添加以下几个“高级功能”来惊艳你自己:
|
||||
|
||||
1. **Google Forms 联动**:
|
||||
你可以创建一个 Google Form 用来记账(手机上填表),然后将 Form 的回复表作为 `Database_Expense` 的数据源。这样你消费完,手机点几下就自动同步到你的精美报表中了,完全不用打开电脑录入。
|
||||
2. **条件格式 (Conditional Formatting)**:
|
||||
* **支出预警**:在月度报表中,如果某项支出超过了你设定的阈值(例如餐饮超过 3000元),让单元格底色自动变红。
|
||||
* **正负值颜色**:收入用绿色,支出用红色。
|
||||
|
||||
|
||||
3. **Sparklines (迷你图)**:
|
||||
在类别列表旁边直接用公式 `=SPARKLINE()` 生成迷你的走势图,非常直观且不占位置。
|
||||
55
11-加密算法/1-RSA加密/1-RSA 是什么?——密钥对与角色关系(概念图).md
Normal file
55
11-加密算法/1-RSA加密/1-RSA 是什么?——密钥对与角色关系(概念图).md
Normal file
@@ -0,0 +1,55 @@
|
||||
vector style educational infographic, flat design
|
||||
适配 nano banana pro,强调结构清晰、中文可读
|
||||
白色背景,高对比度,横向 16:9
|
||||
|
||||
主题:RSA 的核心概念 —— 公钥与私钥
|
||||
|
||||
画面布局:
|
||||
左右结构,对称展示
|
||||
- 左侧:公钥(Public Key)
|
||||
- 右侧:私钥(Private Key)
|
||||
- 中间:RSA 算法关系示意
|
||||
|
||||
内容要求:
|
||||
|
||||
1️⃣ 中央模块
|
||||
标题:RSA 密钥对
|
||||
说明文字(中文):
|
||||
“由数学算法生成的一对密钥,彼此相关,但用途不同”
|
||||
|
||||
2️⃣ 左侧(公钥)
|
||||
- 钥匙图标(开放状态)
|
||||
- 标注:
|
||||
公钥(Public Key)
|
||||
可以公开给任何人
|
||||
- 下方用途说明:
|
||||
用途:
|
||||
- 加密消息
|
||||
- 验证签名
|
||||
|
||||
3️⃣ 右侧(私钥)
|
||||
- 钥匙图标(加锁状态,红色强调)
|
||||
- 标注:
|
||||
私钥(Private Key)
|
||||
只能自己保存
|
||||
- 下方用途说明:
|
||||
用途:
|
||||
- 解密消息
|
||||
- 生成签名
|
||||
|
||||
4️⃣ 中央箭头说明
|
||||
- 公钥 ↔ 私钥 有数学关联
|
||||
- 但:无法从公钥反推出私钥(计算上极难)
|
||||
|
||||
5️⃣ 底部总结区
|
||||
标题:一句话理解
|
||||
“公钥负责‘对外’,私钥负责‘核心安全’”
|
||||
|
||||
文字要求:
|
||||
- 全部简体中文
|
||||
- 不出现复杂公式
|
||||
- 字体清晰、教学风格
|
||||
|
||||
This is a technical teaching diagram.
|
||||
Text clarity is more important than artistic style.
|
||||
Ensure all Chinese characters are legible and not distorted.
|
||||
BIN
11-加密算法/1-RSA加密/1-RSA.png
Normal file
BIN
11-加密算法/1-RSA加密/1-RSA.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 MiB |
53
11-加密算法/1-RSA加密/2-公钥加密 → 私钥解密(保密通信流程).md
Normal file
53
11-加密算法/1-RSA加密/2-公钥加密 → 私钥解密(保密通信流程).md
Normal file
@@ -0,0 +1,53 @@
|
||||
vector educational infographic, flat flowchart
|
||||
适配 nano banana pro,教学信息图风格
|
||||
白色背景,横向 16:9
|
||||
|
||||
主题:RSA 加密通信流程(公钥加密,私钥解密)
|
||||
|
||||
整体布局:
|
||||
从左到右三栏,用留白或分隔线区分
|
||||
1️⃣ 发送方 Alice
|
||||
2️⃣ 不安全网络 Network
|
||||
3️⃣ 接收方 Bob
|
||||
|
||||
详细流程:
|
||||
|
||||
(步骤 1|Bob)
|
||||
模块:生成 RSA 密钥对
|
||||
- 公钥 (n, e):可公开
|
||||
- 私钥 (n, d):必须保密(加锁、红色)
|
||||
|
||||
(步骤 2|公钥分发)
|
||||
公钥从 Bob → Alice
|
||||
标注文字:公钥可以被任何人获取
|
||||
|
||||
(步骤 3|Alice 加密)
|
||||
- 明文消息 M(文件图标)
|
||||
- 模块:RSA 加密
|
||||
- 模块内公式(大字号、居中):
|
||||
C = M^e mod n
|
||||
- 输出:密文 C
|
||||
|
||||
(步骤 4|Network)
|
||||
- 密文 C 在网络中传输
|
||||
- 窃听者 Eve:
|
||||
气泡文字:
|
||||
“我看到了密文,但没有私钥”
|
||||
|
||||
(步骤 5|Bob 解密)
|
||||
- 模块:RSA 解密
|
||||
- 输入:密文 C + 私钥
|
||||
- 模块内公式(大字号):
|
||||
M = C^d mod n
|
||||
- 输出:原始明文 M
|
||||
|
||||
底部总结区:
|
||||
标题:为什么安全?
|
||||
- 只有私钥才能解密
|
||||
- 没有私钥,几乎无法从密文还原明文
|
||||
|
||||
文字清晰、公式完整、中文教学风格
|
||||
|
||||
This is a technical teaching diagram.
|
||||
Text clarity is more important than artistic style.
|
||||
Ensure all Chinese characters are legible and not distorted.
|
||||
55
11-加密算法/1-RSA加密/3-私钥签名 → 公钥验签(身份与防篡改).md
Normal file
55
11-加密算法/1-RSA加密/3-私钥签名 → 公钥验签(身份与防篡改).md
Normal file
@@ -0,0 +1,55 @@
|
||||
vector educational infographic, flat style
|
||||
适配 nano banana pro,结构化流程图
|
||||
白色背景,横向 16:9
|
||||
|
||||
主题:RSA 数字签名机制(私钥签名,公钥验签)
|
||||
|
||||
布局:
|
||||
左:发送方 Bob(签名者)
|
||||
中:不安全网络
|
||||
右:接收方 Alice(验证者)
|
||||
|
||||
流程步骤:
|
||||
|
||||
(步骤 1|Bob)
|
||||
生成 RSA 密钥对
|
||||
- 私钥:加锁,强调“仅自己持有”
|
||||
- 公钥:可公开
|
||||
|
||||
(步骤 2|消息处理)
|
||||
- 原始消息 M
|
||||
- 模块:Hash 计算
|
||||
显示:
|
||||
h = Hash(M)
|
||||
|
||||
(步骤 3|私钥签名)
|
||||
- 模块:RSA 签名
|
||||
- 输入:h + 私钥
|
||||
- 公式(大字号):
|
||||
S = h^d mod n
|
||||
- 得到:签名 S
|
||||
|
||||
(步骤 4|发送)
|
||||
发送:
|
||||
- 消息 M
|
||||
- 签名 S
|
||||
|
||||
(步骤 5|Alice 验证)
|
||||
- 重新计算:
|
||||
h1 = Hash(M)
|
||||
- 用 Bob 公钥计算:
|
||||
h2 = S^e mod n
|
||||
- 对比结果模块:
|
||||
h1 == h2 → 签名有效(绿色)
|
||||
不等 → 无效 / 被篡改(红色)
|
||||
|
||||
右下角总结区:
|
||||
- 私钥证明“你是谁”
|
||||
- Hash 保护“内容没被改”
|
||||
- 没有私钥无法伪造签名
|
||||
|
||||
全部中文,公式清晰,教学优先
|
||||
|
||||
This is a technical teaching diagram.
|
||||
Text clarity is more important than artistic style.
|
||||
Ensure all Chinese characters are legible and not distorted.
|
||||
BIN
11-加密算法/1-RSA加密/4-RSA-full.png
Normal file
BIN
11-加密算法/1-RSA加密/4-RSA-full.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 MiB |
124
11-加密算法/1-RSA加密/4-一张图总览.md
Normal file
124
11-加密算法/1-RSA加密/4-一张图总览.md
Normal file
@@ -0,0 +1,124 @@
|
||||
vector style educational infographic, flat design
|
||||
optimized for nano banana pro
|
||||
white background, high contrast, clean layout
|
||||
text clarity is more important than artistic style
|
||||
|
||||
【画面比例】
|
||||
横向 16:9,教学信息图
|
||||
|
||||
【主题标题(顶部居中)】
|
||||
RSA 非对称加密机制总览
|
||||
(副标题:公钥、私钥、加密、解密、签名、验签)
|
||||
|
||||
【整体布局(四大区域,强结构)】
|
||||
|
||||
┌───────────────┐
|
||||
│ 密钥生成区 │ (顶部)
|
||||
└───────────────┘
|
||||
┌───────┬───────────────┬───────┐
|
||||
│ 加密 │ 不安全网络 │ 解密 │
|
||||
│ 通信 │ & 攻击者 │ 通信 │
|
||||
└───────┴───────────────┴───────┘
|
||||
┌───────────────┐
|
||||
│ 数字签名区 │ (底部)
|
||||
└───────────────┘
|
||||
|
||||
━━━━━━━━━━━━━━━━━━
|
||||
|
||||
【区域 1|密钥生成(顶部)】
|
||||
模块标题:RSA 密钥生成
|
||||
|
||||
内容:
|
||||
- 生成一对密钥(数学相关)
|
||||
- 公钥(Public Key):(n, e)
|
||||
标注:可以公开,用于加密 / 验签
|
||||
- 私钥(Private Key):(n, d)
|
||||
标注:必须保密,用于解密 / 签名
|
||||
私钥图标必须加锁并用红色强调
|
||||
|
||||
注释小字:
|
||||
“从公钥无法在可行时间内推导出私钥”
|
||||
|
||||
━━━━━━━━━━━━━━━━━━
|
||||
|
||||
【区域 2|左侧:公钥加密(保密通信)】
|
||||
标题:公钥加密(保证只有接收方能看)
|
||||
|
||||
流程:
|
||||
1. 明文消息 M
|
||||
2. 使用接收方公钥加密
|
||||
公式(大字号、居中):
|
||||
C = M^e mod n
|
||||
3. 得到密文 C
|
||||
|
||||
箭头指向中间网络区域
|
||||
|
||||
━━━━━━━━━━━━━━━━━━
|
||||
|
||||
【区域 3|中间:不安全网络】
|
||||
标题:不安全网络
|
||||
|
||||
内容:
|
||||
- 密文 C 在网络中传输
|
||||
- 窃听者 Eve(人物图标)
|
||||
气泡文字:
|
||||
“我能看到密文,但没有私钥,解不开”
|
||||
|
||||
强调:
|
||||
- 即使公钥和密文都被看到,也无法解密
|
||||
|
||||
━━━━━━━━━━━━━━━━━━
|
||||
|
||||
【区域 4|右侧:私钥解密】
|
||||
标题:私钥解密(恢复原文)
|
||||
|
||||
流程:
|
||||
1. 接收到密文 C
|
||||
2. 使用私钥解密
|
||||
公式(大字号、居中):
|
||||
M = C^d mod n
|
||||
3. 恢复明文消息 M
|
||||
|
||||
━━━━━━━━━━━━━━━━━━
|
||||
|
||||
【区域 5|底部:数字签名(身份 + 防篡改)】
|
||||
标题:数字签名机制
|
||||
|
||||
左半(签名):
|
||||
- 消息 M
|
||||
- Hash 计算:
|
||||
h = Hash(M)
|
||||
- 使用私钥签名:
|
||||
公式(大字号):
|
||||
S = h^d mod n
|
||||
|
||||
右半(验签):
|
||||
- 接收方计算 Hash(M) → h1
|
||||
- 使用公钥验签:
|
||||
h2 = S^e mod n
|
||||
- 对比:
|
||||
h1 == h2 → 签名有效
|
||||
不等 → 消息被篡改或伪造
|
||||
|
||||
━━━━━━━━━━━━━━━━━━
|
||||
|
||||
【右下角总结区(终极一句话)】
|
||||
RSA 做了什么?
|
||||
- 公钥:对外(加密、验签)
|
||||
- 私钥:对内(解密、签名)
|
||||
- 加密保证机密性
|
||||
- 签名保证身份与完整性
|
||||
|
||||
━━━━━━━━━━━━━━━━━━
|
||||
|
||||
【强制文字与风格要求】
|
||||
- 全部使用简体中文
|
||||
- 数学公式清晰、无遮挡、字号偏大
|
||||
- 不使用渐变、不使用手绘风
|
||||
- 教科书级别信息图风格
|
||||
- 所有模块边界清晰
|
||||
|
||||
【额外稳定性增强(nano banana pro)】
|
||||
This is a technical teaching diagram.
|
||||
Ensure all Chinese text is legible and not distorted.
|
||||
Avoid decorative elements.
|
||||
BIN
11-加密算法/2-TOTP算法/1-TOTP.png
Normal file
BIN
11-加密算法/2-TOTP算法/1-TOTP.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.4 MiB |
103
11-加密算法/2-TOTP算法/1-nbp-prompt.md
Normal file
103
11-加密算法/2-TOTP算法/1-nbp-prompt.md
Normal file
@@ -0,0 +1,103 @@
|
||||
绘制一张 **白色背景的中文学术风格技术示意图**,用于**严谨、清晰地解释 TOTP(基于时间的一次性密码)算法的核心原理**,整体风格接近 **RFC 文档 / 学术论文中的算法图示**。
|
||||
|
||||
**整体风格要求:**
|
||||
|
||||
* 纯白背景
|
||||
* 极简、理性、克制的学术风格
|
||||
* 黑色与深灰色线条为主,辅以少量浅蓝色用于强调关键步骤
|
||||
* 扁平二维矢量风格(flat vector)
|
||||
* 无阴影、无渐变、无装饰性元素
|
||||
* 适合黑白打印
|
||||
* 所有文字 **全部使用简体中文**
|
||||
|
||||
---
|
||||
|
||||
**整体布局(自上而下,论文式结构):**
|
||||
|
||||
① 共享密钥(K)
|
||||
|
||||
* 显示服务器与身份验证器之间共享的秘密值
|
||||
* 使用简单矩形框表示
|
||||
* 中文标注:**“共享密钥 K(双方预先约定)”**
|
||||
|
||||
↓
|
||||
|
||||
② 时间计数器计算
|
||||
|
||||
* 展示当前 Unix 时间被时间步长整除
|
||||
* 显示公式(使用等宽字体):
|
||||
`T = ⌊(当前时间 − T0) / X⌋`
|
||||
* 注释文字:**“时间步长 X 通常为 30 秒”**
|
||||
|
||||
↓
|
||||
|
||||
③ HMAC-SHA1 运算
|
||||
|
||||
* 使用标准算法框表示
|
||||
* 输入:共享密钥 K 与时间计数器 T
|
||||
* 输出:20 字节哈希值
|
||||
* 中文标注:**“HMAC-SHA1(K, T)”**
|
||||
|
||||
↓
|
||||
|
||||
④ 动态截断
|
||||
|
||||
* 从哈希结果中截取 4 字节
|
||||
* 展示偏移量选择与整数转换
|
||||
* 中文标注:**“动态截断(Dynamic Truncation)”**
|
||||
|
||||
↓
|
||||
|
||||
⑤ 一次性密码(TOTP)
|
||||
|
||||
* 显示取模公式:
|
||||
`TOTP = 截断结果 mod 10⁶`
|
||||
* 输出一个 **6 位十进制数字**
|
||||
* 注释:**“验证码在当前时间窗口内有效”**
|
||||
|
||||
---
|
||||
|
||||
**右侧或底部补充说明(论文注释风格):**
|
||||
|
||||
* “服务器与客户端在相同时间窗口内可生成相同验证码”
|
||||
* “时间变化会自动导致验证码失效”
|
||||
* “TOTP 本质上是以时间作为计数器的 HOTP 变体”
|
||||
|
||||
---
|
||||
|
||||
**对比说明(RFC 风格小表格或注释):**
|
||||
|
||||
| 算法 | 驱动因素 |
|
||||
| ---- | ---- |
|
||||
| HOTP | 计数器 |
|
||||
| TOTP | 时间 |
|
||||
|
||||
注释文字:**“TOTP = HOTP + 时间”**
|
||||
|
||||
---
|
||||
|
||||
**图形规范:**
|
||||
|
||||
* 使用直线与箭头表示数据流
|
||||
* 所有公式使用等宽字体
|
||||
* 字号清晰、间距均匀
|
||||
* 不使用图标、不使用拟物化元素
|
||||
|
||||
---
|
||||
|
||||
**禁止内容:**
|
||||
|
||||
* 不出现真实密钥、二维码或品牌
|
||||
* 不使用渐变、阴影、3D 效果
|
||||
* 不出现营销或装饰性文案
|
||||
|
||||
---
|
||||
|
||||
**输出质量:**
|
||||
|
||||
* 矢量感强,线条清晰
|
||||
* 高分辨率,适合直接嵌入论文或 RFC 解读文档
|
||||
* 中文内容完全可读
|
||||
|
||||
`RFC 风格算法图,学术论文插图,极简技术示意图,白底矢量图`
|
||||
|
||||
BIN
11-加密算法/2-TOTP算法/2-TOTP.png
Normal file
BIN
11-加密算法/2-TOTP算法/2-TOTP.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 MiB |
833
11-加密算法/加密算法综述.md
Normal file
833
11-加密算法/加密算法综述.md
Normal file
@@ -0,0 +1,833 @@
|
||||
## 计算机编程中的加密技术全面解析
|
||||
|
||||
### 引言
|
||||
|
||||
在现代计算机系统中,加密技术是保障数据安全、隐私保护和身份认证的核心基础设施。从网上银行交易到即时通讯,从密码存储到区块链技术,加密算法无处不在。本报告将系统性地分析当前计算机编程中最常用的加密方式,深入探讨其技术原理、实现细节和应用场景。
|
||||
|
||||
### 一、对称加密(Symmetric Encryption)
|
||||
|
||||
#### 基本原理
|
||||
|
||||
对称加密是一种使用相同密钥进行数据加密和解密的加密方法。在这种体系中,加密密钥和解密密钥要么完全相同,要么存在简单的数学变换关系。这种加密方式的核心特征是:发送方和接收方必须事先共享一个秘密密钥,并确保这个密钥不被第三方获取。
|
||||
|
||||
对称加密的主要优势在于其计算效率高、处理速度快,特别适合大批量数据的加密处理。由于使用相对简单的数学运算(不涉及复杂的代数操作),对称加密算法通常比非对称加密快数百倍甚至数千倍。这使得对称加密成为实时通信、大文件传输和磁盘加密等场景的首选方案。
|
||||
|
||||
#### 典型算法实现
|
||||
|
||||
**1. AES (Advanced Encryption Standard)**
|
||||
|
||||
AES是目前最流行和广泛使用的对称加密算法,也被称为Rijndael算法。它于2001年12月被美国国家标准与技术研究院(NIST)正式批准,取代了老旧的DES标准。AES的核心特性包括:
|
||||
|
||||
- **块大小**:固定为128位(16字节)
|
||||
- **密钥长度**:支持128位、192位、256位三种规格
|
||||
- **轮数**:根据密钥长度不同,分别执行10轮(128位)、12轮(192位)或14轮(256位)加密
|
||||
- **算法结构**:基于替换-置换网络(Substitution-Permutation Network)
|
||||
|
||||
AES的安全性已经过20多年的严格审查,至今未发现实际可行的破解方法。它在硬件和软件平台上都有出色的性能表现,特别是在配备AES-NI指令集的现代处理器上,性能更是得到了显著提升。
|
||||
|
||||
**2. ChaCha20**
|
||||
|
||||
ChaCha20是由Daniel J. Bernstein于2008年设计的现代流密码。相比AES这样的块密码,ChaCha20采用了完全不同的设计理念:
|
||||
|
||||
- **密钥长度**:256位(提供极高的安全强度)
|
||||
- **Nonce**:96位随机数(确保每次加密的唯一性)
|
||||
- **计数器**:32位计数器(支持最多2^38字节的数据流)
|
||||
- **算法类型**:流密码,通过生成密钥流与明文进行XOR运算
|
||||
|
||||
ChaCha20的独特优势在于,它在没有硬件加速的设备上(如移动设备)通常比AES-GCM更快。这是因为ChaCha20仅使用加法、旋转和异或(ARX)这三种简单的位操作,避免了查表操作,从而消除了缓存时序攻击的风险。
|
||||
|
||||
在实际应用中,ChaCha20通常与Poly1305消息认证码结合使用,形成ChaCha20-Poly1305认证加密方案。这种组合被广泛应用于TLS、OpenSSH、WireGuard VPN等现代安全协议中。
|
||||
|
||||
**3. 3DES (Triple DES)**
|
||||
|
||||
3DES是DES算法的增强版本,通过将DES算法执行三次来提升安全性。其工作流程采用"加密-解密-加密"(EDE)模式:
|
||||
|
||||
- 使用密钥K1进行第一次加密
|
||||
- 使用密钥K2进行"解密"操作(实际是增加混淆)
|
||||
- 使用密钥K3进行第三次加密
|
||||
|
||||
这种设计使3DES的有效密钥长度达到168位(3×56位),实际安全强度约为112位。然而,由于性能较慢且存在已知的安全弱点(如Sweet32攻击),NIST已于2019年弃用3DES,并要求在2023年底前停止使用。目前3DES已被更安全、更高效的AES完全取代。
|
||||
|
||||
**4. 其他对称算法**
|
||||
|
||||
除了上述主流算法,还有多种对称加密算法在特定场景中使用:
|
||||
|
||||
- **Twofish**:AES候选算法之一,支持最大256位密钥,具有优秀的安全性
|
||||
- **Blowfish**:支持32-448位可变密钥长度,处理速度快但块大小仅64位
|
||||
- **Camellia**:由日本NTT和三菱开发,性能与AES相当
|
||||
- **Serpent**:另一个AES候选算法,安全裕度大但速度稍慢
|
||||
|
||||
#### 加密模式分类
|
||||
|
||||
对称加密算法可根据数据处理方式分为两大类:
|
||||
|
||||
**流密码(Stream Ciphers)**:逐字节或逐位加密数据,典型代表包括ChaCha20、RC4(已弃用)、Salsa20等。流密码特别适合实时数据传输和流媒体加密。
|
||||
|
||||
**块密码(Block Ciphers)**:将数据分成固定大小的块进行加密,如AES(128位块)、3DES(64位块)。块密码需要配合特定的操作模式使用,常见模式包括:
|
||||
- ECB(电子密码本模式):最简单但不安全
|
||||
- CBC(密码块链接模式):使用初始化向量增强安全性
|
||||
- GCM(伽罗瓦计数器模式):提供认证加密功能
|
||||
- XTS(XEX-based tweaked-codebook mode):专为磁盘加密设计
|
||||
|
||||
### 二、非对称加密(Asymmetric Encryption)
|
||||
|
||||
#### 基本原理
|
||||
|
||||
非对称加密,也称为公钥加密,使用一对数学相关但不相同的密钥:公钥和私钥。这种加密体系的核心特征是:
|
||||
|
||||
- **公钥**:可以公开分发给任何人,用于加密数据或验证签名
|
||||
- **私钥**:必须严格保密,用于解密数据或生成签名
|
||||
- **单向性**:从公钥推导私钥在计算上不可行(基于数学难题)
|
||||
|
||||
非对称加密解决了对称加密中密钥分发的难题——两个从未见过面的人可以在不安全的网络上建立安全通信,而无需事先共享秘密。这个突破性的概念是由Whitfield Diffie和Martin Hellman在1976年提出的,彻底改变了密码学领域。
|
||||
|
||||
非对称加密的两个主要应用场景:
|
||||
|
||||
1. **身份认证**:使用私钥对消息签名,任何人都可以用公钥验证签名的真实性
|
||||
2. **保密通信**:使用公钥加密消息,只有私钥持有者能够解密
|
||||
|
||||
#### 典型算法实现
|
||||
|
||||
**1. RSA (Rivest-Shamir-Adleman)**
|
||||
|
||||
RSA是1977年由Ron Rivest、Adi Shamir和Leonard Adleman发明的,是目前应用最广泛的非对称加密算法。它是唯一能够同时用于加密和数字签名的非对称算法。
|
||||
|
||||
**数学基础**:RSA的安全性建立在大整数因式分解的困难性上。给定两个大质数相乘很容易,但将乘积分解回原始质数却极其困难。
|
||||
|
||||
**密钥生成过程**:
|
||||
1. 随机选择两个大质数 p 和 q(通常各1024位或2048位)
|
||||
2. 计算模数 n = p × q
|
||||
3. 计算欧拉函数 φ(n) = (p-1) × (q-1)
|
||||
4. 选择公钥指数 e,满足 1 < e < φ(n) 且 gcd(e, φ(n)) = 1(常用65537)
|
||||
5. 计算私钥指数 d,满足 d × e ≡ 1 (mod φ(n))
|
||||
6. 公钥为 (n, e),私钥为 (n, d)
|
||||
|
||||
**加密过程**:密文 C = M^e mod n(M为明文)
|
||||
**解密过程**:明文 M = C^d mod n
|
||||
|
||||
**实际应用中的密钥长度**:
|
||||
- 2048位:当前标准,提供112位安全强度
|
||||
- 3072位:高安全性应用,等效于128位对称加密
|
||||
- 4096位:极高安全需求,但性能开销显著
|
||||
|
||||
RSA的主要劣势是计算开销大、处理速度慢。因此在实际应用中,RSA通常只用于加密对称密钥或生成数字签名,而不直接加密大量数据。
|
||||
|
||||
**2. DSA (Digital Signature Algorithm)**
|
||||
|
||||
DSA是由美国国家安全局(NSA)设计,并于1994年被NIST采纳为联邦信息处理标准FIPS 186的数字签名算法。与RSA不同,DSA是一个专用签名算法,不能用于加密。
|
||||
|
||||
**数学基础**:DSA基于有限域上的离散对数问题,这个问题与RSA使用的因式分解问题一样难以求解。
|
||||
|
||||
**工作流程**:
|
||||
1. **签名生成**:
|
||||
- 计算消息的哈希值(如SHA-256)
|
||||
- 使用私钥和随机数k生成签名对(r, s)
|
||||
- r和s都是通过复杂的模指数运算得到
|
||||
|
||||
2. **签名验证**:
|
||||
- 接收者使用发送者的公钥
|
||||
- 重新计算验证值v
|
||||
- 如果v等于r,则签名有效
|
||||
|
||||
DSA提供三个关键安全特性:
|
||||
- **消息认证**:确认消息来自声称的发送者
|
||||
- **完整性**:确保消息未被篡改
|
||||
- **不可否认性**:发送者无法否认曾签署该消息
|
||||
|
||||
值得注意的是,FIPS 186-5标准表明DSA将不再被批准用于新的数字签名生成,但可以继续用于验证旧签名。这是因为椭圆曲线变体(ECDSA)在相同安全强度下具有更小的密钥尺寸和更快的性能。
|
||||
|
||||
**3. Diffie-Hellman密钥交换**
|
||||
|
||||
Diffie-Hellman不是严格意义上的加密算法,而是一个密钥协商协议。它允许两方在不安全的信道上协商出一个共享密钥,而这个密钥从未在网络上传输过。
|
||||
|
||||
**协议流程**:
|
||||
1. 双方公开协定两个参数:大质数p和生成元g
|
||||
2. Alice选择私密随机数a,计算公开值 A = g^a mod p
|
||||
3. Bob选择私密随机数b,计算公开值 B = g^b mod p
|
||||
4. Alice和Bob交换A和B
|
||||
5. Alice计算共享密钥:s = B^a mod p
|
||||
6. Bob计算共享密钥:s = A^b mod p
|
||||
7. 双方得到相同的共享密钥s
|
||||
|
||||
**安全性**:即使攻击者截获了p、g、A和B,也无法计算出共享密钥s,因为这需要解决离散对数问题。
|
||||
|
||||
**现代变体**:
|
||||
- **静态DH**:至少一方使用固定公钥(不提供前向保密)
|
||||
- **临时DH (Ephemeral DH/DHE)**:双方都生成临时密钥对(提供前向保密性)
|
||||
|
||||
临时DH是TLS 1.3的核心密钥交换机制,确保即使长期私钥泄露,过去的会话数据仍然安全。
|
||||
|
||||
**4. ECC (Elliptic Curve Cryptography)**
|
||||
|
||||
椭圆曲线密码学是一种基于椭圆曲线代数结构的公钥加密方法。相比RSA,ECC能够用更短的密钥提供相同级别的安全性。
|
||||
|
||||
**数学基础**:椭圆曲线定义为满足方程 y² = x³ + ax + b 的点集。在密码学中,使用有限域上的椭圆曲线,这意味着x和y坐标限制在特定的整数范围内。
|
||||
|
||||
**优势对比**:
|
||||
| 安全强度 | RSA密钥长度 | ECC密钥长度 |
|
||||
|---------|------------|-----------|
|
||||
| 80位 | 1024位 | 160位 |
|
||||
| 112位 | 2048位 | 224位 |
|
||||
| 128位 | 3072位 | 256位 |
|
||||
| 256位 | 15360位 | 512位 |
|
||||
|
||||
**ECC变体**:
|
||||
- **ECDSA**:椭圆曲线数字签名算法(对应DSA)
|
||||
- **ECDH**:椭圆曲线Diffie-Hellman密钥交换
|
||||
- **ECIES**:椭圆曲线集成加密方案
|
||||
|
||||
**实际应用**:ECC广泛应用于比特币和以太坊等加密货币、移动设备安全、物联网设备以及TLS 1.3协议中。其较小的密钥尺寸意味着更低的存储需求、更少的带宽消耗和更快的计算速度。
|
||||
|
||||
### 三、SSL/TLS中RSA握手的详细流程
|
||||
|
||||
SSL/TLS握手是建立安全HTTPS连接的基础过程,其目标是在客户端和服务器之间协商加密参数、交换密钥并验证身份。以下详细解析TLS 1.2中使用RSA密钥交换的完整流程。
|
||||
|
||||
#### 握手阶段详解
|
||||
|
||||
**阶段一:初始问候与参数协商**
|
||||
|
||||
1. **ClientHello消息**
|
||||
客户端(通常是浏览器)向服务器发送握手请求,包含:
|
||||
- 支持的TLS协议版本列表(如TLS 1.2, TLS 1.1)
|
||||
- 支持的密码套件列表(按优先级排序)
|
||||
- 客户端随机数(Client Random):32字节的随机值,用于后续密钥生成
|
||||
- 会话ID:如果希望恢复之前的会话
|
||||
- 扩展字段:如SNI(服务器名称指示)、支持的签名算法等
|
||||
|
||||
2. **ServerHello消息**
|
||||
服务器响应客户端,选择加密参数:
|
||||
- 选定的TLS协议版本
|
||||
- 选定的密码套件(从客户端列表中选择)
|
||||
- 服务器随机数(Server Random):另一个32字节随机值
|
||||
- 会话ID:用于会话恢复
|
||||
- 扩展响应
|
||||
|
||||
如果客户端和服务器没有共同支持的密码套件,连接将立即终止。
|
||||
|
||||
**阶段二:服务器认证与密钥信息**
|
||||
|
||||
3. **Certificate消息**
|
||||
服务器发送其数字证书链,包含:
|
||||
- 服务器的SSL/TLS证书
|
||||
- 中间证书(如有)
|
||||
- 证书中包含服务器的公钥、域名信息、有效期、证书颁发机构(CA)签名
|
||||
|
||||
客户端会验证证书的有效性:
|
||||
- 检查证书是否由可信CA签发
|
||||
- 验证证书未过期
|
||||
- 确认证书中的域名与访问的网站匹配
|
||||
- 检查证书未被吊销
|
||||
|
||||
4. **ServerKeyExchange消息**(可选)
|
||||
对于某些密码套件(如DHE、ECDHE),服务器需要发送额外的密钥交换参数。但对于RSA密钥交换,这一步通常省略。
|
||||
|
||||
5. **CertificateRequest消息**(可选)
|
||||
如果需要客户端认证(双向TLS),服务器会请求客户端证书。
|
||||
|
||||
6. **ServerHelloDone消息**
|
||||
服务器发送此消息表明已完成其部分的握手消息发送,等待客户端响应。
|
||||
|
||||
**阶段三:客户端密钥交换与验证**
|
||||
|
||||
7. **Certificate消息**(可选)
|
||||
如果服务器请求了客户端证书,客户端在此发送其证书。
|
||||
|
||||
8. **ClientKeyExchange消息**
|
||||
这是RSA握手中最关键的步骤:
|
||||
- 客户端生成48字节的预主密钥(Pre-Master Secret)
|
||||
- 使用服务器证书中的公钥加密预主密钥
|
||||
- 将加密后的预主密钥发送给服务器
|
||||
|
||||
只有拥有对应私钥的服务器才能解密这个预主密钥,从而证明服务器的身份。
|
||||
|
||||
9. **CertificateVerify消息**(可选)
|
||||
如果发送了客户端证书,客户端需要证明拥有对应的私钥:
|
||||
- 对之前所有握手消息的哈希进行签名
|
||||
- 使用客户端私钥签名
|
||||
- 发送签名给服务器验证
|
||||
|
||||
**阶段四:密钥生成与握手完成**
|
||||
|
||||
10. **会话密钥推导**
|
||||
此时双方都拥有三个关键信息:
|
||||
- 客户端随机数(Client Random)
|
||||
- 服务器随机数(Server Random)
|
||||
- 预主密钥(Pre-Master Secret)
|
||||
|
||||
双方使用相同的伪随机函数(PRF)从这三个值派生出主密钥(Master Secret),然后再从主密钥派生出实际使用的会话密钥:
|
||||
- 对称加密密钥(用于加密数据)
|
||||
- MAC密钥(用于消息完整性验证)
|
||||
- 初始化向量IV(用于某些加密模式如CBC)
|
||||
|
||||
11. **ChangeCipherSpec消息**
|
||||
双方各自发送此消息,表明后续通信将使用刚协商的加密参数和密钥。这是一个单字节消息(0x01)。
|
||||
|
||||
12. **Finished消息**
|
||||
双方各自发送加密的Finished消息,包含:
|
||||
- 所有之前握手消息的哈希值
|
||||
- 使用刚生成的会话密钥加密
|
||||
|
||||
这个消息验证:
|
||||
- 双方正确推导出了相同的密钥
|
||||
- 握手过程未被篡改
|
||||
- 密钥交换成功
|
||||
|
||||
如果双方的Finished消息都验证通过,TLS握手完成,后续的应用数据将使用对称加密传输。
|
||||
|
||||
#### RSA握手的安全考虑
|
||||
|
||||
虽然RSA握手机制经过了长期验证,但它存在一个重要缺陷:**不提供前向保密性**(Forward Secrecy)。如果服务器的私钥在未来被攻击者获取,攻击者可以解密所有之前被录制的通信内容(因为可以解密预主密钥)。
|
||||
|
||||
正因如此,TLS 1.3完全移除了RSA密钥交换,强制使用提供前向保密的临时Diffie-Hellman(DHE/ECDHE)机制。在这种机制下,即使长期私钥泄露,之前的会话密钥仍然无法被推导出来。
|
||||
|
||||
### 四、SHA-256算法的实现原理与安全性
|
||||
|
||||
SHA-256是SHA-2(Secure Hash Algorithm 2)家族中最广泛使用的成员,由美国国家安全局(NSA)设计,于2001年由NIST发布。"256"表示该算法产生256位(32字节)的固定长度哈希输出,无论输入数据有多大。
|
||||
|
||||
#### 算法实现原理
|
||||
|
||||
**第一阶段:预处理**
|
||||
|
||||
1. **消息填充(Padding)**
|
||||
SHA-256要求输入消息的长度必须是512位(64字节)的整数倍。填充过程如下:
|
||||
- 在原始消息末尾添加一个'1'位
|
||||
- 添加若干个'0'位,使得(消息长度 + 填充长度 + 64位)≡ 0 (mod 512)
|
||||
- 最后64位存储原始消息的长度(以位为单位)
|
||||
|
||||
例如,如果原始消息是"abc"(24位),填充后变成:
|
||||
```
|
||||
01100001 01100010 01100011 1 000...000 00...011000
|
||||
(a) (b) (c) (分隔) (423个0) (24的二进制)
|
||||
```
|
||||
|
||||
2. **消息分块(Parsing)**
|
||||
将填充后的消息分成N个512位的消息块M₁, M₂, ..., M_N。每个块将被独立处理。
|
||||
|
||||
3. **初始化哈希值**
|
||||
设置8个32位初始哈希值H₀到H₇,这些值是前8个质数(2, 3, 5, 7, 11, 13, 17, 19)的平方根小数部分的前32位:
|
||||
```
|
||||
H₀ = 0x6a09e667
|
||||
H₁ = 0xbb67ae85
|
||||
H₂ = 0x3c6ef372
|
||||
...
|
||||
H₇ = 0x5be0cd19
|
||||
```
|
||||
|
||||
**第二阶段:哈希计算**
|
||||
|
||||
对每个512位消息块执行以下操作:
|
||||
|
||||
1. **准备消息调度(Message Schedule)**
|
||||
将512位块分成16个32位字W₀到W₁₅,然后扩展到64个字W₀到W₆₃:
|
||||
```
|
||||
对于 i = 16 到 63:
|
||||
W[i] = σ₁(W[i-2]) + W[i-7] + σ₀(W[i-15]) + W[i-16]
|
||||
```
|
||||
|
||||
其中σ₀和σ₁是位旋转和移位操作的组合:
|
||||
- σ₀(x) = ROTR⁷(x) ⊕ ROTR¹⁸(x) ⊕ SHR³(x)
|
||||
- σ₁(x) = ROTR¹⁷(x) ⊕ ROTR¹⁹(x) ⊕ SHR¹⁰(x)
|
||||
|
||||
2. **初始化工作变量**
|
||||
设置8个工作变量a到h,初始值为当前哈希值H₀到H₇。
|
||||
|
||||
3. **主循环(64轮)**
|
||||
对于每一轮t(0到63):
|
||||
```
|
||||
T₁ = h + Σ₁(e) + Ch(e,f,g) + K[t] + W[t]
|
||||
T₂ = Σ₀(a) + Maj(a,b,c)
|
||||
h = g
|
||||
g = f
|
||||
f = e
|
||||
e = d + T₁
|
||||
d = c
|
||||
c = b
|
||||
b = a
|
||||
a = T₁ + T₂
|
||||
```
|
||||
|
||||
其中:
|
||||
- **Ch(e,f,g)** = (e AND f) XOR (NOT e AND g):选择函数
|
||||
- **Maj(a,b,c)** = (a AND b) XOR (a AND c) XOR (b AND c):多数函数
|
||||
- **Σ₀(a)** = ROTR²(a) ⊕ ROTR¹³(a) ⊕ ROTR²²(a)
|
||||
- **Σ₁(e)** = ROTR⁶(e) ⊕ ROTR¹¹(e) ⊕ ROTR²⁵(e)
|
||||
- **K[t]**:第t个预定义常量(前64个质数的立方根小数部分的前32位)
|
||||
|
||||
4. **更新哈希值**
|
||||
处理完当前块后,将工作变量加到哈希值上:
|
||||
```
|
||||
H₀ = H₀ + a
|
||||
H₁ = H₁ + b
|
||||
...
|
||||
H₇ = H₇ + h
|
||||
```
|
||||
|
||||
5. **最终输出**
|
||||
处理完所有消息块后,将H₀到H₇连接起来,得到256位的最终哈希值。
|
||||
|
||||
#### SHA-256为何难以破解
|
||||
|
||||
**1. 碰撞抵抗(Collision Resistance)**
|
||||
|
||||
SHA-256具有极强的碰撞抵抗能力,主要来源于其巨大的输出空间。256位哈希意味着存在2²⁵⁶ ≈ 1.16×10⁷⁷种可能的哈希值。要找到两个不同的输入产生相同的哈希值(碰撞),需要:
|
||||
|
||||
- **生日攻击**:根据生日悖论,需要大约2¹²⁸(约3.4×10³⁸)次尝试才有50%的概率找到碰撞
|
||||
- **当前最佳攻击**:学术界最先进的攻击只能破解简化到31-38步的SHA-256(完整版64步)
|
||||
|
||||
如果地球上每个人(70亿人)每秒生成100万个对象,持续100万年,找到碰撞的概率仍然微乎其微(约10⁻¹⁷)。
|
||||
|
||||
**2. 原像抵抗(Preimage Resistance)**
|
||||
|
||||
给定一个哈希值h,找到任何输入m使得SHA-256(m) = h被称为原像攻击。对于SHA-256,这需要2²⁵⁶次暴力尝试。即使是量子计算机使用Grover算法,仍需要2¹²⁸次操作。
|
||||
|
||||
假设量子计算机每秒执行100亿次计算,破解一个SHA-256哈希需要:
|
||||
```
|
||||
2¹²⁸ / (10¹⁰ × 60 × 60 × 24 × 365) ≈ 1.08×10²¹年
|
||||
(宇宙年龄仅约138亿年)
|
||||
```
|
||||
|
||||
**3. 雪崩效应(Avalanche Effect)**
|
||||
|
||||
SHA-256的设计确保输入的微小变化会导致输出的巨大变化。例如:
|
||||
- SHA-256("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
|
||||
- SHA-256("hallo") = d3751d33f9cd5049c4af2b462735457e4d3baf130bcbb87f389e349fbaeb20b9
|
||||
|
||||
仅改变一个字符,输出中有128位(50%)发生了变化。这种特性使得任何试图逆向工程或寻找规律的尝试都变得极其困难。
|
||||
|
||||
**4. 多层混淆结构**
|
||||
|
||||
SHA-256的64轮处理形成了一个复杂的迷宫。每一轮使用不同的常量K[t]、不同的消息字W[t],并通过多个非线性函数(Ch、Maj、Σ₀、Σ₁)进行混合。即使攻击者能够逆向一轮,还需要面对其他63轮的障碍。
|
||||
|
||||
**5. 无已知数学弱点**
|
||||
|
||||
SHA-256已经经受了超过20年的密码学分析,全球密码学家未能发现任何实用的数学弱点。与之相比:
|
||||
- **MD5**:已被完全破解,可在秒级时间内生成碰撞
|
||||
- **SHA-1**:2017年首次成功碰撞攻击(需要巨大计算资源)
|
||||
- **SHA-256**:无已知碰撞,仍被认为安全
|
||||
|
||||
#### 实际应用场景
|
||||
|
||||
SHA-256在现代信息技术中有广泛应用:
|
||||
|
||||
1. **区块链技术**:比特币使用SHA-256进行工作量证明和区块哈希
|
||||
2. **数字签名**:RSA和ECDSA签名前都需要对消息进行SHA-256哈希
|
||||
3. **密码存储**:将用户密码的SHA-256哈希(加盐)存储在数据库中
|
||||
4. **SSL/TLS证书**:证书指纹和签名验证
|
||||
5. **文件完整性验证**:软件下载的校验和
|
||||
|
||||
需要注意的是,直接使用SHA-256存储密码是**不够安全的**,因为GPU可以每秒计算数十亿次SHA-256哈希。正确的做法是使用专门设计的慢速哈希函数,如bcrypt、scrypt或Argon2,它们通过增加计算复杂度来抵御暴力破解。
|
||||
|
||||
### 五、CPU中的AES加密模块
|
||||
|
||||
#### AES-NI指令集概述
|
||||
|
||||
AES-NI(Advanced Encryption Standard New Instructions)是Intel于2008年3月首次提出的x86指令集扩展,旨在通过硬件加速提升AES加密和解密的性能。这套指令集在2010年随着Intel Westmere微架构首次实现,随后AMD也在其处理器中集成了相同的指令。
|
||||
|
||||
#### 技术实现
|
||||
|
||||
**指令集组成**
|
||||
|
||||
AES-NI由六条新指令组成,每条指令对应AES算法中计算密集的特定部分:
|
||||
|
||||
1. **AESENC**(AES加密轮)
|
||||
- 执行一轮AES加密操作
|
||||
- 将AES的四个步骤(ShiftRows、SubBytes、MixColumns、AddRoundKey)合并为单条指令
|
||||
- 每轮只需1-2个CPU时钟周期
|
||||
|
||||
2. **AESENCLAST**(最后一轮AES加密)
|
||||
- 执行最后一轮加密,不包含MixColumns步骤
|
||||
- 合并ShiftRows、SubBytes和AddRoundKey
|
||||
|
||||
3. **AESDEC**(AES解密轮)
|
||||
- 执行一轮AES解密操作
|
||||
- 合并InvShiftRows、InvSubBytes、InvMixColumns、AddRoundKey
|
||||
|
||||
4. **AESDECLAST**(最后一轮AES解密)
|
||||
- 执行最后一轮解密
|
||||
- 合并InvShiftRows、InvSubBytes和AddRoundKey
|
||||
|
||||
5. **AESKEYGENASSIST**(密钥生成辅助)
|
||||
- 辅助生成AES轮密钥(密钥扩展过程)
|
||||
- 简化密钥调度算法的实现
|
||||
|
||||
6. **AESIMC**(逆混合列辅助)
|
||||
- 将加密轮密钥转换为解密可用的格式
|
||||
- 应用逆混合列变换
|
||||
|
||||
**数据处理方式**
|
||||
|
||||
AES-NI指令操作128位的XMM寄存器(或256/512位的YMM/ZMM寄存器,在AVX/AVX-512中)。典型指令格式为:
|
||||
```assembly
|
||||
AESENC xmm1, xmm2/m128
|
||||
```
|
||||
其中xmm1是源和目标寄存器,xmm2/m128是第二个操作数(可以是寄存器或内存)。
|
||||
|
||||
#### 性能提升
|
||||
|
||||
**加速效果**
|
||||
|
||||
使用AES-NI相比纯软件实现,性能提升显著:
|
||||
- **吞吐量提升**:3-10倍,具体取决于加密模式和数据块大小
|
||||
- **延迟降低**:单块加密时间从数百个时钟周期降至几十个
|
||||
- **能效提升**:更少的CPU周期意味着更低的功耗
|
||||
|
||||
例如,在Intel Core i7-980X上测试:
|
||||
| 加密模式 | 软件实现速度 | AES-NI速度 | 提升倍数 |
|
||||
|---------|------------|-----------|---------|
|
||||
| ECB | 130 MB/s | 1200 MB/s | 9.2x |
|
||||
| CBC加密 | 130 MB/s | 1200 MB/s | 9.2x |
|
||||
| CBC解密 | 130 MB/s | 3200 MB/s | 24.6x |
|
||||
| CTR | 130 MB/s | 3500 MB/s | 26.9x |
|
||||
|
||||
#### 安全性优势
|
||||
|
||||
除了性能提升,AES-NI还显著改善了安全性:
|
||||
|
||||
**1. 抵御侧信道攻击**
|
||||
|
||||
传统软件实现AES依赖查找表(S-box),这会导致多种侧信道攻击风险:
|
||||
- **缓存时序攻击**:攻击者通过测量缓存访问时间推断密钥信息
|
||||
- **功耗分析**:通过监控CPU功耗波动获取密钥
|
||||
- **电磁辐射分析**:捕获处理器的电磁泄漏
|
||||
|
||||
AES-NI完全在硬件中执行加密运算,不需要查找表,因此消除了这些攻击向量。所有操作都在恒定时间内完成,不依赖于密钥或数据的具体值。
|
||||
|
||||
**2. 密钥隔离**
|
||||
|
||||
在支持的系统中,密钥可以保存在专用寄存器中,减少在内存中暴露的时间,降低密钥被恶意软件窃取的风险。
|
||||
|
||||
#### 实际应用场景
|
||||
|
||||
**1. 全盘加密**
|
||||
|
||||
现代操作系统的磁盘加密功能(如Windows BitLocker、Linux dm-crypt)大量使用AES-NI,使得加密开销几乎可以忽略不计。没有AES-NI的系统可能会遇到明显的性能下降。
|
||||
|
||||
**2. VPN和网络安全**
|
||||
|
||||
VPN服务(如OpenVPN、IPsec)使用AES加密所有网络流量。AES-NI使得高速VPN连接成为可能,在千兆甚至万兆网络上也能保持低延迟。
|
||||
|
||||
**3. SSL/TLS通信**
|
||||
|
||||
Web服务器和浏览器使用AES-NI加速HTTPS连接。这对于处理大量并发连接的服务器尤为重要,可以显著降低CPU使用率。
|
||||
|
||||
**4. 数据库加密**
|
||||
|
||||
支持透明数据加密(TDE)的数据库系统利用AES-NI实现实时加密和解密,对查询性能的影响降到最低。
|
||||
|
||||
#### 如何检测和使用AES-NI
|
||||
|
||||
**Linux系统检测**
|
||||
```bash
|
||||
# 方法1:查看CPU标志
|
||||
grep -o 'aes' /proc/cpuinfo
|
||||
|
||||
# 方法2:使用lscpu
|
||||
lscpu | grep -i aes
|
||||
|
||||
# 方法3:使用cpuid工具
|
||||
cpuid | grep -i aes
|
||||
```
|
||||
|
||||
**编程接口**
|
||||
|
||||
开发者可以通过多种方式利用AES-NI:
|
||||
1. **编译器内置函数**(Intrinsics):直接调用AES-NI指令
|
||||
2. **密码学库**:OpenSSL、Intel IPP、Crypto++等自动检测并使用AES-NI
|
||||
3. **汇编优化**:对性能关键代码手写汇编
|
||||
|
||||
大多数现代密码学库会在运行时自动检测CPU是否支持AES-NI,并选择最优实现路径。
|
||||
|
||||
### 六、TOTP算法原理与分类
|
||||
|
||||
#### TOTP算法定义
|
||||
|
||||
TOTP(Time-based One-Time Password,基于时间的一次性密码)是一种用于生成临时验证码的算法,广泛应用于双因素认证(2FA)系统。它是HOTP(HMAC-based One-Time Password)算法的时间变体,于2011年5月被IETF采纳为标准RFC 6238。
|
||||
|
||||
TOTP的核心思想是:使用当前时间作为动态因子,结合预共享的秘密密钥,生成仅在短时间内有效的一次性密码。这种方法解决了HOTP基于计数器的同步问题,只要客户端和服务器的时钟大致同步,就能正常工作。
|
||||
|
||||
#### 算法原理详解
|
||||
|
||||
**数学公式**
|
||||
|
||||
TOTP算法可以用以下公式表示:
|
||||
```
|
||||
TOTP = HOTP(K, T)
|
||||
其中:T = floor((Current_Unix_Time - T₀) / X)
|
||||
```
|
||||
|
||||
参数说明:
|
||||
- **K**:共享密钥(Shared Secret),双方预先协商的秘密
|
||||
- **T₀**:起始时间点,默认为0(Unix纪元:1970年1月1日00:00:00 UTC)
|
||||
- **X**:时间步长(Time Step),默认为30秒
|
||||
- **T**:时间计数器,当前时间窗口的编号
|
||||
|
||||
**生成流程**
|
||||
|
||||
完整的TOTP生成过程包含以下步骤:
|
||||
|
||||
**步骤1:计算时间计数器**
|
||||
```python
|
||||
import time
|
||||
import math
|
||||
|
||||
def generate_counter_value():
|
||||
current_unix_time = int(time.time()) # 获取当前Unix时间戳(秒)
|
||||
time_step = 30 # 时间步长(秒)
|
||||
counter = math.floor(current_unix_time / time_step)
|
||||
return counter
|
||||
```
|
||||
|
||||
例如,如果当前Unix时间戳是1704470400(2024-01-05 16:00:00 UTC):
|
||||
```
|
||||
T = floor(1704470400 / 30) = 56815680
|
||||
```
|
||||
|
||||
**步骤2:生成HMAC哈希**
|
||||
|
||||
使用共享密钥K和时间计数器T作为输入,计算HMAC-SHA1(或SHA-256/SHA-512):
|
||||
```python
|
||||
import hmac
|
||||
import hashlib
|
||||
|
||||
def generate_hmac(secret_key, counter):
|
||||
# 将计数器转换为8字节大端序
|
||||
counter_bytes = counter.to_bytes(8, byteorder='big')
|
||||
|
||||
# 计算HMAC-SHA1
|
||||
hmac_hash = hmac.new(
|
||||
secret_key.encode('utf-8'),
|
||||
counter_bytes,
|
||||
hashlib.sha1
|
||||
).digest()
|
||||
|
||||
return hmac_hash # 返回20字节的哈希值
|
||||
```
|
||||
|
||||
HMAC(Hash-based Message Authentication Code)本身是一个两轮哈希计算过程:
|
||||
```
|
||||
HMAC(K, M) = H((K ⊕ opad) || H((K ⊕ ipad) || M))
|
||||
```
|
||||
其中H是哈希函数(如SHA-1),ipad和opad是固定的填充常量。
|
||||
|
||||
**步骤3:动态截断(Dynamic Truncation)**
|
||||
|
||||
从20字节的HMAC结果中提取4字节(32位):
|
||||
```python
|
||||
def dynamic_truncate(hmac_hash):
|
||||
# 取最后一个字节的低4位作为偏移量
|
||||
offset = hmac_hash[-1] & 0x0F
|
||||
|
||||
# 从偏移位置提取4字节
|
||||
truncated = int.from_bytes(hmac_hash[offset:offset+4], byteorder='big')
|
||||
|
||||
# 清除最高位(确保为正数)
|
||||
truncated &= 0x7FFFFFFF
|
||||
|
||||
return truncated
|
||||
```
|
||||
|
||||
这种动态截断方法确保输出的随机性和均匀分布。
|
||||
|
||||
**步骤4:生成最终OTP**
|
||||
```python
|
||||
def generate_totp(truncated_value, digits=6):
|
||||
# 对10^digits取模,得到指定位数的OTP
|
||||
otp = truncated_value % (10 ** digits)
|
||||
|
||||
# 左侧补零,确保位数正确
|
||||
return str(otp).zfill(digits)
|
||||
```
|
||||
|
||||
**完整示例**
|
||||
|
||||
假设:
|
||||
- 共享密钥:JBSWY3DPEHPK3PXP(Base32编码)
|
||||
- 当前时间计数器:56815680
|
||||
- 期望输出:6位数字
|
||||
|
||||
执行流程:
|
||||
1. T = 56815680
|
||||
2. HMAC-SHA1(K, T) = [某20字节哈希值]
|
||||
3. 动态截断 = 1234567890
|
||||
4. TOTP = 1234567890 mod 1000000 = 567890
|
||||
|
||||
最终生成的验证码是:**567890**
|
||||
|
||||
#### TOTP的安全机制
|
||||
|
||||
**1. 时间窗口**
|
||||
|
||||
TOTP的关键安全特性是验证码的时效性。默认30秒的时间窗口意味着:
|
||||
- 每30秒生成一个新的验证码
|
||||
- 旧的验证码在新时间窗口立即失效
|
||||
- 即使验证码被截获,攻击者只有短暂的时间可用
|
||||
|
||||
实际实现中,服务器通常允许一定的时间偏差(±1个时间步)来处理网络延迟和时钟漂移:
|
||||
```python
|
||||
def verify_totp(user_otp, secret_key, tolerance=1):
|
||||
current_counter = generate_counter_value()
|
||||
|
||||
# 检查当前和相邻的时间窗口
|
||||
for offset in range(-tolerance, tolerance + 1):
|
||||
counter = current_counter + offset
|
||||
expected_otp = generate_totp_from_counter(secret_key, counter)
|
||||
|
||||
if user_otp == expected_otp:
|
||||
return True
|
||||
|
||||
return False
|
||||
```
|
||||
|
||||
**2. 防重放攻击**
|
||||
|
||||
服务器应该记录已使用的OTP,防止同一个验证码在同一时间窗口内被多次使用。这确保了"一次性"的特性。
|
||||
|
||||
**3. 共享密钥安全**
|
||||
|
||||
共享密钥是TOTP安全的基础,必须:
|
||||
- 使用密码学安全的随机数生成器创建
|
||||
- 通过安全信道传输(通常用QR码在设备间传递)
|
||||
- 在客户端和服务器端安全存储(加密保存)
|
||||
- 永不在网络上明文传输
|
||||
|
||||
#### TOTP算法的分类
|
||||
|
||||
**1. 算法类型归属**
|
||||
|
||||
TOTP**不属于加密算法**,而属于**认证算法**:
|
||||
|
||||
| 算法类别 | 目的 | 可逆性 | 代表算法 |
|
||||
|---------|------|-------|---------|
|
||||
| 加密算法 | 保护数据机密性 | 可逆(可解密) | AES, RSA |
|
||||
| 哈希算法 | 数据完整性验证 | 不可逆 | SHA-256 |
|
||||
| 认证算法 | 身份验证 | 不适用 | **TOTP**, HOTP |
|
||||
| 消息认证码 | 完整性+认证 | 不可逆 | HMAC |
|
||||
|
||||
**2. TOTP的技术栈**
|
||||
|
||||
从技术组成来看,TOTP基于以下层次结构:
|
||||
|
||||
```
|
||||
TOTP(认证层)
|
||||
↓
|
||||
HOTP(一次性密码层)
|
||||
↓
|
||||
HMAC(消息认证层)
|
||||
↓
|
||||
SHA-1/SHA-256/SHA-512(哈希层)
|
||||
```
|
||||
|
||||
虽然TOTP使用了哈希函数和对称密钥原理,但其主要用途是**身份认证**而非数据加密。
|
||||
|
||||
**3. 与HOTP的区别**
|
||||
|
||||
| 特性 | HOTP | TOTP |
|
||||
|------|------|------|
|
||||
| 动态因子 | 计数器(事件触发) | 时间(周期性) |
|
||||
| 同步要求 | 需要服务器记录计数器 | 只需时钟同步 |
|
||||
| OTP有效期 | 直到下次生成 | 30-90秒 |
|
||||
| 安全性 | 较低(OTP可能长期有效) | 较高(自动过期) |
|
||||
| 应用场景 | 硬件令牌 | 移动应用(Google Authenticator, Authy) |
|
||||
|
||||
#### 实际应用与实现
|
||||
|
||||
**主流TOTP实现**
|
||||
|
||||
1. **Google Authenticator**:使用TOTP标准,6位数字,30秒时间窗口
|
||||
2. **Microsoft Authenticator**:支持TOTP和推送通知
|
||||
3. **Authy**:多设备同步的TOTP应用
|
||||
4. **YubiKey**:硬件令牌支持TOTP
|
||||
|
||||
**开发者集成示例**
|
||||
|
||||
服务器端通常需要:
|
||||
1. 为每个用户生成唯一的共享密钥
|
||||
2. 以Base32格式展示密钥(或生成QR码)
|
||||
3. 用户扫描QR码将密钥导入认证应用
|
||||
4. 验证用户输入的6位验证码
|
||||
|
||||
Python实现示例:
|
||||
```python
|
||||
import pyotp
|
||||
|
||||
# 生成密钥
|
||||
secret = pyotp.random_base32()
|
||||
print(f"Secret Key: {secret}")
|
||||
|
||||
# 生成二维码URI
|
||||
uri = pyotp.totp.TOTP(secret).provisioning_uri(
|
||||
name="user@example.com",
|
||||
issuer_name="My App"
|
||||
)
|
||||
print(f"QR Code URI: {uri}")
|
||||
|
||||
# 验证用户输入的OTP
|
||||
totp = pyotp.TOTP(secret)
|
||||
user_input = "123456"
|
||||
is_valid = totp.verify(user_input) # 自动处理时间窗口
|
||||
```
|
||||
|
||||
**安全最佳实践**
|
||||
|
||||
1. **密钥管理**:使用至少160位(20字节)的随机密钥
|
||||
2. **时钟同步**:使用NTP确保服务器时钟准确
|
||||
3. **限流保护**:限制验证尝试次数,防止暴力破解
|
||||
4. **备份码**:提供一次性备份码,防止用户丢失设备
|
||||
5. **渐进迁移**:支持更强的哈希算法(SHA-256/SHA-512)
|
||||
|
||||
### 七、加密技术对比与选择指南
|
||||
|
||||
#### 对称加密 vs 非对称加密
|
||||
|
||||
| 维度 | 对称加密 | 非对称加密 |
|
||||
|------|---------|-----------|
|
||||
| 密钥数量 | 1个共享密钥 | 2个密钥(公钥+私钥)|
|
||||
| 速度 | 非常快(适合大数据) | 慢(100-1000倍慢于对称) |
|
||||
| 密钥分发 | 困难(需要安全信道) | 容易(公钥可公开) |
|
||||
| 典型密钥长度 | 128-256位 | 2048-4096位(RSA) |
|
||||
| 主要用途 | 数据加密 | 密钥交换、数字签名 |
|
||||
| 代表算法 | AES, ChaCha20 | RSA, ECC |
|
||||
|
||||
**混合加密方案**
|
||||
|
||||
实际应用中通常采用混合方案:
|
||||
1. 使用非对称加密交换对称密钥(解决密钥分发问题)
|
||||
2. 使用对称加密保护实际数据(解决性能问题)
|
||||
|
||||
这正是TLS/SSL采用的策略:RSA或ECDHE交换密钥,AES或ChaCha20加密数据。
|
||||
|
||||
#### 算法选择建议
|
||||
|
||||
**对于新项目**:
|
||||
- **对称加密**:优先选择AES-256-GCM或ChaCha20-Poly1305
|
||||
- **非对称加密**:优先选择ECC(ECDSA/ECDH)或RSA-2048+
|
||||
- **哈希算法**:使用SHA-256或更高(SHA-384/SHA-512)
|
||||
- **密码存储**:使用Argon2、bcrypt或scrypt,而非单纯的SHA-256
|
||||
|
||||
**遗留系统兼容**:
|
||||
- 避免使用DES、3DES、MD5、SHA-1(已知安全弱点)
|
||||
- 如必须兼容,应在外层增加额外的安全措施
|
||||
|
||||
### 八、结论
|
||||
|
||||
现代密码学已经发展成为一个复杂而精密的技术体系,从对称加密的高效性到非对称加密的灵活性,从哈希函数的完整性保障到认证协议的身份验证,每种技术都在数字安全生态系统中扮演着不可替代的角色。
|
||||
|
||||
**关键要点总结**:
|
||||
|
||||
1. **对称加密**(如AES)提供高性能的数据保护,是大规模数据加密的基石,现代CPU的硬件加速使其性能更加卓越。
|
||||
|
||||
2. **非对称加密**(如RSA、ECC)解决了密钥分发难题,虽然速度较慢但在密钥交换和数字签名中不可或缺。
|
||||
|
||||
3. **SSL/TLS握手**展示了如何优雅地结合对称和非对称加密,在安全性和性能之间取得平衡。TLS 1.3通过引入前向保密进一步提升了安全性。
|
||||
|
||||
4. **SHA-256**作为最广泛使用的哈希算法,其设计的数学基础确保了即使在量子计算时代来临前都难以被破解。其2²⁵⁶的输出空间和64轮混淆机制构成了坚不可摧的防线。
|
||||
|
||||
5. **AES-NI硬件加速**不仅显著提升了加密性能,更重要的是消除了软件实现中的侧信道攻击威胁,展示了硬件安全的重要性。
|
||||
|
||||
6. **TOTP算法**证明了简单而优雅的设计可以提供强大的安全保障。它基于时间和共享密钥的机制,在双因素认证中起到了关键作用。
|
||||
|
||||
**未来展望**:
|
||||
|
||||
随着量子计算的发展,传统的RSA和ECC可能面临威胁,后量子密码学(如基于格的密码学、ML-KEM)正在成为研究热点。同时,零知识证明、同态加密等前沿技术也在不断拓展密码学的应用边界。
|
||||
|
||||
对于开发者而言,理解这些加密技术的原理和适用场景,选择合适的算法并正确实现,是构建安全系统的基础。永远记住:**密码学工具只是手段,正确使用这些工具才是保障安全的关键**。
|
||||
54
12-交付特战队/0-prompt.md
Normal file
54
12-交付特战队/0-prompt.md
Normal file
@@ -0,0 +1,54 @@
|
||||
你是一名优秀的管理组长,具备充分的战略考虑,熟悉中国国企内部组织架构的规则与游戏规则。
|
||||
|
||||
我们本来承担了很多的工作内容,由于之前的组织架构不够合理,我们可能存在大量打黑工的情况,因此我自己撰写了[1-原始文档.md](1-%E5%8E%9F%E5%A7%8B%E6%96%87%E6%A1%A3.md),经过优化与调整 最终上报给领导审批的方案是[2-优化版本.md](2-%E4%BC%98%E5%8C%96%E7%89%88%E6%9C%AC.md).
|
||||
|
||||
效率提升参照的文件为
|
||||
我建议的组织架构图片为
|
||||
|
||||
经过领导的考虑,领导建议参考去年郭忠勇场景特战队模式先成立交付特战队进行试点,如果效果好再考虑分拆成组。参考场景特战队的组建方案按照他们的想法梳理一版交付特战队的方案吧,考核目标和考核办法可以先提供想法和思路,后续我们会拉着研发产品和市场讨论后再确认
|
||||
[3-统一交付特战队.md](3-%E7%BB%9F%E4%B8%80%E4%BA%A4%E4%BB%98%E7%89%B9%E6%88%98%E9%98%9F.md)
|
||||
|
||||
其他信息
|
||||
1. 部门存在技术支撑中心的交付团队,但是他们的角色偏向于交付经理.我们团队的定义是向上承接飞行服务平台和监管平台的市场化交付需求,需要联合技术支撑中心的交付团队,共同完成市场化交付任务.我们侧重于技术类型的交付,如售中交付部署,售中的定制化开发的持续交付部署,售后的版本更新,故障维护等.需要明确责任边界,但是暂时有点困难.
|
||||
2. 考核内容
|
||||
1. 由于市场化项目很多,我们保证的是技术类交付的按时按质完成
|
||||
1. 不要写交付效能提升,而是从交付部署开始时间,在规定时间内完成
|
||||
2. 区分不同的交付部署环境,不同系统部署
|
||||
3. 飞行服务平台
|
||||
1. 纯内网(4A系统) 7个工作日 服务器可以访问公网 3个工作日
|
||||
4. 监管平台
|
||||
1. 纯内网(4A系统) 7个工作日 服务器可以访问公网 4个工作日
|
||||
5. 飞行服务平台+监管平台
|
||||
1. 纯内网(4A系统) 8个工作日 服务器可以访问公网 5个工作日
|
||||
6. 已有任意系统,新增部署另一系统
|
||||
1. 纯内网(4A系统) 3工作日 服务器可以访问公网 2个工作日
|
||||
2. 支持监管与飞行服务平台新功能标准化部署方案
|
||||
1. 如业务系统修改底层架构,新增中间件,底层的标准化部署方案就需要更新
|
||||
2. 应该按照先研发团队自行部署试用,然后才能稳定标准化方案的流程
|
||||
3. 飞行服务平台与监管平台的标准化部署方案
|
||||
3. 项目持续交付更新,需要及时响应,按时完成
|
||||
4. 重要运行环境稳定性保障维护
|
||||
1. DEMO环境 研发考核类项目(CHBN对标测试)环境
|
||||
2. 建立专门的运行环境保障SOP
|
||||
3. 发挥SRE工程师的职责
|
||||
3. 特战队成立的背景
|
||||
1. 任一珂(组长) 王子文 袁雪波 这几年一直都在干相关的工作
|
||||
2. 交付部署就像是果树的果实,是从基础架构设计,运行环境维护,多年技术积累沉淀(犁地,施肥,播种,收割)出来的,水到渠成
|
||||
3. 多年的经验积累与技术沉淀,深谙底层架构的通电与堵点,基础架构技术的优化与实现方法
|
||||
4. 特战队的人员
|
||||
1. 领导安排的人员是 任一珂 王子文 袁雪波 郑岩 龙卫
|
||||
2. 请基于新的特战队人员安排人员职责划分
|
||||
3. 王子文 袁雪波是核心成员
|
||||
5. RMDC系统
|
||||
1. 不只是项目信息管理,重点功能介绍
|
||||
2. 交付物构建:能够一个界面实现从代码到项目交付物的产出
|
||||
3. 持续交付更新:能够实现全国任意项目的一键跨公网微服务升级更新
|
||||
4. 项目统一监控中心:可以查看全国任意项目的实时运行状态,提高系统稳定性
|
||||
5. 权限控制能力:实现谁提交的、谁审批的、谁更新的的全生命周期记录,可以复盘审计
|
||||
6. 项目信息控制:可以查看全国任意项目的详细部署信息,极大提高定制化开发效率
|
||||
6. 两个研发团队需要配合完成国产化信创环境的改造适配工作
|
||||
7. 视频流媒体部分的标准化部署方案
|
||||
8. AI功能 GPU服务器的标准化部署方案
|
||||
|
||||
|
||||
请你基于上述的信息,帮我撰写一份<统一交付特战队>的组建方案
|
||||
125
12-交付特战队/1-原始文档.md
Normal file
125
12-交付特战队/1-原始文档.md
Normal file
@@ -0,0 +1,125 @@
|
||||
剑哥,秉承能够充分发挥自身能力为部门发展,提供研发团队、部门协同流畅性,提高市场交付支撑效率的出发点。
|
||||
|
||||
在此斗胆提出自己一点小小的看法与建议,叨扰剑哥,还望能够查阅指正!
|
||||
|
||||
## 工作传承
|
||||
|
||||
先从软件开发模式讲起,传统时代(刀耕火种)的软件开发,就是前端、后端、运维一起上,就像是镰刀割麦子、弯腰插麦子,这在小农经济时代是可以的,但是人肉推进模式终究是效率很低下,人员损耗率很高,无法应对现在高达50个以上的市场化项目的拓展、交付、维护。
|
||||
|
||||
自动2013年云计算时代,15年之后云原生时代之后,开发进入现代农业机械化时代,大型联合收割机可以进行高效、自动化的作业,是传统小农种植不可比拟的效率及规模。
|
||||
|
||||
想到实现农业机械化时代,需要应用到大量的云原生的技术和方法。国内外大型互联网团队均有基础架构(设施)研发团队,用以研制大型生产机械,这部分现在的组织架构是很欠缺的。
|
||||
|
||||
从20年入职成研院以来,入职的目标即为基础架构(设施)研发团队,但是项目初期阶段,市场化项目规模不大,研发大型农业机械的成本极高,投入产出比不高,小农经济更有性价比。但是现在到了关键时候,市场项目过多,传统小农模式已经到了崩溃的边缘,要么就是海量人力的投入,要么就需要研制大型农业机械,迈入现代化农业生产阶段!
|
||||
|
||||
## 现有架构存在的问题
|
||||
|
||||
像我23年给你提过的一样,组织架构需要交付部署(由于技术支撑团队不承接此部分工作)团队,职责、权责清晰。
|
||||
|
||||
### 交付部署
|
||||
|
||||
1. 在之前云平台的架构里面,交付部署明明重要,却没有单独的职责分配
|
||||
2. 飞行服务组承担 飞行服务、监管平台的交付部署工作
|
||||
3. 监管平台貌似很害怕我和雪波不给他们部署?我搞不懂这种思想产生的原因
|
||||
1. 实际我们不抗拒监管部署,因为对我们来说都是一样的。不满的是职责、权责不清,我们是打黑工的角色
|
||||
2. 可能是极力想隐藏我和雪波的关键工作角色(虽然工作内容价值不高,但是交付环节很重要)
|
||||
|
||||
### 项目维护
|
||||
|
||||
1. 随着市场化项目的增多,项目的售后维护工作日渐增多
|
||||
2. 主要是安全漏洞修复问题
|
||||
1. 服务器漏洞(基线漏洞):全部是我和雪波去修复,占用大量的时间,成果非常不好
|
||||
2. 代码漏洞(渗透漏洞):需要由开发人员修复,我和雪波去部署更新。
|
||||
3. 定制化开发-持续交付部署
|
||||
1. 由于现在定制化项目太多,持续交付部署持续进行,部署环节割裂耗时很长【后文重点优化】,虽然我已经优化了锄头镰刀,但是小农模式下人力还是要被耗死
|
||||
2. 还有伴发的很多问题,如安全、审计问题
|
||||
|
||||
### 基础环境维护
|
||||
|
||||
研发环境维护
|
||||
|
||||
1. 无职责、权责分配
|
||||
2. 存在280台以上的服务器,没有时间进行日常的维护保养工作,
|
||||
3. 经常性的主机故障导致演示平台崩溃,
|
||||
4. 由于部署时间过早,架构老旧,旧演示平台运行极度卡顿
|
||||
|
||||
演示DEMO环境维护
|
||||
|
||||
1. 无职责、权责分配
|
||||
2. 研发人员没有研发环境使用,研发效率极度底下
|
||||
3. 监管组同事因为我和雪波还在极力维护演示环境,居然直接拿演示DEMO环境开发
|
||||
|
||||
重大事故
|
||||
|
||||
1. 25年春节大年三十,演示DEMO环境崩溃,主机故障无法恢复
|
||||
2. 一群人拉了一堆群,聊了各种问题,显得很热闹
|
||||
3. 实际还是我和波哥 将DEMO环境整体迁移,重新部署才恢复正常
|
||||
|
||||
### 基础架构平台开发
|
||||
|
||||
小农经济不具备此部分的工作,只能抽空进行镰刀、锄头的优化工作。
|
||||
|
||||
### 权责、职责不分、混乱
|
||||
|
||||
1. 出任何事情拉一群人,实际没有干活的
|
||||
2. 监管平台成立交付支撑组
|
||||
1. 存在挂羊头卖狗肉的情况,前端开发全都挂进去
|
||||
2. 监管平台部署,5年的技术沉淀,龙卫根本干不来
|
||||
3. 最终还是袁雪波一个人默默承担了所有
|
||||
4. 即使有独立的小组,仍然无法支撑规模化的市场化项目部署工作
|
||||
3. 还有 既然已经组织切分(分家了),监管平台交付部署的工作,后面我和袁雪波是接还是不接,管还是不管呢?
|
||||
|
||||
## 新的工作内容
|
||||
|
||||
从23年我向您提及至今,深感现在小农经济模式的巨大弊病与限制,研发人力耗损严重,规模化的市场化项目难以支撑与维系。
|
||||
|
||||
在一珂的授意与指挥下,我最近开启了大型联合收割机的研制工作
|
||||
|
||||
### RMDC基础平台
|
||||
|
||||
1. 全称为Runtime Management & Development Operation Center,中文系统名称为 运行环境管理及开发运维中心
|
||||
2. 本系统旨在解决日益繁多的跨公网市场化项目、众多开发环境、演示环境等运行环境的开发、监视、运维问题,提供一站式中心化的管理、控制、操作能力,具备权限控制、流程审批、审计追溯的功能特性。(类似于 一网统飞,此平台实现 中移凌云运行环境的 一网统管)
|
||||
3. 重点功能介绍
|
||||
1. 交付物构建:能够一个界面实现从代码到项目交付物的产出
|
||||
2. 持续交付更新:能够实现全国任意项目的一键跨公网微服务升级更新
|
||||
3. 项目统一监控中心:可以查看全国任意项目的实时运行状态,提高系统稳定性
|
||||
4. 权限控制能力:实现谁提交的、谁审批的、谁更新的,如果出了问题均可以追责
|
||||
5. 项目信息控制:可以查看全国任意项目的详细部署信息,极大提高定制化开发效率
|
||||
4. 效率提升评估
|
||||
1. 交付物构建:一次构建节约人力成本10分钟,平均每天构建150次,每天节约开发人员工时为25小时!
|
||||
2. 持续交付更新:一次更新节约人力成本15分钟,平均每个项目每年更新300次(雄安已经更新超过500次),市场化项目数量为25个计算,每年能够节约人力成本1875小时(8小时工作计算,达到234人天)。考虑到项目的驻点运维人员开支,大概率可以节约1.5个运维人员的年度成本!
|
||||
3. 项目信息控制:一次信息查找节约10分钟,平均每天查找15次以上,每天节约的开发人员工时为 2.5小时!
|
||||
|
||||
### 建议的组织架构
|
||||
|
||||
1. 充分考虑到职责、权责划分,公司也是综合部、 产品中心,研发中心、技术支撑中心
|
||||
2. 提高部门团队研发效率,将研发从小农经济模式转向现代农业机械化时代
|
||||
3. 去除很多组织架构上面的障碍
|
||||
4. 斗胆给出一些部门组织架构的建议
|
||||
1. 将现有监管平台的交付支撑组拆解,形成独立于监管平台和飞行服务平台的组,类似于产品组,整体的架构图就形成为
|
||||
2. 行业组进行市场化拓展 对接产品组
|
||||
3. 产品组负责中移凌云产品打造,对接监管平台 飞行服务平台
|
||||
4. 最后是基础架构组(交付支撑组)用于为研发两个团队提供如下支撑工作
|
||||
1. 交付部署职责
|
||||
2. 项目售后维护职责
|
||||
3. 研发环境维护管理职责
|
||||
4. 演示DEMO环境维护管理职责
|
||||
5. 准生产、生产环境维护管理职责
|
||||
6. 基础架构平台开发职责(大型联合收割机)
|
||||
5. 基础架构组 人员清单
|
||||
1. 组长 任一珂
|
||||
2. 王子文
|
||||
3. 袁雪波
|
||||
4. 郑岩
|
||||
5. 郝昊
|
||||
6. 之前的工作模式就是这样的划分,现在可以明确职责、权责
|
||||
|
||||
平台基础架构 (软件开发中的话术,类似于平台运行的地基)
|
||||
|
||||
平台运行环境维护(DEMO环境 生产环境 研发环境等 280多台服务器 )
|
||||
|
||||
交付部署 (项目第一次落地)
|
||||
|
||||
持续交付更新(定制化开发需要,每次更新很费时间)
|
||||
|
||||
项目售后维护 (50多个项目需要维护,安全问题维护、版本升级、运行环境清理等)
|
||||
BIN
12-交付特战队/1.1-efficiency.png
Normal file
BIN
12-交付特战队/1.1-efficiency.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 MiB |
BIN
12-交付特战队/1.2-group-arch.png
Normal file
BIN
12-交付特战队/1.2-group-arch.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 899 KiB |
94
12-交付特战队/2-优化版本.md
Normal file
94
12-交付特战队/2-优化版本.md
Normal file
@@ -0,0 +1,94 @@
|
||||
**文档主题:关于优化部门组织架构以提升规模化交付效能的建议汇报**
|
||||
**汇报人:** 王子文
|
||||
**日期:** 2026年1月9日
|
||||
|
||||
---
|
||||
|
||||
### 剑哥:
|
||||
|
||||
您好!
|
||||
|
||||
基于对部门当前业务高速发展的观察,秉承“提升研发团队协同流畅性、提高市场交付支撑效率”的初衷,我结合近期在基础架构平台(RMDC)上的实践,对部门组织架构和工作模式有一些思考。
|
||||
|
||||
现整理如下,供您审阅参考。
|
||||
|
||||
### 一、 背景:从“手工作业”迈向“自动化工厂”
|
||||
|
||||
自2013年云计算普及至2015年云原生时代到来,软件研发与交付模式发生了质的飞跃。
|
||||
|
||||
* **传统模式(小农经济)**:开发、运维、交付高度耦合,依靠人力堆砌(“镰刀割麦”)。在项目初期或单一项目阶段,这种模式具备灵活性,性价比尚可。
|
||||
* **现代化模式(机械化作业)**:依托云原生技术与自动化平台(“联合收割机”),实现标准化、规模化交付。
|
||||
|
||||
**现状判断:**
|
||||
从2020年入职至今,我见证了团队的快速发展。目前我们面临着**50+市场化项目**并发交付、维护的挑战。现有的“人肉推进”模式由于边际成本过高,人员损耗率上升,已难以支撑当前的市场规模。我们需要从“人力密集型”向“技术密集型”转型,补齐基础架构(设施)研发能力的短板。
|
||||
|
||||
### 二、 现有架构面临的痛点与挑战
|
||||
|
||||
**[此处建议插入图片:现有工作流程中的痛点鱼骨图或流程阻塞图,展示开发、交付、维护中的断点]**
|
||||
|
||||
当前组织架构在面对大规模市场化交付时,主要存在职责边界模糊、权责不对等的问题:
|
||||
|
||||
#### 1. 交付部署职责不清
|
||||
|
||||
* **现状**:交付部署环节在流程中缺乏独立明确的责任主体。目前实际上由特定技术人员承担了监管平台、飞行服务平台等全线的交付压力。
|
||||
* **问题**:由于缺乏明确的部门职能定义,交付工作往往被视为“临时性支撑”,导致工作量无法量化,权责边界模糊,难以形成标准化的交付SOP。
|
||||
|
||||
#### 2. 存量项目维护压力巨大
|
||||
|
||||
随着市场化项目增多,售后维护成为巨大的隐形负担:
|
||||
|
||||
* **安全漏洞修复**:基线漏洞目前完全依赖核心开发人员逐一修复,耗时巨大且效果难以复用。
|
||||
* **持续交付瓶颈**:定制化项目频繁变更,导致部署环节割裂。即使优化了工具,在现有模式下依然耗费大量人力。
|
||||
|
||||
#### 3. 基础环境维护缺失
|
||||
|
||||
目前约有 **280台+** 服务器缺乏专职维护:
|
||||
|
||||
* **演示环境不稳定**:无专人对演示DEMO环境进行生命周期管理。以25年春节期间DEMO环境故障为例,虽然最终通过紧急迁移恢复,但暴露了缺乏日常巡检和应急预案的短板。
|
||||
* **研发效率受损**:由于资源管理混乱,甚至出现演示环境被直接用于开发的情况,严重影响了资源的隔离性和稳定性。
|
||||
|
||||
---
|
||||
|
||||
### 三、 解决方案:工具+组织双管齐下
|
||||
|
||||
为了打破上述瓶颈,在相关领导的指导下,我们已启动了基础架构平台的自研工作,并建议配合组织架构调整以发挥最大效能。
|
||||
|
||||
#### 1. 工具层面:RMDC基础平台(已落地)
|
||||
|
||||
**系统全称**:运行环境管理及开发运维中心 (Runtime Management & Development Operation Center)
|
||||
**核心定位**:中移凌云体系的“一网统管”底座。旨在解决跨公网、多项目、多环境的集中化管理难题。
|
||||
|
||||
|
||||
**核心效能提升数据(实测):**
|
||||
|
||||
| 功能模块 | 效能提升点 | 量化数据(预估) |
|
||||
| --- | --- | --- |
|
||||
| **交付物构建** | 自动化流水线,替代人工打包 | 单次节约10分钟。日均150次构建 → **每日节约 25工时** |
|
||||
| **持续交付更新** | 一键跨公网微服务升级 | 单次节约15分钟。按25个项目、年均300次更新计算 → **年节约 1875工时** (约234人天) |
|
||||
| **项目信息控制** | 集中化信息看板,告别碎片化查找 | 单次查找节约10分钟。日均15次以上 → **每日节约 2.5工时** |
|
||||
| **综合收益** | **降低对驻点运维的依赖** | **预计可节约 1.5名 运维人员的年度人力成本** |
|
||||
|
||||
#### 2. 组织架构层面:成立“基础架构组”
|
||||
|
||||
建议参考公司“综合部、产品中心、研发中心、技术支撑中心”的标准化架构,在部门内部明确**公共技术底座**的定位。
|
||||
|
||||
|
||||
**建议调整方案:**
|
||||
将现有的交付支撑职能剥离并重组,成立独立于业务线(监管/飞行)之外的**基础架构组(或交付支撑组)**。
|
||||
|
||||
* **职责定位**:
|
||||
1. **基础设施研发**:负责RMDC平台(“联合收割机”)的持续研发与迭代。
|
||||
2. **标准化交付**:统一承担所有产品线的交付部署标准制定与实施。
|
||||
3. **环境全生命周期管理**:统筹管理研发、演示、准生产及生产环境,确保稳定性(包含280+台服务器维护)。
|
||||
4. **技术兜底与售后**:负责共性安全漏洞修复、基线加固及重大技术故障处理。
|
||||
|
||||
|
||||
* **建议人员构成**:
|
||||
* 组长:任一珂
|
||||
* 核心成员:王子文、袁雪波、郑岩、郝昊
|
||||
|
||||
### 四、 总结
|
||||
|
||||
通过“明确分工”与“工具赋能”,我们希望将研发团队从繁琐的重复劳动中解放出来,让业务组聚焦业务逻辑,让基础组聚焦效能提升。这不仅是对现有痛点的解决,更是应对未来更大规模市场化交付的必要准备。
|
||||
|
||||
以上是我的浅见,不当之处,请剑哥批评指正!
|
||||
53
12-交付特战队/3-统一交付特战队.md
Normal file
53
12-交付特战队/3-统一交付特战队.md
Normal file
@@ -0,0 +1,53 @@
|
||||
统一交付特战队组建方案
|
||||
一、背景
|
||||
现状问题:当前生态能力整合不充分,交流多、转化少;标准产品与场景脱节,亟需借助生态力量补足;细分场景需求逐渐凸显,而军团主要统筹大监管、应急领域,对于细分行业场景支撑力有限。
|
||||
核心目标:以3+1场景(机场安防、自然资源、医疗物流、交通)为切口,打通“需求洞察→生态整合→方案设计→商业闭环”的全链路,输出可复用的场景化模板。
|
||||
二、定位
|
||||
聚焦"连接"与“提效”,主动构建场景生态能力通道,将分散的产品能力、生态资源、市场诉求转化为端到端落地的场景解决方案,并输出标准化作战工具。
|
||||
三、人员
|
||||
为确保提升研发团队协同流畅性、提高市场交付支撑效率,团队聚焦XXXX场景,配置核心成员5名:
|
||||
任一珂(兼队长):负责XXXXX
|
||||
王子文(副队长):负责XXXXX
|
||||
袁雪波:负责XXXXX
|
||||
郑岩:负责XXXXX
|
||||
龙卫:负责XXXXX
|
||||
四、职责
|
||||
(一)负责机场安防、自然资源、医疗物流、交通3+1场景市场化闭环
|
||||
1.解决方案:
|
||||
围绕机场安防、自然资源、医疗物流、交通形成3+1融生态解决方案,同时形成场景营销套件,并分享至军团与行业组,开展区域中心解决方案培训推介,支撑省市公司业务拓展;
|
||||
2.衔接生态:
|
||||
围绕机场安防、自然资源、医疗物流、交通3+1场景,灵活运用现有服务包与引入服务包强化生态协作,联动生态场景能力丰富场景解决方案,发展行业渠道生态建立,场景顶层对接拓宽商机触点,协同行业组开展属地生态对接,提升3+1场景下的属地支撑能力;
|
||||
3.拓展项目:
|
||||
围绕机场安防、自然资源、医疗物流、交通3+1场景,主动推介方案对接项目需求,积极配合军团与行业组,开展有效商机跟进支撑;
|
||||
4.商务模式:
|
||||
围绕机场安防、自然资源、医疗物流、交通3+1场景,基于现有产品化能力包装场景拓展策略,优化商务模式,立足项目实际设计项目落地模式,对于有价值的商务模式,组织军团及行业组开展专题探讨并尝试复制;
|
||||
5.形成样板间
|
||||
围绕机场安防、自然资源、医疗物流、交通3+1场景,形成可演示、可参观、可推介的项目样板,及时分享军团及行业组,支撑项目拓展复制;
|
||||
(二)负责服务包全生命周期管理
|
||||
1.引入
|
||||
协同军团及行业组,收集提炼产品服务包需求,周期性开展服务包引入立项及采购相关工作;
|
||||
2.定价
|
||||
协同产品组,开展已产品服务包院内相关定价工作;
|
||||
3.上架
|
||||
针对完成定价的产品服务包,协助完成集团上架相关流程推动;
|
||||
4.销售
|
||||
根据市场化需求,制定服务包销售策略(融入场景、资费包等),助力产品服务包市场化销售;
|
||||
5.流程穿通
|
||||
针对在售产品服务包相关业务,制定使用流程和验收规范、明确业务交付标准,指导军团、行业组的使用交付过程合规,助力产品市场化交付;
|
||||
五、分工原则
|
||||
(一)与产品与解决方案组紧密协作,完成产品能力可秀可售可落;
|
||||
(二)与监管军团高效协同,完成3大场景方案商业闭环;
|
||||
(三)与南、北区行业组有效衔接,完成场景项目需求支撑落地。
|
||||
六、考核目标(第一季度开始执行)
|
||||
可先提供关键考核点供部门参考:
|
||||
(一)利润指标(30%):500万,根据完成金额线性得分;(第三季度考核值300万)
|
||||
(二)场景样板(30%):6个,根据完成数量线性得分;(第三季度考核值4个)
|
||||
(三)解决方案(15%):形成4大场景营销套件并持续共享更新,根据完成情况评分;
|
||||
(四)服务包管理(20%):打通服务包流程,形成销售&交付标准,根据完成情况评分;
|
||||
(五)其他工作(5%):完成领导交办工作,根据完成情况评分;
|
||||
七、考核办法
|
||||
可先提供关键考核思路部门参考:
|
||||
(一)场景应用打造解决方案特战队与支撑条线(云平台系统组-监视控制组、应急军团组、监管军团组)拉通考核。
|
||||
(二)根据考核目标完成情况,部门与产品组共同考核,分别占60%和40%。
|
||||
(三)专项攻坚特战队成员绩效名额进行单列,不回归所在班组名额比例管理。
|
||||
|
||||
BIN
12-交付特战队/关于优化部门组织架构以提升规模化交付效能的建议汇报.docx
Normal file
BIN
12-交付特战队/关于优化部门组织架构以提升规模化交付效能的建议汇报.docx
Normal file
Binary file not shown.
115
2-需求转换专业设计/DDS转AgentSkill.md
Normal file
115
2-需求转换专业设计/DDS转AgentSkill.md
Normal file
@@ -0,0 +1,115 @@
|
||||
你是一位专业的 Agent Skills 架构师与 Claude Code Skills 作者。你的任务是:把给定的 RMDC 系统设计文档(DDS/PRD/架构说明)转换为一套可落地的 Claude Code Skills(系统级 Skill + 模块级 Skills + 横切 Skills),并输出完整目录树与每个 Skill 的 SKILL.md。
|
||||
|
||||
# 输入
|
||||
- 源文档路径由参数传入:$ARGUMENTS
|
||||
- 你必须使用动态注入读取输入内容(至少 2 处):
|
||||
- !`ls -la $(dirname "$ARGUMENTS")`
|
||||
- !`sed -n '1,200p' "$ARGUMENTS"`
|
||||
- (可选)!`grep -nE "模块|module|service|接口|API|事件|MQTT|topic|表|schema|RBAC|鉴权|状态机" "$ARGUMENTS" | head -n 50`
|
||||
|
||||
如果无法读取文件:明确说明缺少源文档内容,并输出“继续所需的最小信息清单”(模块接口/事件/表/状态机/依赖关系等),不要臆造细节。
|
||||
|
||||
# 系统模块(必须按此拆分)
|
||||
| 模块 | 职责 | 关键技术 |
|
||||
| rmdc-core | API Gateway、鉴权、路由 | Go + Gin |
|
||||
| rmdc-jenkins-branch-dac | Jenkins分支权限(DAC)、构建管理 | Jenkins API, MinIO |
|
||||
| rmdc-exchange-hub | MQTT消息网关、指令生命周期 | MQTT, PostgreSQL |
|
||||
| rmdc-watchdog | 边缘代理、K8S操作、二级授权 | K8S API, TOTP |
|
||||
| rmdc-project-management | 项目管理、一级授权中心 | PostgreSQL |
|
||||
| rmdc-work-procedure | 工单管理、工单流程、生命周期管理 | 状态机 |
|
||||
| rmdc-audit-log | 审计日志 | PostgreSQL |
|
||||
| rmdc-user-auth | 用户认证、权限管理 | JWT, RBAC |
|
||||
|
||||
# 输出目标(必须一次性给全)
|
||||
我将把输出落盘到 Windows 目录:
|
||||
- C:/Users/wddsh/Documents/IdeaProjects/ProjectAGiPrompt/1-AgentSkills
|
||||
但你输出展示路径必须使用 Unix 风格(/)。
|
||||
|
||||
你需要输出:
|
||||
1) 全部 Skills 的目录结构树(tree)
|
||||
2) 每个 Skill 的 SKILL.md 完整内容(frontmatter + body)
|
||||
3) 如需要,补充 reference/ examples/ scripts/ 中关键文件内容(只给必要内容)
|
||||
4) 最后输出全局自检结果(逐条 PASS/FAIL + 修复建议)
|
||||
|
||||
# Skill 组织架构(必须遵守)
|
||||
生成以下 3 类 Skills:
|
||||
A) 系统级(1个):rmdc-system(跨模块一致性、依赖规则、版本/兼容策略、全局变更流程)
|
||||
B) 模块级(7个):每个模块 1 个 Skill(高频开发:实现步骤 + 依赖影响检查)
|
||||
C) 横切(至少3个):建议包含
|
||||
- designing-contracts(API/事件/Schema 契约、兼容策略、版本策略)
|
||||
- database-migrations(PostgreSQL迁移与回滚、字段演进)
|
||||
- observability-audit(日志/指标/trace/审计一致性;与 rmdc-audit-log 对齐)
|
||||
(你可按源文档内容增减,但不得少于 3 个横切 Skill)
|
||||
|
||||
# 规范约束(硬性)
|
||||
## 1. Frontmatter(极其重要)
|
||||
- name:小写字母/数字/连字符;动名词形式;<=64 字符
|
||||
- description:必须【单行】且 <1024 字符;第三人称;包含功能说明 + 触发场景 + 关键词
|
||||
- allowed-tools:最小授权原则(默认仅允许读取文件/执行本地命令/grep/sed;除非源文档明确需要网络或外部工具)
|
||||
- 必须出现 argument-hint(提示 $ARGUMENTS 的格式)
|
||||
|
||||
## 2. 内容精简与拆分
|
||||
- 删除 Claude 已知常识,只保留领域特定知识与具体操作步骤
|
||||
- 删除现有系统提示词prompt中多余的部分
|
||||
- 代码规范要求已经存在单独的SKILL,可以删除
|
||||
- 有重复说明设计内容,请去除不必要的重复内容
|
||||
- 冗长内容拆分进 reference/(例如:模块依赖、状态机、事件topic列表、表结构、错误码)拆分需要合理,能够获取到必要设计信息,不会获取过多无关信息
|
||||
- 示例代码放 examples/(只放与模块职责强相关的骨架示例)
|
||||
- 工具脚本放 scripts/(提供至少 1 个 verify 脚本:用于检查契约/迁移/编译或 lint;脚本要可运行,写明依赖)
|
||||
|
||||
## 3. 工作流(计划-验证-执行)
|
||||
每个 SKILL.md 必须包含以下结构:
|
||||
- Plan:产物清单 + 决策点(例如:涉及哪些模块、改动边界、是否影响契约/事件/表)
|
||||
- Verify:Checklist(可勾选),并明确“验证点”(例如:契约兼容、topic/事件字段一致、DB migration 可回滚、RBAC 不破坏)
|
||||
- Execute:可操作步骤(命令化短句,按顺序)
|
||||
- Pitfalls:3~8 条常见坑(必须与该模块/横切主题相关)
|
||||
|
||||
## 4. 参数与动态上下文
|
||||
- 每个 Skill 必须使用 $ARGUMENTS(例如:模块名、变更类型、输入文档路径、目标目录等)
|
||||
- 每个 Skill 必须至少包含 2 处 !`command` 动态注入示例(用于:读取仓库结构、查找接口/事件/表、运行测试)
|
||||
- 所有示例命令需在类 Unix shell 下可执行;避免 OS 特定路径
|
||||
|
||||
## 5. 质量标准
|
||||
- 每个 SKILL.md 主体 <500 行
|
||||
- 术语一致:模块名、契约、事件、topic、授权层级(一级/二级)、DAC、RBAC、JWT、审计
|
||||
- 输出必须“可落地”:能指导真实开发或文档对齐/审查
|
||||
|
||||
# 目录模板(必须套用)
|
||||
<skill-name>/
|
||||
SKILL.md
|
||||
reference/
|
||||
examples/
|
||||
scripts/
|
||||
|
||||
# 生成步骤(你必须按此顺序输出)
|
||||
步骤1:先给出 Skills 清单(系统级/模块级/横切),并为每个 Skill 提供 2~3 个 name 候选,最终选择 1 个 name(附一句理由)
|
||||
步骤2:输出总目录树(Unix 路径)
|
||||
步骤3:依次输出每个 Skill 的 SKILL.md(完整内容)
|
||||
步骤4:如有 supporting files:按“文件路径 -> 文件内容”逐个输出(只给关键必要文件)
|
||||
步骤5:输出全局 Verify Checklist 自检结果(逐条 PASS/FAIL + 修复建议)
|
||||
|
||||
# 特殊要求(与模块强相关)
|
||||
- rmdc-core:必须包含鉴权/路由变更对下游接口的影响检查(API契约、错误码、JWT claims)
|
||||
- rmdc-jenkins-branch-dac:必须包含 DAC 规则变更的回归检查与最小权限策略;涉及 Jenkins API 与 MinIO 访问的验证点
|
||||
- rmdc-exchange-hub:必须包含 MQTT topic/指令生命周期 的契约检查与幂等处理验证点
|
||||
- rmdc-watchdog:必须包含 K8S API 操作的安全边界与 TOTP 二级授权的验证点
|
||||
- rmdc-project-management:必须包含一级授权中心的字段/状态变更影响检查
|
||||
- rmdc-audit-log:必须包含审计不可篡改/字段完整性/写入链路的验证点
|
||||
- rmdc-user-auth:必须包含 RBAC/JWT/会话安全变更的兼容与回归验证点
|
||||
|
||||
现在开始生成。源文档路径由我通过 $ARGUMENTS 传入。
|
||||
|
||||
|
||||
### RMDC-系统
|
||||
|
||||
### claude name命名 rmdc-project-management-skill
|
||||
项目管理系统的设计说明见目录 C:\Users\wddsh\Documents\IdeaProjects\ProjectAGiPrompt\8-CMII-RMDC\4-rmdc-project-management
|
||||
请将详细系统设计转换为单独的SKILL
|
||||
|
||||
### claude name命名 rmdc-work-procedure-skill
|
||||
工单系统模块的设计文档说明见目录 C:\Users\wddsh\Documents\IdeaProjects\ProjectAGiPrompt\8-CMII-RMDC\7-rmdc-work-procedure
|
||||
请将详细系统设计转换为单独的SKILL
|
||||
|
||||
### claude name命名 rmdc-watchdog-skill
|
||||
watchdog模块的设计文档说明见目录 C:\Users\wddsh\Documents\IdeaProjects\ProjectAGiPrompt\8-CMII-RMDC\6-rmdc-watchdog
|
||||
请将详细系统设计转换为单独的SKILL
|
||||
71
2-需求转换专业设计/Prompt转换AgentSkill.md
Normal file
71
2-需求转换专业设计/Prompt转换AgentSkill.md
Normal file
@@ -0,0 +1,71 @@
|
||||
你是一位专业的 Agent Skills 架构师与 Claude Code Skills 作者。你的任务是:把给定的 prompt/规范文档转换为符合 Claude Code(Agent Skills open standard) 的一个完整 Skill 文件夹(含 SKILL.md + supporting files)。
|
||||
|
||||
## 输入
|
||||
- 源文档路径(用于动态注入内容):
|
||||
@C:\Users\wddsh\Documents\IdeaProjects\ProjectAGiPrompt\1-Vue3项目\vue3-typescript-style-v2.md
|
||||
@C:\Users\wddsh\Documents\IdeaProjects\ProjectAGiPrompt\1-Vue3项目\frontend-design-skill.md 请重点参考此SKILL的写法
|
||||
- 你将从该源文档中提炼“领域特定规则、具体操作步骤、可执行检查清单”,删除常识性解释。
|
||||
|
||||
> 若无法读取文件,则明确指出“缺少源文档内容”,并输出你需要用户提供的最小信息清单(不要继续臆造细节)。
|
||||
|
||||
## 目标输出(必须一次性给全)
|
||||
输出以“Unix 风格路径”展示(使用 /),但我会将整个 Skill 文件夹落盘到 Windows 目录:
|
||||
- Windows 目标根目录:C:/Users/wddsh/Documents/IdeaProjects/ProjectAGiPrompt/1-AgentSkills
|
||||
|
||||
你需要输出:
|
||||
1) 目录结构树(tree)
|
||||
2) SKILL.md 完整内容(含 YAML frontmatter + markdown body)
|
||||
3) 如需要,补充 reference/、examples/、scripts/ 里的关键文件内容
|
||||
|
||||
## 规范约束(硬性)
|
||||
### A. Frontmatter(极其重要)
|
||||
- description 必须【单行】(绝对不能换行,避免 skill 被忽略)
|
||||
- name:小写字母/数字/连字符,动名词形式(如 processing-data),<=64 字符
|
||||
- description:第三人称 + 功能说明 + 触发场景 + 关键词;<1024 字符;单行
|
||||
- 仅使用必要 frontmatter 字段(推荐:name、description;可选:argument-hint、allowed-tools、disable-model-invocation)
|
||||
|
||||
### B. 内容组织(progressive disclosure)
|
||||
- SKILL.md 只写“最小可用主流程 + 检查清单 + 验证点 + 常见陷阱”
|
||||
- 冗长说明拆到 reference/(并在 SKILL.md 指明“什么时候读取哪个 reference 文件”)
|
||||
- 示例放 examples/
|
||||
- 工具脚本放 scripts/(脚本要可在本地运行;如需依赖必须写明)
|
||||
|
||||
### C. 工作流模式(计划-验证-执行)
|
||||
在 SKILL.md 中必须包含:
|
||||
- Plan:产物清单 + 决策点(例如:如何命名、如何拆分 reference)
|
||||
- Verify:逐条 checklist(可复制勾选),包含“验证点”(例如:frontmatter 单行、行数限制、路径风格、引用文件存在)
|
||||
- Execute:具体步骤(尽量命令化、短句、可操作)
|
||||
|
||||
### D. 参数与动态上下文
|
||||
- SKILL.md 必须出现 $ARGUMENTS,并说明期望参数格式
|
||||
- 必须至少使用 2 处动态注入:
|
||||
- !`command` 用于读入/总结上下文(例如:读取源文件、列出目录、grep 关键字等)
|
||||
- 如果 skill 需要执行命令,必须通过 allowed-tools 最小授权(最小集合原则)
|
||||
|
||||
### E. 质量标准
|
||||
- SKILL.md 主体 <500 行
|
||||
- 术语一致;不要混用同义词(例如:Skill/AgentSkill、reference/refs)
|
||||
- 不要写 Claude 已知的通用常识(只保留领域特定与操作步骤)
|
||||
|
||||
## 生成步骤(你必须按此顺序输出)
|
||||
1) 先给出 skill 的命名候选 3 个(均为动名词风格),并选择 1 个作为最终 name(附一句理由)
|
||||
2) 输出目录树(Unix 路径)
|
||||
3) 输出 SKILL.md(完整)
|
||||
4) 如有 supporting files:按“文件路径 -> 文件内容”的形式逐个输出
|
||||
5) 最后输出 Verify Checklist 的“自检结果”(逐条 PASS/FAIL;FAIL 要说明原因与修复)
|
||||
|
||||
## 目录模板(参考,按需增删)
|
||||
<skill-name>/
|
||||
SKILL.md
|
||||
reference/
|
||||
...
|
||||
examples/
|
||||
...
|
||||
scripts/
|
||||
...
|
||||
|
||||
现在开始。源文档路径由我通过参数传入:$ARGUMENTS
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -13,4 +13,7 @@
|
||||
- 语言专业化: 使用业界标准的术语和概念来描述架构组件、数据流和设计决策。
|
||||
|
||||
|
||||
请你遵守前端项目的风格vue3-typescript-stye.md,针对mask_binance_account.md的初始设计文稿,给出专业性的优化设计,回答内容必须是中文
|
||||
请你遵守前端项目的风格vue3-typescript-stye.md,针对mask_binance_account.md的初始设计文稿,给出专业性的优化设计,回答内容必须是中文
|
||||
|
||||
|
||||
|
||||
|
||||
65
7-JenBranchRBAC/1-原始需求/1-初始需求稿.md
Normal file
65
7-JenBranchRBAC/1-原始需求/1-初始需求稿.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# JenBranchRBAC (Jenkins Branch-level Role-Based Access Control)
|
||||
|
||||
# 项目简介
|
||||
|
||||
本项目基于JenkinsAPI模块,设计用户使用界面,用户使用自己的用户名密码登录,能够查看和构建其拥有权限的项目及分支情况
|
||||
|
||||
# 项目说明
|
||||
|
||||
## 管理页面
|
||||
- 页面显示-管理页面
|
||||
|
||||
### 用户添加
|
||||
- 使用sqlite3进行持久化
|
||||
- 存储在sqlite3中的密码需要进行加密
|
||||
- 注册用户名及密码
|
||||
- 密码过期时间为6个月
|
||||
|
||||
### 用户查询
|
||||
- 用户信息包括
|
||||
- 用户名
|
||||
- 明文密码
|
||||
- 权限信息
|
||||
- 查询sqlite3获取用户信息
|
||||
- 能够通过用户名模糊查询用户信息
|
||||
- 能够查询所有的用户名及明文密码
|
||||
- 支持一键导出用户名密码及分支
|
||||
|
||||
### 用户编辑
|
||||
- 支持用户密码修改
|
||||
- 支持用户权限修改
|
||||
- 权限修改调用[用户权限绑定]模块的功能
|
||||
|
||||
### 用户权限绑定
|
||||
- 使用sqlite3进行持久化保存
|
||||
- 通过[JenkinsAPI模块]获取全部项目全部分支信息
|
||||
- 通过左右两侧列表的形式,选择赋予项目及分支权限
|
||||
- 全部项目及分支 支持模糊查询
|
||||
|
||||
## 用户页面
|
||||
- 页面显示-用户页面
|
||||
- 用户使用账户名密码登录
|
||||
- 密码传递必须进行加密
|
||||
|
||||
### Jenkins项目及分支
|
||||
- 能够查看,构建自己拥有权限的项目及分支
|
||||
- 权限检验
|
||||
- 构建时需要检测是否符合权限信息
|
||||
|
||||
### Jenkins构建信息显示
|
||||
- 能够实时查看获取jenkins特定分支构建信息
|
||||
- 能够查看特定项目特定分支的历史构建信息
|
||||
|
||||
## 配置管理
|
||||
- sqlite3的保存位置
|
||||
- 前后端密码加密密钥
|
||||
- jenkins服务器url
|
||||
- jenkins访问的token等信息
|
||||
|
||||
## JenkinsAPI模块
|
||||
- 假设API拥有最高的权限
|
||||
- 能够调用JenkinsAPI
|
||||
- 获取全部项目及分支信息
|
||||
- 能够实时获取Jenkins分支的构建信息输出
|
||||
- 能够查询特定项目特定分支的历史构建信息
|
||||
|
||||
414
7-JenBranchRBAC/1-原始需求/2-优化产品需求文档PRD.md
Normal file
414
7-JenBranchRBAC/1-原始需求/2-优化产品需求文档PRD.md
Normal file
@@ -0,0 +1,414 @@
|
||||
# JenBranchRBAC 产品需求文档
|
||||
|
||||
**Jenkins 分支级权限管理系统**
|
||||
|
||||
## 1. 产品概述
|
||||
|
||||
### 1.1 产品定位
|
||||
|
||||
JenBranchRBAC 是一个基于 Jenkins REST API 的细粒度权限控制平台,提供项目级、分支级的构建权限管理,解决 Jenkins 原生 RBAC 插件无法满足多分支流水线细粒度授权的痛点。
|
||||
|
||||
### 1.2 核心价值
|
||||
|
||||
- **细粒度权限控制**:支持到分支级别的读取、构建权限隔离
|
||||
- **独立认证体系**:与 Jenkins 解耦的用户管理,降低 Jenkins 账户管理复杂度
|
||||
- **审计合规**:完整的操作日志记录,满足企业安全审计需求
|
||||
- **高效稳定**:基于 Go 语言构建,低资源占用,高并发响应
|
||||
|
||||
## 2. 功能架构
|
||||
|
||||
### 2.1 系统角色定义
|
||||
|
||||
| 角色 | 权限范围 | 典型场景 |
|
||||
|------|---------|---------|
|
||||
| 超级管理员 | 系统全部功能 | 平台运维人员 |
|
||||
| 项目管理员 | 指定项目的权限分配 | 项目 Leader |
|
||||
| 普通用户 | 已授权分支的查看和构建 | 开发工程师 |
|
||||
|
||||
## 3. 详细功能设计
|
||||
|
||||
### 3.1 管理端功能模块
|
||||
|
||||
#### 3.1.1 用户生命周期管理
|
||||
|
||||
**用户注册**
|
||||
|
||||
- 支持单个/批量导入用户(CSV 格式)
|
||||
- 密码策略:
|
||||
- 最小长度 8 位,包含大小写字母、数字、特殊字符
|
||||
- 采用 bcrypt 算法加密存储(cost factor=12)
|
||||
- 默认密码有效期 180 天,到期前 7 天邮件提醒
|
||||
- 首次登录强制修改初始密码
|
||||
|
||||
**用户查询与导出**
|
||||
|
||||
- 支持按用户名、角色、权限范围、状态(启用/禁用/锁定)多维度筛选
|
||||
- 导出格式包含:用户名、角色、授权项目-分支清单、账户状态、密码到期时间、最后登录时间
|
||||
- **安全约束**:导出支持 CSV/Excel 格式,敏感数据(密码哈希)不可导出,管理员仅可重置无法查看明文。
|
||||
|
||||
**用户编辑**
|
||||
|
||||
- 密码重置:管理员可重置用户密码并强制下次登录修改
|
||||
- 账户状态管理:启用/禁用/锁定(连续 5 次登录失败自动锁定 30 分钟)
|
||||
- 权限变更:调用权限绑定模块进行项目-分支授权调整
|
||||
|
||||
#### 3.1.2 权限绑定系统
|
||||
|
||||
**Jenkins 数据同步**
|
||||
|
||||
- **定时拉取**:后端使用 `cron` 协程定时(默认 5 分钟)调用 Jenkins API。
|
||||
- **手动立即同步**:管理界面提供“立即同步”按钮,后端通过 Goroutine 异步执行同步任务,前端通过 WebSocket 或轮询获取进度。
|
||||
- **缓存机制**:本地缓存分支信息,API 调用失败时使用缓存数据。
|
||||
- **差异检测**:自动识别新增/删除的项目和分支,提示管理员处理孤立权限。
|
||||
|
||||
**权限分配界面**
|
||||
|
||||
- 左右穿梭框设计(Material UI 风格):
|
||||
- 左侧:所有项目-分支树状结构
|
||||
- 右侧:已授权清单
|
||||
- 支持项目级全选/分支级精选
|
||||
- 搜索功能:支持项目名、分支名模糊匹配及正则过滤(如 `feature/*`)。
|
||||
- 权限模板:保存常用权限组合为模板,快速应用于同类用户。
|
||||
|
||||
#### 3.1.3 操作审计日志
|
||||
|
||||
**日志记录维度**
|
||||
|
||||
- 用户操作:登录/登出、密码修改、权限变更
|
||||
- 构建操作:构建触发人、项目、分支、参数、时间戳
|
||||
- 管理操作:用户创建/删除、权限模板变更、配置修改
|
||||
- API 调用:Jenkins API 调用记录、响应状态、耗时
|
||||
|
||||
**日志查询与分析**
|
||||
|
||||
- 支持时间范围、操作类型、用户、项目多维度筛选
|
||||
- 统计分析:用户活跃度排行、构建频率热力图、异常操作告警
|
||||
|
||||
### 3.2 用户端功能模块
|
||||
|
||||
#### 3.2.1 安全认证机制
|
||||
|
||||
**登录流程**
|
||||
|
||||
- 前端使用 RSA 加密密码传输(公钥前端存储,私钥后端保管)
|
||||
- 后端验证:
|
||||
1. 解密密码并与数据库 bcrypt 哈希比对
|
||||
2. 校验账户状态(未过期、未锁定)
|
||||
3. 生成 JWT Token(有效期 8 小时,支持刷新)
|
||||
- 支持 MFA(可选):集成 TOTP 双因素认证
|
||||
|
||||
**会话管理**
|
||||
|
||||
- 单设备登录限制(可配置允许多会话)
|
||||
- Token 自动续期机制:活跃用户无感刷新
|
||||
|
||||
#### 3.2.2 项目与分支管理
|
||||
|
||||
**项目视图**
|
||||
|
||||
- 卡片式展示已授权项目(Material Card 组件):
|
||||
- 项目名称、描述、授权分支数量、最新构建状态图标
|
||||
- 分组与排序:按项目名称、最后构建时间排序,支持收藏。
|
||||
|
||||
**分支视图**
|
||||
|
||||
- DataGrid 表格展示分支信息:
|
||||
- 分支名称、最后构建状态、时间、Commit 信息
|
||||
- 快速构建按钮:一键触发构建
|
||||
|
||||
#### 3.2.3 构建管理
|
||||
|
||||
**构建触发**
|
||||
|
||||
- 参数化构建支持:自动识别 Jenkinsfile parameters,前端动态生成 Material UI 表单。
|
||||
- 权限二次校验:调用 Jenkins API 前验证用户对该分支的构建权限。
|
||||
|
||||
**实时构建监控**
|
||||
|
||||
- 基于 WebSocket:后端 Go Gin 开启 WebSocket 路由,实时推送 Jenkins 日志。
|
||||
- **日志渲染优化**:前端解析 ANSI Color Codes,还原 Jenkins 控制台的红/黄/绿色高亮。
|
||||
|
||||
**历史构建查询**
|
||||
|
||||
- 分页展示历史构建(编号、结果、触发人、时长)。
|
||||
- **构建产物下载**:提供 .jar/.war/.apk 等构建产物的直接下载链接。
|
||||
|
||||
### 3.3 配置管理模块
|
||||
|
||||
#### 3.3.1 系统配置
|
||||
|
||||
**数据存储配置**
|
||||
|
||||
```json
|
||||
{
|
||||
"database": {
|
||||
"type": "sqlite3",
|
||||
"path": "./data/jenbranchrbac.db",
|
||||
"options": {
|
||||
"journal_mode": "WAL", // 显式开启 WAL 模式以提升并发性能
|
||||
"synchronous": "NORMAL"
|
||||
},
|
||||
"backup": {
|
||||
"enabled": true,
|
||||
"interval": "daily",
|
||||
"retention": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**安全配置**
|
||||
|
||||
```json
|
||||
{
|
||||
"security": {
|
||||
"password_encryption": {
|
||||
"algorithm": "bcrypt",
|
||||
"cost_factor": 12
|
||||
},
|
||||
"rsa_key_pair": {
|
||||
"public_key_path": "./keys/public.pem",
|
||||
"private_key_path": "./keys/private.pem"
|
||||
},
|
||||
"jwt": {
|
||||
"secret": "CHANGE_THIS_SECRET",
|
||||
"expiry": "8h",
|
||||
"refresh_window": "1h"
|
||||
},
|
||||
"mfa": {
|
||||
"enabled": false,
|
||||
"issuer": "JenBranchRBAC"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Jenkins 集成配置**
|
||||
|
||||
```json
|
||||
{
|
||||
"jenkins": {
|
||||
"url": "https://jenkins.example.com",
|
||||
"credentials": {
|
||||
"username": "api_user",
|
||||
"token": "API_TOKEN_HERE"
|
||||
},
|
||||
"sync": {
|
||||
"interval": 300,
|
||||
"timeout": 30
|
||||
},
|
||||
"proxy": {
|
||||
"enabled": false,
|
||||
"url": "http://proxy.example.com:8080"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3.2 通知配置(增强功能)
|
||||
|
||||
**邮件通知**
|
||||
|
||||
- SMTP 配置:支持 TLS/SSL
|
||||
- 通知场景:
|
||||
- 密码即将过期提醒
|
||||
- 构建失败告警(可配置失败次数阈值)
|
||||
- 账户异常登录告警
|
||||
|
||||
**Webhook 集成**
|
||||
|
||||
- 支持发送构建事件到外部系统(如钉钉、企业微信、Slack)
|
||||
- 自定义消息模板
|
||||
|
||||
### 3.4 Jenkins API 封装模块
|
||||
|
||||
#### 3.4.1 API 能力清单
|
||||
|
||||
| API 端点 | 功能 | 实现要点 |
|
||||
|---------|------|---------|
|
||||
| `/api/json?tree=jobs[...]` | 获取所有项目 | 使用 depth 和 tree 参数优化响应大小 |
|
||||
| `/job/{name}/api/json` | 获取多分支流水线的分支 | 递归解析 jobs 字段 |
|
||||
| `/job/{job}/job/{branch}/build` | 触发无参构建 | POST 请求,需 Jenkins Crumb |
|
||||
| `/job/{job}/job/{branch}/buildWithParameters` | 触发参数化构建 | 表单数据传递参数 |
|
||||
| `/job/{job}/job/{branch}/{num}/logText/progressiveText` | 增量获取构建日志 | 使用 start 参数实现断点续传 |
|
||||
| `/job/.../{num}/api/json?tree=artifacts` | 获取构建产物 | **新增:解析下载路径** |
|
||||
| `/queue/api/json` | 查询构建队列 | 估算等待时间 |
|
||||
|
||||
#### 3.4.2 API 调用优化
|
||||
|
||||
**性能优化**
|
||||
|
||||
- 使用 `tree` 参数精简返回字段,避免全量数据拉取
|
||||
- 启用 HTTP/2 和连接池复用
|
||||
- 缓存静态数据(项目列表、分支列表)
|
||||
|
||||
**可靠性保障**
|
||||
|
||||
- 重试机制:API 调用失败自动重试 3 次(指数退避)
|
||||
- 熔断降级:Jenkins 不可用时,显示缓存数据并提示用户
|
||||
- 请求限流:防止单用户频繁调用 Jenkins API
|
||||
|
||||
**安全增强**
|
||||
|
||||
- 使用 Jenkins API Token 而非用户名密码
|
||||
- 所有 POST 请求携带 Jenkins Crumb 防止 CSRF
|
||||
- API Token 加密存储在配置文件中
|
||||
|
||||
## 4. 数据库设计
|
||||
|
||||
|
||||
### 4.1 核心表结构
|
||||
|
||||
**用户表(users)**
|
||||
|
||||
```sql
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username VARCHAR(50) UNIQUE NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
role VARCHAR(20) DEFAULT 'user',
|
||||
status VARCHAR(20) DEFAULT 'active',
|
||||
password_expires_at DATETIME NOT NULL,
|
||||
failed_login_attempts INTEGER DEFAULT 0,
|
||||
locked_until DATETIME,
|
||||
mfa_secret VARCHAR(32),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login_at DATETIME
|
||||
);
|
||||
```
|
||||
|
||||
**权限表(permissions)**
|
||||
|
||||
```sql
|
||||
CREATE TABLE permissions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
project_name VARCHAR(255) NOT NULL,
|
||||
branch_name VARCHAR(255) NOT NULL,
|
||||
can_view BOOLEAN DEFAULT 1,
|
||||
can_build BOOLEAN DEFAULT 1,
|
||||
granted_by INTEGER NOT NULL,
|
||||
granted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (granted_by) REFERENCES users(id),
|
||||
UNIQUE(user_id, project_name, branch_name)
|
||||
);
|
||||
```
|
||||
|
||||
**审计日志表(audit_logs)**
|
||||
|
||||
```sql
|
||||
CREATE TABLE audit_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER,
|
||||
action VARCHAR(50) NOT NULL,
|
||||
resource_type VARCHAR(50),
|
||||
resource_id VARCHAR(255),
|
||||
details TEXT,
|
||||
ip_address VARCHAR(45),
|
||||
user_agent TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
);
|
||||
CREATE INDEX idx_audit_logs_user_time ON audit_logs(user_id, created_at);
|
||||
CREATE INDEX idx_audit_logs_action ON audit_logs(action);
|
||||
```
|
||||
|
||||
**权限模板表(permission_templates)**
|
||||
|
||||
```sql
|
||||
CREATE TABLE permission_templates (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR(100) UNIQUE NOT NULL,
|
||||
description TEXT,
|
||||
permissions_json TEXT NOT NULL,
|
||||
created_by INTEGER NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (created_by) REFERENCES users(id)
|
||||
);
|
||||
```
|
||||
|
||||
## 5. 技术选型建议 (Updated)
|
||||
|
||||
### 5.1 后端技术栈
|
||||
|
||||
- **语言**:Go (Golang) 1.24+
|
||||
- **Web 框架**:**Gin**
|
||||
- *选择理由*:高性能、轻量级、中间件生态丰富,适合构建高并发 REST API。
|
||||
- **ORM 框架**:**GORM**
|
||||
- *选择理由*:Go 语言事实标准的 ORM,支持 SQLite/MySQL/PostgreSQL 驱动,提供 AutoMigrate 能力,方便未来无缝迁移数据库。
|
||||
- **身份认证**:`golang-jwt/jwt` (JWT) + `golang.org/x/crypto/bcrypt` (密码哈希)。
|
||||
- **任务调度**:`robfig/cron`
|
||||
- *架构优势*:利用 Go 的协程 (Goroutine) 处理定时同步和异步任务,**无需** 额外部署 Redis + Celery 等复杂的消息队列组件,大幅降低运维成本。
|
||||
- **Jenkins SDK**:`bndr/gojenkins` 或基于 `resty` 封装的 HTTP Client。
|
||||
- **日志处理**:`zap` 或 `logrus` (高性能结构化日志)。
|
||||
|
||||
### 5.2 前端技术栈
|
||||
|
||||
- **框架**:**Vue 3** (Composition API)
|
||||
- **语言**:**TypeScript**
|
||||
- *选择理由*:提供静态类型检查,增强代码可维护性和智能提示,适合企业级后台管理系统。
|
||||
- **UI 组件库**:**Vuetify 3** (Material Design for Vue)
|
||||
- *说明*:严格遵循 Material Design 规范的 Vue 组件库,提供丰富的现成组件(Card, DataGrid, Dialog 等),响应式设计优秀。
|
||||
- **状态管理**:**Pinia** (Type-safe store)。
|
||||
- **网络请求**:`Axios` (封装拦截器处理 JWT 刷新)。
|
||||
- **日志渲染**:`ansi-to-html` (解析构建日志颜色)。
|
||||
|
||||
### 5.3 部署方案
|
||||
|
||||
- **编译构建**:后端编译为**单二进制文件** (Static Binary),前端编译为静态资源 (dist)。
|
||||
- **容器化**:使用 Multi-stage Dockerfile 构建,最终镜像体积可控制在 20MB-50MB 级别(使用 Alpine 基础镜像)。
|
||||
- **反向代理**:Nginx 托管前端静态文件,并反向代理 API 请求到 Go 服务。
|
||||
|
||||
## 6. 非功能性需求
|
||||
|
||||
### 6.1 性能指标
|
||||
|
||||
- **高并发**:得益于 Go 的 Goroutine 模型,单实例可轻松支持 **1000+ 并发连接**(WebSocket 日志推送)。
|
||||
- **低延迟**:API 响应时间 < 100ms。
|
||||
- **资源占用**:空闲状态下内存占用 < 50MB。
|
||||
- **快速启动**:服务启动时间 < 1s。
|
||||
|
||||
### 6.2 安全与合规
|
||||
|
||||
- 密码零明文存储(Bcrypt)。
|
||||
- API 接口实施 RBAC 中间件拦截。
|
||||
- SQL 注入防御(GORM 参数化查询)。
|
||||
- XSS 防御(Vue 自动转义 + Gin Secure JSON)。
|
||||
|
||||
### 6.3 可维护性
|
||||
|
||||
- 前后端完全分离,通过 Swagger/OpenAPI (swag 库自动生成) 定义接口。
|
||||
- Go 强类型语言特性减少运行时错误。
|
||||
|
||||
## 7. 实施路线图
|
||||
|
||||
### Phase 1 - MVP(4 周)
|
||||
|
||||
- 初始化 Go Gin 项目脚手架与 GORM 模型设计。
|
||||
- 实现基于 JWT 的用户认证与权限中间件。
|
||||
- 完成 Jenkins API 的 Go 语言封装与数据同步协程。
|
||||
- 前端 Vue3 + TS + Vuetify 基础框架搭建及登录页。
|
||||
- **验证**:SQLite WAL 模式下的 GORM 并发读写稳定性。
|
||||
|
||||
### Phase 2 - 增强功能(3 周)
|
||||
|
||||
- 实现 WebSocket 路由,对接 Jenkins 实时日志流。
|
||||
- 开发前端日志组件(支持 ANSI 颜色渲染)。
|
||||
- 实现构建产物解析与下载接口。
|
||||
- 完善审计日志的中间件埋点(Aspect-Oriented Programming)。
|
||||
|
||||
### Phase 3 - 企业级特性(3 周)
|
||||
|
||||
- 集成 TOTP (MFA) 认证。
|
||||
- 增加 Webhook 通知模块(Go 协程异步发送)。
|
||||
- 压力测试:验证 500+ 用户同时在线时的内存泄漏情况(pprof 分析)。
|
||||
- 编写 Dockerfile 与 CI/CD 流水线。
|
||||
|
||||
## 8. 风险与挑战 (针对新栈)
|
||||
|
||||
- **前端学习曲线**:TypeScript 和 Vuetify 的系统性较强,需确保前端团队熟悉类型系统。
|
||||
- *缓解*:制定 TS 规范,提供类型定义文件 (*.d.ts)。
|
||||
- **Jenkins API 结构变动**:Go 是强类型语言,解析 Jenkins 不规范的 JSON 可能报错。
|
||||
- *缓解*:在 JSON 解析层定义松散的 struct 或使用 `interface{}` 配合断言处理动态字段。
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user