RMDC系统设计文档 整体转换为SKILL

This commit is contained in:
zeaslity
2026-01-21 16:15:49 +08:00
parent fc72a7312e
commit 631cce9e1e
163 changed files with 37099 additions and 114 deletions

View 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
```

View 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
}

View 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, "用户创建成功")
}

View 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
}

View 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. 审计敏感操作
所有写操作需通过审计中间件记录。

View File

@@ -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 |

View File

@@ -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()`

View 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 "未知错误"
}

View 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)
```

View 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 日志禁止缺少堆栈信息

View File

@@ -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
```

View 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"
```

View File

@@ -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 "=== 验证完成 ==="

View 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.

View 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',
}),
}

View 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>

View 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>

View 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 : []
)
}
```

View 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>
```

View 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
```

View 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>
```

View 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`

View File

@@ -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
}

View File

@@ -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"
}

View File

@@ -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
}

View File

@@ -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 只能转授自己拥有的权限 |
| 权限撤销 | 不影响已创建的草稿/工单 |
| 项目归档 | 保留权限记录,但无法访问 |

View File

@@ -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"
}
]
}
]
}
}
```

View File

@@ -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);
```

View File

@@ -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 (保留) |

View File

@@ -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 回调方案。

View File

@@ -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

View 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) |

View File

@@ -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)
}
}

View File

@@ -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`

View File

@@ -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 分钟 |

View File

@@ -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+ |

View File

@@ -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

View File

@@ -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

View 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-managementwatchdog)与二级授权(watchdogagent/node)使用不同参数
2. **时间偏移未处理**授权文件需计算 `timeOffset = now - firstAuthTime`
3. **Node离线未检测**转发主机指令前需 `CheckHostOnline(host_id)`
4. **日志截断遗漏**业务故障日志仅回传最近300行
5. **密钥公网传输**tier_one_secret/tier_two_secret 必须通过配置文件离线部署禁止MQTT传输
6. **响应TOTP缺失**双向验证要求服务端返回TOTP供客户端校验
7. **心跳间隔不一致**watchdogexchange-hub 5秒agent/nodewatchdog 10秒默认
## Reference
- [状态机](reference/state-machine.md)
- [MQTT Topics](reference/mqtt-topics.md)
- [API端点](reference/api-endpoints.md)
- [安全机制](reference/security-mechanisms.md)

View 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"`
}
```

View 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"`
}
```

View File

@@ -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校验写审计日志 |

View 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指令
- 动作:清除本地授权存储

View 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 "=== 验证完成 ==="

View 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
### 产物清单
- [ ] 状态机配置/代码(状态定义、转换规则、权限矩阵)
- [ ] 数据库 Migrationworkflows主表、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)

View File

@@ -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()
}

View File

@@ -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
}

View File

@@ -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"
)

View File

@@ -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
权限列表

View File

@@ -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;
```

View File

@@ -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: 提交审核
```

View File

@@ -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: '处理完成'
});
```

View File

@@ -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 │ ○ 定时执行 │ │
│ │ 备注: [文本框] │ │
└─────────────────┴─────────────────────────┴────────────────────┘
```

View File

@@ -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

View 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

View File

@@ -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

View 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)

View File

@@ -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 | 计入失败,等待授权恢复 |

View File

@@ -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 |

View File

@@ -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 "=== 验证完成 ==="

View 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)

View File

@@ -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
}
}
```

View 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

View 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 "=== 验证完成 ==="

View 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用SHA256Tier-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)

View 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. 业务进程优雅终止
```

View 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个周期 |

View File

@@ -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

View 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. 审计敏感操作
所有写操作需通过审计中间件记录。

View File

@@ -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. **错误处理必须完整,关键错误必须记录日志**

View 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中如何处理循环依赖的关系

View 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.

View 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说明

View 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>
```
---

