From 93624efdab2cf9fc00a3a79014805dbafe43165e Mon Sep 17 00:00:00 2001 From: zeaslity Date: Thu, 22 Jan 2026 11:58:03 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0rmdc-project-management?= =?UTF-8?q?=E7=9A=84SKILL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../developing-project-management/SKILL.md | 146 +++--- .../reference/data-structures.md | 428 +++++++++++++++ .../reference/frontend-design.md | 496 ++++++++++++++++++ .../reference/version-control-design.md | 448 ++++++++++++++++ 4 files changed, 1446 insertions(+), 72 deletions(-) create mode 100644 1-AgentSkills/developing-project-management/reference/data-structures.md create mode 100644 1-AgentSkills/developing-project-management/reference/frontend-design.md create mode 100644 1-AgentSkills/developing-project-management/reference/version-control-design.md diff --git a/1-AgentSkills/developing-project-management/SKILL.md b/1-AgentSkills/developing-project-management/SKILL.md index e33177e..ac49880 100644 --- a/1-AgentSkills/developing-project-management/SKILL.md +++ b/1-AgentSkills/developing-project-management/SKILL.md @@ -1,7 +1,7 @@ --- 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 mangement, project lifecycle, version snapshot, ACL, TOTP, workflow callback, SuperAdmin. -argument-hint: " [target]" where change-type is one of: api|entity|service|migration|frontend|auth. Example: "api draft-submit" or "migration add-field" +description: Guides development of rmdc-project-management module including project lifecycle management, Git-like version control with snapshot/diff, 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, diff algorithm, ACL, TOTP, workflow callback, SuperAdmin, optimistic lock. +argument-hint: " [target]" where change-type is one of: api|entity|service|migration|frontend|auth|version. Example: "api draft-submit" or "version diff-algorithm" allowed-tools: - Read - Glob @@ -17,9 +17,10 @@ allowed-tools: ## 模块定位 -- **核心职责**: 项目 CRUD、版本控制(Git-like)、细粒度 ACL 权限、一级 TOTP 授权 +- **核心职责**: 项目 CRUD、Git-like 版本控制、细粒度 ACL 权限、一级 TOTP 授权 - **技术栈**: Go + Gin + GORM + PostgreSQL (JSONB) - **架构**: 模块化单体,通过接口注入与 `rmdc-work-procedure` 工单模块协作 +- **版本控制思想**: 类似 Git 的分支管理(Master 主线 + 用户草稿分支) ## 动态上下文注入 @@ -29,8 +30,8 @@ allowed-tools: # 查看项目管理模块目录结构 !`find . -path "*/rmdc-project-management/*" -name "*.go" | head -20` -# 查找生命周期状态相关代码 -!`grep -rn "lifecycle_status\|LifecycleStatus" --include="*.go" | head -15` +# 查找版本控制相关代码 +!`grep -rn "VersionSnapshot\|CompareVersions\|DiffResult" --include="*.go" | head -15` ``` --- @@ -41,32 +42,23 @@ allowed-tools: 根据 `$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 交互 | +| 变更类型 | 产物文件 | 影响模块 | 参考文档 | +|:---|:---|:---|:---| +| `api` | `handler/*.go`, `router.go` | rmdc-core 路由注册 | `reference/api-endpoints.md` | +| `entity` | `entity/*.go` | 数据库迁移、DTO 映射 | `reference/data-structures.md` | +| `service` | `service/*.go` | 业务逻辑、版本快照 | `reference/version-control-design.md` | +| `migration` | `migrations/*.sql` | 数据库 Schema | `reference/database-schema.md` | +| `frontend` | `pages/*.vue`, `components/*.vue` | 前端联调 | `reference/frontend-design.md` | +| `auth` | `service/auth_*.go` | TOTP 授权、Exchange-Hub 交互 | `reference/acl-permission-model.md` | +| `version` | `service/version_*.go` | 版本快照、Diff 算法 | `reference/version-control-design.md` | ### 决策点 -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` +1. **是否涉及生命周期状态变更?** → 检查 `reference/lifecycle-state-machine.md` +2. **是否修改版本快照结构?** → 检查 `reference/version-control-design.md` 第5节 +3. **是否涉及并发修改冲突?** → 检查乐观锁实现(base_version 校验) +4. **是否变更 ACL 权限模型?** → 检查 `reference/acl-permission-model.md` +5. **是否影响工单模块回调?** → 检查 `reference/workflow-state-mapping.md` --- @@ -77,20 +69,25 @@ allowed-tools: - [ ] **生命周期状态机完整性**: 所有状态转换有明确的触发条件和权限控制 - [ ] **版本快照一致性**: `projects` 表与 `project_versions` 表数据同步 - [ ] **乐观锁检查**: 并发修改时 `base_version == current_version` 校验存在 -- [ ] **ACL 权限验证**: 接口权限注解与业务逻辑一致 +- [ ] **超管直改版本生成**: SuperAdmin 直接修改必须同时生成版本记录(原子事务) +- [ ] **Diff 算法正确性**: 版本对比结果按模块分组,字段路径完整,中文名映射正确 +- [ ] **ACL 权限验证**: 接口权限注解与业务逻辑一致,授权模块仅 SuperAdmin 可见 - [ ] **工单回调幂等**: 状态更新操作具备幂等性 - [ ] **敏感字段加密**: 密码字段使用 AES-256 加密存储 - [ ] **审计日志**: 所有写操作记录到 `rmdc-audit-log` -- [ ] **TOTP 授权安全**: 一级密钥仅 SuperAdmin 可访问 +- [ ] **Namespace 校验**: 符合 RFC 1123 DNS 标签规范 ### 验证命令 ```bash -# 检查实体字段与数据库 Schema 一致性 -!`grep -rn "gorm:\"" entity/project.go | head -20` +# 检查版本服务实现 +!`grep -rn "CompareVersions\|CreateOfficialVersion\|VersionSnapshot" service/*.go` -# 检查 API 路由权限注解 -!`grep -rn "RequireRole\|RequirePermission" handler/*.go` +# 检查乐观锁实现 +!`grep -rn "base_version\|BaseVersion\|VersionConflict\|409" --include="*.go"` + +# 检查敏感字段加密 +!`grep -rn "EncryptAES\|DecryptAES\|admin_password\|ssh_pwd" --include="*.go"` # 运行模块单元测试 go test ./internal/project/... -v -cover @@ -102,19 +99,26 @@ go test ./internal/project/... -v -cover ### 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. 定义请求/响应结构体 → `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 结果的字段名映射表 +1. 更新 `VersionSnapshot` 结构体定义 → `reference/data-structures.md` +2. 更新字段名映射表 `fieldNameMap` → 确保 Diff 显示中文名 +3. 确保 `CompareVersions` Diff 算法兼容新字段 4. 测试历史版本查看功能不受影响 +### SuperAdmin 直改流程 + +1. 更新 `projects` 表 + 插入 `project_versions` 表**必须在同一事务** +2. `workflow_id` 设为空或 `DIRECT_EDIT` 标识 +3. `committer_id` 记录 SuperAdmin ID +4. 更新 `current_version` 字段 + ### 生命周期状态变更流程 1. 更新 `reference/lifecycle-state-machine.md` 状态图 @@ -122,32 +126,25 @@ go test ./internal/project/... -v -cover 3. 同步更新 `ProjectLifecycleUpdater` 接口实现 4. 验证与工单模块的状态映射表一致 -### 授权功能变更流程 - -1. 检查 `project_auth_configs` 表结构 -2. 更新 `AuthorizationInfo` 结构体 -3. 确保 TOTP 密钥生成/验证逻辑正确 -4. 测试与 Exchange-Hub 的授权指令下发 - --- ## Pitfalls(常见问题) -1. **超管直改未生成版本**: SuperAdmin 直接修改 `projects` 表时,必须同时插入 `project_versions` 记录,否则版本链断裂 +1. **超管直改未生成版本**: SuperAdmin 直接修改 `projects` 表时,必须同时插入 `project_versions` 记录,否则版本链断裂,后续 Diff 失效 -2. **草稿基准版本过期**: 用户 A 基于 v3 创建草稿,超管修改产生 v4,用户 A 提交时需检测冲突并提示 Rebase +2. **草稿基准版本过期**: 用户 A 基于 v3 创建草稿,超管修改产生 v4,用户 A 提交时需检测冲突(`draft.base_version != project.current_version`)并返回 409 Conflict 3. **工单回调重复处理**: 工单模块可能重试回调,`ProjectLifecycleUpdater` 实现必须幂等 4. **ACL 权限遗漏授权模块**: `authorization_info` 模块仅 SuperAdmin 可见,其他角色查询时需过滤 -5. **密码字段明文泄露**: `AdminPassword`、`SSHPwd` 等字段响应时必须脱敏或不返回 +5. **密码字段明文泄露**: `AdminPassword`、`SSHPwd` 等字段响应时必须脱敏(返回 `********`) -6. **省市级联校验缺失**: 前端省市级联选择后,后端需校验省市对应关系有效性 +6. **Namespace 唯一性**: 创建项目时必须校验 `namespace` 全局唯一且符合 RFC 1123 DNS 标签规范(小写字母开头,只含小写字母/数字/-/.) -7. **Namespace 唯一性**: 创建项目时必须校验 `namespace` 全局唯一且符合 RFC 1123 DNS 标签规范 +7. **JSONB 字段空值处理**: `basic_info`、`deploy_business` 等 JSONB 字段为空时,需返回空对象 `{}` 而非 `null` -8. **JSONB 字段空值处理**: `basic_info`、`deploy_business` 等 JSONB 字段为空时,需返回空对象 `{}` 而非 `null` +8. **版本号混淆**: 草稿版本号为 0,正式版本从 1 开始递增,切勿混淆;正式版本必须保证唯一性 --- @@ -156,29 +153,34 @@ go test ./internal/project/... -v -cover ``` rmdc-project-management ├── → rmdc-user-auth (用户鉴权、ACL 权限查询) -├── → rmdc-work-procedure (工单创建、状态转换) +├── ↔ 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 | +| 类别 | 路径 | 权限 | 说明 | +|:---|:---|:---|:---| +| 项目列表 | `POST /api/project/list` | Login | 自动过滤 ACL | +| 项目详情 | `POST /api/project/detail` | View ACL | Master 版本 | +| 创建项目 | `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 | 仅 official 类型 | +| 版本对比 | `POST /api/project/version/diff` | 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` +| 文档 | 内容 | +|:---|:---| +| `reference/lifecycle-state-machine.md` | 生命周期状态机、状态转换条件 | +| `reference/version-control-design.md` | 版本快照、Diff 算法、乐观锁、冲突检测 | +| `reference/database-schema.md` | DDL、索引、JSONB 结构示例 | +| `reference/data-structures.md` | 实体定义、枚举常量、字段校验规则 | +| `reference/acl-permission-model.md` | RBAC/ACL 权限模型、权限检查流程 | +| `reference/workflow-state-mapping.md` | 工单状态与项目生命周期映射、回调接口 | +| `reference/api-endpoints.md` | API 清单、请求/响应示例 | diff --git a/1-AgentSkills/developing-project-management/reference/data-structures.md b/1-AgentSkills/developing-project-management/reference/data-structures.md new file mode 100644 index 0000000..38e1f98 --- /dev/null +++ b/1-AgentSkills/developing-project-management/reference/data-structures.md @@ -0,0 +1,428 @@ +# 数据结构定义 + +本文档定义项目管理模块中所有核心数据结构,包括实体、DTO、JSONB 存储结构。 + +--- + +## 1. 项目主表实体 (Project) + +```go +// 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"` + + common.BaseModel // CreatedAt, UpdatedAt, DeletedAt +} + +func (Project) TableName() string { + return "projects" +} +``` + +--- + +## 2. 版本表实体 (ProjectVersion) + +```go +// 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"` +} + +func (ProjectVersion) TableName() string { + return "project_versions" +} +``` + +--- + +## 3. 项目工单关联表 (ProjectWorkflow) + +```go +// ProjectWorkflow 项目与工单关联表 +type ProjectWorkflow struct { + ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` + ProjectID string `gorm:"type:varchar(64);index;not null" json:"project_id"` + WorkflowID string `gorm:"type:varchar(64);uniqueIndex;not null" json:"workflow_id"` + + // 工单类型: fill(填写)/modify(修改) + WorkflowType string `gorm:"type:varchar(32);not null" json:"workflow_type"` + + // 工单状态 (冗余存储,便于查询) + Status string `gorm:"type:varchar(32)" json:"status"` + + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} +``` + +### 项目与工单关系说明 + +| 关系类型 | 项目状态 | 约束 | +|:---|:---|:---| +| 项目:填写工单 = 1:1 | INIT/DRAFTING | 项目创建时只能有一个填写工单 | +| 项目:修改工单 = 1:N | RELEASED/MODIFYING | 已发布项目可以有多个修改工单 | +| 用户:修改工单 = 1:1 (per project) | - | 非SuperAdmin用户同一项目只能有一个活跃修改工单 | + +--- + +## 4. 授权配置表 (ProjectAuthConfig) + +```go +// 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"` // 加密存储 + TimeOffset int `gorm:"default:30" json:"time_offset"` // 允许时间偏移(秒) + TOTPEnabled bool `gorm:"default:false" json:"totp_enabled"` + + // 二级授权 (来自 Watchdog) + TierTwoSecret string `gorm:"type:varchar(128)" json:"tier_two_secret"` // 加密存储 + + // 授权状态 + 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"` +} +``` + +--- + +## 5. JSONB 结构定义 + +### 5.1 基本信息 (BasicInfo) + +```go +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"` // 项目性质(枚举) +} +``` + +**项目性质枚举**: + +| 值 | 说明 | +|:---|:---| +| `research` | 科研 | +| `test` | 测试 | +| `trial` | 试用 | +| `market` | 市场化 | +| `sub_platform` | 二级平台 | + +### 5.2 部署业务 (DeployBusiness) + +```go +type DeployBusiness struct { + DeployerName string `json:"deployer_name"` // 部署人姓名 + DeployerPhone string `json:"deployer_phone"` // 部署人电话 + DeployStartTime string `json:"deploy_start_time"` // 部署开始时间 (YYYY-MM-DD) + DeployEndTime string `json:"deploy_end_time"` // 部署结束时间 (YYYY-MM-DD) + SystemVersion string `json:"system_version"` // 部署系统版本 + SystemType string `json:"system_type"` // 系统类型(枚举) + MainEntrance string `json:"main_entrance"` // 业务主要入口URL + AdminUsername string `json:"admin_username"` // 系统超管用户名 + AdminPassword string `json:"admin_password"` // 系统超管密码 ⚠️加密存储 +} +``` + +**系统类型枚举**: + +| 值 | 说明 | +|:---|:---| +| `business` | 老行业平台 | +| `fly-control` | 新飞控平台 | +| `supervisor` | 监管平台 | + +### 5.3 部署环境 (DeployEnv) + +```go +type DeployEnv struct { + // 主机信息列表 + Hosts []HostInfo `json:"hosts"` + + // 网络环境 + NetworkType string `json:"network_type"` // 网络类型(枚举) + MainPublicIP string `json:"main_public_ip"` // 主要公网IP + DomainURL string `json:"domain_url"` // 域名URL + SSLEnabled bool `json:"ssl_enabled"` // 是否开启SSL + + // 管理方式 + ManagementType string `json:"management_type"` // 管理类型(枚举) + ManagementURL string `json:"management_url"` // 管理后台URL + ManagementUser string `json:"management_user"` // 管理后台用户名 + ManagementPwd string `json:"management_pwd"` // 管理后台密码 ⚠️加密存储 + + // 统计信息 + HostCount int `json:"host_count"` // 主机台数 + TotalCPU int `json:"total_cpu"` // CPU总核数 + CPUModel string `json:"cpu_model"` // CPU型号 + TotalMemory int `json:"total_memory"` // 内存总大小(GB) + TotalStorage int `json:"total_storage"` // 存储总大小(GB) +} + +type HostInfo struct { + Hostname string `json:"hostname"` // 主机名 + InternalIP string `json:"internal_ip"` // 内网IP + PublicIP string `json:"public_ip"` // 公网IP(可选) + CanAccessPublic bool `json:"can_access_public"` // 能否访问公网 + SSHPort int `json:"ssh_port"` // SSH端口 + SSHUser string `json:"ssh_user"` // SSH用户名 + SSHPwd string `json:"ssh_pwd"` // SSH密码 ⚠️加密存储 + Role string `json:"role"` // 主机角色(枚举) +} +``` + +**网络类型枚举**: + +| 值 | 说明 | +|:---|:---| +| `internal` | 完全内网 | +| `single_public` | 单主机公网 | +| `full_public` | 全访问公网 | + +**管理类型枚举**: + +| 值 | 说明 | +|:---|:---| +| `bastion` | 堡垒机 | +| `whitelist` | 白名单 | +| `vpn` | VPN | + +**主机角色枚举**: + +| 值 | 说明 | +|:---|:---| +| `master` | 主节点 | +| `worker` | 工作节点 | +| `storage` | 存储节点 | + +### 5.4 部署中间件 (DeployMiddleware) + +```go +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"` // 公网IP + PublicPort int `json:"public_port"` // 公网端口 + InternalIP string `json:"internal_ip"` // 内网IP + InternalPort int `json:"internal_port"` // 内网端口 + K8SAddress string `json:"k8s_address"` // K8S集群内访问地址 (Service Name) + K8SPort int `json:"k8s_port"` // K8S端口 + AdminUser string `json:"admin_user"` // 超管用户名 + AdminPwd string `json:"admin_pwd"` // 超管密码 ⚠️加密存储 + Version string `json:"version"` // 中间件版本 +} +``` + +--- + +## 6. 版本快照结构 (VersionSnapshot) + +```go +// VersionSnapshot 版本快照结构(存储在 project_versions.snapshot_data) +type VersionSnapshot struct { + BasicInfo *BasicInfo `json:"basic_info"` + DeployBusiness *DeployBusiness `json:"deploy_business"` + DeployEnv *DeployEnv `json:"deploy_env"` + DeployMiddleware *DeployMiddleware `json:"deploy_middleware"` +} +``` + +--- + +## 7. 状态常量定义 + +```go +// 生命周期状态 +const ( + LifecycleInit = "INIT" // 已创建,等待填写 + LifecycleDrafting = "DRAFTING" // 填写中 + LifecycleReviewing = "REVIEWING" // 审核中 + LifecycleReleased = "RELEASED" // 已发布 + LifecycleModifying = "MODIFYING" // 变更中 + LifecycleArchived = "ARCHIVED" // 已归档 +) + +// 认证状态 +const ( + CertificationDraft = "draft" // 草稿 + CertificationPending = "pending" // 待审核 + CertificationOfficial = "official" // 正式 +) + +// 版本类型 +const ( + VersionTypeOfficial = "official" // 正式版本 + VersionTypeFillDraft = "fill_draft" // 填写草稿 + VersionTypeModifyDraft = "modify_draft" // 修改草稿 +) + +// 工单类型 +const ( + WorkflowTypeFill = "fill" // 填写工单 + WorkflowTypeModify = "modify" // 修改工单 +) +``` + +--- + +## 8. 敏感字段加密说明 + +以下字段必须使用 **AES-256** 加密存储,密钥使用项目的 `TierOneSecret`: + +| 结构体 | 字段 | 说明 | +|:---|:---|:---| +| DeployBusiness | `admin_password` | 系统超管密码 | +| DeployEnv | `management_pwd` | 管理后台密码 | +| HostInfo | `ssh_pwd` | SSH密码 | +| MiddlewareInfo | `admin_pwd` | 中间件超管密码 | +| ProjectAuthConfig | `tier_one_secret` | 一级TOTP密钥 | +| ProjectAuthConfig | `tier_two_secret` | 二级TOTP密钥 | + +### 加密/解密示例 + +```go +// 加密敏感字段 +func (s *CryptoService) EncryptSensitiveFields(data *DeployBusiness, key []byte) error { + if data.AdminPassword != "" { + encrypted, err := s.EncryptAES256(data.AdminPassword, key) + if err != nil { + return err + } + data.AdminPassword = encrypted + } + return nil +} + +// 解密敏感字段(返回给前端时脱敏) +func (s *CryptoService) MaskSensitiveFields(data *DeployBusiness) { + if data.AdminPassword != "" { + data.AdminPassword = "********" // 脱敏处理 + } +} +``` + +--- + +## 9. 字段校验规则 + +### Namespace 校验 (RFC 1123 DNS 标签规范) + +```go +var namespaceRegex = regexp.MustCompile(`^[a-z][a-z0-9.-]{0,251}[a-z0-9]$`) + +func ValidateNamespace(namespace string) error { + if len(namespace) > 253 { + return errors.New("命名空间长度不能超过253个字符") + } + if !namespaceRegex.MatchString(namespace) { + return errors.New("命名空间只能包含小写字母、数字、'-'和'.',必须以字母开头,以字母或数字结尾") + } + return nil +} +``` + +### IP 地址校验 + +```go +func ValidateIP(ip string) error { + if ip == "" || ip == "无" { + return nil // 允许空值 + } + if net.ParseIP(ip) == nil { + return errors.New("无效的IP地址格式") + } + return nil +} +``` + +### 省市级联校验 + +```go +// 后端需维护省市对应关系表,校验城市是否属于所选省份 +func ValidateProvinceCity(province, city string) error { + validCities, ok := provinceCityMap[province] + if !ok { + return errors.New("无效的省份") + } + for _, c := range validCities { + if c == city { + return nil + } + } + return errors.New("城市不属于所选省份") +} +``` diff --git a/1-AgentSkills/developing-project-management/reference/frontend-design.md b/1-AgentSkills/developing-project-management/reference/frontend-design.md new file mode 100644 index 0000000..4a4ebe0 --- /dev/null +++ b/1-AgentSkills/developing-project-management/reference/frontend-design.md @@ -0,0 +1,496 @@ +# 前端页面设计规范 + +本文档定义项目详情页面的前端设计规范,包括页面架构、组件设计、交互行为和视觉规范。 + +--- + +## 1. 页面文件结构 + +``` +frontend/src/modules/admin/ +├── pages/ +│ ├── admin/ +│ │ └── ProjectDetail.vue # 超级管理员端项目详情 +│ └── user/ +│ └── UserProjectDetail.vue # 普通用户端项目详情 +├── components/ +│ ├── BasicInfoForm.vue # 基本信息编辑表单 +│ ├── BasicInfoReadonly.vue # 基本信息只读展示 +│ ├── BusinessInfoReadonly.vue # 业务信息只读展示 +│ ├── DeploymentBusinessForm.vue # 部署业务编辑表单 +│ ├── DeploymentEnvironmentForm.vue # 部署环境编辑表单 +│ ├── EnvironmentInfoReadonly.vue # 环境信息只读展示 +│ ├── HostsInfoReadonly.vue # 主机信息只读展示 +│ ├── HostsManagement.vue # 主机管理组件 +│ ├── MiddlewareCardsGrid.vue # 中间件卡片网格 +│ ├── MiddlewareInfoReadonly.vue # 中间件只读展示 +│ ├── AuthorizationManagement.vue # 授权管理 (SuperAdmin Only) +│ ├── VersionHistory.vue # 版本历史 (SuperAdmin Only) +│ ├── SaveConfirmDialog.vue # 保存确认对话框 +│ ├── CopyableField.vue # 可复制字段组件 +│ └── DiffTextField.vue # 差异高亮输入框 +``` + +--- + +## 2. 页面架构设计 + +### 2.1 整体布局 + +采用 **「固定头部 + 固定 Tab 导航 + 可滚动内容区域」** 三段式布局: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ [固定区域] 生命周期状态提示横幅 (Alert Banner) │ +├─────────────────────────────────────────────────────────────────────────┤ +│ [固定区域] 页面头部 Header │ +│ ┌─────────────────────────────────────┬─────────────────────────────┐ │ +│ │ ← 返回 项目名称 │ [查看工单] [打回] [通过] │ │ +│ │ Namespace | 省份 城市 │ [下载配置] [编辑/保存] │ │ +│ │ 状态标签组 │ │ │ +│ └─────────────────────────────────────┴─────────────────────────────┘ │ +├─────────────────────────────────────────────────────────────────────────┤ +│ [固定区域] Tab 导航栏 │ +│ ┌─────────────────────────────────────────────────────────────────────┐│ +│ │ 基本信息 | 部署业务 | 部署环境 | 主机管理 | 中间件 | 授权 | 版本历史 ││ +│ └─────────────────────────────────────────────────────────────────────┘│ +├─────────────────────────────────────────────────────────────────────────┤ +│ [滚动区域] Tab 内容区域 │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### 2.2 CSS 布局核心 + +```css +.project-detail-page { + height: 100%; + max-height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.header-section { + flex-shrink: 0; + background: rgb(var(--v-theme-surface)); + z-index: 1; +} + +.content-area { + flex: 1 1 auto; + overflow-y: auto; + overflow-x: hidden; + min-height: 0; /* 关键:防止 Flex 子元素撑破父容器 */ + padding-bottom: 24px; +} +``` + +--- + +## 3. 查看/编辑状态分离 + +### 3.1 状态定义 + +| 状态 | 变量名 | 说明 | +|:---|:---|:---| +| **查看状态** | `isEditMode = false` | 默认状态,展示 `*Readonly.vue` 组件 | +| **编辑状态** | `isEditMode = true` | 编辑模式,展示 `*Form.vue` 组件 | + +### 3.2 查看状态交互 + +| 交互 | 实现 | +|:---|:---| +| 一键复制 | `CopyableField` 组件,点击图标复制到剪贴板 | +| 密码查看 | 点击"小眼睛"切换明文/密文 | +| 链接跳转 | URL 字段点击新窗口打开 | + +### 3.3 编辑状态数据流 + +```typescript +// 进入编辑模式 +const enterEditMode = () => { + editForm.value = JSON.parse(JSON.stringify(masterData.value)) // 深拷贝 + isEditMode.value = true +} + +// 脏数据检测 +const hasChanges = computed(() => { + return JSON.stringify(editForm.value) !== JSON.stringify(masterData.value) +}) + +// 退出保护 +const exitEditMode = () => { + if (hasChanges.value) { + exitConfirmDialog.value = true // 弹出确认对话框 + } else { + isEditMode.value = false + } +} +``` + +--- + +## 4. 用户侧 vs 管理侧差异 + +| 特性 | 管理员端 (ProjectDetail.vue) | 用户端 (UserProjectDetail.vue) | +|:---|:---|:---| +| **默认模式** | 查看模式 | 根据工单状态决定 | +| **授权信息 Tab** | ✅ 可见 | ❌ 不可见 | +| **版本历史 Tab** | ✅ 可见 | ❌ 不可见 | +| **主机管理 Tab** | ✅ 可见 | ❌ 不可见 | +| **基本信息** | 可编辑 | 只读(由管理员填写) | +| **编辑操作** | 直接保存(上帝模式) | 草稿 → 提交审核(工单流程) | +| **审批按钮** | ✅ 通过/打回 | ❌ 无 | +| **保存按钮** | 「保存修改」 | 「保存草稿」 | + +### Tab 导航配置 + +```html + + + 基本信息 + 部署业务 + 部署环境 + 主机管理 + 中间件 + 授权信息 + 版本历史 + + + + + 基本信息 + 部署业务 + 部署环境 + 中间件 + +``` + +--- + +## 5. 生命周期状态展示 + +### 5.1 状态标签配置 + +```typescript +// 生命周期状态枚举 +export const LIFECYCLE_STATUS = { + init: '初始化', + drafting: '填写中', + reviewing: '审核中', + released: '已发布', + modifying: '变更中', + archived: '已归档' +} + +// 状态颜色映射 +export const LIFECYCLE_STATUS_COLORS: Record = { + init: 'grey', + drafting: 'info', + reviewing: 'warning', + released: 'success', + modifying: 'primary', + archived: 'grey-darken-1' +} + +// 状态图标 +const LIFECYCLE_STATUS_ICONS: Record = { + init: 'mdi-clock-outline', + drafting: 'mdi-pencil', + reviewing: 'mdi-eye', + released: 'mdi-check-circle', + modifying: 'mdi-sync', + archived: 'mdi-archive' +} +``` + +### 5.2 生命周期提示横幅 + +根据当前状态显示上下文提示: + +```typescript +const lifecycleStatusAlert = computed(() => { + const status = masterData.value?.lifecycle_status + switch (status) { + case 'init': + return { type: 'info', message: '项目已创建,等待指定填写人录入详细信息' } + case 'drafting': + return { type: 'info', message: `项目详情正在由 ${masterData.value.detail_filler_name} 填写中` } + case 'reviewing': + return { type: 'warning', message: '项目详情已提交,等待审核' } + case 'modifying': + return { type: 'info', message: '项目存在活跃的变更工单,主线数据不受影响' } + case 'archived': + return { type: 'warning', message: '项目已归档,仅保留历史数据' } + default: + return null + } +}) +``` + +--- + +## 6. 工单关联与跳转 + +### 6.1 工单按钮显示逻辑 + +```typescript +const showWorkflowButton = computed(() => { + if (!masterData.value?.workflow_id) return false + const status = masterData.value.lifecycle_status + return ['drafting', 'reviewing', 'modifying'].includes(status) +}) + +const workflowButtonText = computed(() => { + const status = masterData.value?.lifecycle_status + switch (status) { + case 'drafting': return '查看填写工单' + case 'reviewing': return '查看审核工单' + case 'modifying': return '查看修改工单' + default: return '查看工单' + } +}) +``` + +### 6.2 多工单场景(MODIFYING 状态) + +当存在多个修改工单时,使用下拉菜单或对话框展示工单列表: + +```html + + + + + {{ wf.workflow_id }} + {{ wf.creator_name }} | {{ formatDate(wf.created_at) }} + + + +``` + +--- + +## 7. 模块字段规范 + +### 7.1 基本信息模块 + +| 字段 | 只读模式 | 编辑模式 | +|:---|:---|:---| +| 项目名称 | 文本 + 复制 | `v-text-field` | +| 命名空间 | 文本 + 复制 | `disabled` 不可编辑 | +| 省份/城市 | 文本 | 级联选择器 | +| 项目性质 | 文本 | `v-select` | + +### 7.2 部署业务模块 + +| 字段 | 只读模式 | 编辑模式 | +|:---|:---|:---| +| 部署人姓名 | 文本 | `v-text-field` 或用户搜索 | +| 业务入口 URL | 可点击链接 | `v-text-field` | +| 超管密码 | 脱敏 `******` + 查看按钮 | `v-text-field` 密码输入 | + +### 7.3 中间件模块 + +采用 **卡片网格** 设计: +- 每个中间件一张卡片,响应式布局 +- 卡片包含:类型图标 + 标题 + IP/Port +- 编辑模式:右上角显示「编辑」「删除」按钮 +- 列表末尾显示「添加中间件」虚线框卡片 + +```typescript +const MIDDLEWARE_ICONS: Record = { + 'mysql': 'mdi-database', + 'redis': 'mdi-database-clock', + 'emqx': 'mdi-broadcast', + 'minio': 'mdi-bucket', + 'influxdb': 'mdi-chart-timeline-variant', + 'nacos': 'mdi-cog-outline', + 'k8s-dashboard': 'mdi-kubernetes' +} +``` + +--- + +## 8. 核心组件设计 + +### 8.1 CopyableField - 可复制字段 + +```html + +``` + +### 8.2 SaveConfirmDialog - 保存确认 + +展示变更 Diff 表格: + +```html + + + 字段修改前修改后 + + + + {{ item.label }} + {{ item.oldValue || '空' }} + {{ item.newValue || '空' }} + + + +``` + +### 8.3 DiffTextField - 差异高亮输入框 + +编辑模式下显示与主线数据的差异: + +```html + + + + + +``` + +--- + +## 9. 视觉设计规范 + +### 9.1 色彩系统 + +| 用途 | Vuetify 类 | +|:---|:---| +| 主色调 | `color="primary"` (Deep Purple) | +| 成功状态 | `color="success"` (Green) | +| 警告状态 | `color="warning"` (Orange) | +| 错误状态 | `color="error"` (Red) | +| 页面背景 | `bg-grey-lighten-4` | + +### 9.2 卡片设计 + +```html + + + +``` + +### 9.3 排版规范 + +| 元素 | 样式类 | +|:---|:---| +| 页面标题 | `text-h4 font-weight-bold` | +| 卡片标题 | `text-h6` | +| 字段标签 | `text-medium-emphasis text-body-2` | +| 字段值 | `text-high-emphasis` | + +### 9.4 间距规范(8px 网格) + +| 间距 | 类 | 值 | +|:---|:---|:---| +| 紧凑 | `pa-2` | 8px | +| 标准 | `pa-4` | 16px | +| 宽松 | `pa-6` | 24px | + +--- + +## 10. 响应式设计 + +### 10.1 断点 + +| 断点 | 宽度 | +|:---|:---| +| xs | < 600px | +| sm | 600px - 960px | +| md | 960px - 1280px | +| lg | 1280px - 1920px | + +### 10.2 中间件卡片响应式 + +```html + + + + + +``` + +--- + +## 11. TypeScript 类型定义 + +```typescript +// 项目详情 +interface ProjectDetail { + id: number + project_id: string + project_name: string + namespace: string + province: string + city: string + project_nature: string + lifecycle_status: string + project_certification: string + workflow_id: string + detail_filler_id: number + detail_filler_name: string + deployment_business: DeploymentBusiness | null + deployment_environment: DeploymentEnvironment | null + middlewares: Middleware[] + hosts: Host[] + draft_data: Record | null +} + +// Diff 项 +interface DiffItem { + field: string + label: string + oldValue: string | number | boolean + newValue: string | number | boolean +} +``` + +--- + +## 12. 组件清单 + +| 组件 | 说明 | 复用范围 | +|:---|:---|:---| +| `BasicInfoForm.vue` | 基本信息编辑表单 | 管理员/用户 | +| `BasicInfoReadonly.vue` | 基本信息只读 | 管理员/用户 | +| `DeploymentBusinessForm.vue` | 业务信息表单 | 管理员/用户 | +| `DeploymentEnvironmentForm.vue` | 环境信息表单 | 管理员/用户 | +| `MiddlewareCardsGrid.vue` | 中间件卡片网格 | 管理员/用户 | +| `AuthorizationManagement.vue` | 授权管理 | 仅管理员 | +| `VersionHistory.vue` | 版本历史 | 仅管理员 | +| `HostsManagement.vue` | 主机管理 | 仅管理员 | +| `SaveConfirmDialog.vue` | 保存确认对话框 | 管理员 | +| `CopyableField.vue` | 可复制字段 | 通用 | +| `DiffTextField.vue` | 差异高亮输入框 | 通用 | diff --git a/1-AgentSkills/developing-project-management/reference/version-control-design.md b/1-AgentSkills/developing-project-management/reference/version-control-design.md new file mode 100644 index 0000000..393a976 --- /dev/null +++ b/1-AgentSkills/developing-project-management/reference/version-control-design.md @@ -0,0 +1,448 @@ +# 版本控制设计 (Git-like) + +## 设计原则 + +采用**统一版本表**设计,将正式版本和草稿版本存储在同一张表中,通过 `version_type` 字段区分。项目信息采用类似 Git 的分支管理模式: +- **Master 分支**: 由 SuperAdmin 审核维护的正式版本 +- **用户草稿**: 每个用户都有自己的临时分支,提交审核后合并入 Master + +## 版本类型 + +| 版本类型 | 代码 | version 值 | 说明 | +|:---|:---|:---|:---| +| 正式版本 | `official` | 1, 2, 3... (递增) | 审核通过后的正式版本,构成版本历史 | +| 填写草稿 | `fill_draft` | 0 | 项目创建时填写人的草稿 | +| 修改草稿 | `modify_draft` | 0 | 发起变更工单时的草稿 | + +## 版本与工单关系 + +| 关系 | 说明 | +|:---|:---| +| 填写草稿 : 填写工单 | 1:1 关联 | +| 修改草稿 : 修改工单 | 1:1 关联 | +| 正式版本 : 工单 | 审核通过后由草稿转化而来 | +| 项目 : 修改草稿 | 1:N(一个项目可有多个修改草稿) | + +## 版本快照机制 + +每次审核通过后,系统自动生成一个**完整快照**存储到 `project_versions` 表中。 + +### 快照结构 + +```go +// VersionSnapshot 版本快照结构 +type VersionSnapshot struct { + BasicInfo *BasicInfo `json:"basic_info"` + DeployBusiness *DeployBusiness `json:"deploy_business"` + DeployEnv *DeployEnv `json:"deploy_env"` + DeployMiddleware *DeployMiddleware `json:"deploy_middleware"` +} +``` + +### 快照生成时机 + +| 场景 | 版本号 | 版本类型 | 说明 | +|:---|:---|:---|:---| +| 项目首次审批通过 | v1 | official | 项目初始版本 | +| 修改工单审批通过 | v(N+1) | official | 增量版本 | +| **超管直接修改** | v(N+1) | official | **重要:超管直改也必须生成新版本** | +| 用户保存草稿 | 0 | fill_draft/modify_draft | 临时版本,不计入历史 | + +## 超级管理员直改与版本一致性 + +### 问题风险 + +如果超级管理员直接修改 `projects` 表数据而不生成版本历史,会导致: +1. 版本链断裂 +2. 后续基于旧版本的工单 Diff 结果失效或产生误导 +3. 审计日志不完整 + +### 解决方案 + +超级管理员的 "Direct Edit" 操作必须被视为一次**自动审批通过的事务**: + +1. **原子操作**:更新 `projects` 表 + 插入 `project_versions` 表必须在同一数据库事务中完成 +2. **版本归属**: + - `workflow_id` 为空或特定系统标识(如 `DIRECT_EDIT`) + - `committer_id` 记录为 SuperAdmin ID + - `commit_message` 强制填写或自动生成(如 "SuperAdmin Direct Update") +3. **结果**:确保 `projects.current_version` 永远指向最新的 `project_versions.version` + +### 实现代码 + +```go +// SuperAdmin 直接修改项目(必须同时生成版本) +func (s *ProjectService) DirectUpdate(ctx context.Context, req *DirectUpdateRequest) error { + return s.db.Transaction(func(tx *gorm.DB) error { + // 1. 获取当前项目 + var project entity.Project + if err := tx.Where("project_id = ?", req.ProjectID).First(&project).Error; err != nil { + return err + } + + // 2. 更新项目主表 + newVersion := project.CurrentVersion + 1 + if err := tx.Model(&project).Updates(map[string]interface{}{ + "basic_info": req.BasicInfo, + "deploy_business": req.DeployBusiness, + "deploy_env": req.DeployEnv, + "deploy_middleware": req.DeployMiddleware, + "current_version": newVersion, + }).Error; err != nil { + return err + } + + // 3. 同时生成版本记录(关键!) + version := &entity.ProjectVersion{ + ProjectID: req.ProjectID, + Version: newVersion, + VersionType: "official", + BaseVersion: project.CurrentVersion, + SnapshotData: buildSnapshot(req), + CommitMessage: req.CommitMessage, // 或自动生成 + CommitterID: req.OperatorID, + CommitterName: req.OperatorName, + } + if err := tx.Create(version).Error; err != nil { + return err + } + + // 4. 记录审计日志 + return s.auditSvc.Log(ctx, tx, AuditLog{ + Resource: "project", + Action: "direct_update", + ResourceID: req.ProjectID, + Details: map[string]interface{}{"new_version": newVersion}, + }) + }) +} +``` + +## 并发修改与冲突检测 (Optimistic Locking) + +由于超级管理员可能在其他用户编辑草稿期间直接修改项目,需要引入乐观锁机制处理冲突。 + +### 冲突场景 + +``` +时间线: +T1: 用户 A 基于 v3 版本创建草稿 (Draft.base_version = 3) +T2: 超级管理员直接修改项目,版本升级为 v4 (Project.current_version = 4) +T3: 用户 A 提交草稿审核 → 检测到冲突! +``` + +### 处理策略 + +1. **提交时校验**:工单提交/审核接口需校验 `draft.base_version == project.current_version` + +2. **冲突提示**:如果版本不一致,后端返回 `409 Conflict` 错误 + +3. **前端交互**: + - 提示用户:"项目已被修改,当前草稿已过期" + - 提供 **"Rebase" (变基)** 选项:将当前草稿的修改重新应用到最新版本 + - 或提供 **"Diff Check"**:让用户查看当前草稿与最新版本的差异 + +### 冲突检测代码 + +```go +// 提交草稿时检测版本冲突 +func (s *DraftService) SubmitDraft(ctx context.Context, req *SubmitDraftRequest) error { + // 1. 获取草稿 + var draft entity.ProjectVersion + if err := s.db.Where("project_id = ? AND user_id = ? AND version_type IN (?, ?)", + req.ProjectID, req.UserID, "fill_draft", "modify_draft").First(&draft).Error; err != nil { + return err + } + + // 2. 获取项目当前版本 + var project entity.Project + if err := s.db.Where("project_id = ?", req.ProjectID).First(&project).Error; err != nil { + return err + } + + // 3. 乐观锁检查 + if draft.BaseVersion != project.CurrentVersion { + return &VersionConflictError{ + DraftBaseVersion: draft.BaseVersion, + CurrentVersion: project.CurrentVersion, + Message: "项目已被修改,当前草稿已过期,请重新基于最新版本编辑", + } + } + + // 4. 继续提交流程... + return s.workflowTransitioner.TransitionWorkflow(draft.WorkflowID, "complete", ...) +} +``` + +### 错误响应格式 + +```json +{ + "code": 40901, + "message": "版本冲突:项目已被修改", + "data": { + "draft_base_version": 3, + "current_version": 4, + "suggestion": "请点击\"重新加载\"获取最新版本后重新编辑" + } +} +``` + +--- + +## 版本 Diff 算法 + +采用 **JSON Diff** 算法,对比两个版本快照的差异,按模块分组展示。 + +### 差异结构 + +```go +// DiffResult 差异结果(按模块分组) +type DiffResult struct { + Module string `json:"module"` // 模块名称(中文) + ModuleCode string `json:"module_code"` // 模块代码 + FieldDiffs []FieldDiff `json:"field_diffs"` // 字段差异列表 +} + +// FieldDiff 字段差异 +type FieldDiff struct { + FieldPath string `json:"field_path"` // 字段路径 如 "deploy_env.host_count" + FieldName string `json:"field_name"` // 字段中文名 + OldValue interface{} `json:"old_value"` // 旧值 + NewValue interface{} `json:"new_value"` // 新值 + ChangeType string `json:"change_type"` // add/modify/delete +} +``` + +### 字段名映射表 + +```go +// fieldNameMap 字段路径到中文名的映射 +var fieldNameMap = 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.deployer_phone": "部署人电话", + "deploy_business.deploy_start_time": "部署开始时间", + "deploy_business.deploy_end_time": "部署结束时间", + "deploy_business.system_version": "系统版本", + "deploy_business.system_type": "系统类型", + "deploy_business.main_entrance": "业务主入口", + "deploy_business.admin_username": "超管用户名", + + // 部署环境 + "deploy_env.network_type": "网络环境", + "deploy_env.main_public_ip": "主要公网IP", + "deploy_env.domain_url": "域名URL", + "deploy_env.ssl_enabled": "是否开启SSL", + "deploy_env.host_count": "主机台数", + "deploy_env.total_cpu": "CPU总核数", + "deploy_env.total_memory": "内存总大小(GB)", + "deploy_env.total_storage": "存储总大小(GB)", + + // 部署中间件 + "deploy_middleware.mysql.internal_port": "MySQL内网端口", + "deploy_middleware.redis.internal_port": "Redis内网端口", + // ... 其他字段 +} +``` + +### Diff 实现 + +```go +// CompareVersions 比较两个版本的差异 +// @param baseVersion 基准版本(通常是较早的版本或 master) +// @param targetVersion 目标版本(通常是较新的版本或草稿) +// @return []DiffResult 差异结果列表,按模块分组 +func (s *VersionService) CompareVersions( + ctx context.Context, + baseVersion, targetVersion *VersionSnapshot, +) ([]DiffResult, error) { + var results []DiffResult + + // 分模块对比 + modules := []struct { + Name string + Code string + Base interface{} + Target interface{} + }{ + {"基本信息", "basic_info", baseVersion.BasicInfo, targetVersion.BasicInfo}, + {"部署业务", "deploy_business", baseVersion.DeployBusiness, targetVersion.DeployBusiness}, + {"部署环境", "deploy_env", baseVersion.DeployEnv, targetVersion.DeployEnv}, + {"部署中间件", "deploy_middleware", baseVersion.DeployMiddleware, targetVersion.DeployMiddleware}, + } + + for _, m := range modules { + diffs := s.diffJSON(m.Code, m.Base, m.Target) + if len(diffs) > 0 { + results = append(results, DiffResult{ + Module: m.Name, + ModuleCode: m.Code, + FieldDiffs: diffs, + }) + } + } + return results, nil +} + +// diffJSON 对比两个 JSON 对象的差异 +func (s *VersionService) diffJSON(moduleCode string, base, target interface{}) []FieldDiff { + var diffs []FieldDiff + + baseMap := structToMap(base) + targetMap := structToMap(target) + + // 检查修改和删除 + for key, oldVal := range baseMap { + fieldPath := moduleCode + "." + key + if newVal, exists := targetMap[key]; exists { + if !reflect.DeepEqual(oldVal, newVal) { + diffs = append(diffs, FieldDiff{ + FieldPath: fieldPath, + FieldName: getFieldName(fieldPath), + OldValue: oldVal, + NewValue: newVal, + ChangeType: "modify", + }) + } + } else { + diffs = append(diffs, FieldDiff{ + FieldPath: fieldPath, + FieldName: getFieldName(fieldPath), + OldValue: oldVal, + NewValue: nil, + ChangeType: "delete", + }) + } + } + + // 检查新增 + for key, newVal := range targetMap { + if _, exists := baseMap[key]; !exists { + fieldPath := moduleCode + "." + key + diffs = append(diffs, FieldDiff{ + FieldPath: fieldPath, + FieldName: getFieldName(fieldPath), + OldValue: nil, + NewValue: newVal, + ChangeType: "add", + }) + } + } + + return diffs +} +``` + +--- + +## 版本历史查询 + +### 版本列表结构 + +```go +// VersionHistory 版本历史记录 +type VersionHistory struct { + Version int `json:"version"` // 版本号 + VersionType string `json:"version_type"` // 版本类型 + CommitMessage string `json:"commit_message"` // 变更说明 + CommitterID int64 `json:"committer_id"` // 提交人 ID + CommitterName string `json:"committer_name"` // 提交人姓名 + WorkflowID string `json:"workflow_id"` // 关联工单 ID(可跳转) + CreatedAt time.Time `json:"created_at"` // 创建时间 + ChangeSummary string `json:"change_summary"` // 变更摘要(如:修改了 3 个字段) + IsCurrent bool `json:"is_current"` // 是否为当前版本 +} +``` + +### 版本历史 API + +| 方法 | 路径 | 描述 | +|:---|:---|:---| +| POST | `/api/project/version/list` | 获取版本历史列表 | +| POST | `/api/project/version/detail` | 获取指定版本详情(完整快照) | +| POST | `/api/project/version/diff` | 对比两个版本差异 | +| POST | `/api/project/version/diff-with-current` | 对比指定版本与当前版本差异 | + +--- + +## 前端展示设计 + +### 版本历史页面 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 项目版本历史 - [项目名称] │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ●──v3 (当前版本) 2026-01-14 15:30 张三 │ +│ │ └─ 变更说明: 更新部署环境信息 │ +│ │ └─ 关联工单: #WF-20260114-001 [点击跳转] │ +│ │ └─ 变更摘要: 修改了 2 个字段 │ +│ │ │ +│ ●──v2 2026-01-10 10:00 李四 │ +│ │ └─ 变更说明: 修改中间件配置 │ +│ │ └─ 关联工单: #WF-20260110-002 │ +│ │ │ +│ ●──v1 (初始版本) 2026-01-05 09:00 王五 │ +│ └─ 变更说明: 项目初始填写 │ +│ └─ 关联工单: #WF-20260105-001 │ +│ │ +│ [查看详情] [对比版本] │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Diff 对比页面 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 版本对比: v2 → v3 │ +├─────────────────────────────────────────────────────────────────┤ +│ 模块: 部署环境 │ +│ ┌───────────────────────────────────────────────────────────┐ │ +│ │ 字段 │ v2 (旧值) │ v3 (新值) │ │ +│ ├───────────────────────────────────────────────────────────┤ │ +│ │ 主机台数 │ 3 │ 5 [修改] │ │ +│ │ 主要公网 IP │ 10.0.0.1 │ 192.168.1.100 [修改] │ │ +│ │ 域名 URL │ - │ www.example.com [新增] │ │ +│ └───────────────────────────────────────────────────────────┘ │ +│ │ +│ 模块: 部署中间件 │ +│ ┌───────────────────────────────────────────────────────────┐ │ +│ │ 字段 │ v2 (旧值) │ v3 (新值) │ │ +│ ├───────────────────────────────────────────────────────────┤ │ +│ │ MySQL.内网端口 │ 3306 │ 3307 [修改] │ │ +│ └───────────────────────────────────────────────────────────┘ │ +│ │ +│ 变更统计: 共 4 个字段变更 (新增: 1, 修改: 3, 删除: 0) │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 草稿编辑页面 Diff 提示 + +在用户编辑草稿时,实时显示与主线版本的差异: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 编辑项目详情 - [项目名称] [保存草稿] [提交审核] │ +├─────────────────────────────────────────────────────────────────┤ +│ ⚠️ 您的草稿基于 v3 版本,与当前版本有以下差异: │ +│ ┌───────────────────────────────────────────────────────────┐ │ +│ │ • 主机台数: 3 → 5 │ │ +│ │ • 系统版本: v2.0.0 → v2.1.0 │ │ +│ └───────────────────────────────────────────────────────────┘ │ +│ │ +│ [基本信息] [部署业务] [部署环境] [部署中间件] │ +│ ───────────────────────────────────────────────────────────── │ +│ 省份: [北京市 ▼] │ +│ 城市: [北京市 ▼] │ +│ ... │ +└─────────────────────────────────────────────────────────────────┘ +```