View 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. 加载中和无数据时,是否提供了美观的反馈界面?
请确保你的代码方案在视觉和交互上是 “生产级” 的,而不仅仅是逻辑跑通。

View 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标准进行深度的优化

View 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()` 生成迷你的走势图,非常直观且不占位置。

View 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

View File

@@ -0,0 +1,53 @@
vector educational infographic, flat flowchart
适配 nano banana pro教学信息图风格
白色背景,横向 16:9
主题RSA 加密通信流程(公钥加密,私钥解密)
整体布局:
从左到右三栏,用留白或分隔线区分
1⃣ 发送方 Alice
2⃣ 不安全网络 Network
3⃣ 接收方 Bob
详细流程:
(步骤 1Bob
模块:生成 RSA 密钥对
- 公钥 (n, e):可公开
- 私钥 (n, d):必须保密(加锁、红色)
(步骤 2公钥分发
公钥从 Bob → Alice
标注文字:公钥可以被任何人获取
(步骤 3Alice 加密)
- 明文消息 M文件图标
- 模块RSA 加密
- 模块内公式(大字号、居中):
C = M^e mod n
- 输出:密文 C
(步骤 4Network
- 密文 C 在网络中传输
- 窃听者 Eve
气泡文字:
“我看到了密文,但没有私钥”
(步骤 5Bob 解密)
- 模块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.

View File

@@ -0,0 +1,55 @@
vector educational infographic, flat style
适配 nano banana pro结构化流程图
白色背景,横向 16:9
主题RSA 数字签名机制(私钥签名,公钥验签)
布局:
左:发送方 Bob签名者
中:不安全网络
右:接收方 Alice验证者
流程步骤:
(步骤 1Bob
生成 RSA 密钥对
- 私钥:加锁,强调“仅自己持有”
- 公钥:可公开
(步骤 2消息处理
- 原始消息 M
- 模块Hash 计算
显示:
h = Hash(M)
(步骤 3私钥签名
- 模块RSA 签名
- 输入h + 私钥
- 公式(大字号):
S = h^d mod n
- 得到:签名 S
(步骤 4发送
发送:
- 消息 M
- 签名 S
(步骤 5Alice 验证)
- 重新计算:
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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 MiB

View 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

View 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 风格算法图,学术论文插图,极简技术示意图,白底矢量图`

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

View 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**将数据分成固定大小的块进行加密如AES128位块、3DES64位块。块密码需要配合特定的操作模式使用常见模式包括
- ECB电子密码本模式最简单但不安全
- CBC密码块链接模式使用初始化向量增强安全性
- GCM伽罗瓦计数器模式提供认证加密功能
- XTSXEX-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 nM为明文
**解密过程**明文 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
**安全性**即使攻击者截获了pgA和B也无法计算出共享密钥s因为这需要解决离散对数问题
**现代变体**
- **静态DH**至少一方使用固定公钥不提供前向保密
- **临时DH (Ephemeral DH/DHE)**双方都生成临时密钥对提供前向保密性
临时DH是TLS 1.3的核心密钥交换机制确保即使长期私钥泄露过去的会话数据仍然安全
**4. ECC (Elliptic Curve Cryptography)**
椭圆曲线密码学是一种基于椭圆曲线代数结构的公钥加密方法相比RSAECC能够用更短的密钥提供相同级别的安全性
**数学基础**椭圆曲线定义为满足方程 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 Random32字节的随机值用于后续密钥生成
- 会话ID如果希望恢复之前的会话
- 扩展字段如SNI服务器名称指示)、支持的签名算法等
2. **ServerHello消息**
服务器响应客户端选择加密参数
- 选定的TLS协议版本
- 选定的密码套件从客户端列表中选择
- 服务器随机数Server Random另一个32字节随机值
- 会话ID用于会话恢复
- 扩展响应
如果客户端和服务器没有共同支持的密码套件连接将立即终止
**阶段二:服务器认证与密钥信息**
3. **Certificate消息**
服务器发送其数字证书链包含
- 服务器的SSL/TLS证书
- 中间证书如有
- 证书中包含服务器的公钥域名信息有效期证书颁发机构(CA)签名
客户端会验证证书的有效性
- 检查证书是否由可信CA签发
- 验证证书未过期
- 确认证书中的域名与访问的网站匹配
- 检查证书未被吊销
4. **ServerKeyExchange消息**可选
对于某些密码套件如DHEECDHE服务器需要发送额外的密钥交换参数但对于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-HellmanDHE/ECDHE机制在这种机制下即使长期私钥泄露之前的会话密钥仍然无法被推导出来
### 四、SHA-256算法的实现原理与安全性
SHA-256是SHA-2Secure 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轮**
对于每一轮t0到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-NIAdvanced 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算法定义
TOTPTime-based One-Time Password基于时间的一次性密码是一种用于生成临时验证码的算法广泛应用于双因素认证(2FA)系统。它是HOTPHMAC-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₀**起始时间点默认为0Unix纪元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时间戳是17044704002024-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字节的哈希值
```
HMACHash-based Message Authentication Code本身是一个两轮哈希计算过程
```
HMAC(K, M) = H((K ⊕ opad) || H((K ⊕ ipad) || M))
```
其中H是哈希函数如SHA-1ipad和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)
```
**完整示例**
假设:
- 共享密钥JBSWY3DPEHPK3PXPBase32编码
- 当前时间计数器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
- **非对称加密**优先选择ECCECDSA/ECDH或RSA-2048+
- **哈希算法**使用SHA-256或更高SHA-384/SHA-512
- **密码存储**使用Argon2bcrypt或scrypt而非单纯的SHA-256
**遗留系统兼容**
- 避免使用DES3DESMD5SHA-1已知安全弱点
- 如必须兼容应在外层增加额外的安全措施
### 八、结论
现代密码学已经发展成为一个复杂而精密的技术体系从对称加密的高效性到非对称加密的灵活性从哈希函数的完整性保障到认证协议的身份验证每种技术都在数字安全生态系统中扮演着不可替代的角色
**关键要点总结**
1. **对称加密**如AES提供高性能的数据保护是大规模数据加密的基石现代CPU的硬件加速使其性能更加卓越
2. **非对称加密**如RSAECC解决了密钥分发难题虽然速度较慢但在密钥交换和数字签名中不可或缺
3. **SSL/TLS握手**展示了如何优雅地结合对称和非对称加密在安全性和性能之间取得平衡TLS 1.3通过引入前向保密进一步提升了安全性
4. **SHA-256**作为最广泛使用的哈希算法其设计的数学基础确保了即使在量子计算时代来临前都难以被破解其2²⁵⁶的输出空间和64轮混淆机制构成了坚不可摧的防线
5. **AES-NI硬件加速**不仅显著提升了加密性能更重要的是消除了软件实现中的侧信道攻击威胁展示了硬件安全的重要性
6. **TOTP算法**证明了简单而优雅的设计可以提供强大的安全保障它基于时间和共享密钥的机制在双因素认证中起到了关键作用
**未来展望**
随着量子计算的发展传统的RSA和ECC可能面临威胁后量子密码学如基于格的密码学ML-KEM正在成为研究热点同时零知识证明同态加密等前沿技术也在不断拓展密码学的应用边界
对于开发者而言理解这些加密技术的原理和适用场景选择合适的算法并正确实现是构建安全系统的基础永远记住**密码学工具只是手段正确使用这些工具才是保障安全的关键**。

View 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).
效率提升参照的文件为![1.1-efficiency.png](1.1-efficiency.png)
我建议的组织架构图片为![1.2-group-arch.png](1.2-group-arch.png)
经过领导的考虑,领导建议参考去年郭忠勇场景特战队模式先成立交付特战队进行试点,如果效果好再考虑分拆成组。参考场景特战队的组建方案按照他们的想法梳理一版交付特战队的方案吧,考核目标和考核办法可以先提供想法和思路,后续我们会拉着研发产品和市场讨论后再确认
[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服务器的标准化部署方案
请你基于上述的信息,帮我撰写一份<统一交付特战队>的组建方案

View 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多个项目需要维护安全问题维护、版本升级、运行环境清理等

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 KiB

View 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. **技术兜底与售后**:负责共性安全漏洞修复、基线加固及重大技术故障处理。
* **建议人员构成**
* 组长:任一珂
* 核心成员:王子文、袁雪波、郑岩、郝昊
### 四、 总结
通过“明确分工”与“工具赋能”,我们希望将研发团队从繁琐的重复劳动中解放出来,让业务组聚焦业务逻辑,让基础组聚焦效能提升。这不仅是对现有痛点的解决,更是应对未来更大规模市场化交付的必要准备。
以上是我的浅见,不当之处,请剑哥批评指正!

View 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%。
(三)专项攻坚特战队成员绩效名额进行单列,不回归所在班组名额比例管理。

View 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-contractsAPI/事件/Schema 契约、兼容策略、版本策略)
- database-migrationsPostgreSQL迁移与回滚、字段演进
- 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产物清单 + 决策点例如涉及哪些模块改动边界是否影响契约/事件/
- VerifyChecklist可勾选并明确验证点”(例如契约兼容topic/事件字段一致DB migration 可回滚RBAC 不破坏
- Execute可操作步骤命令化短句按顺序
- Pitfalls3~8 条常见坑必须与该模块/横切主题相关
## 4. 参数与动态上下文
- 每个 Skill 必须使用 $ARGUMENTS例如模块名变更类型输入文档路径目标目录等
- 每个 Skill 必须至少包含 2 !`command` 动态注入示例用于读取仓库结构查找接口/事件/运行测试
- 所有示例命令需在类 Unix shell 下可执行避免 OS 特定路径
## 5. 质量标准
- 每个 SKILL.md 主体 <500
- 术语一致模块名契约事件topic授权层级一级/二级)、DACRBACJWT审计
- 输出必须可落地”:能指导真实开发或文档对齐/审查
# 目录模板(必须套用)
<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

View File

@@ -0,0 +1,71 @@
你是一位专业的 Agent Skills 架构师与 Claude Code Skills 作者。你的任务是:把给定的 prompt/规范文档转换为符合 Claude CodeAgent 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 字段推荐namedescription可选argument-hintallowed-toolsdisable-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/AgentSkillreference/refs
- 不要写 Claude 已知的通用常识只保留领域特定与操作步骤
## 生成步骤(你必须按此顺序输出)
1) 先给出 skill 的命名候选 3 均为动名词风格并选择 1 个作为最终 name附一句理由
2) 输出目录树Unix 路径
3) 输出 SKILL.md完整
4) 如有 supporting files文件路径 -> 文件内容”的形式逐个输出
5) 最后输出 Verify Checklist 的“自检结果”(逐条 PASS/FAILFAIL 要说明原因与修复)
## 目录模板(参考,按需增删)
<skill-name>/
SKILL.md
reference/
...
examples/
...
scripts/
...
现在开始。源文档路径由我通过参数传入:$ARGUMENTS

View File

@@ -13,4 +13,7 @@
- 语言专业化: 使用业界标准的术语和概念来描述架构组件、数据流和设计决策。
请你遵守前端项目的风格vue3-typescript-stye.md针对mask_binance_account.md的初始设计文稿给出专业性的优化设计回答内容必须是中文
请你遵守前端项目的风格vue3-typescript-stye.md针对mask_binance_account.md的初始设计文稿给出专业性的优化设计回答内容必须是中文

View 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分支的构建信息输出
- 能够查询特定项目特定分支的历史构建信息

View 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 - MVP4 周)
- 初始化 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