更新RMDC系统的模块SKILL

This commit is contained in:
zeaslity
2026-02-02 15:06:28 +08:00
parent 93624efdab
commit a02ac14481
89 changed files with 8101 additions and 2417 deletions

6
.idea/data_source_mapping.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourcePerFileMappings">
<file url="file://$PROJECT_DIR$/101-数据库学习/3-SQLite/9-题目/SQLite基础考察.sql" value="33bcd8d4-0085-46de-8fa2-dfc09570882c" />
</component>
</project>

11
.idea/go.imports.xml generated Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GoImports">
<option name="excludedPackages">
<array>
<option value="github.com/pkg/errors" />
<option value="golang.org/x/net/context" />
</array>
</option>
</component>
</project>

7
.idea/sqldialects.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/101-数据库学习/3-SQLite/9-题目/SQLite基础考察.sql" dialect="SQLite" />
<file url="PROJECT" dialect="SQLite" />
</component>
</project>

Binary file not shown.

BIN
0-pandoc-失败/output.pdf Normal file

Binary file not shown.

View File

View File

@@ -1,7 +1,19 @@
---
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"
name: developing-go-gin-gorm wdd-后端开发
description: >
使用 Gin + GORM 生成、编写、修改、评审 production-ready 的 Go 后端代码Generate & Review Go backend code with Gin/GORM
强制分层架构 handler → service → dao/repository避免业务逻辑堆在 handlerDAO/Repo 只做数据访问与查询组装),并统一 API 响应包装
consistent response envelopecode/message/data + request_id/trace_id 等可观测字段)。接口风格默认推荐 POST + JSON RequestBody
as default必要时遵循 REST 语义与幂等约定),规范 DTO/VO/DO 命名与字段映射 conventions入参 DTO、出参 VO、持久化 DO/Model
代码注释使用中文Chinese comments for maintainability时间处理默认 Asia/Shanghaitime zone aware time handling
采用结构化日志 structured logging携带 request_id/trace_id/user_id/path/latency 等上下文),并遵循 Gin/GORM 工程化最佳实践
(transactions, context propagation, error wrapping, pagination, soft delete, optimistic locking when needed)。
触发场景 Trigger: Go 后端开发 / Gin Handler 创建 / GORM DAO/Repository 实现 / 代码走查与 Reviewrefactor suggestions, bug fixes, performance tips
argument-hint: "<动作 action> <目标 target>" 例如/ e.g.:
"create user-handler", "review service/order.go", "scaffold api/v1/product", "add repo for table/users", "optimize gorm query"
allowed-tools:
- Read
- Write
@@ -9,12 +21,13 @@ allowed-tools:
- Glob
- Grep
- Bash
---
# Go GIN/GORM 开发规范 Skill
## 触发条件
- 用户请求创建/修改 Go 后端代码GIN handler、GORM dao、service
- 用户请求创建/修改 Go 后端代码
- 用户请求代码审查
- 用户提及 API 开发、数据库操作、统一响应、日志、时间处理
- 用户请求设计 API 接口、DTO 结构

View File

@@ -150,6 +150,8 @@ type ListRequest struct {
| 项目管理 | `/api/projects/` |
| 用户 | `/api/users/` |
| 权限 | `/api/permissions/` |
| 权限-Jenkins | `/api/permissions/jenkins/` |
| 权限-项目 | `/api/permissions/projects/` |
| 审计 | `/api/audit/` |
| Exchange-Hub | `/api/exchange-hub/` |
| DCU | `/api/dcu/` |

View File

@@ -1,141 +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.
description: Build production-grade Vue 3 + TypeScript + Vuetify 3 interfaces with architectural rigor. 构建生产级 Vue 3 + TypeScript + Vuetify 3 界面。Use when creating Vue components, pages, layouts, Pinia stores, or API modules. 用于创建 Vue 组件、页面、布局、Pinia 状态管理或 API 模块。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.
本技能指导构建架构严谨、类型安全、视觉精致的 Vue 3 + Vuetify 3 代码。每个组件都应该达到生产级代码库的标准——让资深工程师也引以为傲。
The user provides: $ARGUMENTS (component specs, page requirements, feature requests, or architectural questions).
用户输入:$ARGUMENTS组件规格、页面需求、功能请求或架构问题
## 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.
- **组件身份**:这是页面(Page)、布局(Layout)、可复用组件(Component)、组合式函数(Composable)、状态仓库(Store),还是 API 模块?每种都有独特模式。
- **数据重力**状态住在哪里Props 向下流动Events 向上冒泡。跨组件状态用 Pinia。深层级传递用 `provide/inject`
- **滚动策略**:哪个容器拥有滚动权?永远不是 body。必须显式声明。必须可控。
- **失败模式**:数据为 `null` 时怎么办?空数组?网络超时?先为不幸路径设计。
**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`
### TypeScript 绝对主义
- `<script setup lang="ts">`唯一可接受的写法
- `any` 被禁止 — 使用 `unknown` + 类型守卫、泛型、工具类型
- 每个 propemitref、API 响应都必须穿戴类型
- 类型定义放在 `@/types/`,按领域组织:`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
### Composition API 纯粹性
- `ref``reactive``computed``watchEffect`掌握这四大金刚
- `shallowRef``readonly``toRaw`知道何时使用优化手段
- 生命周期用 `onMounted``onUnmounted`绝不混用 Options API
- Pinia stores:类型化的 state、类型化的 getters、类型化的 actions — 无例外
### 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
- 所有 UI 通过 Vuetify 组件实现 — UI 元素不使用原生 HTML
- 始终主题感知`rgb(var(--v-theme-surface))`,绝不 `#ffffff`
- `useDisplay()` 处理响应式逻辑 — 断点是一等公民
- 密度很重要 — 数据密集界面使用 `density="compact"`
### Layout Philosophy
### 布局哲学
```
┌─────────────────────────────────┐
Toolbar (flex-shrink-0) │
工具栏 (flex-shrink-0)
├─────────────────────────────────┤
│ │
Content Area
内容区域
│ (flex-grow-1, overflow-y-auto) │
│ (min-height: 0) ← CRITICAL
│ (min-height: 0) ← 关键!
│ │
├─────────────────────────────────┤
Footer (flex-shrink-0) │
底部栏 (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
- **禁止 body 滚动** — 视口锁定,内容在容器中滚动
- **Flexbox 陷阱**`flex-grow-1` 子元素必须有 `min-height: 0`
- **粘性元素**:筛选栏、表头 — 滚动时始终可见
## Data Robustness Patterns
## 数据健壮性模式
Treat all external data as hostile:
将所有外部数据视为不可信:
```typescript
// Defensive access
const userName = user?.profile?.name ?? 'Unknown'
// 防御性访问
const userName = user?.profile?.name ?? '未知'
// 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
## UI 状态三位一体
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 |
| 状态 | 组件 | 禁止行为 |
|------|------|----------|
| **加载中** | `v-skeleton-loader` | 显示过期数据或空白屏幕 |
| **空数据** | `v-empty-state` + 操作按钮 | 留下白茫茫一片 |
| **错误** | Snackbar + 重试选项 | 静默失败 |
## 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
- 每个 `v-data-table` 都要 `fixed-header` — 没有商量余地
- 截断文本必须配 `v-tooltip` — 用户有权 hover 看到完整内容
- 100+ 条数据?用 `v-virtual-scroll` — DOM 节点数保持恒定
- 列宽显式指定 — 不玩布局抽奖
## 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
- TypeScript 项目中出现 `.js` 文件
- 没有正当理由使用 `any`
- 硬编码颜色:`color="#1976d2"` 应该用 `color="primary"`
- SPA 布局中出现 body 级滚动
- 表格没有固定表头
- 截断文本没有 tooltip
- 空状态真的"空空如也"
- 加载状态冻结 UI
- API 调用没有错误处理
## 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` |
| 高级 TypeScript 模式 | `reference/typescript-rules.md` |
| 复杂布局结构 | `reference/layout-patterns.md` |
| API 客户端架构 | `reference/api-patterns.md` |
| 表格、列表、表单、反馈 | `reference/ui-interaction.md` |
## Project Anatomy
## 项目结构
```
src/
├── api/ # Axios instance + modules
├── components/ # Shared components
├── composables/ # Reusable hooks
├── layouts/ # Page shells
├── pages/ # Route views
├── api/ # Axios 实例 + 模块
├── components/ # 共享组件
├── composables/ # 可复用 hooks
├── layouts/ # 页面外壳
├── pages/ # 路由视图
├── plugins/ # Vuetify, Pinia, Router
├── store/ # Pinia stores
├── styles/ # Global SCSS
├── types/ # Type definitions
└── utils/ # Pure functions
├── styles/ # 全局 SCSS
├── types/ # 类型定义
└── utils/ # 纯函数
```
## 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
1. 陈述架构方案2-3 句话)
2. 列出要创建的文件及其用途
3. 完整实现每个文件 — 无占位符,无 TODO
4. 对照反模式清单验证
5. 指出任何假设或权衡取舍
---
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.
记住:你不是在写"能跑的代码"。你是在写能跑、能扩展、能维护、能令人愉悦的代码。每个 `ref` 都有类型。每个边界情况都有处理。每个加载状态都很美观。这就是"生产级"的含义。

View File

@@ -0,0 +1,105 @@
---
name: designing-contracts
description: "Guides API contract design, event schema definition, and version compatibility management for RMDC system. Triggered when defining new APIs, modifying request/response structures, designing MQTT message schemas, or planning breaking changes. Keywords: API versioning, backward compatibility, schema evolution, OpenAPI, event contract, MQTT payload, breaking change."
allowed-tools:
- Read
- Glob
- Grep
- Bash
argument-hint: "$ARGUMENTS: <contract-type> [module] — contract-type: api|event|schema|breaking-change"
---
# designing-contracts
## 概述
本 Skill 指导 RMDC 系统的契约设计,包括 API 接口、事件消息、数据 Schema 的定义与版本管理。
## 动态上下文注入
### 查找现有 API 定义
!`grep -rn "router\.\(GET\|POST\|PUT\|DELETE\)" --include="*.go" | head -30`
### 查找 MQTT topic 定义
!`grep -rn "topic\|Topic\|MQTT" --include="*.go" | head -20`
---
## Plan规划阶段
### 契约变更类型判定
| 类型 | 影响范围 | 兼容性要求 |
|:---|:---|:---|
| 新增字段(可选) | 低 | 向后兼容 |
| 新增必填字段 | 高 | Breaking Change |
| 修改字段类型 | 高 | Breaking Change |
| 删除字段 | 高 | Breaking Change |
| 修改字段语义 | 高 | Breaking Change |
### 决策点
- [ ] 是否为 Breaking Change
- [ ] 是否需要版本化v1 -> v2
- [ ] 影响哪些下游模块/客户端?
- [ ] 是否需要过渡期(同时支持新旧版本)?
---
## Verify验证清单
### API 契约检查
- [ ] 新增字段有默认值
- [ ] 必填字段未被删除
- [ ] 字段类型未变更
- [ ] 错误码向后兼容
- [ ] 响应结构保持一致
### 事件契约检查
- [ ] Topic 命名遵循规范
- [ ] Payload 字段向后兼容
- [ ] 消息版本字段存在
- [ ] 消费者兼容新字段
### 验证命令
```bash
# 对比 API 变更
git diff HEAD~1 --name-only | grep -E "handler|router"
# 检查 breaking change
./scripts/verify-api-compatibility.sh
```
---
## Execute执行步骤
### 新增 API
1. 定义请求/响应结构体
2. 添加字段校验规则
3. 注册路由
4. 更新 API 文档
5. 通知下游模块
### Breaking Change 处理
1. 创建新版本路由(/v2/...
2. 保留旧版本兼容期
3. 添加废弃警告
4. 通知所有消费者
5. 设定旧版本下线时间
---
## Pitfalls常见坑
1. **删除字段未通知下游**:前端/其他模块仍在使用该字段,导致解析失败。
2. **修改字段类型**:如 `string``int`JSON 解析会失败。
3. **错误码语义变更**:下游按错误码做分支处理,语义变更会导致逻辑错误。
4. **MQTT payload 无版本字段**:无法做向后兼容处理。
5. **必填字段无默认值**:旧客户端无法正常调用。
---
## 相关文件
| 用途 | 路径 |
|:---|:---|
| 版本策略 | [reference/api-versioning-policy.md](reference/api-versioning-policy.md) |
| 事件规则 | [reference/event-schema-rules.md](reference/event-schema-rules.md) |
| Breaking Change 清单 | [reference/breaking-change-checklist.md](reference/breaking-change-checklist.md) |

View File

@@ -0,0 +1,46 @@
# API 版本策略
## 版本号规范
- URL 路径版本:`/api/v1/users`, `/api/v2/users`
- 仅在 Breaking Change 时升级主版本
## 向后兼容规则
### 兼容变更(无需升版本)
- 新增可选请求字段
- 新增响应字段
- 新增 API 端点
- 新增枚举值(消费者需忽略未知值)
### Breaking Change必须升版本
- 删除/重命名字段
- 修改字段类型
- 修改字段语义
- 删除 API 端点
- 修改 URL 路径
- 修改认证方式
## 版本过渡期
1. 新版本发布时,旧版本继续可用
2. 旧版本标记 `Deprecated` 响应头
3. 过渡期建议2-4 周
4. 过渡期结束前通知所有消费者
5. 下线旧版本
## 示例
```go
// v1 路由组
v1 := router.Group("/api/v1")
{
v1.GET("/users", handler.ListUsersV1)
}
// v2 路由组Breaking Change 后)
v2 := router.Group("/api/v2")
{
v2.GET("/users", handler.ListUsersV2)
}
```

View File

@@ -0,0 +1,35 @@
# Breaking Change 检查清单
## 变更前
- [ ] 确认变更类型是否为 Breaking Change
- [ ] 列出所有受影响的消费者(模块/前端/第三方)
- [ ] 评估影响范围和严重程度
- [ ] 确定是否可以用兼容方式实现
## 设计阶段
- [ ] 创建新版本 API如 v2
- [ ] 设计过渡期方案
- [ ] 编写迁移指南文档
- [ ] 确定旧版本下线时间表
## 实施阶段
- [ ] 实现新版本接口
- [ ] 旧版本添加 Deprecated 标记
- [ ] 更新 API 文档
- [ ] 通知所有消费者
## 过渡期
- [ ] 监控旧版本调用量
- [ ] 跟进消费者迁移进度
- [ ] 提供迁移支持
## 下线阶段
- [ ] 确认无活跃的旧版本调用
- [ ] 最终通知
- [ ] 下线旧版本
- [ ] 清理旧代码

View File

@@ -0,0 +1,44 @@
# 事件 Schema 规则
## MQTT Topic 命名规范
```
rmdc/{module}/{resource}/{action}
```
示例:
- `rmdc/exchange-hub/command/created`
- `rmdc/watchdog/deployment/started`
- `rmdc/user-auth/user/registered`
## Payload 结构规范
```json
{
"version": "1.0",
"timestamp": "2026-01-23T10:00:00Z",
"trace_id": "abc-123",
"event_type": "user.registered",
"source": "rmdc-user-auth",
"data": {
// 业务数据
}
}
```
## 必须字段
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| version | string | Schema 版本 |
| timestamp | string | ISO 8601 时间戳 |
| trace_id | string | 追踪ID |
| event_type | string | 事件类型 |
| source | string | 来源模块 |
| data | object | 业务数据 |
## 演进规则
- 新增字段:在 `data` 中添加,消费者忽略未知字段
- 删除字段:先标记废弃,保留 2 个版本后删除
- 类型变更:升级 `version` 字段

View File

@@ -0,0 +1,57 @@
#!/bin/bash
# verify-api-compatibility.sh - 验证 API 契约兼容性
# 依赖: git, jq (可选)
# 用法: ./verify-api-compatibility.sh [base-branch]
set -e
BASE_BRANCH=${1:-main}
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "=== API 契约兼容性检查 ==="
echo "对比分支: ${BASE_BRANCH}"
echo ""
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
pass() { echo -e "${GREEN}[PASS]${NC} $1"; }
fail() { echo -e "${RED}[FAIL]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
# 获取变更的 handler 文件
CHANGED_HANDLERS=$(git diff ${BASE_BRANCH} --name-only | grep -E "handler.*\.go$" || true)
if [ -z "$CHANGED_HANDLERS" ]; then
pass "无 handler 文件变更"
exit 0
fi
echo "变更的 Handler 文件:"
echo "$CHANGED_HANDLERS"
echo ""
# 检查是否有字段删除
for file in $CHANGED_HANDLERS; do
if [ -f "$file" ]; then
# 检查是否有结构体字段删除
DELETED_LINES=$(git diff ${BASE_BRANCH} -- "$file" | grep "^-" | grep -E "^\s+\w+\s+\w+" || true)
if [ -n "$DELETED_LINES" ]; then
warn "可能的字段删除 in $file:"
echo "$DELETED_LINES"
fi
# 检查是否有新增必填字段
ADDED_REQUIRED=$(git diff ${BASE_BRANCH} -- "$file" | grep "^+" | grep 'binding:"required"' || true)
if [ -n "$ADDED_REQUIRED" ]; then
warn "新增必填字段 in $file (可能是 Breaking Change):"
echo "$ADDED_REQUIRED"
fi
fi
done
echo ""
echo "=== 检查完成 ==="
echo "请人工确认上述变更是否为 Breaking Change"

View File

@@ -1,7 +1,8 @@
---
name: developing-project-management
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: "<change-type> [target]" where change-type is one of: api|entity|service|migration|frontend|auth|version. Example: "api draft-submit" or "version diff-algorithm"
name: developing-project-management 项目管理模块开发指南
description: "Guides development of rmdc-project-management 模块项目全生命周期管理Project Lifecycle)、类 Git 版本管理snapshot/diff支持草稿/发布/回滚、ACL 权限控制、TOTP 二次授权、与工单(work-procedure)的联动回调,以及 Vue3 + Vuetify 前端页面/组件开发。Triggered when you modify 项目 CRUD、草稿/版本相关 APIdraft/version、权限授予与授权能力ACL/TOTP/SuperAdmin、工作流回调与状态同步、数据库迁移或前端 ProjectDetail 等页面与组件。Keywords: 项目生命周期, 版本快照, diff 算法, ACL, TOTP, workflow callback, SuperAdmin, optimistic lock/乐观锁, Vue3, Vuetify, ProjectDetail."
argument-hint: "<change-type> [target] change-type 可选: api|entity|service|migration|frontend|auth|version|component. Examples: \"api draft-submit\" / \"frontend ProjectDetail\" / \"version diff-algorithm\""
allowed-tools:
- Read
- Glob
@@ -18,7 +19,8 @@ allowed-tools:
## 模块定位
- **核心职责**: 项目 CRUD、Git-like 版本控制、细粒度 ACL 权限、一级 TOTP 授权
- **技术栈**: Go + Gin + GORM + PostgreSQL (JSONB)
- **后端技术栈**: Go + Gin + GORM + PostgreSQL (JSONB)
- **前端技术栈**: Vue3 + TypeScript + Vuetify3
- **架构**: 模块化单体,通过接口注入与 `rmdc-work-procedure` 工单模块协作
- **版本控制思想**: 类似 Git 的分支管理Master 主线 + 用户草稿分支)
@@ -27,11 +29,17 @@ allowed-tools:
使用前先获取当前仓库状态:
```bash
# 查看项目管理模块目录结构
# 查看项目管理模块后端目录结构
!`find . -path "*/rmdc-project-management/*" -name "*.go" | head -20`
# 查看前端组件目录结构
!`find . -path "*/admin/components/*" -name "*.vue" | head -20`
# 查找版本控制相关代码
!`grep -rn "VersionSnapshot\|CompareVersions\|DiffResult" --include="*.go" | head -15`
# 查找前端生命周期状态相关代码
!`grep -rn "lifecycle_status\|LIFECYCLE_STATUS" --include="*.vue" --include="*.ts" | head -15`
```
---
@@ -44,27 +52,30 @@ allowed-tools:
| 变更类型 | 产物文件 | 影响模块 | 参考文档 |
|:---|:---|:---|:---|
| `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` |
| `api` | `handler/*.go`, `router.go` | rmdc-core 路由注册 | `reference/06-api-design/api-endpoints.md` |
| `entity` | `entity/*.go` | 数据库迁移、DTO 映射 | `reference/05-database-schema/data-structures.md` |
| `service` | `service/*.go` | 业务逻辑、版本快照 | `reference/04-version-control/version-design.md` |
| `migration` | `migrations/*.sql` | 数据库 Schema | `reference/05-database-schema/database-schema.md` |
| `frontend` | `pages/*.vue`, `components/*.vue` | 前端页面 | `reference/07-frontend-design/` |
| `auth` | `service/auth_*.go` | TOTP 授权、Exchange-Hub | `reference/03-permission-model/acl-permission.md` |
| `version` | `service/version_*.go` | 版本快照、Diff 算法 | `reference/04-version-control/version-design.md` |
| `component` | `components/*.vue` | 前端组件开发 | `reference/07-frontend-design/component-specifications.md` |
### 决策点
1. **是否涉及生命周期状态变更?** → 检查 `reference/lifecycle-state-machine.md`
2. **是否修改版本快照结构?** → 检查 `reference/version-control-design.md` 第5节
1. **是否涉及生命周期状态变更?** → 检查 `reference/02-lifecycle-state-machine/lifecycle-states.md`
2. **是否修改版本快照结构?** → 检查 `reference/04-version-control/version-design.md`
3. **是否涉及并发修改冲突?** → 检查乐观锁实现base_version 校验)
4. **是否变更 ACL 权限模型?** → 检查 `reference/acl-permission-model.md`
5. **是否影响工单模块回调?** → 检查 `reference/workflow-state-mapping.md`
4. **是否变更 ACL 权限模型?** → 检查 `reference/03-permission-model/acl-permission.md`
5. **是否影响工单模块回调?** → 检查 `reference/02-lifecycle-state-machine/workflow-state-mapping.md`
6. **是否涉及前端页面修改?** → 检查 `reference/07-frontend-design/page-architecture.md`
7. **是否涉及用户侧/管理侧差异?** → 检查 `reference/07-frontend-design/user-admin-difference.md`
---
## Verify验证阶段
### Checklist
### 后端 Checklist
- [ ] **生命周期状态机完整性**: 所有状态转换有明确的触发条件和权限控制
- [ ] **版本快照一致性**: `projects` 表与 `project_versions` 表数据同步
@@ -77,6 +88,17 @@ allowed-tools:
- [ ] **审计日志**: 所有写操作记录到 `rmdc-audit-log`
- [ ] **Namespace 校验**: 符合 RFC 1123 DNS 标签规范
### 前端 Checklist
- [ ] **状态分离**: 查看/编辑模式正确切换,`isEditMode` 状态管理正确
- [ ] **脏数据检测**: `hasChanges` computed 正确计算,退出时有确认对话框
- [ ] **角色差异化**: SuperAdmin 与普通用户看到的 Tab 和操作按钮符合设计
- [ ] **生命周期展示**: 状态标签颜色、图标、Alert Banner 符合规范
- [ ] **工单关联**: 多工单场景正确处理,跳转链接正确
- [ ] **组件复用**: 共用组件正确抽离Props 和 Emits 设计合理
- [ ] **响应式布局**: 移动端适配正确,断点设置符合 Vuetify 规范
- [ ] **TypeScript 类型**: 类型定义完整,无 any 类型
### 验证命令
```bash
@@ -89,6 +111,12 @@ allowed-tools:
# 检查敏感字段加密
!`grep -rn "EncryptAES\|DecryptAES\|admin_password\|ssh_pwd" --include="*.go"`
# 检查前端生命周期状态
!`grep -rn "LIFECYCLE_STATUS\|getLifecycleStatusColor" --include="*.vue" --include="*.ts"`
# 检查前端组件引用
!`grep -rn "BasicInfoReadonly\|SaveConfirmDialog" --include="*.vue"`
# 运行模块单元测试
go test ./internal/project/... -v -cover
```
@@ -97,7 +125,7 @@ go test ./internal/project/... -v -cover
## Execute执行阶段
### API 开发流程
### 后端 API 开发流程
1. 定义请求/响应结构体 → `dto/project_dto.go`
2. 实现 Service 方法 → `service/project_service.go`
@@ -107,7 +135,7 @@ go test ./internal/project/... -v -cover
### 版本快照变更流程
1. 更新 `VersionSnapshot` 结构体定义 → `reference/data-structures.md`
1. 更新 `VersionSnapshot` 结构体定义 → `reference/05-database-schema/data-structures.md`
2. 更新字段名映射表 `fieldNameMap` → 确保 Diff 显示中文名
3. 确保 `CompareVersions` Diff 算法兼容新字段
4. 测试历史版本查看功能不受影响
@@ -119,20 +147,30 @@ go test ./internal/project/... -v -cover
3. `committer_id` 记录 SuperAdmin ID
4. 更新 `current_version` 字段
### 生命周期状态变更流程
### 前端组件开发流程
1. 更新 `reference/lifecycle-state-machine.md` 状态图
2. 修改 `service/lifecycle_service.go` 状态转换逻辑
3. 同步更新 `ProjectLifecycleUpdater` 接口实现
4. 验证与工单模块的状态映射表一致
1. **只读组件**`*Readonly.vue`,使用 `v-row/v-col` 布局
2. **表单组件**`*Form.vue`,使用 Vuetify 表单组件
3. **组件导出** → 更新 `components/index.ts`
4. **页面集成** → 在 `ProjectDetail.vue``UserProjectDetail.vue` 中引用
5. **类型定义** → 更新 `types/*.ts`
### 前端状态管理流程
1. **进入编辑模式**: 深拷贝 `masterData``editForm`
2. **修改检测**: 使用 `hasChanges` computed
3. **保存前确认**: 使用 `SaveConfirmDialog` 展示 Diff
4. **退出保护**: 有未保存修改时弹出确认对话框
---
## Pitfalls常见问题
1. **超管直改未生成版本**: SuperAdmin 直接修改 `projects` 表时,必须同时插入 `project_versions` 记录,否则版本链断裂,后续 Diff 失效
### 后端
2. **草稿基准版本过期**: 用户 A 基于 v3 创建草稿,超管修改产生 v4用户 A 提交时需检测冲突(`draft.base_version != project.current_version`)并返回 409 Conflict
1. **超管直改未生成版本**: SuperAdmin 直接修改 `projects` 表时,必须同时插入 `project_versions` 记录,否则版本链断裂
2. **草稿基准版本过期**: 用户 A 基于 v3 创建草稿,超管修改产生 v4用户 A 提交时需检测冲突409 Conflict
3. **工单回调重复处理**: 工单模块可能重试回调,`ProjectLifecycleUpdater` 实现必须幂等
@@ -140,11 +178,13 @@ go test ./internal/project/... -v -cover
5. **密码字段明文泄露**: `AdminPassword``SSHPwd` 等字段响应时必须脱敏(返回 `********`
6. **Namespace 唯一性**: 创建项目时必须校验 `namespace` 全局唯一且符合 RFC 1123 DNS 标签规范(小写字母开头,只含小写字母/数字/-/.
### 前端
7. **JSONB 字段空值处理**: `basic_info``deploy_business` 等 JSONB 字段为空时,需返回空对象 `{}` 而非 `null`
6. **编辑模式状态未同步**: 切换 Tab 时 `isEditMode` 状态可能丢失,需使用 `v-window` 而非条件渲染
8. **版本号混淆**: 草稿版本号为 0正式版本从 1 开始递增,切勿混淆;正式版本必须保证唯一性
7. **Diff 计算不完整**: 对比时遗漏嵌套字段,需使用递归 JSON Diff
8. **工单按钮显示错误**: 多工单场景下 `workflow_id` 可能是数组,需正确处理
---
@@ -169,18 +209,26 @@ rmdc-project-management
| 直接更新 | `POST /api/project/update` | SuperAdmin | 必须生成新版本 |
| 保存草稿 | `POST /api/project/draft/save` | View ACL | 更新草稿快照 |
| 提交审核 | `POST /api/project/draft/submit` | View ACL | 检测版本冲突 |
| 草稿差异 | `POST /api/project/draft/diff` | View ACL | 草稿与主线 Diff |
| 版本历史 | `POST /api/project/version/list` | View ACL | 仅 official 类型 |
| 版本对比 | `POST /api/project/version/diff` | View ACL | 按模块分组 |
| 权限分配 | `POST /api/project/permission/grant` | SuperAdmin | 模块级权限 |
## 相关文档
## 前端页面速查
| 文档 | 内容 |
| 页面 | 路径 | 角色 | 说明 |
|:---|:---|:---|:---|
| 管理员项目详情 | `pages/admin/ProjectDetail.vue` | SuperAdmin | 全功能,含授权/版本历史 |
| 用户项目详情 | `pages/user/UserProjectDetail.vue` | User | 草稿编辑,提交审核 |
## 相关文档(章节分层)
| 章节目录 | 内容 |
|:---|:---|
| `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 清单、请求/响应示例 |
| `reference/01-architecture-overview/` | 模块依赖关系 |
| `reference/02-lifecycle-state-machine/` | 生命周期状态机、工单状态映射 |
| `reference/03-permission-model/` | ACL 权限模型、权限检查流程 |
| `reference/04-version-control/` | 版本快照、Diff 算法、乐观锁 |
| `reference/05-database-schema/` | DDL、索引、数据结构定义 |
| `reference/06-api-design/` | API 清单、业务流程 |
| `reference/07-frontend-design/` | 前端页面架构、组件规范、交互时序 |

View File

@@ -0,0 +1,54 @@
# 模块依赖关系
> DDS-Section: 2. 系统架构
> DDS-Lines: L33-L61
## 模块定位
`rmdc-project-management` 是 RMDC 系统的核心业务模块,负责维护以 K8s Namespace 为粒度的"项目"全生命周期管理。
## 依赖关系图
```mermaid
graph TB
subgraph 核心层
Core[rmdc-core<br/>API Gateway]
end
subgraph 业务协作
PM[rmdc-project-management]
WP[rmdc-work-procedure<br/>工单流程]
UA[rmdc-user-auth<br/>用户认证/权限]
Aud[rmdc-audit-log<br/>审计日志]
end
subgraph 边缘交互
EH[rmdc-exchange-hub<br/>消息网关]
WD[rmdc-watchdog]
end
Core --> PM
PM -->|用户鉴权/查询| UA
PM -->|项目权限管理| UA
PM -->|发起审批/接收回调| WP
PM -->|记录操作日志| Aud
PM -->|下发授权指令| EH
EH <--> WD
```
## 依赖方向说明
| 依赖方向 | 说明 |
|:---|:---|
| `PM → UA` | 用户鉴权、ACL 权限查询 |
| `PM ↔ WP` | 工单创建/状态转换 + 回调更新生命周期 |
| `PM → Aud` | 操作审计记录 |
| `PM → EH` | 授权指令下发 |
| `Core → PM` | 路由注册、依赖注入 |
## 核心职责
1. **项目全生命周期管理**: 创建、维护(编辑/通过工单)、发布、归档/删除
2. **版本控制**: 记录项目配置的变更历史支持版本回溯与差异对比Diff
3. **细粒度权限控制**: 基于 ACL 的权限控制,精确到用户与项目模块
4. **授权管理**: 管理项目的一级 TOTP 授权信息,与 Watchdog 交互

View File

@@ -0,0 +1,58 @@
# 项目生命周期状态机
> DDS-Section: 3. 项目生命周期管理
> DDS-Lines: L65-L106
## 状态定义
| 状态 | 说明 | 触发动作 | 权限 |
|:---|:---|:---|:---|
| **INIT** | 项目元数据已创建,等待详细信息录入 | 超级管理员创建项目 | SuperAdmin |
| **DRAFTING** | 正在进行初始信息填写(关联填写工单) | 指定填写人保存/编辑 | 填写人/SuperAdmin |
| **REVIEWING** | 初始信息或变更信息提交审核 | 提交审核 | SuperAdmin |
| **RELEASED** | 审核通过,正常运行中 | 审核通过 | All (View) |
| **MODIFYING** | 存在活跃的变更工单(不影响主线运行) | 发起修改工单 | Owner/SuperAdmin |
| **ARCHIVED** | 软删除状态,不可见但保留数据 | 删除项目 | SuperAdmin |
## 状态转换图
```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: 可同时存在多个变更工单
```
## 状态转换触发条件
| From | To | 触发条件 | 执行操作 |
|:---|:---|:---|:---|
| `[*]` | `INIT` | SuperAdmin 创建项目 | 创建 Project 记录,生成 project_id |
| `INIT` | `DRAFTING` | 分配填写人 | 创建填写工单,关联用户 |
| `DRAFTING` | `DRAFTING` | 填写人保存草稿 | 更新 ProjectVersion 草稿 |
| `DRAFTING` | `REVIEWING` | 填写人提交审核 | 工单状态 → pending_review |
| `REVIEWING` | `DRAFTING` | 审核人打回 | 工单状态 → returned |
| `REVIEWING` | `RELEASED` | 审核人通过 | 生成正式版本certification → official |
| `RELEASED` | `MODIFYING` | 发起修改工单 | 创建修改工单+草稿 |
| `MODIFYING` | `REVIEWING` | 提交变更审核 | 工单状态 → pending_review |
| `MODIFYING` | `RELEASED` | 撤销变更 或 审核通过 | 删除草稿 或 合并修改 |
| `RELEASED` | `ARCHIVED` | SuperAdmin 删除 | 软删除,设置 deleted_at |

View File

@@ -1,14 +1,62 @@
# 工单状态映射
# 生命周期与工单状态映射
## 设计原则
> DDS-Section: 3.3-3.6 生命周期与工单状态映射 + 状态同步机制设计
> DDS-Lines: L108-L296
项目模块(`rmdc-project-management`)与工单模块(`rmdc-work-procedure`)之间需要双向协作:
## 工单事件映射表
| 工单事件 | 工单目标状态 | 项目生命周期状态 | 说明 |
|:---|:---|:---|:---|
| create | created | INIT→DRAFTING | 创建填写工单 |
| draft_save | in_progress | DRAFTING/MODIFYING(保持) | 保存草稿 |
| complete | pending_review | REVIEWING | 提交审核 |
| resubmit | pending_review | REVIEWING | 被打回后重新提交 |
| return | returned | DRAFTING | 审核人打回 |
| approve | approved→closed | RELEASED | 审核人通过 |
## 状态同步机制设计
### 设计原则
项目模块与工单模块之间需要双向协作:
1. **项目 → 工单**:项目模块调用工单模块创建/转换工单
2. **工单 → 项目**:工单状态变更后同步更新项目生命周期状态
当前系统采用 **"模块化单体"架构**使**接口注入(依赖注入)** 方式实现模块间回调。
当前系统采用 **"模块化单体"架构****接口注入(依赖注入)** 方式实现模块间回调。
## 填写工单 (project_detail)
### 接口定义
**项目模块调用工单模块的接口**
```go
// WorkflowTransitioner 工单状态转换接口
type WorkflowTransitioner interface {
// TransitionWorkflow 触发工单状态转换
// @param workflowID string - 工单ID
// @param event string - 事件类型 (complete/submit/resubmit等)
// @param operatorID uint64 - 操作人ID
// @param operatorName string - 操作人姓名
// @param remark string - 操作备注
// @return newStatus string - 工单新状态
TransitionWorkflow(workflowID, event string, operatorID uint64,
operatorName string, remark string) (newStatus string, err error)
}
```
**工单模块回调项目模块的接口**
```go
// ProjectLifecycleUpdater 项目生命周期状态更新接口
type ProjectLifecycleUpdater interface {
UpdateLifecycleStatus(projectID, lifecycleStatus string) error
SetLifecycleToDrafting(projectID string) error
SetLifecycleToReviewing(projectID string) error
SetLifecycleToReleased(projectID string) error
SetLifecycleToModifying(projectID string) error
}
```
## 填写工单 (project_detail) 映射
| 工单事件 | 工单From状态 | 工单To状态 | 项目生命周期状态 | 说明 |
|:---|:---|:---|:---|:---|
@@ -21,7 +69,7 @@
| `approve` | `pending_review` | `approved` | `RELEASED` | 审核通过 |
| `revoke` | any | `revoked` | `INIT` | 撤销工单 |
## 修改工单 (project_modify)
## 修改工单 (project_modify) 映射
| 工单事件 | 工单From状态 | 工单To状态 | 项目生命周期状态 | 说明 |
|:---|:---|:---|:---|:---|
@@ -34,84 +82,16 @@
| `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:
@@ -125,22 +105,12 @@ func (s *WorkflowService) handleProjectLifecycleCallback(workflow *entity.Workfl
}
```
## 回调处理要点
## 接口注入优势
1. **幂等性**: 使用 `projectID + event + timestamp` 作为幂等键,防止重复处理
2. **事务边界**: 状态更新与版本快照生成应在同一事务
3. **审计日志**: 记录状态变更操作到 `rmdc-audit-log`
4. **错误处理**: 回调失败不应阻塞工单状态转换,记录日志后继续
## 与 HTTP 回调的对比
| 特性 | 接口注入(当前实现) | HTTP 回调 |
|:---|:---|:---|
| **模块解耦** | ⚠️ 接口级解耦 | ✅ 完全解耦 |
| **分布式支持** | ❌ 不支持 | ✅ 支持 |
| **性能** | ✅ 进程内调用 | ⚠️ 网络开销 |
| **复杂度** | ✅ 简单直接 | ⚠️ 需要重试/幂等处理 |
| **事务一致性** | ✅ 强一致性 | ⚠️ 最终一致性 |
| **适用场景** | 模块化单体架构 | 微服务架构 |
> 如果未来系统需要微服务化,可参考备选文档迁移到 HTTP 回调方案。
| 特性 | 说明 |
|:---|:---|
| **简单高效** | 进程内调用,无网络开销 |
| **类型安全** | 编译期检查接口实现 |
| **事务一致性** | 可在同一数据库事务中执行 |
| **无需重试** | 不涉及网络失败,无需复杂的重试机制 |
| **开发调试** | 调试更简单,堆栈清晰 |

View File

@@ -0,0 +1,82 @@
# ACL 权限控制模型
> DDS-Section: 4. 权限控制模型
> DDS-Lines: L300-L347
## 设计原则
权限控制分为 **功能权限** (RBAC) 和 **数据权限** (ACL)**数据权限需精确到项目模块级别**。
> **重要决策**:项目权限相关表设计在 `rmdc-user-auth` 模块中,由该模块统一管理所有权限数据。
## 功能权限 (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 可见
## 权限表 DDL (位于 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"`
// 授权信息
GrantedBy int64 `json:"granted_by"`
GrantedAt time.Time `json:"granted_at"`
UpdatedAt time.Time `json:"updated_at"`
}
```
## 权限管理接口 (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 |

View File

@@ -0,0 +1,164 @@
# 版本控制设计 (Git-like)
> DDS-Section: 5. 版本控制设计 (GIT-like)
> DDS-Lines: L350-L567
## 设计原则
采用**统一版本表**设计,将正式版本和草稿版本存储在同一张表中,通过 `version_type` 字段区分。
## 版本类型
| 版本类型 | 代码 | 说明 |
|:---|:---|:---|
| 正式版本 | `official` | 审核通过后的正式版本,构成版本历史 |
| 填写草稿 | `fill_draft` | 项目创建时填写人的草稿 |
| 修改草稿 | `modify_draft` | 发起变更工单时的草稿 |
## 版本与工单关系
1. **填写草稿**: 与填写工单 1:1 关联
2. **修改草稿**: 与修改工单 1:1 关联
3. **正式版本**: 审核通过后由草稿转化而来
4. **一个项目可以有多个修改草稿**(对应多个修改工单)
## 版本快照机制
每次审核通过后,系统自动生成一个**完整快照**存储到 `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` 表数据而不生成版本历史,会导致版本链断裂,后续基于旧版本的工单 Diff 结果将失效或产生误导。
**解决方案**
SuperAdmin 的 "Direct Edit" 操作必须被视为一次**自动审批通过的事务**
1. **原子操作**:更新 `projects` 表 + 插入 `project_versions` 表必须在同一数据库事务中完成。
2. **版本归属**:生成的 Version 记录中,`workflow_id` 为空(或特定系统标识),`committer_id` 记录为 SuperAdmin ID。
3. **结果**:确保 `projects.current_version` 永远指向最新的 `project_versions.version`
## 并发修改与冲突检测 (Optimistic Locking)
### 冲突场景
1. 用户 A 基于 v3 版本创建草稿Draft based on v3
2. 超级管理员直接修改项目,版本升级为 v4Current = v4
3. 用户 A 提交草稿审核。
### 处理策略
1. **提交时校验**:工单提交/审核接口需校验 `draft.base_version == project.current_version`
2. **冲突提示**:如果版本不一致,后端返回 `409 Conflict` 错误。
3. **前端交互**
* 提示用户:"项目已被修改,当前草稿已过期"。
* 提供 **"Rebase" (变基)** 选项。
* 或者提供 **"Diff Check"**:让用户查看差异。
## 版本 Diff 算法
采用 **JSON Diff** 算法,对比两个版本快照的差异。
### 差异结构
```go
// 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
}
```
### 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
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.Base, m.Target)
if len(diffs) > 0 {
results = append(results, DiffResult{
Module: m.Name,
FieldDiffs: diffs,
})
}
}
return results, nil
}
```
## 版本历史查询
### 版本列表结构
```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"` // 变更摘要
}
```
### 版本历史 API
| 方法 | 路径 | 描述 |
|:---|:---|:---|
| POST | `/api/project/version/list` | 获取版本历史列表 |
| POST | `/api/project/version/detail` | 获取指定版本详情 |
| POST | `/api/project/version/diff` | 对比两个版本差异 |
| POST | `/api/project/version/diff-with-current` | 对比指定版本与当前版本差异 |

View File

@@ -0,0 +1,172 @@
# 数据结构定义
> DDS-Section: 10. 附录 - 结构体定义
> DDS-Lines: L880-L993
## 基本信息结构体 (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
}
```
### 项目性质枚举
| 代码 | 说明 |
|:---|:---|
| `research` | 科研 |
| `test` | 测试 |
| `trial` | 试用 |
| `market` | 市场化 |
| `sub_platform` | 二级平台 |
## 业务部署结构体 (DeployBusiness)
```go
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"` // 业务主要入口URL
AdminUsername string `json:"admin_username"` // 系统超管用户名
AdminPassword string `json:"admin_password"` // 系统超管密码 (加密存储)
}
```
### 系统类型枚举
| 代码 | 说明 |
|:---|:---|
| `business` | 业务系统 |
| `fly-control` | 飞控系统 |
| `supervisor` | 监管系统 |
## 部署环境结构体 (DeployEnv)
```go
type DeployEnv struct {
// 主机信息
Hosts []HostInfo `json:"hosts"`
// 网络环境
NetworkType string `json:"network_type"` // internal/single_public/full_public
MainPublicIP string `json:"main_public_ip"` // 主要公网IP
DomainURL string `json:"domain_url"` // 域名URL
SSLEnabled bool `json:"ssl_enabled"` // 是否开启SSL
// 管理方式
ManagementType string `json:"management_type"` // bastion/whitelist/vpn
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"`
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"` // SSH密码 (加密存储)
Role string `json:"role"` // master/worker/storage
}
```
### 网络类型枚举
| 代码 | 说明 |
|:---|:---|
| `internal` | 完全内网 |
| `single_public` | 单主机公网 |
| `full_public` | 全访问公网 |
### 管理方式枚举
| 代码 | 说明 |
|:---|:---|
| `bastion` | 堡垒机 |
| `whitelist` | 白名单 |
| `vpn` | VPN |
### 主机角色枚举
| 代码 | 说明 |
|:---|:---|
| `master` | 主节点 |
| `worker` | 工作节点 |
| `storage` | 存储节点 |
## 部署中间件结构体 (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"`
PublicPort int `json:"public_port"`
InternalIP string `json:"internal_ip"`
InternalPort int `json:"internal_port"`
K8SAddress string `json:"k8s_address"` // K8S集群内访问地址
K8SPort int `json:"k8s_port"`
AdminUser string `json:"admin_user"`
AdminPwd string `json:"admin_pwd"` // 超管密码 (加密存储)
Version string `json:"version"` // 中间件版本
}
```
## 项目授权信息结构体 (AuthorizationInfo)
```go
type AuthorizationInfo struct {
TierOneSecret string `json:"tier_one_secret"` // 一级TOTP密钥
TimeOffset int `json:"time_offset"` // 允许时间偏移(秒)
TOTPEnabled bool `json:"totp_enabled"` // 是否开启TOTP
TierTwoSecret string `json:"tier_two_secret"` // 二级TOTP密钥 (来自Watchdog)
AuthType string `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 `json:"is_offline"` // 是否离线授权
}
```
### 授权类型枚举
| 代码 | 说明 |
|:---|:---|
| `permanent` | 永久授权 |
| `time_limited` | 限时授权 |
## 敏感字段加密规则
遵循系统规范,以下字段必须加密存储 (AES-256):
- `DeployBusiness.AdminPassword`
- `DeployEnv.SSHPassword`
- `DeployEnv.ManagementPassword`
- `DeployMiddleware.*.AdminPassword`

View File

@@ -0,0 +1,195 @@
# 数据库模型设计
> DDS-Section: 6. 数据模型设计
> DDS-Lines: L570-L778
## 设计决策
| 决策点 | 决策 | 理由 |
|:---|:---|:---|
| JSONB vs 分表 | 使用JSONB存储 | 1. 项目信息结构复杂但查询简单 2. 版本快照需完整存储 3. 减少JOIN提升性能 |
| 草稿存储位置 | project_versions表 | 统一版本管理方便Diff比较 |
| 权限表位置 | rmdc-user-auth模块 | 统一权限管理,便于跨模块授权 |
## ER 图
```mermaid
erDiagram
projects ||--o{ project_versions : has
projects ||--o{ project_workflows : has
projects ||--|| project_auth_configs : has
projects {
bigint id PK
string project_id UK
string name
string namespace UK
string lifecycle_status
string certification_status
bigint detail_filler_id
string detail_filler_name
int current_version
jsonb basic_info
jsonb deploy_business
jsonb deploy_env
jsonb deploy_middleware
datetime created_at
datetime updated_at
datetime deleted_at
}
project_versions {
bigint id PK
string project_id FK
int version
string version_type
bigint user_id
string workflow_id
jsonb snapshot_data
string commit_message
bigint committer_id
string committer_name
datetime created_at
datetime updated_at
}
project_workflows {
bigint id PK
string project_id FK
string workflow_id UK
string workflow_type
string status
datetime created_at
}
project_auth_configs {
bigint id PK
string project_id FK
string tier_one_secret
int time_offset
bool totp_enabled
string auth_type
int auth_days
datetime created_at
datetime updated_at
}
```
## 表关系说明
| 关系 | 说明 |
|:---|:---|
| `projects``project_versions` | 一对多,一个项目有多个版本记录(含草稿) |
| `projects``project_workflows` | 一对多,一个项目可关联多个工单 |
| `projects``project_auth_configs` | 一对一,一个项目对应一个授权配置 |
## 字段说明
| 表 | 字段 | 说明 |
|:---|:---|:---|
| projects | lifecycle_status | INIT/DRAFTING/REVIEWING/RELEASED/MODIFYING/ARCHIVED |
| projects | certification_status | draft/pending/official |
| project_versions | version_type | official/fill_draft/modify_draft |
| project_versions | workflow_id | 关联 rmdc-work-procedure 模块的工单 ID |
| project_workflows | workflow_type | fill(填写)/modify(修改) |
## 主表 DDL - projects
```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"`
// 生命周期状态
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
}
```
## 版本表 DDL - project_versions
```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"`
// 草稿所属用户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"`
}
```
## 项目工单关联表 DDL - project_workflows
```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"`
}
```
## 索引策略
| 表 | 索引 | 用途 |
|:---|:---|:---|
| projects | `UK: project_id` | 主键查询 |
| projects | `UK: namespace` | 唯一性约束 |
| project_versions | `IX: project_id` | 版本历史查询 |
| project_versions | `IX: workflow_id` | 工单关联查询 |
| project_workflows | `UK: workflow_id` | 工单唯一关联 |
| project_workflows | `IX: project_id` | 项目关联查询 |

View File

@@ -0,0 +1,141 @@
# API 接口设计
> DDS-Section: 7. 接口设计 (API)
> DDS-Lines: L781-L818
## 项目管理接口
| 方法 | 路径 | 描述 | 权限 |
|:---|:---|:---|:---|
| 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/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 |
| POST | `/api/project/draft/diff` | 获取草稿与主线的差异 | 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 |
## 请求/响应示例
### 创建项目
**请求**
```json
{
"project_name": "测试项目",
"namespace": "test-project-ns",
"detail_filler_id": 123,
"detail_filler_name": "张三"
}
```
**响应**
```json
{
"code": 0,
"message": "success",
"data": {
"project_id": "proj_20260120_001",
"workflow_id": "wf_20260120_001"
}
}
```
### 保存草稿
**请求**
```json
{
"project_id": "proj_20260120_001",
"form_data": {
"deploy_business": {
"deployer_name": "李四",
"deployer_phone": "13800138000",
"system_version": "v2.0.0"
},
"deploy_env": {
"network_type": "internal",
"host_count": 3
}
},
"middlewares": [
{
"middleware_type": "mysql",
"internal_ip": "10.0.0.1",
"internal_port": 3306
}
]
}
```
### 获取版本差异
**请求**
```json
{
"project_id": "proj_20260120_001",
"base_version": 1,
"target_version": 2
}
```
**响应**
```json
{
"code": 0,
"data": {
"diffs": [
{
"module": "部署业务",
"field_diffs": [
{
"field_path": "deploy_business.system_version",
"field_name": "系统版本",
"old_value": "v1.0.0",
"new_value": "v2.0.0",
"change_type": "modify"
}
]
}
]
}
}
```
## 错误码定义
| 错误码 | 说明 |
|:---|:---|
| `PROJECT_NOT_FOUND` | 项目不存在 |
| `NAMESPACE_EXISTS` | 命名空间已存在 |
| `VERSION_CONFLICT` | 版本冲突(并发修改) |
| `PERMISSION_DENIED` | 权限不足 |
| `INVALID_LIFECYCLE_STATUS` | 无效的生命周期状态 |
| `DRAFT_NOT_FOUND` | 草稿不存在 |
| `WORKFLOW_ERROR` | 工单操作失败 |

View File

@@ -0,0 +1,98 @@
# 业务流程设计
> DDS-Section: 8. 业务流程
> DDS-Lines: L822-L856
## 项目创建流程
```mermaid
sequenceDiagram
participant Admin as 超级管理员
participant PM as rmdc-project-management
participant WP as rmdc-work-procedure
participant User as 填写人
Admin->>PM: POST /api/project/create
PM->>PM: 创建Project记录 (status=INIT)
PM->>WP: 创建project_detail工单
WP-->>PM: 返回workflow_id
PM->>PM: 创建ProjectVersion草稿记录
PM->>PM: 创建ProjectWorkflow关联记录
PM-->>Admin: 返回项目创建成功
PM->>User: 通知填写人
User->>PM: POST /api/project/draft/save
PM->>PM: 更新ProjectVersion草稿
User->>PM: POST /api/project/draft/submit
PM->>WP: 状态转换为pending_review
PM->>PM: 更新lifecycle_status=REVIEWING
```
## 项目与工单关系
| 关系类型 | 项目状态 | 说明 |
|:---|:---|:---|
| 项目:填写工单 = 1:1 | INIT/DRAFTING | 项目创建时只能有一个填写工单 |
| 项目:修改工单 = 1:N | RELEASED/MODIFYING | 已发布项目可以有多个修改工单 |
| 用户:修改工单 = 1:1 (per project) | - | 非SuperAdmin用户同一项目只能有一个活跃修改工单 |
## 修改工单流程
```mermaid
sequenceDiagram
participant User as 普通用户
participant PM as rmdc-project-management
participant WP as rmdc-work-procedure
participant Admin as 超级管理员
User->>PM: POST /api/project/draft/create (修改工单)
PM->>PM: 检查用户项目权限
PM->>WP: 创建project_modify工单
WP-->>PM: 返回workflow_id
PM->>PM: 创建ProjectVersion草稿 (base_version=current)
PM->>PM: 更新lifecycle_status=MODIFYING
PM-->>User: 返回工单创建成功
loop 填写草稿
User->>PM: POST /api/project/draft/save
PM->>PM: 更新草稿数据
end
User->>PM: POST /api/project/draft/submit
PM->>PM: 检查版本冲突 (base_version == current_version?)
alt 无冲突
PM->>WP: 状态转换为pending_review
PM->>PM: 更新lifecycle_status=REVIEWING
else 有冲突
PM-->>User: 返回409 Conflict
end
Admin->>PM: POST /api/workflow/approve
PM->>PM: 合并草稿到主线
PM->>PM: 生成新版本 (official)
PM->>PM: 更新lifecycle_status=RELEASED
```
## 超管直改流程
```mermaid
sequenceDiagram
participant Admin as 超级管理员
participant PM as rmdc-project-management
participant DB as PostgreSQL
Admin->>PM: POST /api/project/update
PM->>PM: 验证权限 (SuperAdmin)
PM->>DB: BEGIN TRANSACTION
PM->>DB: UPDATE projects SET ...
PM->>DB: INSERT project_versions (official)
PM->>DB: COMMIT
PM-->>Admin: 返回更新成功 + 新版本号
```
## 审计日志
所有对 `projects` 表的写操作均需通过 `rmdc-audit-log` 记录:
- **Resource**: `project`
- **Action**: `create`, `update`, `publish_version`, `delete`, `permission_grant`
- **Payload**: 记录关键变更字段

View File

@@ -0,0 +1,151 @@
# 核心组件设计规范
> DDS-Section: Frontend DDS - 8. 组件设计规范
> DDS-Lines: L696-L783
## CopyableField 组件
用于展示可复制的字段值:
```html
<template>
<div class="copyable-field d-flex align-center gap-2">
<span class="field-value">{{ displayValue }}</span>
<v-btn
icon="mdi-content-copy"
size="x-small"
variant="text"
@click="copyToClipboard"
>
<v-tooltip activator="parent" location="top">复制</v-tooltip>
</v-btn>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useClipboard } from '@vueuse/core'
const props = defineProps<{
value: string
mask?: boolean
}>()
const { copy } = useClipboard()
const displayValue = computed(() => {
if (props.mask) return '******'
return props.value
})
const copyToClipboard = () => {
copy(props.value)
}
</script>
```
## SaveConfirmDialog 组件
保存前的确认对话框,展示变更差异:
```html
<v-dialog v-model="visible" max-width="600">
<v-card>
<v-card-title class="bg-primary text-white">
<v-icon start>mdi-check-circle</v-icon>
确认保存修改
</v-card-title>
<v-card-text>
<v-alert type="info" variant="tonal" class="mb-4">
以下字段将被修改:
</v-alert>
<v-table density="compact">
<thead>
<tr>
<th>字段</th>
<th>修改前</th>
<th>修改后</th>
</tr>
</thead>
<tbody>
<tr v-for="item in diffItems" :key="item.field">
<td>{{ item.label }}</td>
<td class="text-error">{{ item.oldValue || '空' }}</td>
<td class="text-success">{{ item.newValue || '空' }}</td>
</tr>
</tbody>
</v-table>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn variant="text" @click="cancel">取消</v-btn>
<v-btn color="primary" :loading="loading" @click="confirm">确认保存</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
```
## DiffTextField 组件
编辑模式下显示与主线数据差异的输入框:
```html
<template>
<v-text-field
v-model="inputValue"
:label="label"
:class="{ 'diff-highlight': hasDiff }"
:hint="hasDiff ? `主线值: ${masterValue}` : ''"
persistent-hint
>
<template v-slot:prepend-inner v-if="hasDiff">
<v-icon color="warning" size="small">mdi-alert-circle</v-icon>
</template>
</v-text-field>
</template>
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<{
modelValue: string
masterValue: string
label: string
}>()
const emit = defineEmits(['update:modelValue'])
const inputValue = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
const hasDiff = computed(() => props.modelValue !== props.masterValue)
</script>
<style scoped>
.diff-highlight :deep(.v-field__outline) {
--v-field-border-color: rgb(var(--v-theme-warning));
}
</style>
```
## 组件清单
| 组件 | 文件路径 | 说明 |
|:---|:---|:---|
| BasicInfoForm | `components/BasicInfoForm.vue` | 基本信息编辑表单 |
| BasicInfoReadonly | `components/BasicInfoReadonly.vue` | 基本信息只读 |
| BusinessInfoReadonly | `components/BusinessInfoReadonly.vue` | 业务信息只读 |
| DeploymentBusinessForm | `components/DeploymentBusinessForm.vue` | 业务信息表单 |
| DeploymentEnvironmentForm | `components/DeploymentEnvironmentForm.vue` | 环境信息表单 |
| EnvironmentInfoReadonly | `components/EnvironmentInfoReadonly.vue` | 环境信息只读 |
| HostsInfoReadonly | `components/HostsInfoReadonly.vue` | 主机信息只读 |
| HostsManagement | `components/HostsManagement.vue` | 主机管理 |
| MiddlewareCardsGrid | `components/MiddlewareCardsGrid.vue` | 中间件卡片网格 |
| MiddlewareInfoReadonly | `components/MiddlewareInfoReadonly.vue` | 中间件只读 |
| AuthorizationManagement | `components/AuthorizationManagement.vue` | 授权管理 |
| VersionHistory | `components/VersionHistory.vue` | 版本历史 |
| SaveConfirmDialog | `components/SaveConfirmDialog.vue` | 保存确认对话框 |
| CopyableField | `components/CopyableField.vue` | 可复制字段 |
| ProjectBasicInfoCard | `components/ProjectBasicInfoCard.vue` | 项目基本信息卡片 |

View File

@@ -0,0 +1,128 @@
# 交互时序图
> DDS-Section: Frontend DDS - 9. 交互时序图
> DDS-Lines: L787-L878
## 管理员编辑保存流程
```mermaid
sequenceDiagram
participant Admin as 超级管理员
participant Page as ProjectDetail.vue
participant Dialog as SaveConfirmDialog
participant API as 后端API
Admin->>Page: 点击[编辑]按钮
Page->>Page: isEditMode = true
Page->>Page: editForm = deepClone(masterData)
Admin->>Page: 修改字段
Page->>Page: hasChanges = true
Admin->>Page: 点击[保存修改]
Page->>Page: 计算 diffItems
Page->>Dialog: 显示确认对话框
Admin->>Dialog: 确认保存
Dialog->>API: updateProject()
API-->>Dialog: 成功
Dialog->>Page: emit('confirm')
Page->>API: loadProject()
Page->>Page: isEditMode = false
Page-->>Admin: Snackbar: 保存成功
```
## 用户草稿提交流程
```mermaid
sequenceDiagram
participant User as 普通用户
participant Page as UserProjectDetail.vue
participant Dialog as SubmitDialog
participant API as 后端API
participant WF as 工单模块
User->>Page: 填写表单
User->>Page: 点击[保存草稿]
Page->>API: saveDraft()
API-->>Page: 草稿保存成功
User->>Page: 点击[提交审核]
Page->>Dialog: 显示确认对话框
User->>Dialog: 填写备注并确认
Dialog->>API: saveDraft() (最终版本)
Dialog->>API: submitProjectDetail()
API->>WF: 触发工单状态转换
WF-->>API: 成功
API-->>Dialog: 提交成功
Page-->>User: 跳转至工单详情页
```
## 管理员审批流程
```mermaid
sequenceDiagram
participant Admin as 超级管理员
participant Page as ProjectDetail.vue
participant Dialog as ApproveDialog/RejectDialog
participant API as 后端API
participant WF as 工单模块
Note over Page: lifecycle_status = reviewing
Admin->>Page: 查看待审核内容
alt 通过审批
Admin->>Page: 点击[通过]
Page->>Dialog: 显示审批对话框
Admin->>Dialog: 填写备注并确认
Dialog->>API: transitionWorkflow(approve)
API->>WF: 工单状态 → approved
WF-->>API: 回调更新项目状态
API-->>Dialog: 成功
Page->>API: updateProjectCertification('official')
Page-->>Admin: Snackbar: 审批通过
else 打回修改
Admin->>Page: 点击[打回]
Page->>Dialog: 显示打回对话框
Admin->>Dialog: 填写打回原因并确认
Dialog->>API: transitionWorkflow(return)
API->>WF: 工单状态 → returned
WF-->>API: 回调更新项目状态
API-->>Dialog: 成功
Page-->>Admin: Snackbar: 已打回
end
```
## 版本冲突处理流程
```mermaid
sequenceDiagram
participant User as 普通用户
participant Page as UserProjectDetail.vue
participant API as 后端API
participant Admin as 超级管理员
Note over User,API: 用户 A 基于 v3 版本创建草稿
Admin->>API: 直接修改项目 (v3 → v4)
User->>Page: 点击[提交审核]
Page->>API: submitDraft() (base_version=v3)
API->>API: 检查 base_version != current_version
API-->>Page: 返回 409 Conflict
Page-->>User: 显示冲突提示对话框
alt 选择 Rebase
User->>Page: 点击 [Rebase]
Page->>API: getProject() (最新 v4 版本)
Page->>Page: 自动合并草稿到 v4
Page-->>User: 显示合并结果,可能需要手动解决冲突
else 选择 Diff Check
User->>Page: 点击 [查看差异]
Page->>API: getDiff(v3, v4)
Page-->>User: 显示 v3 与 v4 的差异
User->>User: 决定是否覆盖或调整
end
```

View File

@@ -0,0 +1,212 @@
# 生命周期状态与工单关联展示
> DDS-Section: Frontend DDS - 5-6. 项目生命周期状态展示 + 工单关联与跳转机制
> DDS-Lines: L334-L582
## 状态标签设计
项目详情页 Header 区域展示三类状态标签:
```html
<div class="d-flex align-center gap-2">
<h1 class="text-h4 font-weight-bold">{{ masterData.project_name }}</h1>
<!-- 1. 连接状态 -->
<v-chip :color="getStatusColor(masterData.status)" size="small" variant="tonal">
{{ PROJECT_STATUS[masterData.status] }}
</v-chip>
<!-- 2. 生命周期状态 -->
<v-chip :color="getLifecycleStatusColor(masterData.lifecycle_status)"
size="small" variant="tonal">
<v-icon start size="small">{{ getLifecycleStatusIcon(masterData.lifecycle_status) }}</v-icon>
{{ LIFECYCLE_STATUS[masterData.lifecycle_status] }}
</v-chip>
<!-- 3. 认证状态 -->
<v-chip :color="masterData.project_certification === 'official' ? 'success' : 'warning'"
size="small" variant="tonal">
{{ PROJECT_CERTIFICATION[masterData.project_certification] }}
</v-chip>
<!-- 4. 编辑模式指示器 -->
<v-chip v-if="isEditMode" color="info" variant="tonal">
<v-icon start size="small">mdi-pencil</v-icon>
编辑模式
</v-chip>
</div>
```
## 生命周期状态配置
```typescript
// 生命周期状态枚举
export const LIFECYCLE_STATUS = {
init: '初始化',
drafting: '填写中',
reviewing: '审核中',
released: '已发布',
modifying: '变更中',
archived: '已归档'
}
// 状态颜色映射
export const LIFECYCLE_STATUS_COLORS: Record<string, string> = {
init: 'grey',
drafting: 'info',
reviewing: 'warning',
released: 'success',
modifying: 'primary',
archived: 'grey-darken-1'
}
// 状态图标
const getLifecycleStatusIcon = (status: string): string => {
const icons: Record<string, string> = {
init: 'mdi-clock-outline',
drafting: 'mdi-pencil',
reviewing: 'mdi-eye',
released: 'mdi-check-circle',
modifying: 'mdi-sync',
archived: 'mdi-archive'
}
return icons[status] || 'mdi-help-circle'
}
```
## 生命周期提示横幅 (Alert Banner)
根据当前生命周期状态,在 Header 下方显示上下文提示:
```typescript
interface LifecycleAlert {
type: 'info' | 'warning' | 'success' | 'error'
message: string
action?: {
text: string
handler: () => void
}
}
const lifecycleStatusAlert = computed((): LifecycleAlert | null => {
if (!masterData.value) return null
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 || '填写人'} 填写中`,
action: masterData.value.workflow_id ? {
text: '查看工单',
handler: () => router.push(`/admin/workflows/${masterData.value?.workflow_id}`)
} : undefined
}
case 'reviewing':
return {
type: 'warning',
message: '项目详情已提交,等待审核',
action: masterData.value.workflow_id ? {
text: '查看工单',
handler: () => router.push(`/admin/workflows/${masterData.value?.workflow_id}`)
} : undefined
}
case 'modifying':
return {
type: 'info',
message: '项目存在活跃的变更工单,主线数据不受影响',
action: masterData.value.workflow_id ? {
text: '查看工单',
handler: () => router.push(`/admin/workflows/${masterData.value?.workflow_id}`)
} : undefined
}
case 'archived':
return {
type: 'warning',
message: '项目已归档,仅保留历史数据'
}
default:
return null
}
})
```
## 工单关联与跳转机制
### 工单与项目的关系
| 工单类型 | 生命周期状态 | 数量关系 | 说明 |
|:---|:---|:---|:---|
| 填写工单 (project_detail) | INIT → DRAFTING | 1:1 | 项目创建时只能有一个 |
| 修改工单 (project_modify) | RELEASED → MODIFYING | 1:N | 已发布项目可有多个 |
### 工单按钮显示逻辑
```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(() => {
if (!masterData.value) return '查看工单'
const status = masterData.value.lifecycle_status
switch (status) {
case 'drafting':
return '查看填写工单'
case 'reviewing':
return '查看审核工单'
case 'modifying':
return '查看修改工单'
default:
return '查看工单'
}
})
```
### 多工单场景处理
当项目处于 `MODIFYING` 状态时,可能同时存在多个修改工单:
```html
<v-menu v-if="multipleWorkflows">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" color="info" variant="tonal" prepend-icon="mdi-sitemap">
查看工单 ({{ workflowCount }})
<v-icon end>mdi-chevron-down</v-icon>
</v-btn>
</template>
<v-list>
<v-list-item
v-for="wf in relatedWorkflows"
:key="wf.workflow_id"
@click="navigateToWorkflow(wf.workflow_id)"
>
<v-list-item-title>{{ wf.workflow_id }}</v-list-item-title>
<v-list-item-subtitle>
{{ wf.creator_name }} | {{ formatDate(wf.created_at) }}
</v-list-item-subtitle>
</v-list-item>
</v-list>
</v-menu>
```
### 从工单页面跳转回项目详情
```typescript
// 工单详情页
const navigateToProject = () => {
if (workflowDetail.value?.business_context?.project_id) {
router.push(`/admin/projects/${workflowDetail.value.business_context.project_id}`)
}
}
```

View File

@@ -0,0 +1,111 @@
# 模块详细设计规范
> DDS-Section: Frontend DDS - 7. 模块详细设计规范
> DDS-Lines: L586-L693
## 基本信息模块 (Basic Info)
| 字段 | 类型 | 只读模式 | 编辑模式 |
|:---|:---|:---|:---|
| 项目名称 | String | 文本展示 + 复制 | `v-text-field` |
| 命名空间 | String | 文本展示 + 复制 | `disabled` 不可编辑 |
| 省份 | Enum | 文本展示 | 级联选择器 |
| 城市 | Enum | 文本展示 | 级联选择器(依赖省份) |
| 项目性质 | Enum | 文本展示 | `v-select` |
| 行业组人员 | String | 文本展示 | `v-text-field` |
| 行业组电话 | String | 文本展示 | `v-text-field` |
| 项目描述 | String | 多行文本 | `v-textarea` |
## 部署业务模块 (Deployment Business)
| 字段 | 类型 | 只读模式 | 编辑模式 |
|:---|:---|:---|:---|
| 部署人姓名 | String | 文本展示 | `v-text-field` 或用户搜索 |
| 部署人电话 | String | 文本展示 | `v-text-field` |
| 部署系统 | Enum | 文本展示 | `v-select` |
| 系统版本 | String | 文本展示 | `v-text-field` |
| 业务入口 URL | String | 可点击链接 | `v-text-field` |
| 超管账号 | String | 文本展示 + 复制 | `v-text-field` |
| 超管密码 | Password | 脱敏 + 查看按钮 | `v-text-field` 密码输入 |
## 部署环境模块 (Deployment Environment)
| 字段 | 类型 | 只读模式 | 编辑模式 |
|:---|:---|:---|:---|
| 网络环境 | Enum | 文本展示 | `v-select` |
| 主公网 IP | String | 文本展示 + 复制 | `v-text-field` IP 校验 |
| 域名 URL | String | 可点击链接 | `v-text-field` |
| 启用 SSL | Boolean | 图标显示 | `v-switch` |
| 主机管理方式 | Enum | 文本展示 | `v-select` |
| 管理控制台 URL | String | 可点击链接 | `v-text-field` |
| 主机数量 | Number | 文本展示 | `v-text-field type=number` |
| CPU 总核数 | Number | 统计卡片 | `v-text-field type=number` |
| 内存总量 | Number | 统计卡片 | `v-text-field type=number` |
| 存储总量 | Number | 统计卡片 | `v-text-field type=number` |
## 中间件模块 (Middleware)
采用 **卡片网格 (Card Grid)** 设计。
### 数据结构
```typescript
interface MiddlewareFormItem {
middleware_type: string
public_ip: string
public_port: number
internal_ip: string
internal_port: number
admin_user: string
admin_password?: string
}
```
### 只读模式
- 每个中间件一张卡片,响应式网格布局
- 卡片包含:类型图标 + 标题 + IP/Port 信息
- 图标映射逻辑:
```typescript
const MIDDLEWARE_ICONS: Record<string, string> = {
'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'
}
```
### 编辑模式
- 现有卡片右上角显示「编辑」「删除」按钮
- 列表末尾显示「添加中间件」虚线框卡片
- 点击添加/编辑弹出对话框:
- 类型选择:`v-combobox` 支持预设 + 自定义
- 选择预设类型时自动填充默认端口
## 主机管理模块 (Hosts Management)
- 复用 `HostsManagement.vue` 组件
- 支持表格展示所有主机信息
- 编辑模式支持添加/删除主机
## 授权信息模块 (Authorization) - SuperAdmin Only
| 功能 | 说明 |
|:---|:---|
| TOTP 密钥展示 | 二维码 + 文本形式 |
| 授权类型切换 | 永久/限时 |
| 授权天数设置 | 数字输入 |
| 下发授权 | 调用 Exchange-Hub 接口 |
| 撤销授权 | 调用 Exchange-Hub 接口 |
## 版本历史模块 (Version History) - SuperAdmin Only
| 功能 | 说明 |
|:---|:---|
| 版本列表 | 时间轴形式展示 |
| 版本详情 | 点击查看完整快照 |
| 版本对比 | 选择两个版本进行 Diff |
| 工单关联 | 点击跳转关联工单 |

View File

@@ -0,0 +1,102 @@
# 前端页面架构设计
> DDS-Section: Frontend DDS - 1-2. 设计概述 + 页面架构设计
> DDS-Lines: L23-L127
## 设计背景
项目详情页面是 RMDC 系统的核心交互界面,需要支持复杂的生命周期管理流程,并满足不同角色用户的差异化需求。
## 核心设计目标
| 目标 | 说明 |
|:---|:---|
| **状态分离** | 明确区分「只读查看模式」与「编辑修改模式」,防止误操作 |
| **角色差异化** | 超级管理员与普通用户看到的内容、操作权限不同,但尽量复用组件 |
| **生命周期可视化** | 清晰展示项目当前状态INIT/DRAFTING/REVIEWING/RELEASED/MODIFYING/ARCHIVED |
| **工单关联** | 支持项目详情页与工单详情页的双向跳转,处理多工单场景 |
| **美观专业** | 采用现代 Material Design 风格,注重留白、排版与微动效 |
| **高复用性** | 最大化组件复用,用户侧与管理侧共用核心表单组件 |
## 页面文件结构
```
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 # 保存确认对话框
│ ├── ProjectBasicInfoCard.vue # 项目基本信息卡片
│ ├── CopyableField.vue # 可复制字段组件
│ └── index.ts # 组件统一导出
```
## 整体布局结构
页面采用 **「固定头部 + 固定 Tab 导航 + 可滚动内容区域」** 的三段式布局:
```
┌─────────────────────────────────────────────────────────────────────────┐
│ [固定区域] 生命周期状态提示横幅 (Alert Banner) │
├─────────────────────────────────────────────────────────────────────────┤
│ [固定区域] 页面头部 Header │
│ ┌─────────────────────────────────────┬─────────────────────────────┐ │
│ │ ← 返回 项目名称 │ [查看工单] [打回] [通过] │ │
│ │ Namespace | 省份 城市 │ [下载配置] [编辑/保存] │ │
│ │ 状态标签组 │ │ │
│ └─────────────────────────────────────┴─────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────┤
│ [固定区域] Tab 导航栏 │
│ ┌─────────────────────────────────────────────────────────────────────┐│
│ │ 基本信息 | 部署业务 | 部署环境 | 主机管理 | 中间件 | 授权 | 版本历史 ││
│ └─────────────────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────────────────┤
│ [滚动区域] Tab 内容区域 │
│ ┌─────────────────────────────────────────────────────────────────────┐│
│ │ ││
│ │ 当前 Tab 对应的表单/只读内容 ││
│ │ ││
│ └─────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────┘
```
## 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;
}
```

View File

@@ -0,0 +1,183 @@
# 用户侧 vs 管理侧差异化设计
> DDS-Section: Frontend DDS - 4. 用户侧 vs 管理侧差异化设计
> DDS-Lines: L220-L331
## 页面对照表
| 特性 | 管理员端 (ProjectDetail.vue) | 用户端 (UserProjectDetail.vue) |
|:---|:---|:---|
| **默认模式** | 查看模式 | 根据工单状态决定 |
| **查看权限** | 所有模块 | ACL 授权模块 |
| **授权信息 Tab** | ✅ 可见 | ❌ 不可见 |
| **版本历史 Tab** | ✅ 可见 | ❌ 不可见 |
| **主机管理 Tab** | ✅ 可见 | ❌ 不可见 |
| **基本信息** | 可编辑 | 只读(由管理员填写) |
| **编辑操作** | 直接保存(上帝模式) | 草稿 → 提交审核(工单流程) |
| **审批操作** | ✅ 通过/打回按钮 | ❌ 无 |
| **保存按钮** | 「保存修改」 | 「保存草稿」 |
| **提交按钮** | 无 | 「提交审核」 |
## Tab 导航差异
### 管理员端 Tabs
```html
<v-tabs v-model="activeTab">
<v-tab value="basic">基本信息</v-tab>
<v-tab value="business">部署业务</v-tab>
<v-tab value="environment">部署环境</v-tab>
<v-tab value="hosts">主机管理</v-tab>
<v-tab value="middlewares">中间件</v-tab>
<v-tab value="authorization" v-if="isSuperAdmin">授权信息</v-tab>
<v-tab value="version-history" v-if="isSuperAdmin">版本历史</v-tab>
</v-tabs>
```
### 用户端 Tabs
```html
<v-tabs v-model="activeTab">
<v-tab value="basic">基本信息</v-tab>
<v-tab value="business">部署业务</v-tab>
<v-tab value="environment">部署环境</v-tab>
<v-tab value="middlewares">中间件</v-tab>
</v-tabs>
```
## 组件复用策略
```mermaid
graph TB
subgraph 共用组件
A[BasicInfoForm]
B[DeploymentBusinessForm]
C[DeploymentEnvironmentForm]
D[MiddlewareCardsGrid]
E[BasicInfoReadonly]
F[BusinessInfoReadonly]
G[EnvironmentInfoReadonly]
H[MiddlewareInfoReadonly]
end
subgraph 管理端专用
I[AuthorizationManagement]
J[VersionHistory]
K[HostsManagement]
L[SaveConfirmDialog]
end
subgraph 用户端专用
M[ProjectBasicInfoCard]
end
Admin[ProjectDetail.vue] --> A
Admin --> B
Admin --> C
Admin --> D
Admin --> E
Admin --> F
Admin --> G
Admin --> H
Admin --> I
Admin --> J
Admin --> K
Admin --> L
User[UserProjectDetail.vue] --> B
User --> C
User --> D
User --> M
```
## 权限控制逻辑
### 管理员端 - 编辑权限判断
```typescript
const canEdit = computed(() => {
if (!masterData.value) return false
const status = masterData.value.lifecycle_status
// 超级管理员在已发布、变更中状态可以编辑
if (isSuperAdmin.value) {
return status === 'released' || status === 'modifying'
}
return false
})
```
### 用户端 - 编辑权限判断
```typescript
const canEdit = computed(() => {
if (!workflowInfo.value) return true // 没有工单信息时默认可编辑
const status = workflowInfo.value.status
// 已分配、处理中、已打回状态可编辑
return ['assigned', 'in_progress', 'returned', 'draft_saved'].includes(status)
})
```
## 操作按钮差异
### 管理员端 Header 按钮
```html
<template v-if="!isEditMode">
<v-btn
v-if="showWorkflowButton"
color="info"
variant="tonal"
@click="navigateToWorkflow"
>
{{ workflowButtonText }}
</v-btn>
<v-btn
v-if="canApprove"
color="success"
@click="handleApprove"
>
通过
</v-btn>
<v-btn
v-if="canApprove"
color="error"
variant="outlined"
@click="handleReject"
>
打回
</v-btn>
<v-btn
v-if="canEdit"
color="primary"
@click="enterEditMode"
>
编辑
</v-btn>
</template>
<template v-else>
<v-btn variant="outlined" @click="handleCancel">取消</v-btn>
<v-btn color="primary" @click="handleSave">保存修改</v-btn>
</template>
```
### 用户端 Header 按钮
```html
<template v-if="canEdit">
<v-btn variant="outlined" @click="handleSaveDraft">
保存草稿
</v-btn>
<v-btn color="primary" @click="handleSubmit">
提交审核
</v-btn>
</template>
<template v-else>
<v-chip color="warning">
工单状态: {{ workflowStatusText }}
</v-chip>
</template>
```

View File

@@ -0,0 +1,128 @@
# 查看/编辑状态分离设计
> DDS-Section: Frontend DDS - 3. 查看状态 vs 编辑状态
> DDS-Lines: L130-L217
## 状态定义
| 状态 | 变量名 | 说明 |
|:---|:---|:---|
| **查看状态** | `isEditMode = false` | 默认状态,展示只读组件 |
| **编辑状态** | `isEditMode = true` | 编辑模式,展示表单组件 |
## 查看状态 (默认)
### 展示形式
- 使用 `*Readonly.vue` 系列组件展示数据
- 采用 `v-row/v-col` 布局,键值对形式展示
- 关键字段IP、URL、密码支持一键复制
- 敏感字段(密码)默认脱敏显示 `******`
### 交互行为
| 交互 | 说明 |
|:---|:---|
| **一键复制** | 使用 `CopyableField` 组件,点击复制图标复制到剪贴板 |
| **密码查看** | 点击"小眼睛"图标切换明文/密文 |
| **链接跳转** | URL 字段支持点击新窗口打开 |
### 组件列表
| 组件名称 | 对应模块 | 说明 |
|:---|:---|:---|
| `BasicInfoReadonly.vue` | 基本信息 | 项目名、NS、省市、性质 |
| `BusinessInfoReadonly.vue` | 部署业务 | 部署人、系统版本、入口URL |
| `EnvironmentInfoReadonly.vue` | 部署环境 | 网络、IP、域名、主机统计 |
| `HostsInfoReadonly.vue` | 主机管理 | 主机列表只读表格 |
| `MiddlewareInfoReadonly.vue` | 中间件 | 中间件卡片只读展示 |
## 编辑状态
### 进入条件
- 点击 Header 的「编辑」按钮
- 用户必须具备编辑权限(基于角色和生命周期状态)
### 数据流
```mermaid
sequenceDiagram
participant User as 用户
participant View as 查看模式
participant Edit as 编辑模式
participant API as 后端API
User->>View: 点击[编辑]按钮
View->>Edit: isEditMode = true
Edit->>Edit: 深拷贝 masterData → editForm
Edit-->>User: 显示表单组件
User->>Edit: 修改字段
Edit->>Edit: computed hasChanges = true
User->>Edit: 点击[保存]
Edit->>Edit: 弹出 SaveConfirmDialog
User->>Edit: 确认保存
Edit->>API: 调用更新接口
API-->>Edit: 返回成功
Edit->>View: isEditMode = false, 刷新数据
```
### 退出保护
```typescript
// 脏数据检测
const hasChanges = computed(() => {
return JSON.stringify(editForm.value) !== JSON.stringify(masterData.value)
})
// 退出确认
const exitEditMode = () => {
if (hasChanges.value) {
exitConfirmDialog.value = true // 弹出确认对话框
} else {
isEditMode.value = false
}
}
```
### 编辑状态指示器
编辑模式下Header 区域显示明显的「编辑模式」标签:
```html
<v-chip v-if="isEditMode" color="info" variant="tonal" class="ml-2">
<v-icon start size="small">mdi-pencil</v-icon>
编辑模式
</v-chip>
```
## 编辑模式数据流示例
```typescript
// 进入编辑模式
const enterEditMode = () => {
editForm.value = JSON.parse(JSON.stringify(masterData.value)) // 深拷贝
isEditMode.value = true
}
// 保存修改
const handleSave = async () => {
try {
await updateProject(projectId.value, editForm.value)
await loadProject() // 重新加载数据
isEditMode.value = false
snackbar.success('保存成功')
} catch (error) {
snackbar.error('保存失败')
}
}
// 取消编辑
const handleCancel = () => {
if (hasChanges.value) {
confirmDialog.value = true
} else {
isEditMode.value = false
}
}
```

View File

@@ -0,0 +1,183 @@
# 视觉设计与响应式规范
> DDS-Section: Frontend DDS - 10-11. 视觉设计规范 + 响应式设计
> DDS-Lines: L882-L980
## 色彩系统
| 用途 | 颜色 | Vuetify 类 |
|:---|:---|:---|
| 主色调 | Deep Purple | `color="primary"` |
| 成功状态 | Green | `color="success"` |
| 警告状态 | Orange | `color="warning"` |
| 错误状态 | Red | `color="error"` |
| 信息状态 | Blue | `color="info"` |
| 页面背景 | Light Grey | `bg-grey-lighten-4` |
| 卡片背景 | White | `bg-white` |
## 卡片设计
```html
<v-card elevation="2" rounded="lg" class="pa-4">
<!-- 卡片内容 -->
</v-card>
```
设计规范:
- 圆角:`rounded-lg` (8px)
- 阴影:`elevation-2`
- 内边距:`pa-4` (16px)
- Hover 效果:轻微上浮 + 阴影加深
## 排版规范
| 元素 | 字体样式 |
|:---|:---|
| 页面标题 | `text-h4 font-weight-bold` |
| 卡片标题 | `text-h6` |
| 字段标签 | `text-medium-emphasis text-body-2` |
| 字段值 | `text-high-emphasis` |
| 辅助文字 | `text-caption text-grey` |
## 间距规范
遵循 8px 网格系统:
| 间距 | Vuetify 类 | 值 |
|:---|:---|:---|
| 紧凑 | `pa-2` / `ma-2` | 8px |
| 标准 | `pa-4` / `ma-4` | 16px |
| 宽松 | `pa-6` / `ma-6` | 24px |
| 组件间距 | `gap-2` | 8px |
| 卡片间距 | `gap-4` | 16px |
## 动画与过渡
| 场景 | 效果 |
|:---|:---|
| Tab 切换 | `v-window` 默认滑动过渡 |
| 模式切换 | `v-expand-transition` |
| 按钮 Hover | 0.2s 缓动 |
| 卡片 Hover | `transform: translateY(-2px)` |
| 加载状态 | `v-skeleton-loader` 骨架屏 |
## 断点配置
遵循 Vuetify 默认断点:
| 断点 | 宽度范围 |
|:---|:---|
| xs | < 600px |
| sm | 600px - 960px |
| md | 960px - 1280px |
| lg | 1280px - 1920px |
| xl | > 1920px |
## 响应式布局适配
### 中间件卡片网格
```html
<v-row>
<v-col
v-for="mw in middlewares"
:key="mw.type"
cols="12"
sm="6"
md="4"
lg="3"
>
<MiddlewareCard :data="mw" />
</v-col>
</v-row>
```
### 移动端适配要点
1. **Tab 导航**:使用 `show-arrows` 支持左右滑动
2. **操作按钮**:使用 `v-bottom-sheet` 或收起到菜单
3. **表单布局**:单列堆叠
4. **表格**:使用 `mobile-breakpoint` 切换卡片视图
## TypeScript 类型定义
```typescript
// 项目详情
interface ProjectDetail {
id: number
project_id: string
project_name: string
namespace: string
province: string
city: string
project_nature: string
industry_group_member: string
industry_group_phone: string
description: string
status: 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<string, unknown> | null
created_at: string
updated_at: string
}
// 表单数据类型
interface BasicFormData {
project_name: string
province: string
city: string
industry_group_member: string
industry_group_phone: string
project_nature: string
description: string
}
interface BusinessFormData {
deployer_name: string
deployer_phone: string
deploy_system: string
system_version: string
business_entry_url: string
super_admin_user: string
super_admin_password: string
}
interface EnvironmentFormData {
network_environment: string
main_public_ip: string
domain_url: string
enable_ssl: boolean
host_management_method: string
management_console_url: string
host_count: number
total_cpu: number
total_memory_gb: number
total_storage_gb: number
}
interface MiddlewareFormItem {
middleware_type: string
public_ip: string
public_port: number
internal_ip: string
internal_port: number
admin_user: string
admin_password?: string
}
// Diff 项
interface DiffItem {
field: string
label: string
oldValue: string | number | boolean
newValue: string | number | boolean
}
```

View File

@@ -1,158 +0,0 @@
# 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

@@ -1,151 +0,0 @@
# 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

@@ -1,428 +0,0 @@
# 数据结构定义
本文档定义项目管理模块中所有核心数据结构包括实体、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("城市不属于所选省份")
}
```

View File

@@ -1,239 +0,0 @@
# 数据库 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

@@ -1,496 +0,0 @@
# 前端页面设计规范
本文档定义项目详情页面的前端设计规范,包括页面架构、组件设计、交互行为和视觉规范。
---
## 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
<!-- 管理员端 -->
<v-tabs v-model="activeTab">
<v-tab value="basic">基本信息</v-tab>
<v-tab value="business">部署业务</v-tab>
<v-tab value="environment">部署环境</v-tab>
<v-tab value="hosts">主机管理</v-tab>
<v-tab value="middlewares">中间件</v-tab>
<v-tab value="authorization" v-if="isSuperAdmin">授权信息</v-tab>
<v-tab value="version-history" v-if="isSuperAdmin">版本历史</v-tab>
</v-tabs>
<!-- 用户端 -->
<v-tabs v-model="activeTab">
<v-tab value="basic">基本信息</v-tab>
<v-tab value="business">部署业务</v-tab>
<v-tab value="environment">部署环境</v-tab>
<v-tab value="middlewares">中间件</v-tab>
</v-tabs>
```
---
## 5. 生命周期状态展示
### 5.1 状态标签配置
```typescript
// 生命周期状态枚举
export const LIFECYCLE_STATUS = {
init: '初始化',
drafting: '填写中',
reviewing: '审核中',
released: '已发布',
modifying: '变更中',
archived: '已归档'
}
// 状态颜色映射
export const LIFECYCLE_STATUS_COLORS: Record<string, string> = {
init: 'grey',
drafting: 'info',
reviewing: 'warning',
released: 'success',
modifying: 'primary',
archived: 'grey-darken-1'
}
// 状态图标
const LIFECYCLE_STATUS_ICONS: Record<string, string> = {
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
<v-menu v-if="multipleWorkflows">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" color="info" variant="tonal">
查看工单 ({{ workflowCount }})
<v-icon end>mdi-chevron-down</v-icon>
</v-btn>
</template>
<v-list>
<v-list-item
v-for="wf in relatedWorkflows"
:key="wf.workflow_id"
@click="navigateToWorkflow(wf.workflow_id)"
>
<v-list-item-title>{{ wf.workflow_id }}</v-list-item-title>
<v-list-item-subtitle>{{ wf.creator_name }} | {{ formatDate(wf.created_at) }}</v-list-item-subtitle>
</v-list-item>
</v-list>
</v-menu>
```
---
## 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<string, string> = {
'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
<template>
<div class="copyable-field d-flex align-center gap-2">
<span class="field-value">{{ displayValue }}</span>
<v-btn icon="mdi-content-copy" size="x-small" variant="text" @click="copyToClipboard">
<v-tooltip activator="parent" location="top">复制</v-tooltip>
</v-btn>
</div>
</template>
```
### 8.2 SaveConfirmDialog - 保存确认
展示变更 Diff 表格:
```html
<v-table density="compact">
<thead>
<tr><th>字段</th><th>修改前</th><th>修改后</th></tr>
</thead>
<tbody>
<tr v-for="item in diffItems" :key="item.field">
<td>{{ item.label }}</td>
<td class="text-error">{{ item.oldValue || '空' }}</td>
<td class="text-success">{{ item.newValue || '空' }}</td>
</tr>
</tbody>
</v-table>
```
### 8.3 DiffTextField - 差异高亮输入框
编辑模式下显示与主线数据的差异:
```html
<v-text-field
v-model="inputValue"
:label="label"
:class="{ 'diff-highlight': hasDiff }"
:hint="hasDiff ? `主线值: ${masterValue}` : ''"
persistent-hint
>
<template v-slot:prepend-inner v-if="hasDiff">
<v-icon color="warning" size="small">mdi-alert-circle</v-icon>
</template>
</v-text-field>
<style scoped>
.diff-highlight :deep(.v-field__outline) {
--v-field-border-color: rgb(var(--v-theme-warning));
}
</style>
```
---
## 9. 视觉设计规范
### 9.1 色彩系统
| 用途 | Vuetify 类 |
|:---|:---|
| 主色调 | `color="primary"` (Deep Purple) |
| 成功状态 | `color="success"` (Green) |
| 警告状态 | `color="warning"` (Orange) |
| 错误状态 | `color="error"` (Red) |
| 页面背景 | `bg-grey-lighten-4` |
### 9.2 卡片设计
```html
<v-card elevation="2" rounded="lg" class="pa-4">
<!-- 圆角 8px, 阴影 level-2, 内边距 16px -->
</v-card>
```
### 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
<v-row>
<v-col v-for="mw in middlewares" :key="mw.type"
cols="12" sm="6" md="4" lg="3"
>
<MiddlewareCard :data="mw" />
</v-col>
</v-row>
```
---
## 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<string, unknown> | 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` | 差异高亮输入框 | 通用 |

View File

@@ -1,90 +0,0 @@
# 项目生命周期状态机
## 状态定义
| 状态 | 说明 | 触发动作 | 权限 |
|:---|:---|:---|:---|
| **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

@@ -1,448 +0,0 @@
# 版本控制设计 (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 │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ [基本信息] [部署业务] [部署环境] [部署中间件] │
│ ───────────────────────────────────────────────────────────── │
│ 省份: [北京市 ▼] │
│ 城市: [北京市 ▼] │
│ ... │
└─────────────────────────────────────────────────────────────────┘
```

View File

@@ -1,231 +1,186 @@
#!/bin/bash
# verify-project-module.sh
# 验证 rmdc-project-management 模块的完整性
# 依赖: go, grep, find
# 用法: ./verify-project-module.sh [project-root-path]
# 验证 developing-project-management Skill 的完整性
set -e
SKILL_DIR="$(dirname "$0")/.."
REFERENCE_DIR="$SKILL_DIR/reference"
PROJECT_ROOT="${1:-.}"
echo "=========================================="
echo "RMDC Project Management Module Verification"
echo "=========================================="
echo "Project Root: $PROJECT_ROOT"
echo "========================================"
echo "验证 developing-project-management Skill"
echo "========================================"
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++))
pass() {
echo "PASS: $1"
PASS_COUNT=$((PASS_COUNT + 1))
}
check_fail() {
echo -e "${RED}[FAIL]${NC} $1"
((FAIL_COUNT++))
fail() {
echo "FAIL: $1"
FAIL_COUNT=$((FAIL_COUNT + 1))
}
check_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
((WARN_COUNT++))
}
# ========================================
# 1. 验证章节分层目录结构
# ========================================
echo "--- 1. 验证章节分层目录结构 ---"
# 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"
# 检查必需的章节目录
for chapter in "01-architecture-overview" "02-lifecycle-state-machine" "03-permission-model" "04-version-control" "05-database-schema" "06-api-design" "07-frontend-design"; do
if [ -d "$REFERENCE_DIR/$chapter" ]; then
pass "章节目录存在: $chapter"
else
check_fail "Lifecycle state '$state' not found"
fail "章节目录缺失: $chapter"
fi
done
# 3. 检查 API 路由
# ========================================
# 2. 验证 reference 文件包含 DDS 追溯信息
# ========================================
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"
echo "--- 2. 验证 DDS 追溯信息 ---"
# 检查 DDS-Section 标记
if grep -r "DDS-Section:" "$REFERENCE_DIR" > /dev/null 2>&1; then
pass "存在 DDS-Section 追溯标记"
else
fail "缺少 DDS-Section 追溯标记"
fi
# 检查 DDS-Lines 标记
if grep -r "DDS-Lines:" "$REFERENCE_DIR" > /dev/null 2>&1; then
pass "存在 DDS-Lines 追溯标记"
else
fail "缺少 DDS-Lines 追溯标记"
fi
# ========================================
# 3. 验证前端设计文档完整性
# ========================================
echo ""
echo "--- 3. 验证前端设计文档完整性 ---"
FRONTEND_DIR="$REFERENCE_DIR/07-frontend-design"
for file in "page-architecture.md" "view-edit-states.md" "user-admin-difference.md" "lifecycle-workflow-display.md" "module-design-specs.md" "component-specifications.md" "visual-design-specs.md" "interaction-sequences.md"; do
if [ -f "$FRONTEND_DIR/$file" ]; then
pass "前端设计文档存在: $file"
else
check_fail "Route '$route' not found"
fail "前端设计文档缺失: $file"
fi
done
# 4. 检查权限中间件
# ========================================
# 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"
echo "--- 4. 验证后端设计文档完整性 ---"
# 检查模块依赖文档
if [ -f "$REFERENCE_DIR/01-architecture-overview/module-dependencies.md" ]; then
pass "模块依赖文档存在"
else
check_warn "Permission middleware not detected - verify manually"
fail "模块依赖文档缺失"
fi
# 5. 检查版本服务
# 检查状态机文档
if [ -f "$REFERENCE_DIR/02-lifecycle-state-machine/lifecycle-states.md" ]; then
pass "生命周期状态文档存在"
else
fail "生命周期状态文档缺失"
fi
# 检查工单映射文档
if [ -f "$REFERENCE_DIR/02-lifecycle-state-machine/workflow-state-mapping.md" ]; then
pass "工单状态映射文档存在"
else
fail "工单状态映射文档缺失"
fi
# 检查权限模型文档
if [ -f "$REFERENCE_DIR/03-permission-model/acl-permission.md" ]; then
pass "ACL 权限模型文档存在"
else
fail "ACL 权限模型文档缺失"
fi
# 检查版本控制文档
if [ -f "$REFERENCE_DIR/04-version-control/version-design.md" ]; then
pass "版本控制设计文档存在"
else
fail "版本控制设计文档缺失"
fi
# 检查数据库schema文档
if [ -f "$REFERENCE_DIR/05-database-schema/database-schema.md" ]; then
pass "数据库 Schema 文档存在"
else
fail "数据库 Schema 文档缺失"
fi
# 检查API文档
if [ -f "$REFERENCE_DIR/06-api-design/api-endpoints.md" ]; then
pass "API 端点文档存在"
else
fail "API 端点文档缺失"
fi
# ========================================
# 5. 验证 SKILL.md 结构
# ========================================
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"
echo "--- 5. 验证 SKILL.md 结构 ---"
SKILL_FILE="$SKILL_DIR/SKILL.md"
# 检查 frontmatter
if grep -q "^name:" "$SKILL_FILE" && grep -q "^description:" "$SKILL_FILE"; then
pass "SKILL.md frontmatter 结构正确"
else
check_fail "Version comparison service not found"
fail "SKILL.md frontmatter 结构缺失"
fi
if grep -rq "CreateOfficialVersion" "$PROJECT_ROOT"/internal/project/service/*.go 2>/dev/null; then
check_pass "CreateOfficialVersion method exists"
# 检查章节引用
if grep -q "reference/07-frontend-design/" "$SKILL_FILE"; then
pass "SKILL.md 引用了前端设计文档"
else
check_fail "CreateOfficialVersion method not found"
fail "SKILL.md 未引用前端设计文档"
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"
# 检查前端相关内容
if grep -q "Vue3\|Vuetify\|frontend" "$SKILL_FILE"; then
pass "SKILL.md 包含前端开发指导"
else
check_fail "ProjectLifecycleUpdater interface not found"
fail "SKILL.md 缺少前端开发指导"
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"
# 检查核心章节
for section in "Plan" "Verify" "Execute" "Pitfalls"; do
if grep -q "## $section" "$SKILL_FILE"; then
pass "SKILL.md 包含 $section 章节"
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"
fail "SKILL.md 缺少 $section 章节"
fi
done
# ========================================
# 总结
# ========================================
echo ""
echo "========================================"
echo "验证完成"
echo "========================================"
echo "PASS: $PASS_COUNT"
echo "FAIL: $FAIL_COUNT"
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}"
if [ "$FAIL_COUNT" -eq 0 ]; then
echo "所有验证通过!"
exit 0
else
echo ""
echo -e "${GREEN}All checks passed!${NC}"
exit 0
echo "存在 $FAIL_COUNT 个验证失败项"
exit 1
fi

View File

@@ -0,0 +1,109 @@
---
name: developing-rmdc
description: "System-level guidance for RMDC platform development covering cross-module consistency, dependency rules, version compatibility, and global change workflows. Triggered when making changes affecting multiple modules, modifying shared contracts, or planning system-wide updates. Keywords: rmdc-core, rmdc-user-auth, rmdc-jenkins-branch-dac, rmdc-exchange-hub, rmdc-watchdog, rmdc-project-management, rmdc-work-procedure, rmdc-audit-log, cross-module, dependency."
allowed-tools:
- Read
- Glob
- Grep
- Bash
argument-hint: "$ARGUMENTS: <scope> [modules...] — scope: cross-module|dependency|version|release"
---
# developing-rmdc
## 概述
本 Skill 提供 RMDC 系统级开发指导,确保跨模块一致性、依赖管理与版本兼容。
## 动态上下文注入
### 查看模块结构
!`ls -la internal/ 2>/dev/null || find . -maxdepth 2 -type d -name "rmdc-*"`
### 查看模块依赖
!`grep -rn "import.*rmdc" --include="*.go" | grep -v "_test.go" | head -30`
---
## 模块依赖关系
```
rmdc-core (API Gateway)
├── rmdc-user-auth (认证/权限)
│ ├── rmdc-work-procedure (工单)
│ └── rmdc-jenkins-branch-dac (Jenkins权限数据)
├── rmdc-jenkins-branch-dac (构建管理)
│ └── rmdc-audit-log
├── rmdc-exchange-hub (MQTT网关)
│ └── rmdc-audit-log
├── rmdc-watchdog (边缘代理)
│ └── rmdc-project-management (一级授权)
├── rmdc-project-management (项目管理)
│ └── rmdc-audit-log
├── rmdc-work-procedure (工单)
│ └── rmdc-audit-log
└── rmdc-audit-log (审计)
```
---
## Plan规划阶段
### 跨模块变更检查
| 变更类型 | 影响评估 |
|:---|:---|
| JWT Claims 变更 | 影响所有需鉴权模块 |
| RBAC 角色变更 | 影响 user-auth + 所有权限检查点 |
| 审计字段变更 | 影响所有写审计的模块 |
| 工单流程变更 | 影响 user-auth + project-management |
| MQTT Topic 变更 | 影响 exchange-hub + watchdog |
### 决策点
- [ ] 识别所有受影响模块
- [ ] 确定变更顺序(先依赖后被依赖)
- [ ] 确定是否需要版本兼容期
---
## Verify验证清单
### 依赖一致性
- [ ] 所有模块使用相同版本的 rmdc-common
- [ ] JWT Claims 定义在所有模块一致
- [ ] 错误码无冲突
- [ ] 审计字段格式统一
### 接口兼容性
- [ ] 内部 API 向后兼容
- [ ] MQTT 消息格式兼容
- [ ] 数据库 Schema 兼容
---
## Execute执行步骤
### 跨模块变更流程
1. 创建变更计划文档
2. 识别所有受影响模块
3. 按依赖顺序更新(先 common后业务
4. 在每个模块运行验证
5. 集成测试
6. 统一发布
---
## Pitfalls常见坑
1. **依赖版本不一致**:不同模块使用不同版本的 common 包。
2. **JWT Claims 不同步**:一个模块新增字段,其他模块未解析。
3. **发布顺序错误**:被依赖模块未先发布。
4. **审计格式不统一**:不同模块的审计记录格式不同。
5. **错误码冲突**:不同模块定义了相同的错误码。
---
## 相关文件
| 用途 | 路径 |
|:---|:---|
| 模块依赖 | [reference/module-dependencies.md](reference/module-dependencies.md) |
| 术语表 | [reference/terminology.md](reference/terminology.md) |
| 版本兼容 | [reference/version-compatibility.md](reference/version-compatibility.md) |

View File

@@ -0,0 +1,32 @@
# RMDC 模块依赖关系
## 依赖矩阵
| 模块 | 依赖模块 | 被依赖模块 |
|:---|:---|:---|
| rmdc-core | user-auth, jenkins-dac, exchange-hub, watchdog, project-mgmt, work-procedure, audit-log | - |
| rmdc-user-auth | work-procedure, jenkins-dac, common | core |
| rmdc-jenkins-branch-dac | audit-log, common | core, user-auth |
| rmdc-exchange-hub | audit-log, common | core |
| rmdc-watchdog | project-mgmt, common | core |
| rmdc-project-management | audit-log, common | core, watchdog |
| rmdc-work-procedure | audit-log, common | core, user-auth |
| rmdc-audit-log | common | jenkins-dac, exchange-hub, project-mgmt, work-procedure |
| rmdc-common | - | 所有模块 |
## 变更影响传播
```
修改 rmdc-common → 需要重新测试所有模块
修改 rmdc-user-auth JWT → 需要更新 rmdc-core 中间件
修改 rmdc-audit-log 字段 → 需要更新所有写审计的模块
```
## 发布顺序
1. rmdc-common (基础)
2. rmdc-audit-log (审计基础)
3. rmdc-work-procedure (工单基础)
4. rmdc-jenkins-branch-dac / rmdc-project-management (并行)
5. rmdc-user-auth / rmdc-exchange-hub / rmdc-watchdog (并行)
6. rmdc-core (网关,最后)

View File

@@ -0,0 +1,18 @@
# RMDC 术语表
| 术语 | 定义 |
|:---|:---|
| 一级授权 | 由 rmdc-project-management 管理的项目级权限 |
| 二级授权 | 由 rmdc-watchdog 执行的 TOTP 动态授权 |
| DAC | Discretionary Access Control自主访问控制Jenkins 分支权限) |
| RBAC | Role-Based Access Control基于角色的访问控制 |
| JWT | JSON Web Token用户认证令牌 |
| 工单 | 需要审批的变更请求,由 rmdc-work-procedure 管理 |
| 指令生命周期 | MQTT 指令从发送到完成的状态流转 |
| SuperAdmin | 超级管理员,拥有系统全部权限 |
| Admin | 管理员,可管理普通用户和三方用户 |
| Normal | 普通用户 |
| Third | 三方用户,最低权限 |
| 谁注册谁管理 | 用户管理原则:注册人负责管理被注册用户的生命周期 |
| RSA-OAEP | RSA 最优非对称加密填充,用于密码传输加密 |
| bcrypt | 密码哈希算法,用于密码存储 |

View File

@@ -0,0 +1,36 @@
# RMDC 版本兼容策略
## 版本号规范
采用语义化版本:`MAJOR.MINOR.PATCH`
- **MAJOR**: 不兼容的 API 变更
- **MINOR**: 向后兼容的功能新增
- **PATCH**: 向后兼容的问题修复
## 兼容性规则
### API 兼容
- 新增可选字段:兼容
- 新增必填字段Breaking Change
- 删除字段Breaking Change
- 修改字段类型Breaking Change
### 数据库兼容
- 新增可空列:兼容
- 新增非空列(有默认值):兼容
- 删除列Breaking Change
- 修改列类型:需评估
### MQTT 消息兼容
- 新增可选字段:兼容
- 消息必须包含版本字段
- 消费者必须忽略未知字段
## Breaking Change 处理流程
1. 提前通知所有相关模块负责人
2. 创建新版本接口v2
3. 旧版本标记废弃Deprecated
4. 设定过渡期(建议 2 个迭代)
5. 过渡期结束后下线旧版本

View File

@@ -0,0 +1,56 @@
#!/bin/bash
# verify-module-deps.sh - 验证模块依赖一致性
# 依赖: go, grep
# 用法: ./verify-module-deps.sh
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="${SCRIPT_DIR}/../../.."
echo "=== RMDC 模块依赖验证 ==="
echo ""
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
pass() { echo -e "${GREEN}[PASS]${NC} $1"; }
fail() { echo -e "${RED}[FAIL]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
# 检查 go.mod 中的依赖版本一致性
check_common_version() {
echo "--- 检查 rmdc-common 版本一致性 ---"
VERSIONS=$(find "${PROJECT_ROOT}" -name "go.mod" -exec grep "rmdc-common" {} \; 2>/dev/null | \
grep -oE "v[0-9]+\.[0-9]+\.[0-9]+" | sort | uniq)
VERSION_COUNT=$(echo "$VERSIONS" | wc -l)
if [ "$VERSION_COUNT" -eq 1 ]; then
pass "rmdc-common 版本一致: $VERSIONS"
else
fail "rmdc-common 版本不一致: $VERSIONS"
fi
}
# 检查循环依赖
check_circular_deps() {
echo "--- 检查循环依赖 ---"
cd "${PROJECT_ROOT}"
if go mod graph 2>/dev/null | grep -E "rmdc.*rmdc" | head -20; then
warn "发现模块间依赖,请确认无循环"
else
pass "未发现明显循环依赖"
fi
}
# 执行检查
check_common_version
check_circular_deps
echo ""
echo "=== 依赖验证完成 ==="

View File

@@ -0,0 +1,271 @@
---
name: developing-user-auth
description: >
指导开发 rmdc-user-auth 用户认证与权限模块v2.0),覆盖:
JWT认证HS256、4h有效期、claims注入、RSA-OAEP加密登录2048密钥、30天轮换
密码安全bcrypt存储、3个月过期、首次登录强制改密 must_change_password
账户有效期account_expires_at、非SuperAdmin必须设置有效期
RBAC权限模型superadmin/admin/normal/third四级角色、谁注册谁管理原则、registered_by_id
统一权限架构PermissionModule枚举、jenkins_acls层级权限、project_acls模块级权限、user_permission_caches L2缓存
用户生命周期disabled→active→locked状态机、工单审批激活
用户注册/管理工单集成接口注入机制、UserStatusUpdater/WorkflowCreator回调、工单由用户接口内部自动创建
触发场景修改auth handlers、permission services、user CRUD、workflow callbacks、password policies、JWT/RSA实现。
Keywords: JWT / RSA-OAEP / bcrypt / RBAC / PermissionModule / jenkins_acls / project_acls / account_expires_at / must_change_password / workflow integration.
allowed-tools:
- Read
- Glob
- Grep
- Bash
- Edit
- Write
argument-hint: "$ARGUMENTS: <变更类型> [目标文件] — 变更类型: auth|permission|user-crud|workflow|password-policy|jwt|rsa|account-validity"
---
# developing-user-auth用户认证模块
## 概述
本 Skill 指导 `rmdc-user-auth` 模块的开发,覆盖认证、权限、用户生命周期、工单集成等核心功能。基于 DDS v2.0 设计规范。
## 动态上下文注入
### 查看模块结构
!`find . -type f -name "*.go" -path "*user-auth*" | head -30`
### 查找权限相关代码
!`grep -rn "PermissionModule\|CheckPermission\|jenkins_acls\|project_acls" --include="*.go" | head -20`
### 查看用户表结构
!`grep -A 60 "type User struct" internal/models/*.go`
### 查看工单回调接口
!`grep -rn "UserStatusUpdater\|WorkflowCreator\|ActivateUser" --include="*.go" | head -15`
---
## Plan规划阶段
### 产物清单
根据 `$ARGUMENTS` 确定变更范围:
| 变更类型 | 涉及文件 | 下游影响 |
|:---|:---|:---|
| auth | `auth_handler.go`, `auth_service.go` | 前端登录流程、JWT claims、密码过期检查 |
| permission | `permission_handler.go`, `*_permission_service.go`, `*_acl_dao.go` | Jenkins DAC、Project权限、其他模块权限检查 |
| user-crud | `user_handler.go`, `user_service.go`, `user_dao.go` | 工单模块回调、审计日志、账户有效期 |
| workflow | `user_workflow_handler.go`, `*_workflow_service.go` | rmdc-work-procedure 回调 |
| password-policy | `auth_service.go`, `rsa_service.go` | password_expires_at、must_change_password |
| jwt | `auth_middleware.go`, `jwt_utils.go` | 所有需鉴权接口、account_expires_at 检查 |
| rsa | `rsa_service.go`, `rsa_keypair_dao.go` | 前端公钥获取、密钥轮换 |
| account-validity | `user_model.go`, `auth_service.go` | 登录检查、有效期设置规则 |
### 决策点
- [ ] 是否涉及 JWT claims 字段变更?→ See: [reference/03-authentication/jwt-claims.md](reference/03-authentication/jwt-claims.md)
- [ ] 是否涉及 RBAC 角色定义变更?→ See: [reference/05-rbac/rbac-roles.md](reference/05-rbac/rbac-roles.md)
- [ ] 是否涉及用户表 schema 变更?→ See: [reference/09-data-model/user-table-schema.md](reference/09-data-model/user-table-schema.md)
- [ ] 是否涉及工单回调逻辑变更?→ See: [reference/06-registration-workflow/](reference/06-registration-workflow/) + [reference/07-management-workflow/](reference/07-management-workflow/)
- [ ] 是否涉及权限模块枚举 PermissionModule 变更?→ See: [reference/08-permission-model/permission-architecture.md](reference/08-permission-model/permission-architecture.md)
- [ ] 是否涉及 jenkins_acls/project_acls 表结构变更?→ See: [reference/09-data-model/permission-tables-schema.md](reference/09-data-model/permission-tables-schema.md)
---
## Verify验证清单
### RBAC 兼容性检查
- [ ] 角色层级未被破坏superadmin > admin > normal > third验证 `reference/05-rbac/rbac-roles.md` 角色层级)
- [ ] "谁注册谁管理"原则未被违反grep `registered_by_id` 校验逻辑
- [ ] SuperAdmin 始终具有全部权限:所有 CheckPermission 首先判断 superadmin
- [ ] Admin 不可创建/升级 SuperAdmin
- [ ] 注册权限矩阵正确SuperAdmin→所有角色Admin→normal/thirdNormal→third
### JWT/Session 安全检查
- [ ] JWT 签名算法仍为 HS256对照 `reference/03-authentication/jwt-claims.md`
- [ ] Token 有效期未超过 4h
- [ ] Claims 包含必要字段user_id, username, role, status
- [ ] 仅 status=active 用户可通过校验
- [ ] 账户有效期 account_expires_at 过期检查已实现
### 密码与账户有效期检查
- [ ] 密码传输使用 RSA-OAEP(SHA-256, 2048) 加密(参考 `reference/03-authentication/login-design.md`
- [ ] 密码存储使用 bcrypt
- [ ] 密码有效期为 3 个月password_expires_at
- [ ] 首次登录/密码重置后 must_change_password = true
- [ ] 非 SuperAdmin 创建用户必须设置 account_expires_at
- [ ] 登录时检查账户有效期并返回 account_expire_days 提醒
### 权限模块检查
- [ ] PermissionModule 枚举定义完整且一致(对照 `reference/08-permission-model/permission-architecture.md`
- [ ] jenkins_acls 层级继承正确Org→Repo→Branch参考 `reference/08-permission-model/jenkins-acls.md`
- [ ] project_acls 模块代码与 JSONB 映射正确(参考 `reference/08-permission-model/project-acls.md`
- [ ] user_permission_caches L2 缓存失效逻辑正确
- [ ] L1 内存缓存与 L2 DB 缓存同步清除
### API 契约检查
- [ ] 请求/响应字段向后兼容(对照 `reference/10-api-design/api-endpoints.md`
- [ ] 错误码未被移除或语义变更
- [ ] 新增字段有默认值must_change_password, account_expires_at
### 工单集成检查
- [ ] 注册工单:用户初始状态为 disabled参考 `reference/06-registration-workflow/registration-workflow.md`
- [ ] 审批通过回调ActivateUser 状态变更为 active
- [ ] 撤销回调DeletePendingUser 删除 status=disabled 的用户
- [ ] 管理工单ExecuteUserManagement 正确执行 update/enable/disable/delete/reset_password/extend_validity参考 `reference/07-management-workflow/management-workflow.md`
- [ ] 工单由用户管理接口内部自动创建(前端不直接调用创建工单接口)
### 验证命令
```bash
# 运行单元测试
go test ./internal/user-auth/... -v
# 检查 RBAC 定义一致性
grep -rn "superadmin\|admin\|normal\|third" --include="*.go" | sort | uniq
# 检查 PermissionModule 使用
grep -rn "PermissionModule\|ModuleJenkins\|ModuleProject" --include="*.go"
# 验证 JWT middleware 与账户有效期检查
grep -rn "account_expires_at\|AccountExpiresAt" --include="*.go"
# 验证工单回调注入
grep -rn "UserStatusUpdater\|WorkflowCreator\|SetUserStatusUpdater" --include="*.go"
```
---
## Execute执行步骤
### 1. 认证相关变更 (auth)
```bash
# 1. 定位认证处理器
grep -rn "func.*Login\|func.*Register" --include="*.go"
# 2. 修改认证逻辑(注意 must_change_password 和 account_expires_at 检查)
# 3. 更新登录响应(含 password_expire_days, account_expire_days, must_change_password
# 4. 验证 RSA 加密流程
go test ./internal/user-auth/service/auth_service_test.go -v
```
### 2. 权限相关变更 (permission)
```bash
# 1. 定位权限检查逻辑
grep -rn "CheckPermission\|CheckHierarchical\|PermissionModule" --include="*.go"
# 2. 修改权限逻辑(注意 jenkins_acls 层级继承、project_acls 模块级)
# 3. 更新权限缓存逻辑L1 内存 + L2 DB user_permission_caches
# 4. 验证 Jenkins 权限层级
go test ./internal/user-auth/service/jenkins_permission_service_test.go -v
```
### 3. 用户 CRUD 变更 (user-crud)
```bash
# 1. 修改用户服务(注意 registered_by_id/registered_by_name 设置)
# 2. 设置 account_expires_at非 SuperAdmin 创建时必须)
# 3. 设置 must_change_password = true新用户/密码重置)
# 4. 同步更新工单回调 executor
# 5. 更新审计日志记录
go test ./internal/user-auth/service/user_service_test.go -v
```
### 4. 工单集成变更 (workflow)
```bash
# 1. 确认接口注入机制rmdc-core 初始化时注入)
# 2. 实现 UserStatusUpdater 接口ActivateUser/DeletePendingUser/ExecuteUserManagement
# 3. 实现 WorkflowCreator 调用CreateRegistrationWorkflow/CreateManagementWorkflow
# 4. 验证工单载荷结构RegistrationWorkflowPayload/ManagementWorkflowPayload
# 5. 确保前端不直接调用创建工单接口
```
### 5. 账户有效期变更 (account-validity)
```bash
# 1. 添加/修改 users 表的 account_expires_at 字段
# 2. 修改登录检查逻辑过期拒绝、7天内提醒
# 3. 修改用户创建逻辑(根据创建者角色设置有效期选项)
# 4. 实现 extend_validity 管理操作
```
---
## Pitfalls常见坑
1. **JWT Claims 变更未同步下游**:修改 claims 字段后,必须通知所有依赖模块更新解析逻辑。参考 `reference/03-authentication/jwt-claims.md` 中的兼容性要求。
2. **RBAC 层级破坏**admin 能创建 superadmin 是严重安全漏洞。每次角色相关变更必须验证 `reference/05-rbac/rbac-roles.md` 中的层级约束。
3. **密码过期时间未刷新**:修改密码、重置密码、创建用户时必须刷新 `password_expires_at` 并设置 `must_change_password`。参考 `reference/04-user-lifecycle/user-lifecycle.md`
4. **工单回调状态不一致**:用户注册工单撤销时,必须确认用户仍为 disabled 状态才能删除。参考 `reference/06-registration-workflow/registration-workflow.md` 中的状态映射表。
5. **RSA 密钥轮换影响**:密钥过期后自动生成新密钥,前端缓存的旧公钥将无法加密。参考 `reference/03-authentication/login-design.md` 中的轮换策略。
6. **权限缓存脏读**:修改权限后必须同时清除对应用户的 L1 内存缓存和 L2 DB 缓存。参考 `reference/08-permission-model/jenkins-acls.md` 中的缓存失效逻辑。
7. **账户有效期校验遗漏**:登录流程和 JWT 中间件都需要检查 `account_expires_at`。参考 `reference/04-user-lifecycle/user-lifecycle.md` 中的有效期检查逻辑。
8. **工单接口直接调用**:前端不应直接调用创建工单接口。参考 `reference/10-api-design/api-endpoints.md` 中的重要约束。
---
## Reference 目录结构
```
reference/
├── 01-overview/
│ └── module-overview.md # 模块概述
├── 02-architecture/
│ ├── module-dependencies.md # 模块依赖关系
│ ├── interface-injection.md # 接口注入机制
│ └── tech-stack.md # 技术栈
├── 03-authentication/
│ ├── login-design.md # 登录设计
│ └── jwt-claims.md # JWT Claims 定义
├── 04-user-lifecycle/
│ └── user-lifecycle.md # 用户生命周期
├── 05-rbac/
│ └── rbac-roles.md # RBAC 角色矩阵
├── 06-registration-workflow/
│ └── registration-workflow.md # 注册工单流程
├── 07-management-workflow/
│ └── management-workflow.md # 管理工单流程
├── 08-permission-model/
│ ├── permission-architecture.md # 统一权限架构
│ ├── jenkins-acls.md # Jenkins 层级权限
│ ├── project-acls.md # 项目模块权限
│ └── business-info-registry.md # 业务信息注册中心
├── 09-data-model/
│ ├── user-table-schema.md # 用户表 Schema
│ └── permission-tables-schema.md # 权限表 Schema
├── 10-api-design/
│ └── api-endpoints.md # API 接口清单
└── 11-security/
└── security-compliance.md # 安全与合规
```
---
## 相关文件速查
| 用途 | 路径 |
|:---|:---|
| 模块概述 | [reference/01-overview/module-overview.md](reference/01-overview/module-overview.md) |
| 模块依赖 | [reference/02-architecture/module-dependencies.md](reference/02-architecture/module-dependencies.md) |
| 接口注入 | [reference/02-architecture/interface-injection.md](reference/02-architecture/interface-injection.md) |
| 登录设计 | [reference/03-authentication/login-design.md](reference/03-authentication/login-design.md) |
| JWT Claims | [reference/03-authentication/jwt-claims.md](reference/03-authentication/jwt-claims.md) |
| 用户生命周期 | [reference/04-user-lifecycle/user-lifecycle.md](reference/04-user-lifecycle/user-lifecycle.md) |
| RBAC 角色 | [reference/05-rbac/rbac-roles.md](reference/05-rbac/rbac-roles.md) |
| 注册工单 | [reference/06-registration-workflow/registration-workflow.md](reference/06-registration-workflow/registration-workflow.md) |
| 管理工单 | [reference/07-management-workflow/management-workflow.md](reference/07-management-workflow/management-workflow.md) |
| 权限架构 | [reference/08-permission-model/permission-architecture.md](reference/08-permission-model/permission-architecture.md) |
| Jenkins 权限 | [reference/08-permission-model/jenkins-acls.md](reference/08-permission-model/jenkins-acls.md) |
| 项目权限 | [reference/08-permission-model/project-acls.md](reference/08-permission-model/project-acls.md) |
| 用户表 Schema | [reference/09-data-model/user-table-schema.md](reference/09-data-model/user-table-schema.md) |
| 权限表 Schema | [reference/09-data-model/permission-tables-schema.md](reference/09-data-model/permission-tables-schema.md) |
| API 端点 | [reference/10-api-design/api-endpoints.md](reference/10-api-design/api-endpoints.md) |
| 安全合规 | [reference/11-security/security-compliance.md](reference/11-security/security-compliance.md) |
| 认证处理器骨架 | [examples/auth-handler-skeleton.go](examples/auth-handler-skeleton.go) |
| 权限检查骨架 | [examples/permission-check-skeleton.go](examples/permission-check-skeleton.go) |
| 工单回调示例 | [examples/workflow-callback-skeleton.go](examples/workflow-callback-skeleton.go) |
| 验证脚本 | [scripts/verify-user-auth.sh](scripts/verify-user-auth.sh) |

View File

@@ -0,0 +1,145 @@
package handler
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
)
// AuthHandler 认证处理器骨架
type AuthHandler struct {
authService *AuthService
rsaService *RSAService
}
// GetPublicKey 获取 RSA 公钥
// GET /api/auth/rsa/public-key
func (h *AuthHandler) GetPublicKey(c *gin.Context) {
publicKey, err := h.rsaService.GetCurrentPublicKey()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取公钥失败"})
return
}
c.JSON(http.StatusOK, gin.H{"public_key": publicKey})
}
// Login 用户登录
// POST /api/auth/login
// Body: {"username": "xxx", "encrypted_password": "RSA加密后的密码"}
func (h *AuthHandler) Login(c *gin.Context) {
var req LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
return
}
// 1. RSA 解密密码
password, err := h.rsaService.Decrypt(req.EncryptedPassword)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "密码解密失败", "code": "AUTH_005"})
return
}
// 2. 验证用户名密码
user, err := h.authService.ValidateCredentials(req.Username, password)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误", "code": "AUTH_001"})
return
}
// 3. 检查用户状态
if user.Status != "active" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "账户未激活或已禁用", "code": "AUTH_003"})
return
}
// 4. 检查账户有效期
if user.AccountExpiresAt != nil && user.AccountExpiresAt.Before(time.Now()) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "账户已过期", "code": "AUTH_007"})
return
}
// 5. 检查密码是否已过期
if user.PasswordExpiresAt != nil && user.PasswordExpiresAt.Before(time.Now()) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "密码已过期,请联系管理员重置", "code": "AUTH_006"})
return
}
// 6. 生成 JWT
token, err := h.authService.GenerateJWT(user)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "生成Token失败"})
return
}
// 7. 构建响应(含附加标识)
resp := LoginResponse{Token: token, User: user.ToDTO()}
// 首次登录需强制改密
if user.MustChangePassword {
resp.MustChangePassword = true
}
// 密码7天内过期提醒
if user.PasswordExpiresAt != nil {
days := int(time.Until(*user.PasswordExpiresAt).Hours() / 24)
if days <= 7 && days > 0 {
resp.PasswordExpireDays = days
}
}
// 账户7天内过期提醒
if user.AccountExpiresAt != nil {
days := int(time.Until(*user.AccountExpiresAt).Hours() / 24)
if days <= 7 && days > 0 {
resp.AccountExpireDays = days
}
}
// 更新最后登录时间
h.authService.UpdateLastLogin(user.ID)
c.JSON(http.StatusOK, resp)
}
// ForceChangePassword 首次登录强制改密
// PUT /api/user/password/force-change
func (h *AuthHandler) ForceChangePassword(c *gin.Context) {
userID := c.GetInt64("user_id")
var req ForceChangePasswordRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
return
}
// 修改密码并更新相关标识
err := h.authService.ForceChangePassword(userID, req.NewPassword)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "修改密码失败"})
return
}
// 密码修改成功后:
// 1. password_expires_at = now + 90天
// 2. must_change_password = false
c.JSON(http.StatusOK, gin.H{"message": "密码修改成功"})
}
type LoginRequest struct {
Username string `json:"username" binding:"required"`
EncryptedPassword string `json:"encrypted_password" binding:"required"`
}
type LoginResponse struct {
Token string `json:"token"`
User UserDTO `json:"user"`
MustChangePassword bool `json:"must_change_password,omitempty"` // 需强制改密
PasswordExpireDays int `json:"password_expire_days,omitempty"` // 密码剩余天数
AccountExpireDays int `json:"account_expire_days,omitempty"` // 账户剩余天数
}
type ForceChangePasswordRequest struct {
NewPassword string `json:"new_password" binding:"required,min=8"`
}

View File

@@ -0,0 +1,137 @@
package permission
import (
"context"
)
// PermissionChecker 对外暴露的权限检查接口
type PermissionChecker struct {
jenkinsPermService *JenkinsPermissionService
projectPermService *ProjectPermissionService
userDao *UserDao
permissionCache *PermissionCache // L1 内存缓存
permissionCacheDao *PermissionCacheDao // L2 DB 缓存
}
// CheckJenkinsPermission 检查 Jenkins 分支权限
// 层级继承Org -> Repo -> Branch
func (p *PermissionChecker) CheckJenkinsPermission(
ctx context.Context,
userID int64,
org, repo, branch string,
requireBuild bool,
) (bool, error) {
// 1. 检查是否为 SuperAdmin直接放行
user, err := p.userDao.GetByID(userID)
if err != nil {
return false, err
}
if user.Role == "superadmin" {
return true, nil
}
// 2. 先查 L1 内存缓存
cacheKey := p.buildCacheKey(userID, org, repo, branch)
if cached, ok := p.permissionCache.Get(cacheKey); ok {
return p.evaluatePermission(cached, requireBuild), nil
}
// 3. 再查 L2 DB 缓存 (user_permission_caches)
if cached, err := p.permissionCacheDao.GetUserPermissionTree(userID); err == nil {
// 从缓存树中查找权限
if perm := cached.FindPermission(org, repo, branch); perm != nil {
p.permissionCache.Set(cacheKey, perm) // 回填 L1
return p.evaluatePermission(perm, requireBuild), nil
}
}
// 4. 层级权限检查
return p.jenkinsPermService.CheckHierarchicalPermission(
userID, org, repo, branch, requireBuild,
)
}
// CheckHierarchicalPermission 层级权限检查逻辑
// 优先级Branch > Repo > Org
func (s *JenkinsPermissionService) CheckHierarchicalPermission(
userID int64,
org, repo, branch string,
requireBuild bool,
) (bool, error) {
// 先查 Branch 级别
perm, err := s.jenkinsAclDao.FindPermission(userID, org, repo, branch)
if err == nil && perm.CanView {
if !requireBuild || perm.CanBuild {
return true, nil
}
}
// 再查 Repo 级别branch 为空)
perm, err = s.jenkinsAclDao.FindPermission(userID, org, repo, "")
if err == nil && perm.CanView {
if !requireBuild || perm.CanBuild {
return true, nil
}
}
// 最后查 Org 级别repo 和 branch 都为空)
perm, err = s.jenkinsAclDao.FindPermission(userID, org, "", "")
if err == nil && perm.CanView {
if !requireBuild || perm.CanBuild {
return true, nil
}
}
return false, nil
}
// CheckProjectModulePermission 检查项目模块权限
func (p *PermissionChecker) CheckProjectModulePermission(
ctx context.Context,
userID int64,
userRole string,
projectID string,
moduleCode string,
permissionType string, // "view" or "export"
) (bool, error) {
// 1. SuperAdmin 拥有所有权限
if userRole == "superadmin" {
return true, nil
}
// 2. authorization_info 模块仅 SuperAdmin 可访问
if moduleCode == "authorization_info" {
return false, nil
}
// 3. 检查是否为项目填写人(自动拥有非授权模块的 view 权限)
if permissionType == "view" {
fillerID, err := p.projectInfoQuerier.GetProjectFillerID(ctx, projectID)
if err == nil && fillerID == userID {
return true, nil
}
}
// 4. 查询 project_acls 表
return p.projectPermService.CheckModulePermission(
userID, projectID, moduleCode, permissionType,
)
}
// InvalidateCache 权限变更时清除缓存
func (p *PermissionChecker) InvalidateCache(userID int64) error {
// 清除 L1 内存缓存
p.permissionCache.DeleteByUser(userID)
// 清除 L2 DB 缓存 (user_permission_caches)
return p.permissionCacheDao.DeleteUserPermissionCache(userID)
}
// 权限缓存管理
// L1: 内存缓存 permissionCache (进程内高速缓存)
// L2: 数据库表 user_permission_caches (JSON格式存储权限树)
//
// 缓存失效时机:
// 1. 权限分配 (POST /api/permissions/jenkins/assign)
// 2. 权限拷贝 (POST /api/permissions/jenkins/copy)
// 3. 权限撤销 (POST /api/permissions/projects/revoke)

View File

@@ -0,0 +1,185 @@
package workflow
import (
"context"
"fmt"
"time"
)
// =====================================================
// 接口定义(由 rmdc-core 在初始化时注入)
// =====================================================
// UserStatusUpdater 用户状态更新接口
// 由工单模块调用,用于处理工单状态变更后的用户操作
type UserStatusUpdater interface {
// ActivateUser 激活用户(注册工单审批通过)
ActivateUser(userID int64) error
// DeletePendingUser 删除待审批用户(工单撤销时,仅删除 status=disabled 的用户)
DeletePendingUser(userID int64) error
// ExecuteUserManagement 执行用户管理操作(管理工单审批通过)
ExecuteUserManagement(userID int64, action string, payload map[string]interface{}) error
}
// WorkflowCreator 工单创建接口
// 由用户模块调用,用于创建用户注册/管理工单
type WorkflowCreator interface {
// CreateRegistrationWorkflow 创建用户注册工单
CreateRegistrationWorkflow(ctx context.Context, req *RegistrationWorkflowRequest) (string, error)
// CreateManagementWorkflow 创建用户管理工单
CreateManagementWorkflow(ctx context.Context, req *ManagementWorkflowRequest) (string, error)
}
// =====================================================
// 工单载荷结构
// =====================================================
// RegistrationWorkflowRequest 注册工单请求
type RegistrationWorkflowRequest struct {
TargetUserID int64 `json:"target_user_id"`
TargetUsername string `json:"target_username"`
TargetRole string `json:"target_role"`
RegisteredByID int64 `json:"registered_by_id"`
RegisteredByName string `json:"registered_by_name"`
AccountExpiresAt time.Time `json:"account_expires_at"`
RegistrationReason string `json:"registration_reason"`
}
// ManagementWorkflowRequest 管理工单请求
type ManagementWorkflowRequest struct {
TargetUserID int64 `json:"target_user_id"`
TargetUsername string `json:"target_username"`
ActionType string `json:"action_type"` // update/enable/disable/delete/reset_password/extend_validity
OperatorID int64 `json:"operator_id"`
OperatorName string `json:"operator_name"`
OriginalData map[string]interface{} `json:"original_data"`
ModifiedData map[string]interface{} `json:"modified_data"`
Reason string `json:"reason"`
}
// =====================================================
// UserStatusUpdater 实现示例
// =====================================================
type userStatusUpdaterImpl struct {
userService *UserService
}
func NewUserStatusUpdater(userService *UserService) UserStatusUpdater {
return &userStatusUpdaterImpl{userService: userService}
}
// ActivateUser 激活用户(注册工单审批通过时调用)
func (u *userStatusUpdaterImpl) ActivateUser(userID int64) error {
return u.userService.UpdateUserStatus(userID, "active")
}
// DeletePendingUser 删除待审批用户(工单撤销时调用)
func (u *userStatusUpdaterImpl) DeletePendingUser(userID int64) error {
// 安全检查:只删除 status=disabled 的用户
user, err := u.userService.GetUserByID(userID)
if err != nil {
return err
}
if user.Status != "disabled" {
return fmt.Errorf("cannot delete user with status: %s", user.Status)
}
return u.userService.DeleteUser(userID)
}
// ExecuteUserManagement 执行用户管理操作(管理工单审批通过时调用)
func (u *userStatusUpdaterImpl) ExecuteUserManagement(
userID int64,
action string,
payload map[string]interface{},
) error {
switch action {
case "update":
return u.userService.UpdateUserInfo(userID, payload)
case "enable":
return u.userService.UpdateUserStatus(userID, "active")
case "disable":
return u.userService.UpdateUserStatus(userID, "disabled")
case "delete":
return u.userService.DeleteUser(userID)
case "reset_password":
return u.userService.ResetPassword(userID)
case "extend_validity":
newExpiresAt, ok := payload["new_expires_at"].(time.Time)
if !ok {
return fmt.Errorf("invalid new_expires_at in payload")
}
return u.userService.ExtendValidity(userID, newExpiresAt)
default:
return fmt.Errorf("unknown action: %s", action)
}
}
// =====================================================
// rmdc-core 初始化时的注入示例
// =====================================================
func initializeModules() {
// 创建服务实例
userService := NewUserService(db)
workflowService := NewWorkflowService(db)
// 1. 将用户状态更新器注入到工单模块
userStatusUpdater := NewUserStatusUpdater(userService)
workflowService.SetUserStatusUpdater(userStatusUpdater)
// 2. 将工单创建器注入到用户模块
workflowCreator := NewWorkflowCreator(workflowService)
userService.SetWorkflowCreator(workflowCreator)
}
// =====================================================
// 工单状态回调处理
// =====================================================
// HandleUserRegistrationCallback 处理用户注册工单回调
func (s *WorkflowService) HandleUserRegistrationCallback(
workflowID string,
event string, // approve/return/revoke
payload map[string]interface{},
) error {
userID := payload["target_user_id"].(int64)
switch event {
case "approve":
// 激活用户
return s.userStatusUpdater.ActivateUser(userID)
case "return":
// 打回,用户状态保持 disabled前端通知用户修改
return nil
case "revoke":
// 撤销,删除待审批用户
return s.userStatusUpdater.DeletePendingUser(userID)
default:
return fmt.Errorf("unknown event: %s", event)
}
}
// HandleUserManagementCallback 处理用户管理工单回调
func (s *WorkflowService) HandleUserManagementCallback(
workflowID string,
event string,
payload map[string]interface{},
) error {
userID := payload["target_user_id"].(int64)
action := payload["action_type"].(string)
switch event {
case "approve":
// 执行管理操作
return s.userStatusUpdater.ExecuteUserManagement(userID, action, payload)
case "return":
// 打回,不执行操作
return nil
default:
return fmt.Errorf("unknown event: %s", event)
}
}

View File

@@ -0,0 +1,28 @@
# 模块概述
---
DDS-Section: 1. 概述
DDS-Lines: L9-L30
---
## 模块定位
`rmdc-user-auth` 提供 RMDC 统一的用户认证、账户生命周期管理与权限服务。
## 核心职责
| 职责 | 描述 | 关键技术 |
|:---|:---|:---|
| 身份认证 | RSA-OAEP 密码加密 + bcrypt 校验,颁发 4h 有效 JWT | Go + Gin + JWT |
| 账号管理 | 用户 CRUD、密码修改、个人资料更新 | GORM + PostgreSQL |
| 密码策略 | 密码过期、首次登录强制改密、状态控制 | bcrypt + RSA |
| 审批工作流 | 用户注册/管理通过工单审批 | rmdc-work-procedure |
| 权限服务 | Jenkins/Project/Delivery权限检查 | 层级ACL + 模块ACL |
| 系统配置 | RSA密钥对、登录策略、注册开关配置 | PostgreSQL |
## 版本修订历史
| 版本 | 日期 | 修订内容 |
|:---|:---|:---|
| v1.0 | 2026-01-23 | 基于现有代码首次形成 DDS |
| v2.0 | 2026-01-27 | 新增账户有效期、强制改密机制、工单流程详细设计 |

View File

@@ -0,0 +1,53 @@
# 接口注入机制
---
DDS-Section: 2. 系统架构 - 2.3 接口注入机制
DDS-Lines: L113-L149
---
## 用户模块与工单模块依赖
采用接口注入(依赖注入)方式实现模块间回调,避免循环依赖。
## 工单模块 → 用户模块回调接口
```go
// UserStatusUpdater 用户状态更新接口
// 由 rmdc-core 在初始化时注入,工单模块状态变更时调用
type UserStatusUpdater interface {
// UpdateUserStatus 更新用户状态(审批通过时激活)
UpdateUserStatus(userID int64, status string) error
// ActivateUser 激活用户(注册审批通过)
ActivateUser(userID int64) error
// DeletePendingUser 删除待审批用户(工单撤销时)
DeletePendingUser(userID int64) error
// ExecuteUserManagement 执行用户管理操作(管理审批通过)
ExecuteUserManagement(userID int64, action string, payload map[string]interface{}) error
}
```
## 用户模块 → 工单模块调用接口
```go
// WorkflowCreator 工单创建接口
// 由 rmdc-core 在初始化时注入,用户模块通过此接口创建工单
type WorkflowCreator interface {
// CreateRegistrationWorkflow 创建用户注册工单
CreateRegistrationWorkflow(ctx context.Context, req *RegistrationWorkflowRequest) (string, error)
// CreateManagementWorkflow 创建用户管理工单
CreateManagementWorkflow(ctx context.Context, req *ManagementWorkflowRequest) (string, error)
}
```
## 注入时机
`rmdc-core/cmd/main.go` 初始化阶段完成所有接口注入:
1. 创建 UserService 实例
2. 创建 WorkflowService 实例
3. 将 UserStatusUpdater 注入 WorkflowService
4. 将 WorkflowCreator 注入 UserService

View File

@@ -0,0 +1,42 @@
# 模块依赖关系
---
DDS-Section: 2. 系统架构 - 2.1 模块依赖关系
DDS-Lines: L33-L73
---
## 依赖关系图
```
rmdc-user-auth
├── 被依赖 ←─────────────────────────────────────┐
│ ├── rmdc-core (API Gateway 入口) │
│ ├── rmdc-project-management (用户鉴权/查询) │
│ └── rmdc-work-procedure (用户状态回调) │
│ │
├── 主动依赖 ────────────────────────────────────┘
│ ├── rmdc-work-procedure (创建工单)
│ ├── rmdc-jenkins-branch-dac (Jenkins资源查询)
│ ├── rmdc-common (公共模块)
│ └── rmdc-audit-log (审计日志)
```
## 接口注入关系
| 注入方 | 注入接口 | 被注入方 | 用途 |
|:---|:---|:---|:---|
| rmdc-core | UserStatusUpdater | rmdc-work-procedure | 工单状态回调用户模块 |
| rmdc-core | WorkflowCreator | rmdc-user-auth | 用户模块创建工单 |
| rmdc-core | BusinessInfoQuerier | rmdc-user-auth | 权限模块查询业务信息 |
| rmdc-core | ModulePermissionChecker | rmdc-project-management | 业务模块权限检查 |
## 数据库连接
`rmdc-user-auth` 依赖 `models.DatabaseConnections` 提供的多库连接:
| 连接名 | 用途 |
|:---|:---|
| User | 用户表、权限表 |
| Jenkins | Jenkins 资源元数据 |
| Core | 系统配置 |
| Workflow | 工单数据 |

View File

@@ -0,0 +1,41 @@
# 技术栈与组件关系
---
DDS-Section: 2. 系统架构 - 2.4-2.6
DDS-Lines: L151-L171
---
## 技术栈
| 技术 | 用途 | 配置 |
|:---|:---|:---|
| Gin | HTTP 路由 | - |
| GORM | ORM | 多库连接 |
| JWT | 访问令牌 | HS256, 4h 有效期 |
| RSA-OAEP | 密码加密 | 2048 位30 天轮换 |
| bcrypt | 密码存储 | 默认成本 |
## 路由分层
| 路径前缀 | 中间件 | 说明 |
|:---|:---|:---|
| `/api/auth/*` | 无 | 公共接口RSA公钥、登录、注册 |
| `/api/users` | AuthMiddleware | 用户管理(部分需 RequireAdmin |
| `/api/user` | AuthMiddleware | 个人资料 |
| `/api/permissions` | AuthMiddleware | 权限管理(部分需 RequireAdmin |
## 中间件职责
| 中间件 | 职责 |
|:---|:---|
| AuthMiddleware | 解析 Bearer JWT校验签名/过期/用户状态,注入用户上下文 |
| RequireAdmin | 判定角色包含 `admin`superadmin/admin |
## 组件关系
| 层 | 组件 | 职责 |
|:---|:---|:---|
| Handler | auth_handler, user_handler, permission_handler | HTTP 路由处理 |
| Service | AuthService, RSAService, UserService, *PermissionService | 业务逻辑 |
| DAO | UserDao, RSAKeypairDao, *PermissionDao, *AclDao | 数据访问 |
| Pkg | permission.PermissionChecker | 对外暴露权限检查接口 |

View File

@@ -0,0 +1,68 @@
# JWT Claims 定义
---
DDS-Section: 3. 认证与登录设计 - 3.3 JWT 中间件行为
DDS-Lines: L193-L197
---
## Claims 结构
```go
type JWTClaims struct {
UserID int64 `json:"user_id"`
Username string `json:"username"`
EnglishName string `json:"english_name"`
Phone string `json:"phone"`
GroupName string `json:"group_name"`
Role string `json:"role"`
DevRole string `json:"dev_role"`
Status string `json:"status"`
jwt.RegisteredClaims
}
```
## Claims 字段说明
| 字段 | 类型 | 必填 | 来源 | 用途 |
|:---|:---|:---|:---|:---|
| `user_id` | int64 | ✅ | users.id | 唯一用户标识 |
| `username` | string | ✅ | users.username | 中文真实姓名 |
| `english_name` | string | ❌ | users.english_username | 英文昵称 |
| `phone` | string | ❌ | users.phone | 手机号 |
| `group_name` | string | ❌ | users.group_name | 所属小组 |
| `role` | string | ✅ | users.role | 系统角色 |
| `dev_role` | string | ❌ | users.dev_role | 开发角色 |
| `status` | string | ✅ | users.status | 账户状态 |
## JWT 配置
| 配置项 | 值 | 说明 |
|:---|:---|:---|
| 签名算法 | HS256 | HMAC SHA-256 |
| 有效期 | 4 小时 | `exp` claim |
| 签发者 | (空) | `iss` claim 未使用 |
| 秘钥来源 | 环境变量/配置 | `JWT_SECRET` |
## 下游模块使用
其他模块从 Gin Context 中提取用户信息:
```go
// 获取用户ID
userID := ctx.GetInt64("user_id")
// 获取角色
role := ctx.GetString("role")
// 判断是否管理员
isAdmin := role == "superadmin" || role == "admin"
```
## 兼容性要求
⚠️ **重要**:修改 Claims 字段时必须:
1. 确认所有下游模块解析逻辑兼容
2. 新增字段需提供默认值
3. 不可删除或重命名现有必填字段
4. 更新本文档与 Context 注入键列表

View File

@@ -0,0 +1,68 @@
# 认证与登录设计
---
DDS-Section: 3. 认证与登录设计
DDS-Lines: L174-L201
---
## 登录流程RSA + JWT
```
1. 前端调用 GET /api/auth/rsa/public-key 获取公钥30天有效
2. 前端用 RSA-OAEP(SHA-256, 2048) 加密密码
3. 前端提交 POST /api/auth/login (encrypted_password)
4. 后端 RSAService 解密
5. 后端 AuthService 用 bcrypt 校验 password_hash
6. 生成 HS256 JWT (4h 有效期)
7. 返回 Token + UserDTO + 附加标识
```
## 登录响应附加标识
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| `must_change_password` | bool | 首次登录或密码重置后需强制改密 |
| `password_expire_days` | int | 密码剩余有效天数7天内提示 |
| `account_expire_days` | int | 账户剩余有效天数7天内提示 |
## 密钥与密码策略
| 配置项 | 值 | 说明 |
|:---|:---|:---|
| RSA 算法 | RSA-OAEP(SHA-256) | 2048 位密钥 |
| RSA 有效期 | 30 天 | 过期自动生成新密钥对 |
| 密码存储 | bcrypt | 默认成本因子 |
| 密码有效期 | 90 天 | 到期需修改密码 |
| JWT 算法 | HS256 | - |
| JWT 有效期 | 4 小时 | 无刷新机制 |
## JWT 中间件行为
| 步骤 | 动作 |
|:---|:---|
| 1 | 解析 `Authorization: Bearer <token>` |
| 2 | 校验签名与过期时间 |
| 3 | 校验 `claims.Status == "active"` |
| 4 | 校验 `account_expires_at` 未过期 |
| 5 | 注入上下文键 |
### Context 注入键
| 键 | 来源 |
|:---|:---|
| `user_id` | JWT claims |
| `username` | JWT claims |
| `english_name` | JWT claims |
| `phone` | JWT claims |
| `group_name` | JWT claims |
| `role` | JWT claims |
| `dev_role` | JWT claims |
| `status` | JWT claims |
## 限制说明
| TBD/TODO | 说明 |
|:---|:---|
| 刷新接口 | 无Token 过期需重新登录 |
| 服务端注销 | 无,前端丢弃 Token |
| 登录失败锁定 | 字段存在,逻辑未实现 |

View File

@@ -0,0 +1,85 @@
# 用户生命周期管理
---
DDS-Section: 4. 用户生命周期管理
DDS-Lines: L203-L288
---
## 用户状态定义
| 状态 | 说明 | 触发条件 |
|:---|:---|:---|
| `disabled` | 待审批状态 | 用户注册后默认状态 |
| `active` | 正常激活 | 注册工单审批通过 |
| `locked` | 临时锁定 | 登录失败过多 / 管理员手动锁定 |
## 状态转换规则
| 原状态 | 事件 | 新状态 |
|:---|:---|:---|
| (新建) | 用户注册 | disabled |
| disabled | 保存草稿 | disabled |
| disabled | 工单打回 | disabled |
| disabled | 审批通过 | active |
| disabled | 工单撤销 | (删除) |
| active | 正常使用 | active |
| active | 信息更新 | active |
| active | 登录失败锁定 | locked |
| active | 管理员禁用 | disabled |
| active | 管理员删除 | (删除) |
| locked | 解锁 | active |
| locked | 管理员禁用 | disabled |
## 账户有效期机制
### 有效期设置规则
| 用户类型 | 有效期选项 | 默认值 | 说明 |
|:---|:---|:---|:---|
| SuperAdmin 创建 | 无限制/自定义 | 永久 | 可设置任意有效期 |
| Admin 创建 | 1/3/6/12个月 | 3个月 | 必须设置有效期 |
| Normal 创建 | 1/3/6/12个月 | 3个月 | 必须设置有效期 |
### 有效期字段
```go
AccountExpiresAt *time.Time `json:"account_expires_at"` // NULL表示永久
```
### 有效期检查逻辑
| 检查点 | 行为 |
|:---|:---|
| 登录时 | 过期则拒绝登录 |
| JWT 中间件 | 校验用户状态时同时检查 |
| 提前提醒 | 7天内登录时返回提醒 |
## 强制修改密码机制
### 触发条件
| 条件 | 标识字段 | 处理方式 |
|:---|:---|:---|
| 首次登录 | `must_change_password = true` | 跳转改密页面 |
| 密码重置后 | `must_change_password = true` | 使用临时密码后强制改密 |
| 密码过期 | `password_expires_at` 已过期 | 拒绝登录 |
### 密码过期时间线
```
创建用户/重置密码
password_expires_at = now + 90天
must_change_password = true
首次登录 → 强制改密
改密后: password_expires_at = now + 90天
must_change_password = false
正常使用
第83天: 提醒
第90天: 强制过期
```

View File

@@ -0,0 +1,86 @@
# RBAC 角色与权限矩阵
---
DDS-Section: 5. 用户注册与管理权限
DDS-Lines: L291-L320
---
## 角色层级
```
superadmin (超级管理员)
admin (管理员)
normal (普通用户)
third (三方用户)
```
## 角色注册权限矩阵
| 操作者角色 | 可注册角色 | 可管理范围 | 说明 |
|:---|:---|:---|:---|
| SuperAdmin | superadmin/admin/normal/third | 所有用户 | 完全权限 |
| Admin | normal/third | 自己注册的用户 | 谁注册谁管理 |
| Normal | third | 自己注册的用户 | 仅可注册三方用户 |
| Third | - | - | 无注册/管理权限 |
## 管理操作权限矩阵
| 操作 | SuperAdmin | Admin | Normal | Third |
|:---|:---|:---|:---|:---|
| 修改用户信息 | ✅ 所有 | ✅ 自己注册的 | ✅ 自己注册的 | ❌ |
| 启用用户 | ✅ 所有 | ✅ 自己注册的 | ✅ 自己注册的 | ❌ |
| 禁用用户 | ✅ 所有 | ✅ 自己注册的 | ✅ 自己注册的 | ❌ |
| 删除用户 | ✅ 所有 | ✅ 自己注册的 | ✅ 自己注册的 | ❌ |
| 重置密码 | ✅ 所有 | ✅ 自己注册的 | ❌ | ❌ |
| 延长有效期 | ✅ 所有 | ❌ | ❌ | ❌ |
## "谁注册谁管理"原则
### 实现机制
1. **注册关系记录**`users.registered_by_id` 记录注册人 ID
2. **权限检查规则**
- SuperAdmin可管理所有用户
- Admin/Normal只能管理 `registered_by_id == current_user_id` 的用户
3. **审批归属**:所有用户注册/管理工单由 SuperAdmin 审批
### 代码检查点
```go
// CheckManagementPermission 检查管理权限
func CheckManagementPermission(currentRole string, currentUserID, targetUserID int64, targetRegisteredByID int64) bool {
// SuperAdmin 可管理所有
if currentRole == "superadmin" {
return true
}
// Admin/Normal 只能管理自己注册的用户
if currentRole == "admin" || currentRole == "normal" {
return targetRegisteredByID == currentUserID
}
// Third 无管理权限
return false
}
```
## 注册权限校验
```go
// CheckRegistrationPermission 检查注册权限
func CheckRegistrationPermission(creatorRole, targetRole string) bool {
switch creatorRole {
case "superadmin":
return true // 可注册所有角色
case "admin":
return targetRole == "normal" || targetRole == "third"
case "normal":
return targetRole == "third"
default:
return false
}
}
```

View File

@@ -0,0 +1,80 @@
# 用户注册工单流程
---
DDS-Section: 6. 用户注册工单流程
DDS-Lines: L322-L392
---
## 工单创建时机
**重要**:前端不允许直接调用创建工单接口,工单由 `POST /api/users` 内部自动创建。
## 流程时序
```
1. 用户 → rmdc-user-auth: POST /api/users (创建用户)
2. rmdc-user-auth: 创建用户记录 (status=disabled, 默认密码)
3. rmdc-user-auth → rmdc-work-procedure: CreateWorkflow(user_registration)
4. rmdc-work-procedure → rmdc-user-auth: workflow_id
5. rmdc-user-auth → 用户: 返回成功工单ID
6. rmdc-work-procedure → SuperAdmin: 通知待审批
```
## 审批分支
| 操作 | 工单状态 | 用户状态 | 后续动作 |
|:---|:---|:---|:---|
| 审批通过 | approved | active | ActivateUser(user_id) |
| 审批打回 | returned | disabled (保持) | 通知修改,用户 PUT /api/users/:id |
| 撤销 | revoked | (删除) | DeletePendingUser(user_id) |
| 重新提交 | pending_review | disabled (保持) | 工单重新进入审批 |
## 工单状态与用户状态映射
| 工单事件 | 工单状态 | 用户状态 | 说明 |
|:---|:---|:---|:---|
| create | pending_review | disabled | 创建用户并提交审核 |
| approve | approved | active | 审核通过,激活用户 |
| return | returned | disabled | 打回修改 |
| resubmit | pending_review | disabled | 重新提交审核 |
| revoke | revoked | (删除) | 撤销工单,删除待审批用户 |
## 注册工单业务载荷
```go
type RegistrationWorkflowPayload struct {
TargetUserID int64 `json:"target_user_id"` // 被注册用户ID
TargetUsername string `json:"target_username"` // 被注册用户名
TargetRole string `json:"target_role"` // 被注册用户角色
RegisteredByID int64 `json:"registered_by_id"` // 注册人ID
RegisteredByName string `json:"registered_by_name"` // 注册人姓名
AccountExpiresAt time.Time `json:"account_expires_at"` // 账户有效期
RegistrationReason string `json:"registration_reason"` // 注册原因
}
```
## 注册接口设计要点
| 要点 | 说明 |
|:---|:---|
| 不暴露密码设置 | 后端自动生成默认密码(如 `Rmdc@2026` |
| 必须设置有效期 | 非 SuperAdmin 必须选择有效期选项 |
| 自动创建工单 | 用户创建接口内部调用工单模块 |
| 工单初始状态 | 直接进入 `pending_review` |
## 回调接口
当工单状态变更时,工单模块通过 UserStatusUpdater 接口回调用户模块:
```go
// 审批通过
func (u *UserStatusUpdaterImpl) ActivateUser(userID int64) error {
return u.userDao.UpdateStatus(userID, "active")
}
// 撤销工单
func (u *UserStatusUpdaterImpl) DeletePendingUser(userID int64) error {
// 仅删除 status=disabled 的用户
return u.userDao.DeleteIfDisabled(userID)
}
```

View File

@@ -0,0 +1,105 @@
# 用户管理工单流程
---
DDS-Section: 7. 用户管理工单流程
DDS-Lines: L394-L480
---
## 管理操作类型
| 操作代码 | 操作名称 | 说明 | 需审批 |
|:---|:---|:---|:---|
| `update` | 修改用户信息 | 修改基本信息 | 是 |
| `enable` | 启用用户 | disabled → active | 是 |
| `disable` | 禁用用户 | active → disabled | 是 |
| `delete` | 删除用户 | 软删除 | 是 |
| `reset_password` | 重置密码 | 重置为默认密码 | 是 |
| `extend_validity` | 延长有效期 | 延长 account_expires_at | 是 |
## 工单创建时机
**重要**:前端不允许直接调用创建工单接口,工单由用户管理接口内部自动创建。
| 前端操作 | 后端接口 | 工单类型 |
|:---|:---|:---|
| 修改用户信息 | `PUT /api/users/:id` | user_management (action=update) |
| 启用用户 | `POST /api/users/:id/enable` | user_management (action=enable) |
| 禁用用户 | `POST /api/users/:id/disable` | user_management (action=disable) |
| 删除用户 | `DELETE /api/users/:id` | user_management (action=delete) |
| 重置密码 | `POST /api/users/:id/reset-password` | user_management (action=reset_password) |
| 延长有效期 | `POST /api/users/:id/extend-validity` | user_management (action=extend_validity) |
## 流程时序
```
1. 操作人 → rmdc-user-auth: PUT /api/users/:id (或其他管理操作)
2. rmdc-user-auth: CheckManagementPermission
3. rmdc-user-auth: 记录原始数据快照
4. rmdc-user-auth → rmdc-work-procedure: CreateWorkflow(user_management)
5. rmdc-work-procedure → rmdc-user-auth: workflow_id
6. rmdc-user-auth → 操作人: 返回成功工单ID
7. rmdc-work-procedure → SuperAdmin: 通知待审批
分支:
- 审批通过: WP → UA: ExecuteUserManagement(action, payload)
- 审批打回: WP → 操作人: 通知修改后重新提交
```
## 管理工单业务载荷
```go
type ManagementWorkflowPayload struct {
TargetUserID int64 `json:"target_user_id"` // 目标用户ID
TargetUsername string `json:"target_username"` // 目标用户名
ActionType string `json:"action_type"` // 操作类型
OperatorID int64 `json:"operator_id"` // 操作人ID
OperatorName string `json:"operator_name"` // 操作人姓名
OriginalData map[string]interface{} `json:"original_data"` // 原始数据快照
ModifiedData map[string]interface{} `json:"modified_data"` // 修改后数据
Reason string `json:"reason"` // 操作原因
}
```
## 管理操作执行器接口
```go
type UserManagementExecutor interface {
UpdateUserInfo(userID int64, payload map[string]interface{}) error
EnableUser(userID int64) error
DisableUser(userID int64) error
DeleteUser(userID int64) error
ResetPassword(userID int64) error
ExtendValidity(userID int64, newExpiresAt time.Time) error
}
```
## 操作执行逻辑
审批通过后,工单模块调用 `ExecuteUserManagement` 执行实际操作:
```go
func (e *UserManagementExecutorImpl) ExecuteUserManagement(
userID int64,
action string,
payload map[string]interface{},
) error {
switch action {
case "update":
return e.UpdateUserInfo(userID, payload)
case "enable":
return e.EnableUser(userID)
case "disable":
return e.DisableUser(userID)
case "delete":
return e.DeleteUser(userID)
case "reset_password":
return e.ResetPassword(userID)
case "extend_validity":
expiresAt := payload["new_expires_at"].(time.Time)
return e.ExtendValidity(userID, expiresAt)
default:
return fmt.Errorf("unknown action: %s", action)
}
}
```

View File

@@ -0,0 +1,81 @@
# BusinessInfoRegistry 注册中心
---
DDS-Section: 8. 权限模型 - 8.3 BusinessInfoRegistry
DDS-Lines: L604-L664
---
## 设计目的
权限模块需要查询业务模块信息(如项目填写人),但不应直接依赖具体业务模块。采用统一注册机制解决模块依赖。
## 注册接口定义
```go
// BusinessInfoQuerier 业务信息查询接口(通用基类)
type BusinessInfoQuerier interface {
GetModuleCode() string
}
// ProjectInfoQuerier 项目信息查询接口
type ProjectInfoQuerier interface {
BusinessInfoQuerier
GetProjectFillerID(ctx context.Context, projectID string) (int64, error)
}
// JenkinsInfoQuerier Jenkins 信息查询接口
type JenkinsInfoQuerier interface {
BusinessInfoQuerier
GetOrganizations(ctx context.Context) ([]string, error)
GetRepositories(ctx context.Context, org string) ([]string, error)
GetBranches(ctx context.Context, org, repo string) ([]string, error)
}
```
## 统一权限检查接口
```go
// ModulePermissionChecker 权限检查器接口(通用)
type ModulePermissionChecker interface {
CheckPermission(ctx context.Context, userID int64, userRole string,
resourceID, resourceType, permissionType string) (bool, error)
GetAccessibleResourceIDs(ctx context.Context, userID int64, userRole string,
resourceType string) ([]string, error)
}
```
## 注册流程
`rmdc-core` 初始化阶段完成注册:
```go
// 1. 创建注册中心
registry := permission.NewBusinessInfoRegistry()
// 2. 注册项目信息查询器
registry.RegisterProjectQuerier(projectService)
// 3. 注册 Jenkins 信息查询器
registry.RegisterJenkinsQuerier(jenkinsService)
// 4. 将注册中心注入权限服务
projectPermissionService.SetBusinessInfoRegistry(registry)
jenkinsPermissionService.SetBusinessInfoRegistry(registry)
// 5. 将权限检查器注入业务模块
projectService.SetPermissionChecker(projectPermissionService)
```
## 依赖关系
```
rmdc-core (初始化)
├── 注册 ProjectInfoQuerier ────────→ BusinessInfoRegistry
├── 注册 JenkinsInfoQuerier ────────→ BusinessInfoRegistry
├── 注入 BusinessInfoRegistry ──────→ ProjectPermissionService
├── 注入 BusinessInfoRegistry ──────→ JenkinsPermissionService
└── 注入 PermissionChecker ─────────→ rmdc-project-management
```

View File

@@ -0,0 +1,115 @@
# Jenkins 层级权限 (jenkins_acls)
---
DDS-Section: 8. 权限模型 - 8.2 Jenkins 层级权限
DDS-Lines: L558-L603
---
## 层级结构
```
Organization (组织)
└── Repository (仓库)
└── Branch (分支)
```
## 设计原则
| 原则 | 说明 |
|:---|:---|
| 层级继承 | 上级权限覆盖下级 |
| 存储最小化 | 一条记录可覆盖子层级 |
| 权限类型 | can_view查看、can_build构建 |
## 权限表结构 (jenkins_acls)
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| `id` | int64 | 主键 |
| `user_id` | int64 | 用户 ID |
| `organization_folder` | string | 组织文件夹(必填) |
| `repository_name` | string | 仓库名称(可空 = Org 级权限) |
| `branch_name` | string | 分支名称(可空 = Repo 级权限) |
| `permission_level` | string | 权限层级org/repo/branch |
| `can_view` | bool | 是否可查看 |
| `can_build` | bool | 是否可构建 |
| `granted_by` | int64 | 授权人 ID |
| `granted_at` | time.Time | 授权时间 |
## 层级覆盖示例
| 记录 | organization | repository | branch | level | 覆盖范围 |
|:---|:---|:---|:---|:---|:---|
| 1 | cmit-dev | (null) | (null) | org | cmit-dev 下所有仓库和分支 |
| 2 | cmit-dev | rmdc-core | (null) | repo | rmdc-core 下所有分支 |
| 3 | cmit-dev | rmdc-core | main | branch | 仅 main 分支 |
## 权限缓存机制
| 层级 | 存储位置 | 说明 |
|:---|:---|:---|
| L1 | 内存 `permissionCache` | 进程内高速缓存 |
| L2 | DB `user_permission_caches` | 用户权限缓存树 JSON |
### 缓存失效
权限变更时必须同时清除 L1 和 L2 缓存:
```go
func InvalidatePermissionCache(userID int64) error {
// 清除 L1 内存缓存
permissionCache.Delete(userID)
// 清除 L2 DB 缓存
return userPermissionCacheDao.DeleteByUserID(userID)
}
```
## 权限检查逻辑
```go
// CheckHierarchicalPermission 检查层级权限
// 1. 先检查 Branch 级权限
// 2. 若无则检查 Repo 级权限
// 3. 若无则检查 Org 级权限
// 4. build 需要 view + build 两个权限
func CheckHierarchicalPermission(
userID int64,
org, repo, branch string,
needBuild bool,
) (bool, error) {
// 尝试从 L1 缓存获取
if cached, ok := permissionCache.Get(userID, org, repo, branch); ok {
return checkCachedPermission(cached, needBuild), nil
}
// L1 miss查询 DB
// 1. 先查 Branch 级
if perm, err := jenkinsAclDao.FindByBranch(userID, org, repo, branch); err == nil && perm != nil {
return checkPermission(perm, needBuild), nil
}
// 2. 再查 Repo 级
if perm, err := jenkinsAclDao.FindByRepo(userID, org, repo); err == nil && perm != nil {
return checkPermission(perm, needBuild), nil
}
// 3. 最后查 Org 级
if perm, err := jenkinsAclDao.FindByOrg(userID, org); err == nil && perm != nil {
return checkPermission(perm, needBuild), nil
}
return false, nil
}
```
## 懒加载接口
按级别逐层加载,减少首次加载数据量:
| 接口 | 返回 |
|:---|:---|
| `/api/permissions/jenkins/my-tree/organizations` | 用户有权限的组织列表 |
| `/api/permissions/jenkins/my-tree/repositories` | 指定组织下的仓库列表 |
| `/api/permissions/jenkins/my-tree/branches` | 指定仓库下的分支列表 |

View File

@@ -0,0 +1,105 @@
# 统一权限架构
---
DDS-Section: 8. 权限模型 - 8.1 统一权限架构
DDS-Lines: L483-L556
---
## 设计模式
RMDC 采用**专用权限表**设计,针对不同业务场景使用独立权限表结构。
## 权限模块枚举 (PermissionModule)
```go
type PermissionModule string
const (
// 用户模块权限
ModuleUserRegister PermissionModule = "user_register"
ModuleUserManage PermissionModule = "user_manage"
ModuleUserPermission PermissionModule = "user_permission"
// 项目模块权限
ModuleProjectCreate PermissionModule = "project_create"
ModuleProjectView PermissionModule = "project_view"
ModuleProjectEdit PermissionModule = "project_edit"
ModuleProjectExport PermissionModule = "project_export"
ModuleProjectAuth PermissionModule = "project_auth"
// Jenkins 模块权限
ModuleJenkinsView PermissionModule = "jenkins_view"
ModuleJenkinsBuild PermissionModule = "jenkins_build"
ModuleJenkinsManage PermissionModule = "jenkins_manage"
// 微服务更新模块权限
ModuleDeliveryView PermissionModule = "delivery_view"
ModuleDeliveryUpdate PermissionModule = "delivery_update"
// 工单模块权限
ModuleWorkflowView PermissionModule = "workflow_view"
ModuleWorkflowApprove PermissionModule = "workflow_approve"
)
```
## 模块信息注册表
```go
type PermissionModuleInfo struct {
Code PermissionModule `json:"code"`
Name string `json:"name"`
Description string `json:"description"`
ACLTable string `json:"acl_table"`
}
var PermissionModules = map[PermissionModule]PermissionModuleInfo{
ModuleJenkinsView: {Code: ModuleJenkinsView, Name: "Jenkins查看", ACLTable: "jenkins_acls"},
ModuleJenkinsBuild: {Code: ModuleJenkinsBuild, Name: "Jenkins构建", ACLTable: "jenkins_acls"},
ModuleProjectView: {Code: ModuleProjectView, Name: "项目查看", ACLTable: "project_acls"},
ModuleProjectEdit: {Code: ModuleProjectEdit, Name: "项目编辑", ACLTable: "project_acls"},
// ...
}
```
## 权限表概览
| 权限类型 | 权限表 | 权限粒度 | 说明 |
|:---|:---|:---|:---|
| Jenkins 权限 | `jenkins_acls` | Org/Repo/Branch 层级 | 支持层级继承 |
| 项目权限 | `project_acls` | 项目模块级 | 项目信息访问权限 |
| 用户权限缓存 | `user_permission_caches` | 用户维度 | L2 缓存树 |
## 权限检查通用规则
```go
// 权限检查通用规则:
// 1. SuperAdmin 拥有所有权限,直接放行
// 2. 根据 PermissionModule 分流到对应的 ACL 表进行检查
// 3. 权限检查结果存入 L1 内存缓存和 L2 DB 缓存
// 4. 权限变更时同时清除 L1 和 L2 缓存
func CheckPermission(ctx context.Context, userID int64, userRole string,
module PermissionModule, resourceID, permissionType string) (bool, error) {
// Rule 1: SuperAdmin 直接放行
if userRole == "superadmin" {
return true, nil
}
// Rule 2: 根据模块分流检查
moduleInfo, ok := PermissionModules[module]
if !ok {
return false, fmt.Errorf("unknown module: %s", module)
}
// 根据 ACLTable 选择对应的检查逻辑
switch moduleInfo.ACLTable {
case "jenkins_acls":
return checkJenkinsPermission(ctx, userID, resourceID, permissionType)
case "project_acls":
return checkProjectPermission(ctx, userID, resourceID, permissionType)
default:
return false, nil
}
}
```

View File

@@ -0,0 +1,94 @@
# 项目权限 (project_acls)
---
DDS-Section: 8. 权限模型 - 8.4 项目权限
DDS-Lines: L666-L691
---
## 设计原则
| 原则 | 说明 |
|:---|:---|
| 权限粒度 | 模块级 |
| SuperAdmin | 默认拥有所有权限 |
| 项目填写人 | 自动获得非授权模块的 view 权限 |
## 模块代码与 JSONB 映射
| 模块代码 | JSONB 字段 | 说明 |
|:---|:---|:---|
| `basic_info` | `projects.basic_info` | 省份、城市、联系人等 |
| `business_info` | `projects.deploy_business` | 部署人、版本、入口等 |
| `environment_info` | `projects.deploy_env` | 主机、网络、管理方式等 |
| `middleware_info` | `projects.deploy_middleware` | MySQL/Redis/EMQX 等 |
| `authorization_info` | `project_auth_configs.*` | TOTP 授权(仅 SuperAdmin |
## 权限表结构 (project_acls)
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| `id` | int64 | 主键 |
| `user_id` | int64 | 用户 ID |
| `project_id` | string | 项目 ID |
| `module_code` | string | 模块代码 |
| `can_view` | bool | 是否可查看 |
| `can_export` | bool | 是否可导出 |
| `granted_by` | int64 | 授权人 ID |
| `granted_at` | time.Time | 授权时间 |
## 索引设计
| 索引名 | 字段 | 用途 |
|:---|:---|:---|
| `idx_project_acl_user` | user_id | 用户维度查询 |
| `idx_project_acl_project` | project_id, module_code | 项目+模块维度查询 |
## 权限检查规则
```go
// CheckProjectModulePermission 检查项目模块权限
func CheckProjectModulePermission(
ctx context.Context,
userID int64,
userRole string,
projectID string,
moduleCode string,
permissionType string, // "view" or "export"
) (bool, error) {
// Rule 1: SuperAdmin 拥有所有权限
if userRole == "superadmin" {
return true, nil
}
// Rule 2: authorization_info 模块仅 SuperAdmin 可访问
if moduleCode == "authorization_info" {
return false, nil
}
// Rule 3: 项目填写人自动拥有非授权模块的 view 权限
if permissionType == "view" {
fillerID, err := businessInfoRegistry.GetProjectFillerID(ctx, projectID)
if err == nil && fillerID == userID {
return true, nil
}
}
// Rule 4: 查询 project_acls 表
acl, err := projectAclDao.Find(userID, projectID, moduleCode)
if err != nil {
return false, err
}
if acl == nil {
return false, nil
}
switch permissionType {
case "view":
return acl.CanView, nil
case "export":
return acl.CanExport, nil
default:
return false, nil
}
}
```

View File

@@ -0,0 +1,120 @@
# 权限表 Schema
---
DDS-Section: 9. 数据模型 - 9.5-9.6 权限表
DDS-Lines: L768-L777
---
## jenkins_acls 表
| 字段 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| `id` | int64 | PK | 主键 |
| `user_id` | int64 | NOT NULL, FK | 用户 ID |
| `organization_folder` | varchar(100) | NOT NULL | 组织文件夹 |
| `repository_name` | varchar(100) | - | 仓库名称(可空 = Org 级) |
| `branch_name` | varchar(100) | - | 分支名称(可空 = Repo 级) |
| `permission_level` | varchar(10) | NOT NULL | org/repo/branch |
| `can_view` | bool | NOT NULL | 查看权限 |
| `can_build` | bool | NOT NULL | 构建权限 |
| `granted_by` | int64 | NOT NULL | 授权人 ID |
| `granted_at` | datetime | NOT NULL | 授权时间 |
| `created_at` | datetime | AUTO | 创建时间 |
| `updated_at` | datetime | AUTO | 更新时间 |
### 索引设计
| 索引 | 字段 | 说明 |
|:---|:---|:---|
| `idx_jenkins_acl_user` | user_id | 用户维度查询 |
| `idx_jenkins_acl_resource` | user_id, organization_folder, repository_name, branch_name | 资源维度查询(层级覆盖) |
---
## project_acls 表
| 字段 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| `id` | int64 | PK | 主键 |
| `user_id` | int64 | NOT NULL, FK | 用户 ID |
| `project_id` | varchar(50) | NOT NULL | 项目 ID |
| `module_code` | varchar(30) | NOT NULL | 模块代码 |
| `can_view` | bool | NOT NULL | 查看权限 |
| `can_export` | bool | NOT NULL | 导出权限 |
| `granted_by` | int64 | NOT NULL | 授权人 ID |
| `granted_at` | datetime | NOT NULL | 授权时间 |
| `created_at` | datetime | AUTO | 创建时间 |
| `updated_at` | datetime | AUTO | 更新时间 |
### 索引设计
| 索引 | 字段 | 说明 |
|:---|:---|:---|
| `idx_project_acl_user` | user_id | 用户维度查询 |
| `idx_project_acl_project` | project_id, module_code | 项目+模块维度查询 |
| `uk_project_acl` | user_id, project_id, module_code | 唯一约束 |
---
## user_permission_caches 表
| 字段 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| `id` | int64 | PK | 主键 |
| `user_id` | int64 | UNIQUE, NOT NULL | 用户 ID |
| `permission_tree` | jsonb | NOT NULL | 权限树 JSON |
| `updated_at` | datetime | AUTO | 更新时间 |
### 缓存结构示例
```json
{
"jenkins": {
"cmit-dev": {
"can_view": true,
"can_build": false,
"repositories": {
"rmdc-core": {
"can_view": true,
"can_build": true,
"branches": {
"main": {"can_view": true, "can_build": true},
"dev": {"can_view": true, "can_build": false}
}
}
}
}
},
"projects": {
"proj-001": {
"basic_info": {"can_view": true, "can_export": false},
"business_info": {"can_view": true, "can_export": true}
}
}
}
```
---
## rsa_keypairs 表
| 字段 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| `id` | int64 | PK | 主键 |
| `public_key` | text | NOT NULL | PEM 格式公钥 |
| `private_key` | text | NOT NULL | PEM 格式私钥 |
| `expires_at` | datetime | NOT NULL | 过期时间 |
| `created_at` | datetime | AUTO | 创建时间 |
---
## system_configs 表
| 字段 | 类型 | 约束 | 说明 |
|:---|:---|:---|:---|
| `id` | int64 | PK | 主键 |
| `key` | varchar(100) | UNIQUE, NOT NULL | 配置键 |
| `value` | text | - | 配置值 |
| `description` | varchar(255) | - | 描述 |
| `created_at` | datetime | AUTO | 创建时间 |
| `updated_at` | datetime | AUTO | 更新时间 |

View File

@@ -0,0 +1,91 @@
# 用户表 Schema (users)
---
DDS-Section: 9. 数据模型 - 9.2 用户表 DDL
DDS-Lines: L695-L764
---
## 表定义
```go
type User struct {
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
Username string `gorm:"uniqueIndex;not null;size:50" json:"username"` // 中文真实姓名
EnglishUsername string `gorm:"size:100" json:"english_username"` // 英文用户名(昵称)
PasswordHash string `gorm:"not null;size:255" json:"-"` // 加密后的密码
AvatarID string `gorm:"size:50;default:'default_1'" json:"avatar_id"` // 头像ID
AvatarFrameID string `gorm:"size:50;default:'default'" json:"avatar_frame_id"` // 头像框ID
Gender string `gorm:"size:10;default:'male'" json:"gender"` // 性别
Email string `gorm:"uniqueIndex;size:100" json:"email"` // 邮箱
Phone string `gorm:"size:20" json:"phone"` // 手机号
ShortNumber string `gorm:"size:10" json:"short_number"` // 短号
WorkID string `gorm:"size:50" json:"work_id"` // 工号
GroupName string `gorm:"size:100" json:"group_name"` // 所属小组
Company string `gorm:"size:100" json:"company"` // 公司名称
DevRole string `gorm:"size:50;default:'unknown'" json:"dev_role"` // 开发角色
// 注册关系
RegisteredByID int64 `json:"registered_by_id"` // 注册人ID
RegisteredByName string `gorm:"size:64" json:"registered_by_name"` // 注册人姓名
// 角色与状态
Role string `gorm:"not null;size:20;default:'normal'" json:"role"` // 系统角色
Status string `gorm:"not null;size:20;default:'disabled'" json:"status"` // 状态
// 密码策略
PasswordExpiresAt *time.Time `json:"password_expires_at"` // 密码过期时间
MustChangePassword bool `gorm:"default:true" json:"must_change_password"` // 是否需强制改密
// 账户有效期
AccountExpiresAt *time.Time `json:"account_expires_at"` // 账户有效期
// 登录锁定
FailedLoginAttempts int `gorm:"default:0" json:"failed_login_attempts"`
LockedUntil *time.Time `json:"locked_until"`
// MFA
MFASecret string `gorm:"size:100" json:"-"`
// 审计字段
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
DeletedAt *time.Time `gorm:"index" json:"deleted_at,omitempty"`
LastLoginAt *time.Time `json:"last_login_at"`
}
```
## 字段说明
| 字段 | 类型 | 约束 | 默认值 | 说明 |
|:---|:---|:---|:---|:---|
| `id` | int64 | PK, AUTO | - | 主键 |
| `username` | varchar(50) | UNIQUE, NOT NULL | - | 中文真实姓名 |
| `english_username` | varchar(100) | - | - | 英文昵称 |
| `password_hash` | varchar(255) | NOT NULL | - | bcrypt 哈希 |
| `email` | varchar(100) | UNIQUE | - | 邮箱 |
| `role` | varchar(20) | NOT NULL | 'normal' | superadmin/admin/normal/third |
| `status` | varchar(20) | NOT NULL | 'disabled' | active/locked/disabled |
| `registered_by_id` | int64 | - | - | 注册人 ID |
| `registered_by_name` | varchar(64) | - | - | 注册人姓名(冗余) |
| `password_expires_at` | datetime | - | - | 密码过期时间 |
| `must_change_password` | bool | - | true | 首次登录强制改密标识 |
| `account_expires_at` | datetime | - | NULL | 账户有效期NULL=永久 |
| `failed_login_attempts` | int | - | 0 | 登录失败次数 |
| `locked_until` | datetime | - | - | 锁定截止时间 |
## 索引设计
| 索引 | 类型 | 字段 |
|:---|:---|:---|
| PRIMARY | PK | id |
| idx_users_username | UNIQUE | username |
| idx_users_email | UNIQUE | email |
| idx_users_deleted_at | INDEX | deleted_at |
## v2.0 新增字段
| 字段 | 类型 | 说明 | 默认值 |
|:---|:---|:---|:---|
| `registered_by_name` | varchar(64) | 注册人姓名(冗余存储) | NULL |
| `must_change_password` | bool | 是否需要强制改密 | true |
| `account_expires_at` | datetime | 账户有效期 | NULL |

View File

@@ -0,0 +1,116 @@
# API 接口设计
---
DDS-Section: 10. 接口设计 (API)
DDS-Lines: L781-L833
---
## 认证接口 (公共)
| 方法 | 路径 | 说明 | 鉴权 |
|:---|:---|:---|:---|
| GET | `/api/auth/rsa/public-key` | 获取 RSA 公钥 | 公共 |
| POST | `/api/auth/login` | RSA 加密密码登录 | 公共 |
| POST | `/api/auth/register` | 自助注册 | 公共 |
### POST /api/auth/login
**Request:**
```json
{
"username": "string",
"encrypted_password": "string"
}
```
**Response:**
```json
{
"token": "string",
"user": {
"id": 0,
"username": "string",
"role": "string",
"status": "string"
},
"must_change_password": false,
"password_expire_days": 0,
"account_expire_days": 0
}
```
---
## 用户管理接口
| 方法 | 路径 | 说明 | 鉴权 |
|:---|:---|:---|:---|
| GET | `/api/users` | 用户列表(分页/筛选) | JWT |
| GET | `/api/users/:id` | 用户详情 | JWT |
| POST | `/api/users` | 创建用户(自动创建注册工单) | JWT + Admin |
| PUT | `/api/users/:id` | 更新用户(草稿/需工单) | JWT + Admin |
| DELETE | `/api/users/:id` | 删除用户 | JWT + SuperAdmin |
| PUT | `/api/user/profile` | 更新本人资料 | JWT |
| PUT | `/api/user/password` | 本人改密 | JWT |
| PUT | `/api/user/password/force-change` | 首次登录强制改密 | JWT |
### GET /api/users 查询参数
| 参数 | 类型 | 说明 |
|:---|:---|:---|
| `page` | int | 页码 |
| `size` | int | 每页数量 |
| `role` | string | 角色筛选 |
| `status` | string | 状态筛选 |
| `group` | string | 小组筛选 |
| `search` | string | 关键词搜索 |
| `scope` | string | 范围:`all`/`created_by_me` |
---
## Jenkins 权限接口
| 方法 | 路径 | 说明 | 鉴权 |
|:---|:---|:---|:---|
| GET | `/api/permissions/jenkins/my-tree/organizations` | 我的组织列表 | JWT |
| POST | `/api/permissions/jenkins/my-tree/repositories` | 我的仓库 | JWT |
| POST | `/api/permissions/jenkins/my-tree/branches` | 我的分支 | JWT |
| GET | `/api/permissions/jenkins/my-tree/full` | 我的完整权限树 | JWT |
| GET | `/api/permissions/jenkins/check/:organization/:branch` | 检查分支权限 | JWT |
| GET | `/api/permissions/jenkins/tree` | 全量权限树 | JWT + Admin |
| GET | `/api/permissions/jenkins/users/role/:role` | 按角色查用户 | JWT + Admin |
| GET | `/api/permissions/jenkins/:userId` | 获取用户权限 | JWT + Admin |
| POST | `/api/permissions/jenkins/assign` | 分配权限 | JWT + Admin |
| POST | `/api/permissions/jenkins/copy` | 拷贝权限 | JWT + Admin |
| GET | `/api/permissions/jenkins/user-tree/:userId/organizations` | 懒加载组织 | JWT + Admin |
| GET | `/api/permissions/jenkins/user-tree/:userId/organizations/:org/repositories` | 懒加载仓库 | JWT + Admin |
| GET | `/api/permissions/jenkins/user-tree/:userId/organizations/:org/repositories/:repo/branches` | 懒加载分支 | JWT + Admin |
---
## 项目权限接口
| 方法 | 路径 | 说明 | 鉴权 |
|:---|:---|:---|:---|
| GET | `/api/permissions/projects/user/:userId/summary` | 用户项目权限摘要 | JWT + Admin |
| POST | `/api/permissions/projects/grant` | 授予项目模块权限 | JWT + Admin |
| POST | `/api/permissions/projects/revoke` | 撤销项目模块权限 | JWT + Admin |
---
## 系统配置接口
| 方法 | 路径 | 说明 | 鉴权 |
|:---|:---|:---|:---|
| GET | `/api/user/system-config` | 获取配置 | JWT |
| PUT | `/api/user/system-config` | 更新配置 | JWT |
---
## 重要约束
⚠️ **前端不允许直接调用创建工单接口**
用户注册/管理工单通过用户管理接口内部自动创建:
- `POST /api/users` → 自动创建 user_registration 工单
- `PUT /api/users/:id` → 自动创建 user_management 工单(正式修改时)

View File

@@ -0,0 +1,71 @@
# 安全与合规
---
DDS-Section: 12. 安全与合规
DDS-Lines: L930-L944
---
## 传输安全
| 要求 | 实现 |
|:---|:---|
| 密码加密 | RSA-OAEP(SHA-256, 2048) |
| 禁止明文 | 登录密码必须加密传输 |
## 存储安全
| 要求 | 实现 |
|:---|:---|
| 密码哈希 | bcrypt默认成本因子 |
| RSA 私钥 | 仅存 DB不下发前端 |
| 敏感字段 | json:"-" 标签,不外露 |
## 账户状态
| 状态 | JWT 校验 |
|:---|:---|
| active | ✅ 通过 |
| locked | ❌ 拒绝 |
| disabled | ❌ 拒绝 |
## 密码策略
| 策略 | 值 | 说明 |
|:---|:---|:---|
| 有效期 | 3 个月 | 修改/创建刷新过期时间 |
| 首次登录 | 强制改密 | must_change_password = true |
| 注册状态 | disabled | 需审批激活 |
## 账户有效期
| 创建者 | 要求 |
|:---|:---|
| 非 SuperAdmin | 必须设置有效期 |
| SuperAdmin | 可设置永久 |
过期账户无法登录。
## Token 安全
| 配置 | 值 |
|:---|:---|
| 算法 | HS256 |
| 有效期 | 4h |
| 刷新 | 无,需重登 |
| 注销 | 无服务端注销 |
## 权限检查
| 规则 | 说明 |
|:---|:---|
| SuperAdmin | 全通 |
| Admin 限制 | 只能管理/授权 normal/third |
| 授权校验 | 授予者必须具备相应权限 |
| 谁注册谁管理 | 非 SuperAdmin 只能管理自己注册的用户 |
## TBD/TODO
| 功能 | 状态 |
|:---|:---|
| 登录失败锁定策略 | 未实现(字段存在) |
| Watchdog 权限检查 | 待补全 |

View File

@@ -0,0 +1,315 @@
#!/bin/bash
# verify-user-auth.sh
# 验证 rmdc-user-auth 模块的设计一致性和代码规范
# 依赖: bash, grep, sed, find
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
REFERENCE_DIR="$SKILL_DIR/reference"
# 颜色定义
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
# 结果输出
pass() {
echo -e "${GREEN}[PASS]${NC} $1"
((PASS_COUNT++))
}
fail() {
echo -e "${RED}[FAIL]${NC} $1"
((FAIL_COUNT++))
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
((WARN_COUNT++))
}
echo "================================================"
echo "rmdc-user-auth Skill 验证脚本"
echo "================================================"
echo ""
# =====================================================
# 1. Reference 目录章节分层检查
# =====================================================
echo "--- 1. Reference 目录章节分层检查 ---"
# 检查是否存在章节目录 (01-* 开头)
if find "$REFERENCE_DIR" -maxdepth 1 -type d -name '0*-*' | grep -q .; then
CHAPTER_COUNT=$(find "$REFERENCE_DIR" -maxdepth 1 -type d -name '0*-*' | wc -l)
pass "Reference 目录包含 $CHAPTER_COUNT 个章节分层目录"
else
fail "Reference 目录缺少章节分层目录 (需 01-*, 02-* 等)"
fi
# 检查至少 2 个 reference 文件位于章节子目录
NON_ROOT_FILES=$(find "$REFERENCE_DIR" -mindepth 2 -type f -name "*.md" | wc -l)
if [ "$NON_ROOT_FILES" -ge 2 ]; then
pass "章节子目录中包含 $NON_ROOT_FILES 个 reference 文件"
else
fail "章节子目录中 reference 文件不足 2 个 (当前: $NON_ROOT_FILES)"
fi
# 检查 DDS-Section 字段
DDS_SECTION_COUNT=$(grep -r "DDS-Section:" "$REFERENCE_DIR" 2>/dev/null | wc -l)
if [ "$DDS_SECTION_COUNT" -ge 3 ]; then
pass "Reference 文件包含 $DDS_SECTION_COUNT 处 DDS-Section 标注"
else
fail "Reference 文件 DDS-Section 标注不足 3 处 (当前: $DDS_SECTION_COUNT)"
fi
# 检查 DDS-Lines 字段
DDS_LINES_COUNT=$(grep -r "DDS-Lines:" "$REFERENCE_DIR" 2>/dev/null | wc -l)
if [ "$DDS_LINES_COUNT" -ge 3 ]; then
pass "Reference 文件包含 $DDS_LINES_COUNT 处 DDS-Lines 标注"
else
fail "Reference 文件 DDS-Lines 标注不足 3 处 (当前: $DDS_LINES_COUNT)"
fi
echo ""
# =====================================================
# 2. 核心设计要素覆盖检查
# =====================================================
echo "--- 2. 核心设计要素覆盖检查 ---"
# API 端点
if [ -f "$REFERENCE_DIR/10-api-design/api-endpoints.md" ]; then
pass "API 端点设计文档存在"
else
fail "API 端点设计文档缺失 (reference/10-api-design/api-endpoints.md)"
fi
# 权限表 Schema
if [ -f "$REFERENCE_DIR/09-data-model/permission-tables-schema.md" ]; then
pass "权限表 Schema 文档存在"
else
fail "权限表 Schema 文档缺失 (reference/09-data-model/permission-tables-schema.md)"
fi
# 用户表 Schema
if [ -f "$REFERENCE_DIR/09-data-model/user-table-schema.md" ]; then
pass "用户表 Schema 文档存在"
else
fail "用户表 Schema 文档缺失 (reference/09-data-model/user-table-schema.md)"
fi
# 状态机/工单流程
if [ -f "$REFERENCE_DIR/06-registration-workflow/registration-workflow.md" ]; then
pass "注册工单流程文档存在"
else
fail "注册工单流程文档缺失 (reference/06-registration-workflow/registration-workflow.md)"
fi
# 权限模型
if [ -f "$REFERENCE_DIR/08-permission-model/permission-architecture.md" ]; then
pass "权限架构设计文档存在"
else
fail "权限架构设计文档缺失 (reference/08-permission-model/permission-architecture.md)"
fi
# 安全模型
if [ -f "$REFERENCE_DIR/11-security/security-compliance.md" ]; then
pass "安全合规文档存在"
else
fail "安全合规文档缺失 (reference/11-security/security-compliance.md)"
fi
echo ""
# =====================================================
# 3. 关键设计内容检查
# =====================================================
echo "--- 3. 关键设计内容检查 ---"
# 检查 JWT Claims 定义
if grep -q "user_id.*username.*role.*status" "$REFERENCE_DIR/03-authentication/jwt-claims.md" 2>/dev/null; then
pass "JWT Claims 包含必要字段 (user_id/username/role/status)"
else
fail "JWT Claims 缺少必要字段定义"
fi
# 检查 RBAC 角色层级
if grep -q "superadmin.*admin.*normal.*third" "$REFERENCE_DIR/05-rbac/rbac-roles.md" 2>/dev/null; then
pass "RBAC 角色层级定义完整"
else
fail "RBAC 角色层级定义不完整"
fi
# 检查 account_expires_at 字段
if grep -q "account_expires_at\|AccountExpiresAt" "$REFERENCE_DIR/09-data-model/user-table-schema.md" 2>/dev/null; then
pass "用户表包含 account_expires_at 字段"
else
fail "用户表缺少 account_expires_at 字段"
fi
# 检查 must_change_password 字段
if grep -q "must_change_password\|MustChangePassword" "$REFERENCE_DIR/09-data-model/user-table-schema.md" 2>/dev/null; then
pass "用户表包含 must_change_password 字段"
else
fail "用户表缺少 must_change_password 字段"
fi
# 检查 jenkins_acls 表定义
if grep -q "jenkins_acls" "$REFERENCE_DIR/08-permission-model/jenkins-acls.md" 2>/dev/null; then
pass "Jenkins 权限表 (jenkins_acls) 已定义"
else
fail "Jenkins 权限表定义缺失"
fi
# 检查 project_acls 表定义
if grep -q "project_acls" "$REFERENCE_DIR/08-permission-model/project-acls.md" 2>/dev/null; then
pass "项目权限表 (project_acls) 已定义"
else
fail "项目权限表定义缺失"
fi
# 检查工单接口约束
if grep -q "前端不允许直接调用创建工单接口\|工单由用户管理接口内部自动创建" "$REFERENCE_DIR/10-api-design/api-endpoints.md" 2>/dev/null; then
pass "API 文档包含工单接口约束说明"
else
warn "API 文档可能缺少工单接口约束说明"
fi
# 检查 PermissionModule 枚举
if grep -q "PermissionModule" "$REFERENCE_DIR/08-permission-model/permission-architecture.md" 2>/dev/null; then
pass "权限模块枚举 (PermissionModule) 已定义"
else
fail "权限模块枚举定义缺失"
fi
echo ""
# =====================================================
# 4. Examples 和 Scripts 检查
# =====================================================
echo "--- 4. Examples 和 Scripts 检查 ---"
# 检查示例代码
if [ -f "$SKILL_DIR/examples/auth-handler-skeleton.go" ]; then
pass "认证处理器骨架代码存在"
else
warn "认证处理器骨架代码缺失 (examples/auth-handler-skeleton.go)"
fi
if [ -f "$SKILL_DIR/examples/permission-check-skeleton.go" ]; then
pass "权限检查骨架代码存在"
else
warn "权限检查骨架代码缺失 (examples/permission-check-skeleton.go)"
fi
if [ -f "$SKILL_DIR/examples/workflow-callback-skeleton.go" ]; then
pass "工单回调骨架代码存在"
else
warn "工单回调骨架代码缺失 (examples/workflow-callback-skeleton.go)"
fi
echo ""
# =====================================================
# 5. SKILL.md 检查
# =====================================================
echo "--- 5. SKILL.md 检查 ---"
SKILL_MD="$SKILL_DIR/SKILL.md"
# 检查 frontmatter
if grep -q "^name: developing-user-auth" "$SKILL_MD" 2>/dev/null; then
pass "SKILL.md name 字段正确"
else
fail "SKILL.md name 字段错误或缺失"
fi
if grep -q "^description:" "$SKILL_MD" 2>/dev/null; then
pass "SKILL.md description 字段存在"
else
fail "SKILL.md description 字段缺失"
fi
if grep -q "^argument-hint:" "$SKILL_MD" 2>/dev/null; then
pass "SKILL.md argument-hint 字段存在"
else
fail "SKILL.md argument-hint 字段缺失"
fi
# 检查 Plan/Verify/Execute/Pitfalls 结构
if grep -q "## Plan" "$SKILL_MD" 2>/dev/null; then
pass "SKILL.md 包含 Plan 章节"
else
fail "SKILL.md 缺少 Plan 章节"
fi
if grep -q "## Verify" "$SKILL_MD" 2>/dev/null; then
pass "SKILL.md 包含 Verify 章节"
else
fail "SKILL.md 缺少 Verify 章节"
fi
if grep -q "## Execute" "$SKILL_MD" 2>/dev/null; then
pass "SKILL.md 包含 Execute 章节"
else
fail "SKILL.md 缺少 Execute 章节"
fi
if grep -q "## Pitfalls" "$SKILL_MD" 2>/dev/null; then
pass "SKILL.md 包含 Pitfalls 章节"
else
fail "SKILL.md 缺少 Pitfalls 章节"
fi
# 检查动态注入示例
DYNAMIC_INJECT_COUNT=$(grep -c '!`' "$SKILL_MD" 2>/dev/null || echo "0")
if [ "$DYNAMIC_INJECT_COUNT" -ge 2 ]; then
pass "SKILL.md 包含 $DYNAMIC_INJECT_COUNT 处动态注入示例"
else
fail "SKILL.md 动态注入示例不足 2 处 (当前: $DYNAMIC_INJECT_COUNT)"
fi
# 检查 Pitfalls 数量
PITFALL_COUNT=$(grep -c "^\d\." "$SKILL_MD" 2>/dev/null || grep -c "^[0-9]\." "$SKILL_MD" 2>/dev/null || echo "0")
if [ "$PITFALL_COUNT" -ge 3 ]; then
pass "SKILL.md Pitfalls 包含 $PITFALL_COUNT"
else
warn "SKILL.md Pitfalls 条目可能不足 (建议 3-8 条)"
fi
# 检查行数 (应 < 500)
LINE_COUNT=$(wc -l < "$SKILL_MD" 2>/dev/null || echo "0")
if [ "$LINE_COUNT" -lt 500 ]; then
pass "SKILL.md 行数 ($LINE_COUNT) 符合规范 (<500)"
else
warn "SKILL.md 行数 ($LINE_COUNT) 超过建议值 500"
fi
echo ""
# =====================================================
# 汇总
# =====================================================
echo "================================================"
echo "验证结果汇总"
echo "================================================"
echo -e "${GREEN}PASS${NC}: $PASS_COUNT"
echo -e "${RED}FAIL${NC}: $FAIL_COUNT"
echo -e "${YELLOW}WARN${NC}: $WARN_COUNT"
echo ""
if [ "$FAIL_COUNT" -eq 0 ]; then
echo -e "${GREEN}✓ 所有必要检查通过${NC}"
exit 0
else
echo -e "${RED}✗ 存在 $FAIL_COUNT 项失败,请修复后重新验证${NC}"
exit 1
fi

View File

@@ -0,0 +1,111 @@
---
name: managing-db-migrations
description: "Guides PostgreSQL database migration and rollback for RMDC system. Triggered when adding tables, modifying columns, creating indexes, or evolving schema. Covers migration naming, rollback scripts, field evolution rules, and data migration. Keywords: PostgreSQL, migration, rollback, schema evolution, GORM, ALTER TABLE."
allowed-tools:
- Read
- Glob
- Grep
- Bash
- Edit
- Write
argument-hint: "$ARGUMENTS: <operation> [table] — operation: add-table|add-column|modify-column|add-index|rollback"
---
# managing-db-migrations
## 概述
本 Skill 指导 RMDC 系统的 PostgreSQL 数据库迁移与回滚。
## 动态上下文注入
### 查看现有迁移文件
!`ls -la migrations/ 2>/dev/null || find . -name "*migration*" -type f | head -20`
### 查看表结构定义
!`grep -rn "type.*struct" --include="*.go" -A 20 | grep -E "gorm:|TableName" | head -30`
---
## Plan规划阶段
### 迁移类型与影响
| 操作 | 可回滚 | 数据风险 | 锁表影响 |
|:---|:---|:---|:---|
| 新增表 | ✓ | 低 | 无 |
| 新增可空列 | ✓ | 低 | 低 |
| 新增非空列(有默认值) | ✓ | 低 | 中 |
| 修改列类型 | 需评估 | 中 | 高 |
| 删除列 | ✗ | 高 | 低 |
| 新增索引 | ✓ | 低 | 高(大表) |
### 决策点
- [ ] 是否需要停机迁移?
- [ ] 是否需要数据迁移脚本?
- [ ] 回滚脚本是否就绪?
- [ ] 是否影响现有查询性能?
---
## Verify验证清单
### 迁移前检查
- [ ] 迁移文件命名符合规范:`YYYYMMDDHHMMSS_description.sql`
- [ ] 包含 UP 和 DOWN 脚本
- [ ] 在测试环境验证通过
- [ ] 回滚脚本已测试
### 迁移后检查
- [ ] 表结构与预期一致
- [ ] 索引正确创建
- [ ] 现有数据未丢失
- [ ] 应用程序正常运行
### 验证命令
```bash
# 检查迁移语法
psql -h localhost -U rmdc -d rmdc_test -f migration.sql --dry-run
# 验证回滚
./scripts/verify-migration-rollback.sh
```
---
## Execute执行步骤
### 新增列
```sql
-- UP
ALTER TABLE users ADD COLUMN IF NOT EXISTS new_field VARCHAR(255) DEFAULT '';
-- DOWN
ALTER TABLE users DROP COLUMN IF EXISTS new_field;
```
### 新增索引(大表使用 CONCURRENTLY
```sql
-- UP
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
-- DOWN
DROP INDEX CONCURRENTLY IF EXISTS idx_users_email;
```
---
## Pitfalls常见坑
1. **非空列无默认值**:现有数据无法满足约束,迁移失败。
2. **大表加索引阻塞**:未使用 CONCURRENTLY 导致表锁。
3. **删除列后回滚**:数据已丢失,无法恢复。
4. **字段类型变更**:如 `VARCHAR(50)``VARCHAR(20)`,可能截断数据。
5. **迁移顺序错误**:依赖的表/列不存在。
---
## 相关文件
| 用途 | 路径 |
|:---|:---|
| 命名规范 | [reference/migration-naming.md](reference/migration-naming.md) |
| 回滚策略 | [reference/rollback-policy.md](reference/rollback-policy.md) |
| 字段演进 | [reference/field-evolution-rules.md](reference/field-evolution-rules.md) |

View File

@@ -0,0 +1,27 @@
-- Migration Template
-- Migration: YYYYMMDDHHMMSS_description
-- Author: xxx
-- Description: 描述此次迁移的目的
-- ==================== UP ====================
BEGIN;
-- 添加新列示例
ALTER TABLE users ADD COLUMN IF NOT EXISTS phone VARCHAR(20);
-- 添加索引示例(小表)
CREATE INDEX IF NOT EXISTS idx_users_phone ON users(phone);
-- 添加索引示例(大表,需要在事务外执行)
-- CREATE INDEX CONCURRENTLY idx_users_phone ON users(phone);
-- 数据迁移示例
-- UPDATE users SET phone = '' WHERE phone IS NULL;
COMMIT;
-- ==================== DOWN ====================
-- 回滚脚本(默认注释,测试时取消注释)
-- BEGIN;
-- ALTER TABLE users DROP COLUMN IF EXISTS phone;
-- COMMIT;

View File

@@ -0,0 +1,58 @@
# 字段演进规则
## 安全演进
### 新增可空列
```sql
ALTER TABLE users ADD COLUMN nickname VARCHAR(100);
```
- ✅ 安全:不影响现有数据
- ✅ 可回滚
### 新增有默认值的列
```sql
ALTER TABLE users ADD COLUMN status VARCHAR(20) DEFAULT 'active';
```
- ✅ 安全:现有行自动填充默认值
- ⚠️ 大表可能较慢
### 扩展字段长度
```sql
ALTER TABLE users ALTER COLUMN username TYPE VARCHAR(500);
```
- ✅ 安全:不丢失数据
- ✅ 可回滚(需确认无超长数据)
## 危险演进
### 缩小字段长度
```sql
-- 危险!可能截断数据
ALTER TABLE users ALTER COLUMN username TYPE VARCHAR(50);
```
- ❌ 可能丢失数据
- 必须先验证:`SELECT MAX(LENGTH(username)) FROM users;`
### 修改字段类型
```sql
-- 危险!可能转换失败
ALTER TABLE users ALTER COLUMN age TYPE INTEGER USING age::integer;
```
- ❌ 可能失败或丢失精度
- 必须先验证数据兼容性
### 删除列
```sql
-- 不可逆!
ALTER TABLE users DROP COLUMN old_field;
```
- ❌ 数据永久丢失
- 必须先备份
## 推荐流程
1. 新增新列(可空)
2. 代码同时写新旧列
3. 数据迁移(旧列 → 新列)
4. 代码只读写新列
5. 删除旧列(确认无使用后)

View File

@@ -0,0 +1,49 @@
# 迁移文件命名规范
## 命名格式
```
YYYYMMDDHHMMSS_description.sql
```
示例:
- `20260123100000_create_users_table.sql`
- `20260123110000_add_phone_to_users.sql`
- `20260123120000_create_index_users_email.sql`
## 命名规则
1. 时间戳精确到秒,确保唯一性
2. description 使用小写字母和下划线
3. 描述清晰表明变更内容
4. 常用动词create, add, drop, modify, rename, create_index
## 文件结构
```sql
-- Migration: 20260123100000_create_users_table
-- Author: developer_name
-- Description: 创建用户表
-- ==================== UP ====================
BEGIN;
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT NOW()
);
COMMIT;
-- ==================== DOWN ====================
-- BEGIN;
-- DROP TABLE IF EXISTS users;
-- COMMIT;
```
## 注意事项
- DOWN 脚本默认注释,测试时取消注释
- 每个迁移文件只做一件事
- 复杂迁移拆分成多个文件

View File

@@ -0,0 +1,43 @@
# 回滚策略
## 回滚原则
1. **每个 UP 必须有对应的 DOWN**
2. **DOWN 必须经过测试**
3. **不可逆操作需要备份**
## 可回滚操作
| 操作 | 回滚方式 |
|:---|:---|
| CREATE TABLE | DROP TABLE |
| ADD COLUMN | DROP COLUMN |
| CREATE INDEX | DROP INDEX |
| INSERT | DELETE |
## 不可逆操作(需特殊处理)
| 操作 | 处理方式 |
|:---|:---|
| DROP TABLE | 迁移前备份表数据 |
| DROP COLUMN | 迁移前备份列数据 |
| TRUNCATE | 迁移前全表备份 |
| 类型变更(缩小) | 验证数据是否超限 |
## 回滚流程
1. 确认回滚范围
2. 备份当前数据(如需要)
3. 执行 DOWN 脚本
4. 验证回滚结果
5. 通知相关方
## 紧急回滚
```bash
# 回滚最近一次迁移
psql -h $DB_HOST -U $DB_USER -d $DB_NAME -f migrations/XXXXXX_xxx_down.sql
# 验证
psql -h $DB_HOST -U $DB_USER -d $DB_NAME -c "\d table_name"
```

View File

@@ -0,0 +1,66 @@
#!/bin/bash
# verify-migration-rollback.sh - 验证迁移回滚
# 依赖: grep
# 用法: ./verify-migration-rollback.sh <migration-file>
set -e
MIGRATION_FILE=$1
if [ -z "$MIGRATION_FILE" ]; then
echo "用法: $0 <migration-file>"
exit 1
fi
echo "=== 迁移回滚验证 ==="
echo "迁移文件: ${MIGRATION_FILE}"
echo ""
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
pass() { echo -e "${GREEN}[PASS]${NC} $1"; }
fail() { echo -e "${RED}[FAIL]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
# 检查文件是否存在
if [ ! -f "$MIGRATION_FILE" ]; then
fail "迁移文件不存在: $MIGRATION_FILE"
exit 1
fi
# 检查是否包含 UP 和 DOWN
if grep -q "UP" "$MIGRATION_FILE"; then
pass "包含 UP 脚本"
else
fail "缺少 UP 脚本"
fi
if grep -q "DOWN" "$MIGRATION_FILE"; then
pass "包含 DOWN 脚本"
else
fail "缺少 DOWN 脚本"
fi
# 检查 DOWN 脚本是否被注释
if grep -E "^--.*DROP|^--.*DELETE|^--.*ALTER.*DROP" "$MIGRATION_FILE" > /dev/null; then
warn "DOWN 脚本被注释,请取消注释后测试回滚"
fi
# 检查是否有危险操作
if grep -i "DROP TABLE\|TRUNCATE\|DELETE FROM" "$MIGRATION_FILE" | grep -v "^--" > /dev/null; then
warn "包含危险操作DROP/TRUNCATE/DELETE请确保已备份"
fi
# 检查命名规范
FILENAME=$(basename "$MIGRATION_FILE")
if echo "$FILENAME" | grep -qE "^[0-9]{14}_[a-z_]+\.sql$"; then
pass "文件名符合规范"
else
warn "文件名不符合规范: YYYYMMDDHHMMSS_description.sql"
fi
echo ""
echo "=== 验证完成 ==="

View File

@@ -0,0 +1,127 @@
---
name: managing-observability
description: "Guides observability implementation including structured logging, metrics, tracing, and audit log alignment for RMDC system. Triggered when adding log statements, defining metrics, implementing traces, or ensuring audit compliance. Keywords: structured log, metrics, trace, audit, Prometheus, OpenTelemetry, rmdc-audit-log."
allowed-tools:
- Read
- Glob
- Grep
- Bash
argument-hint: "$ARGUMENTS: <aspect> [module] — aspect: logging|metrics|tracing|audit"
---
# managing-observability
## 概述
本 Skill 指导 RMDC 系统的可观测性实现,确保日志、指标、追踪与审计的一致性。
## 动态上下文注入
### 查找日志调用
!`grep -rn "log\.\(Info\|Error\|Warn\|Debug\)" --include="*.go" | head -20`
### 查找审计相关代码
!`grep -rn "audit\|Audit\|AuditLog" --include="*.go" | head -20`
---
## Plan规划阶段
### 可观测性维度
| 维度 | 工具 | 对齐模块 |
|:---|:---|:---|
| 日志 | 结构化日志 | rmdc-audit-log |
| 指标 | Prometheus | - |
| 追踪 | OpenTelemetry | - |
| 审计 | PostgreSQL | rmdc-audit-log |
### 决策点
- [ ] 日志级别是否合适?
- [ ] 是否需要添加审计记录?
- [ ] 指标命名是否符合规范?
- [ ] trace_id 是否正确传递?
---
## Verify验证清单
### 日志规范检查
- [ ] 使用结构化日志格式
- [ ] 包含 request_id / trace_id
- [ ] 敏感信息已脱敏
- [ ] 日志级别正确
### 审计对齐检查
- [ ] 关键操作有审计记录
- [ ] 审计字段完整who/when/what/where
- [ ] 审计记录不可篡改
- [ ] 与 rmdc-audit-log 格式一致
### 验证命令
```bash
# 检查日志调用规范
grep -rn "log\." --include="*.go" | grep -v "WithFields" | head -20
# 检查审计记录
grep -rn "AuditLog\|audit" --include="*.go" | head -20
```
---
## Execute执行步骤
### 添加结构化日志
```go
import log "github.com/sirupsen/logrus"
log.WithFields(log.Fields{
"user_id": userID,
"action": "login",
"request_id": requestID,
}).Info("用户登录成功")
```
### 添加审计记录
```go
auditLog.Record(AuditEntry{
UserID: userID,
Action: "UPDATE_USER",
ResourceID: targetUserID,
Details: changes,
Timestamp: time.Now(),
IP: clientIP,
})
```
### 添加 Prometheus 指标
```go
var loginCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "rmdc_user_auth_login_total",
Help: "Total number of login attempts",
},
[]string{"status"},
)
// 使用
loginCounter.WithLabelValues("success").Inc()
```
---
## Pitfalls常见坑
1. **日志泄露敏感信息**密码、Token、身份证号等未脱敏直接打印。
2. **审计字段缺失**无法追溯操作人user_id或操作内容details
3. **日志级别滥用**DEBUG 日志在生产环境大量输出影响性能。
4. **审计记录可被删除**:审计表需要设置写保护,禁止 DELETE/UPDATE。
5. **trace_id 未传递**:跨服务调用时未将 trace_id 传递到下游,无法串联请求链路。
6. **指标命名不规范**:未遵循 `模块_资源_动作_单位` 格式。
---
## 相关文件
| 用途 | 路径 |
|:---|:---|
| 日志格式 | [reference/log-format.md](reference/log-format.md) |
| 指标命名 | [reference/metrics-naming.md](reference/metrics-naming.md) |
| 审计对齐 | [reference/audit-alignment.md](reference/audit-alignment.md) |

View File

@@ -0,0 +1,85 @@
package logging
import (
"time"
log "github.com/sirupsen/logrus"
)
// 结构化日志示例
// LogUserAction 记录用户操作日志
func LogUserAction(userID uint, action string, requestID string, details map[string]interface{}) {
fields := log.Fields{
"user_id": userID,
"action": action,
"request_id": requestID,
"timestamp": time.Now().Format(time.RFC3339),
}
// 合并详情字段
for k, v := range details {
fields[k] = v
}
log.WithFields(fields).Info("用户操作")
}
// LogAPIRequest 记录 API 请求日志
func LogAPIRequest(requestID string, method string, path string, statusCode int, duration time.Duration, userID uint) {
log.WithFields(log.Fields{
"request_id": requestID,
"method": method,
"path": path,
"status_code": statusCode,
"duration_ms": duration.Milliseconds(),
"user_id": userID,
}).Info("API请求")
}
// LogError 记录错误日志
func LogError(requestID string, err error, context map[string]interface{}) {
fields := log.Fields{
"request_id": requestID,
"error": err.Error(),
}
for k, v := range context {
fields[k] = v
}
log.WithFields(fields).Error("发生错误")
}
// 敏感信息脱敏工具
// MaskPhone 手机号脱敏
func MaskPhone(phone string) string {
if len(phone) >= 11 {
return phone[:3] + "****" + phone[7:]
}
return "****"
}
// MaskEmail 邮箱脱敏
func MaskEmail(email string) string {
atIndex := -1
for i, c := range email {
if c == '@' {
atIndex = i
break
}
}
if atIndex > 2 {
return email[:2] + "***" + email[atIndex:]
}
return "***@***"
}
// MaskIDCard 身份证号脱敏
func MaskIDCard(idCard string) string {
if len(idCard) >= 18 {
return idCard[:6] + "********" + idCard[14:]
}
return "******"
}

View File

@@ -0,0 +1,74 @@
# 审计日志对齐规范
## 与 rmdc-audit-log 对齐
所有模块的审计记录必须与 `rmdc-audit-log` 模块的格式保持一致。
## 必须字段
| 字段 | 类型 | 说明 |
|:---|:---|:---|
| id | uint | 审计记录ID |
| user_id | uint | 操作人ID |
| username | string | 操作人用户名 |
| action | string | 操作类型 |
| resource_type | string | 资源类型 |
| resource_id | string | 资源ID |
| details | json | 操作详情 |
| ip_address | string | 客户端IP |
| user_agent | string | 客户端UA |
| timestamp | timestamp | 操作时间 |
| result | string | 操作结果 success/failed |
## 操作类型规范
| 模块 | 操作类型 |
|:---|:---|
| user-auth | USER_LOGIN, USER_LOGOUT, USER_CREATE, USER_UPDATE, USER_DELETE, PASSWORD_CHANGE, PERMISSION_GRANT |
| jenkins-dac | BUILD_TRIGGER, BUILD_CANCEL, PERMISSION_CHANGE |
| exchange-hub | COMMAND_SEND, COMMAND_COMPLETE |
| watchdog | DEPLOYMENT_START, DEPLOYMENT_COMPLETE, TOTP_VERIFY |
| project-mgmt | PROJECT_CREATE, PROJECT_UPDATE, AUTH_GRANT |
| work-procedure | WORKFLOW_CREATE, WORKFLOW_APPROVE, WORKFLOW_REJECT |
## 审计表保护
审计表必须设置以下保护:
1. 禁止 DELETE 操作
2. 禁止 UPDATE 操作(除标记字段外)
3. 定期备份
4. 独立存储(建议)
```sql
-- 创建只允许 INSERT 的触发器
CREATE OR REPLACE FUNCTION prevent_audit_modify()
RETURNS TRIGGER AS $$
BEGIN
RAISE EXCEPTION 'Audit log modification is not allowed';
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER audit_log_protect
BEFORE UPDATE OR DELETE ON audit_logs
FOR EACH ROW EXECUTE FUNCTION prevent_audit_modify();
```
## 审计记录示例
```go
// user-auth 模块
audit.Record(audit.Entry{
UserID: operatorID,
Username: operatorName,
Action: "USER_CREATE",
ResourceType: "user",
ResourceID: strconv.Itoa(int(newUser.ID)),
Details: map[string]interface{}{
"username": newUser.Username,
"role": newUser.Role,
},
IPAddress: c.ClientIP(),
UserAgent: c.Request.UserAgent(),
Result: "success",
})
```

View File

@@ -0,0 +1,58 @@
# 日志格式规范
## 结构化日志
所有日志必须使用结构化格式,禁止字符串拼接。
### 正确示例
```go
log.WithFields(log.Fields{
"user_id": userID,
"action": "login",
"request_id": requestID,
"duration": duration.Milliseconds(),
}).Info("用户登录成功")
```
### 错误示例
```go
// ❌ 禁止
log.Info("用户 " + username + " 登录成功,耗时 " + duration.String())
```
## 必须字段
| 字段 | 说明 | 示例 |
|:---|:---|:---|
| request_id | 请求唯一标识 | uuid |
| user_id | 操作用户ID | 123 |
| action | 操作类型 | login, create_user |
| duration | 耗时(毫秒) | 150 |
## 日志级别
| 级别 | 使用场景 |
|:---|:---|
| ERROR | 错误,需要关注和处理 |
| WARN | 警告,可能的问题 |
| INFO | 重要业务事件 |
| DEBUG | 调试信息,生产环境关闭 |
## 敏感信息脱敏
必须脱敏的字段:
- 密码(任何形式)
- Token / Secret
- 身份证号
- 银行卡号
- 手机号(中间四位)
```go
// 脱敏工具
func maskPhone(phone string) string {
if len(phone) >= 11 {
return phone[:3] + "****" + phone[7:]
}
return "****"
}
```

View File

@@ -0,0 +1,52 @@
# 指标命名规范
## 命名格式
```
rmdc_{module}_{resource}_{action}_{unit}
```
## 命名规则
1. 全小写,下划线分隔
2.`rmdc_` 前缀开头
3. 包含模块名
4. 描述清晰的资源和动作
5. 带单位后缀(如适用)
## 常用后缀
| 后缀 | 说明 | 示例 |
|:---|:---|:---|
| _total | 计数器 | rmdc_user_auth_login_total |
| _seconds | 时间(秒) | rmdc_api_request_duration_seconds |
| _bytes | 大小(字节) | rmdc_file_size_bytes |
| _ratio | 比率 | rmdc_cache_hit_ratio |
## 示例
```go
// 计数器
rmdc_user_auth_login_total{status="success"}
rmdc_user_auth_login_total{status="failed"}
// 直方图
rmdc_user_auth_request_duration_seconds{endpoint="/api/auth/login"}
// Gauge
rmdc_user_auth_active_sessions
```
## 标签规范
- 标签名小写下划线
- 标签值使用小写
- 避免高基数标签(如 user_id
```go
// ✅ 正确
loginCounter.WithLabelValues("success").Inc()
// ❌ 错误 - 高基数
loginCounter.WithLabelValues(userID).Inc()
```

View File

@@ -0,0 +1,128 @@
#!/bin/bash
# verify-observability.sh - 验证可观测性规范
# 依赖: grep
# 用法: ./verify-observability.sh [check-type]
# check-type: all|logging|audit|metrics (默认 all)
set -e
CHECK_TYPE=${1:-all}
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="${SCRIPT_DIR}/../../.."
echo "=== RMDC 可观测性验证 ==="
echo "检查类型: ${CHECK_TYPE}"
echo ""
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
pass() { echo -e "${GREEN}[PASS]${NC} $1"; }
fail() { echo -e "${RED}[FAIL]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
# 1. 日志规范检查
check_logging() {
echo "--- 日志规范检查 ---"
# 检查是否使用结构化日志
UNSTRUCTURED=$(grep -rn 'log\.\(Info\|Error\|Warn\)f\?' --include="*.go" "${PROJECT_ROOT}" 2>/dev/null | \
grep -v "WithFields" | grep -v "_test.go" | head -10)
if [ -n "$UNSTRUCTURED" ]; then
warn "发现非结构化日志调用:"
echo "$UNSTRUCTURED"
else
pass "日志调用规范"
fi
# 检查敏感信息泄露
SENSITIVE=$(grep -rn 'password\|token\|secret' --include="*.go" "${PROJECT_ROOT}" 2>/dev/null | \
grep -i 'log\.' | grep -v "Mask\|mask\|****" | head -5)
if [ -n "$SENSITIVE" ]; then
warn "可能泄露敏感信息的日志:"
echo "$SENSITIVE"
else
pass "未发现敏感信息泄露"
fi
}
# 2. 审计规范检查
check_audit() {
echo "--- 审计规范检查 ---"
# 检查是否有审计记录
AUDIT_CALLS=$(grep -rn "audit\|Audit" --include="*.go" "${PROJECT_ROOT}" 2>/dev/null | \
grep -v "_test.go" | wc -l)
if [ "$AUDIT_CALLS" -gt 0 ]; then
pass "存在审计记录调用 ($AUDIT_CALLS 处)"
else
warn "未找到审计记录调用"
fi
# 检查关键操作是否有审计
for action in "Login\|login" "Create\|create" "Delete\|delete" "Update\|update"; do
ACTION_AUDIT=$(grep -rn "$action" --include="*.go" "${PROJECT_ROOT}" 2>/dev/null | \
grep -i "audit" | head -1)
if [ -n "$ACTION_AUDIT" ]; then
pass "操作 $action 有审计"
else
warn "操作 $action 可能缺少审计"
fi
done
}
# 3. 指标规范检查
check_metrics() {
echo "--- 指标规范检查 ---"
# 检查是否使用 prometheus
PROM_USAGE=$(grep -rn "prometheus\|Prometheus" --include="*.go" "${PROJECT_ROOT}" 2>/dev/null | \
grep -v "_test.go" | wc -l)
if [ "$PROM_USAGE" -gt 0 ]; then
pass "使用 Prometheus 指标 ($PROM_USAGE 处)"
else
warn "未找到 Prometheus 指标使用"
fi
# 检查指标命名规范
METRICS=$(grep -rn 'prometheus\.New' --include="*.go" "${PROJECT_ROOT}" 2>/dev/null | \
grep -oE 'Name:\s*"[^"]+' | grep -oE '"[^"]+' | tr -d '"')
if [ -n "$METRICS" ]; then
echo "发现的指标:"
echo "$METRICS" | while read metric; do
if echo "$metric" | grep -qE "^rmdc_"; then
pass " $metric"
else
warn " $metric (建议以 rmdc_ 开头)"
fi
done
fi
}
# 执行检查
case $CHECK_TYPE in
logging) check_logging ;;
audit) check_audit ;;
metrics) check_metrics ;;
all)
check_logging
echo ""
check_audit
echo ""
check_metrics
echo ""
echo "=== 所有检查完成 ==="
;;
*)
echo "未知检查类型: $CHECK_TYPE"
echo "可选: all|logging|audit|metrics"
exit 1
;;
esac

View File

@@ -0,0 +1,275 @@
你是一位专业的 **Agent Skills 架构师****Claude Code Skills 作者**。你的任务是:把给定的 **RMDC 系统 DDS/PRD/架构说明** 转换为一套**可落地、含设计细节**的 Claude Code Skills系统级 Skill + 模块级 Skills + 横切 Skills并输出完整目录树与每个 Skill 的 `SKILL.md`,以及从 DDS 中抽取出的关键设计内容到 `reference/`
---
# 0. 核心目标(必须遵守)
你生成的不只是“工作流提示词”,而是**能指导真实开发/对齐/审查**的 Skill 套件。
**硬性要求:每个 Skill 必须“绑定 DDS 设计细节”**
* 必须从 DDS 中抽取并落盘reference/
* 接口/API含路径、方法、请求/响应字段、错误码)
* 事件/Topic字段、版本、幂等键、重试语义
* DB 表/Schema字段、索引、约束、迁移策略
* 状态机/流程(状态、转移、守卫条件、回调、补偿)
* 授权模型(一级/二级、JWT claims、RBAC/DAC
* 关键时序跨模块调用链路、Outbox/MQTT/K8S 操作链)
* 如果 DDS 没写清楚:**必须标注 TBDTo Be Defined**,并输出“最小补充信息清单”,禁止脑补。
---
# 1. 输入(从 $ARGUMENTS 注入读取 DDS
* 源文档路径由参数传入:`$ARGUMENTS`
* 你必须进行动态注入读取输入内容(至少 6 处,且覆盖“结构→关键字→细节”):
1. 目录与上下文
* !`ls -la $(dirname "$ARGUMENTS")`
* !`file "$ARGUMENTS"`
2. DDS 正文读取(强制至少 3 段)
* !`sed -n '1,120p' "$ARGUMENTS"`
* !`sed -n '120,240p' "$ARGUMENTS"`
* !`sed -n '240,420p' "$ARGUMENTS"`
3. 设计细节抽取(强制至少 3 次 grep分别聚焦 API/事件/DB/状态机/权限)
* !`grep -nE "API|接口|路径|路由|request|response|错误码|error" "$ARGUMENTS" | head -n 80`
* !`grep -nE "事件|event|MQTT|topic|outbox|消息|payload|幂等|retry" "$ARGUMENTS" | head -n 80`
* !`grep -nE "表|schema|字段|索引|unique|constraint|migration|DDL|PostgreSQL" "$ARGUMENTS" | head -n 80`
* !`grep -nE "状态机|state|transition|流转|工单|workflow|回调|补偿" "$ARGUMENTS" | head -n 80`
* !`grep -nE "RBAC|DAC|鉴权|JWT|claim|一级授权|二级授权|TOTP|权限" "$ARGUMENTS" | head -n 80`
> 若无法读取文件:明确说明缺少源文档内容,并输出“继续所需的最小信息清单”(接口/事件/表/状态机/依赖/权限/错误码),不得臆造细节。
---
# 2 章节驱动的 reference 目录分层(强制新增)
你必须把 DDS 的**章节标题/小节标题**提取出来,并用它们来构建 `reference/` 的分层目录。reference 文件不得全部堆在一个目录里,必须按章节归档。
## 2.1 章节提取(必须动态注入至少 2 处)
你必须在生成任何 reference 文件前先输出“章节目录草案TOC并用动态注入从 DDS 中提取标题:
* !`grep -nE '^(#{1,6}\s+|[0-9]+(\.[0-9]+){0,3}\s+|第[一二三四五六七八九十]+章|第[0-9]+章|[一二三四五六七八九十]+、)' "$ARGUMENTS" | head -n 120`
* !`sed -n '1,200p' "$ARGUMENTS" | nl -ba | sed -n '1,120p'`
### 标题识别规则(启发式,必须使用)
按优先级识别章节标题:
1. Markdown 标题:`# / ## / ### ...`
2. 编号标题:`1 ``1.1 ``2.3.4 `(允许末尾带冒号)
3. 中文章标题:`第X章``第1章`
4. 中文小节:`一、二、三、``(一)(二)`
> 如果识别到的标题少于 3 个:必须进入降级策略(见 2.4),并在最终自检中标记该 DDS “章节结构提取不足” 为 FAIL。
---
## 2.2 reference 目录命名规范(必须)
* `reference/` 下目录必须采用 **有序前缀 + slug** 形式,防止乱序:
* `reference/01-<section-slug>/`
* `reference/01-<section-slug>/02-<subsection-slug>/`(可选)
* slug 规则:
* 全小写
* 非字母数字替换为 `-`
* 连续 `-` 合并
* 截断到 48 字符以内
* 章节序号来自 DDS 中的章节顺序(不是字面编号),例如:
* `01-architecture-overview/`
* `02-security-authz/`
* `03-apis/`
* `04-events-topics/`
* `05-db-schema/`
* `06-state-machine/`
---
## 2.3 reference 文件落盘规则(必须按章节放置)
你生成 reference 内容时,必须把内容放在对应章节目录下,而不是扁平化:
* `reference/<section>/apis.md`
* `reference/<section>/events-topics.md`
* `reference/<section>/db-schema.md`
* `reference/<section>/state-machine.md`
* `reference/<section>/security-model.md`
* `reference/<section>/dependencies.md`
### 章节映射要求(必须)
* 每条 reference 条目必须包含:
* `DDS-Section:` 章节标题(原文)
* `DDS-Lines:` 行号范围(如 `L120-L168` 或 “近似行号”)
* `Extract:` 结构化内容(表格/列表)
* SKILL.md 中引用 reference 时必须引用“章节路径”,例如:
* `See: reference/03-apis/apis.md`
* `See: reference/04-events-topics/events-topics.md`
---
## 2.4 降级策略(无法可靠提取标题时必须执行)
当标题提取不足(少于 3 个)或 DDS 格式混乱时:
* 仍然必须分层,但使用保底目录:
* `reference/00-unknown/`
* `reference/00-unknown/01-apis/`
* `reference/00-unknown/02-events/`
* `reference/00-unknown/03-db/`
* `reference/00-unknown/04-state-machine/`
* `reference/00-unknown/05-security/`
* 同时必须在最终自检中给出 FAIL
* FAIL 原因DDS 标题结构不可识别
* 修复建议:提供 Markdown 标题、或提供章节目录、或提供导出为 md 的版本
---
## 2.5 verify.sh 必须检查章节分层(新增校验点)
每个 Skill 的 `scripts/verify.sh` 必须检查:
* `reference/` 下至少存在 `01-*` 的章节目录(或降级 `00-unknown`
* 至少 2 个 reference 文件位于**非根目录**(在章节子目录里)
* 任意 reference 文件中必须出现 `DDS-Section:``DDS-Lines:` 字段
示例检查(可直接用):
* `find reference -maxdepth 2 -type d -name '01-*' | grep -q .`
* `grep -R "DDS-Section:" -n reference | head -n 5`
* `grep -R "DDS-Lines:" -n reference | head -n 5`
---
# 3. 系统模块(必须按此拆分)
| 模块 | 职责 | 关键技术 |
| 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 |
---
# 4. 输出目标(必须一次性给全)
我将把输出落盘到 Windows 目录:
* `C:/Users/wddsh/Documents/IdeaProjects/ProjectAGiPrompt/1-AgentSkills`
但你输出展示路径必须使用 Unix 风格(`/`)。
你需要输出:
1. 全部 Skills 的目录结构树tree
2. 每个 Skill 的 SKILL.md 完整内容frontmatter + body
3. reference/examples/scripts 中关键文件内容(只给必要内容,但 reference 必须充分)
4. 最后输出全局自检结果(逐条 PASS/FAIL + 修复建议)
---
# 5. Skill 组织架构(必须遵守)
生成以下 3 类 Skills
A) 系统级1个`rmdc-system`(跨模块一致性、依赖规则、版本/兼容策略、全局变更流程)
B) 模块级7个每个模块 1 个 Skill高频开发实现步骤 + 依赖影响检查)
C) 横切至少3个
* `designing-contracts`API/事件/Schema 契约、兼容策略、版本策略)
* `database-migrations`PostgreSQL迁移与回滚、字段演进
* `observability-audit`(日志/指标/trace/审计一致性;对齐 rmdc-audit-log
> 可按 DDS 增减,但不得少于 3 个横切 Skill。
---
# 6. 规范约束(硬性)
## 6.1 Frontmatter极其重要
* name小写字母/数字/连字符;动名词形式; 包含中文的翻译
* description必须【单行】且 <1024 字符第三人称包含功能说明 + 触发场景 + 关键词必须包含模块名必须是中英文混合的模式保证中文和英文均可被命中
* allowed-tools最小授权原则默认仅 Read/Grep/Glob/Bash除非 DDS 明确需要外部工具
* 必须出现 argument-hint提示 $ARGUMENTS 格式
## 6.2 内容精简与拆分(但必须保留设计细节)
* 删除 Claude 常识只保留 **DDS 特有设计** **可操作步骤**
* 重复内容移到 reference/SKILL.md 只保留怎么做 + 查哪里 + 怎么验
* 示例代码放 examples/只放骨架与关键接口签名
* scripts/ 必须至少 1 `verify.sh`
* 能运行并输出 PASS/FAIL
* 检查契约文件存在迁移文件存在接口/事件/表关键字是否匹配 DDS 抽取内容可用 grep
* 写清依赖bashgrepsedgo test 可选
## 6.3 工作流(计划-验证-执行)
每个 SKILL.md 必须包含以下结构
* Plan产物清单 + 决策点涉及哪些模块改动边界是否影响契约/事件/
* VerifyChecklist可勾选+ 明确验证点契约兼容topic schemamigration 可回滚RBAC/DAC 不破坏
* Execute可操作步骤命令化短句按顺序
* Pitfalls3~8 条常见坑必须模块相关且至少 2 条引用 reference 的具体内容
## 6.4 参数与动态上下文
* 每个 Skill 必须使用 $ARGUMENTS模块名变更类型输入文档路径目标目录
* 每个 Skill 必须至少包含 2 !`command` 动态注入示例 API/事件//运行测试
* 命令需类 Unix shell 可执行避免 OS 特定路径
## 6.5 质量标准(新增:设计细节覆盖率门槛)
* 每个模块 Skill reference/ 必须覆盖至少 3 类设计要素API/事件/DB/状态机/权限/依赖
* 每个模块 Skill reference 不足最终自检必须 FAIL并给补齐策略
* 每个 SKILL.md 主体 <500
---
# 7. 特殊要求(与模块强相关,必须引用 reference 的设计细节)
* rmdc-core鉴权/路由变更影响检查API契约错误码JWT claims)→ 引用 `reference/apis.md` + `reference/security-model.md`
* rmdc-jenkins-branch-dacDAC 规则回归 + 最小权限Jenkins API + MinIO 验证点 引用 `reference/security-model.md` + `reference/apis.md`
* rmdc-exchange-hubMQTT topic/指令生命周期契约 + 幂等处理 引用 `reference/events-topics.md` + `reference/state-machine.md`
* rmdc-watchdogK8S API 安全边界 + TOTP 二级授权 引用 `reference/security-model.md` + `reference/apis.md`
* rmdc-project-management一级授权中心字段/状态变更影响 引用 `reference/db-schema.md` + `reference/state-machine.md`
* rmdc-audit-log审计不可篡改/字段完整性/写入链路 引用 `reference/db-schema.md` + `reference/dependencies.md`
* rmdc-user-authRBAC/JWT/会话安全兼容与回归 引用 `reference/security-model.md` + `reference/apis.md`
---
# 8. 生成步骤(必须按顺序输出)
步骤1先给出 Skills 清单系统级/模块级/横切并为每个 Skill 提供 2~3 name 候选最终选择 1 name附一句理由
步骤2输出总目录树Unix 路径
步骤3依次输出每个 Skill SKILL.md完整内容
步骤4输出 supporting filesreference/examples/scripts文件路径 -> 文件内容”逐个输出reference 必须充分)
步骤5输出全局 Verify Checklist 自检结果(逐条 PASS/FAIL + 修复建议)
---
# 9. 输出风格(新增:避免“空话”)
* 禁止只写“检查 API 兼容/检查事件一致性”这种空话
* 必须写成可执行的审查动作,例如:
* “在 `reference/events-topics.md` 找到 topic 列表,对照仓库 grep 出 publish/subscribe 点”
* “校验 JWT claims 是否包含 `tenant_id/project_id/role`(来自 `reference/security-model.md`)”
* “migration 必须包含 down SQLverify.sh grep 检查 `-- +migrate Down` 或回滚段落存在”

View File

@@ -3,9 +3,9 @@
# 输入
- 源文档路径由参数传入:$ARGUMENTS
- 你必须使用动态注入读取输入内容(至少 2 处):
- !`ls -la $(dirname "$ARGUMENTS")`
- !`sed -n '1,200p' "$ARGUMENTS"`
- (可选)!`grep -nE "模块|module|service|接口|API|事件|MQTT|topic|表|schema|RBAC|鉴权|状态机" "$ARGUMENTS" | head -n 50`
- !`ls -la $(dirname "$ARGUMENTS")`
- !`sed -n '1,200p' "$ARGUMENTS"`
- (可选)!`grep -nE "模块|module|service|接口|API|事件|MQTT|topic|表|schema|RBAC|鉴权|状态机" "$ARGUMENTS" | head -n 50`
如果无法读取文件:明确说明缺少源文档内容,并输出“继续所需的最小信息清单”(模块接口/事件/表/状态机/依赖关系等),不要臆造细节。
@@ -112,4 +112,8 @@ scripts/
### claude name命名 rmdc-watchdog-skill
watchdog模块的设计文档说明见目录 C:\Users\wddsh\Documents\IdeaProjects\ProjectAGiPrompt\8-CMII-RMDC\6-rmdc-watchdog
请将详细系统设计转换为单独的SKILL
### claude name命名 rmdc-user-auth-skill
user-auth模块的设计文档说明见C:\Users\wddsh\Documents\IdeaProjects\ProjectAGiPrompt\8-CMII-RMDC\9-rmdc-user-auth\2-user-auth-DDS.md
请将详细系统设计转换为单独的SKILL

View File

@@ -0,0 +1 @@
请你将下面的AgentSkill的FrontFormatter 转换为中英文的形式,要求翻译的尽可能贴切实际开发,可以修改原始的英文

View File

@@ -17,3 +17,10 @@
你是一位专业的 Agent Skills 架构师与 Claude Code Skills 作者。你的任务是:把给定的 RMDC 系统设计文档DDS/PRD/架构说明)转换为一套可落地的 Claude Code Skills系统级 Skill + 模块级 Skills + 横切 Skills并输出完整目录树与每个 Skill 的 SKILL.md。
请你分析并修改 1-AgentSkills/coding-go-gin-gorm
1. 现在我发现LLM很少能够拉到reference里面的详细内容
2. 现在很多地方定义了api的回复的错误code请统一回复的Code定义
3. 回复的Code定义中不要出现HTTP的错误code不需要按照模块区分错误Code
4. 回复结构体请统一标准为 1-AgentSkills/coding-go-gin-gorm/reference/api-response-spec.md

View File

@@ -999,4 +999,4 @@ type AuthorizationInfo struct {
| [项目管理PRD](1-rmdc-project-management-PRD.md) | 产品需求文档 |
| [项目状态说明](4-project-状态说明.md) | 状态转换说明 |
| [工单模块DDS](../7-rmdc-work-procedure/1-rmdc-work-procedure-DDS.md) | 工单流程设计 |
| [用户权限DDS](../9-rmdc-user-auth/1-user-auth-DDS.md) | 用户权限设计 |
| [用户权限DDS](../9-rmdc-user-auth/1-user-auth-PRD.md) | 用户权限设计 |

View File

@@ -0,0 +1,361 @@
## 省份及地级市列表
**河北省**
河北省-石家庄市
河北省-唐山市
河北省-秦皇岛市
河北省-邯郸市
河北省-邢台市
河北省-保定市
河北省-张家口市
河北省-承德市
河北省-沧州市
河北省-廊坊市
河北省-衡水市
**山西省**
山西省-太原市
山西省-大同市
山西省-阳泉市
山西省-长治市
山西省-晋城市
山西省-朔州市
山西省-晋中市
山西省-运城市
山西省-忻州市
山西省-临汾市
山西省-吕梁市
**内蒙古自治区**
内蒙古自治区-呼和浩特市
内蒙古自治区-包头市
内蒙古自治区-乌海市
内蒙古自治区-赤峰市
内蒙古自治区-通辽市
内蒙古自治区-鄂尔多斯市
内蒙古自治区-呼伦贝尔市
内蒙古自治区-巴彦淖尔市
内蒙古自治区-乌兰察布市
**辽宁省**
辽宁省-沈阳市
辽宁省-大连市
辽宁省-鞍山市
辽宁省-抚顺市
辽宁省-本溪市
辽宁省-丹东市
辽宁省-锦州市
辽宁省-营口市
辽宁省-阜新市
辽宁省-辽阳市
辽宁省-盘锦市
辽宁省-铁岭市
辽宁省-朝阳市
辽宁省-葫芦岛市
**吉林省**
吉林省-长春市
吉林省-吉林市
吉林省-四平市
吉林省-辽源市
吉林省-通化市
吉林省-白山市
吉林省-松原市
吉林省-白城市
**黑龙江省**
黑龙江省-哈尔滨市
黑龙江省-齐齐哈尔市
黑龙江省-鸡西市
黑龙江省-鹤岗市
黑龙江省-双鸭山市
黑龙江省-大庆市
黑龙江省-伊春市
黑龙江省-佳木斯市
黑龙江省-七台河市
黑龙江省-牡丹江市
黑龙江省-黑河市
黑龙江省-绥化市
**江苏省**
江苏省-南京市
江苏省-无锡市
江苏省-徐州市
江苏省-常州市
江苏省-苏州市
江苏省-南通市
江苏省-连云港市
江苏省-淮安市
江苏省-盐城市
江苏省-扬州市
江苏省-镇江市
江苏省-泰州市
江苏省-宿迁市
**浙江省**
浙江省-杭州市
浙江省-宁波市
浙江省-温州市
浙江省-嘉兴市
浙江省-湖州市
浙江省-绍兴市
浙江省-金华市
浙江省-衢州市
浙江省-舟山市
浙江省-台州市
浙江省-丽水市
**安徽省**
安徽省-合肥市
安徽省-芜湖市
安徽省-蚌埠市
安徽省-淮南市
安徽省-马鞍山市
安徽省-淮北市
安徽省-铜陵市
安徽省-安庆市
安徽省-黄山市
安徽省-滁州市
安徽省-阜阳市
安徽省-宿州市
安徽省-六安市
安徽省-亳州市
安徽省-池州市
安徽省-宣城市
**福建省**
福建省-福州市
福建省-厦门市
福建省-三明市
福建省-莆田市
福建省-泉州市
福建省-漳州市
福建省-南平市
福建省-龙岩市
福建省-宁德市
**江西省**
江西省-南昌市
江西省-景德镇市
江西省-萍乡市
江西省-九江市
江西省-新余市
江西省-鹰潭市
江西省-赣州市
江西省-吉安市
江西省-宜春市
江西省-抚州市
江西省-上饶市
**山东省**
山东省-济南市
山东省-青岛市
山东省-淄博市
山东省-枣庄市
山东省-东营市
山东省-烟台市
山东省-潍坊市
山东省-济宁市
山东省-泰安市
山东省-威海市
山东省-日照市
山东省-临沂市
山东省-德州市
山东省-聊城市
山东省-滨州市
山东省-菏泽市
**河南省**
河南省-郑州市
河南省-开封市
河南省-洛阳市
河南省-平顶山市
河南省-安阳市
河南省-鹤壁市
河南省-新乡市
河南省-焦作市
河南省-濮阳市
河南省-许昌市
河南省-漯河市
河南省-三门峡市
河南省-南阳市
河南省-商丘市
河南省-信阳市
河南省-周口市
河南省-驻马店市
**湖北省**
湖北省-武汉市
湖北省-黄石市
湖北省-十堰市
湖北省-宜昌市
湖北省-襄阳市
湖北省-鄂州市
湖北省-荆门市
湖北省-孝感市
湖北省-荆州市
湖北省-黄冈市
湖北省-咸宁市
湖北省-随州市
**湖南省**
湖南省-长沙市
湖南省-株洲市
湖南省-湘潭市
湖南省-衡阳市
湖南省-邵阳市
湖南省-岳阳市
湖南省-常德市
湖南省-张家界市
湖南省-益阳市
湖南省-郴州市
湖南省-永州市
湖南省-怀化市
湖南省-娄底市
**广东省**
广东省-广州市
广东省-韶关市
广东省-深圳市
广东省-珠海市
广东省-汕头市
广东省-佛山市
广东省-江门市
广东省-湛江市
广东省-茂名市
广东省-肇庆市
广东省-惠州市
广东省-梅州市
广东省-汕尾市
广东省-河源市
广东省-阳江市
广东省-清远市
广东省-东莞市
广东省-中山市
广东省-潮州市
广东省-揭阳市
广东省-云浮市
**广西壮族自治区**
广西壮族自治区-南宁市
广西壮族自治区-柳州市
广西壮族自治区-桂林市
广西壮族自治区-梧州市
广西壮族自治区-北海市
广西壮族自治区-防城港市
广西壮族自治区-钦州市
广西壮族自治区-贵港市
广西壮族自治区-玉林市
广西壮族自治区-百色市
广西壮族自治区-贺州市
广西壮族自治区-河池市
广西壮族自治区-来宾市
广西壮族自治区-崇左市
**海南省**
海南省-海口市
海南省-三亚市
海南省-三沙市
海南省-儋州市
**四川省**
四川省-成都市
四川省-自贡市
四川省-攀枝花市
四川省-泸州市
四川省-德阳市
四川省-绵阳市
四川省-广元市
四川省-遂宁市
四川省-内江市
四川省-乐山市
四川省-南充市
四川省-眉山市
四川省-宜宾市
四川省-广安市
四川省-达州市
四川省-雅安市
四川省-巴中市
四川省-资阳市
**贵州省**
贵州省-贵阳市
贵州省-六盘水市
贵州省-遵义市
贵州省-安顺市
贵州省-毕节市
贵州省-铜仁市
**云南省**
云南省-昆明市
云南省-曲靖市
云南省-玉溪市
云南省-保山市
云南省-昭通市
云南省-丽江市
云南省-普洱市
云南省-临沧市
**西藏自治区**
西藏自治区-拉萨市
西藏自治区-日喀则市
西藏自治区-昌都市
西藏自治区-林芝市
西藏自治区-山南市
西藏自治区-那曲市
**陕西省**
陕西省-西安市
陕西省-铜川市
陕西省-宝鸡市
陕西省-咸阳市
陕西省-渭南市
陕西省-汉中市
陕西省-延安市
陕西省-榆林市
陕西省-安康市
陕西省-商洛市
**甘肃省**
甘肃省-兰州市
甘肃省-嘉峪关市
甘肃省-金昌市
甘肃省-白银市
甘肃省-天水市
甘肃省-武威市
甘肃省-张掖市
甘肃省-平凉市
甘肃省-酒泉市
甘肃省-庆阳市
甘肃省-定西市
甘肃省-陇南市
**青海省**
青海省-西宁市
青海省-海东市
**宁夏回族自治区**
宁夏回族自治区-银川市
宁夏回族自治区-石嘴山市
宁夏回族自治区-吴忠市
宁夏回族自治区-固原市
宁夏回族自治区-中卫市
**新疆维吾尔自治区**
新疆维吾尔自治区-乌鲁木齐市
新疆维吾尔自治区-克拉玛依市
新疆维吾尔自治区-吐鲁番市
新疆维吾尔自治区-哈密市
**直辖市(与省平级)**
北京市
天津市
上海市
重庆市
**特别行政区**
香港特别行政区
澳门特别行政区
**台湾省**
台湾省

View File

@@ -0,0 +1,954 @@
# RMDC 用户认证模块详细设计说明书 (DDS)
**产品名称**: RMDC 用户认证模块 (rmdc-user-auth)
**版本**: v2.0
**编制日期**: 2026-01-27
---
## 1. 概述
### 1.1 模块定位
`rmdc-user-auth` 提供 RMDC 统一的用户认证、账户生命周期管理与权限服务,支持 RSA 加密登录、JWT 鉴权、密码过期策略,以及通过工单驱动的用户注册/管理审批流程。
### 1.2 核心职责
1. **身份认证**RSA-OAEP 密码加密 + bcrypt 校验,颁发 4h 有效的 JWT。
2. **账号管理**:用户 CRUD、密码修改、个人资料更新支持密码过期、首次登录强制改密与状态控制active/locked/disabled
3. **审批工作流集成**:用户注册/管理通过 `rmdc-work-procedure` 工单审批,遵循"谁注册谁管理"。
4. **权限服务**
1. Jenkins 分支层级权限Org/Repo/Branch权限对外暴露权限检查接口。
2. Project 项目权限(数据权限)权限
3. DeliveryUpdate 微服务更新权限
5. **系统配置**RSA 密钥对、登录策略、注册开关等配置的存取与缓存。
### 1.3 版本修订历史
| 版本 | 日期 | 修订内容 |
|:---|:---|:---|
| v1.0 | 2026-01-23 | 基于现有代码首次形成 DDS覆盖认证、工单、权限与数据模型 |
| v2.0 | 2026-01-27 | 新增用户有效期字段、强制改密机制、用户注册/管理工单流程详细设计、模块依赖关系流程图 |
---
## 2. 系统架构
### 2.1 模块依赖关系
```mermaid
graph TB
subgraph 核心层
Core[rmdc-core<br/>API Gateway]
end
subgraph 用户与权限
UA[rmdc-user-auth<br/>用户认证/权限]
end
subgraph 业务协作
PM[rmdc-project-management<br/>项目管理]
WP[rmdc-work-procedure<br/>工单流程]
JB[rmdc-jenkins-branch-dac<br/>Jenkins分支权限]
end
subgraph 基础设施
CMN[rmdc-common<br/>公共模块]
Aud[rmdc-audit-log<br/>审计日志]
end
Core --> UA
Core --> PM
Core --> WP
UA -->|用户注册/管理工单| WP
UA -->|Jenkins资源查询| JB
UA --> CMN
UA --> Aud
PM -->|用户鉴权/查询| UA
PM -->|项目权限管理| UA
PM -->|项目工单| WP
WP -->|用户状态回调| UA
WP -->|项目状态回调| PM
```
### 2.2 用户模块与工单模块依赖关系详图
```mermaid
graph TB
subgraph rmdc_core["rmdc-core (入口层)"]
INIT[模块初始化]
end
subgraph rmdc_user_auth["rmdc-user-auth (用户认证层)"]
UH[UserHandler<br/>用户接口]
UWH[UserWorkflowHandler<br/>用户工单接口]
US[UserService<br/>用户服务]
URS[UserRegistrationService<br/>注册工单服务]
UMS[UserManagementService<br/>管理工单服务]
end
subgraph rmdc_work_procedure["rmdc-work-procedure (工单层)"]
WH[WorkflowHandler<br/>工单接口]
WS[WorkflowService<br/>工单服务]
WC[WorkflowCallbacks<br/>状态回调]
end
INIT -->|注入用户状态回调| WS
INIT -->|注入工单创建器| US
UH --> US
UWH --> URS
UWH --> UMS
URS -->|创建注册工单| WS
UMS -->|创建管理工单| WS
WC -->|审批通过: 激活用户| US
WC -->|审批打回: 通知修改| URS
WC -->|撤销: 删除待审批用户| US
WC -->|管理审批通过: 执行变更| UMS
```
### 2.3 接口注入机制
采用 **接口注入(依赖注入)** 方式实现模块间回调,与项目管理模块保持一致的架构模式。
#### 2.3.1 工单模块回调用户模块的接口
```go
// UserStatusUpdater 用户状态更新接口
// 由 rmdc-core 在初始化时注入,工单模块状态变更时调用
type UserStatusUpdater interface {
// UpdateUserStatus 更新用户状态(审批通过时激活)
UpdateUserStatus(userID int64, status string) error
// ActivateUser 激活用户(注册审批通过)
ActivateUser(userID int64) error
// DeletePendingUser 删除待审批用户(工单撤销时)
DeletePendingUser(userID int64) error
// ExecuteUserManagement 执行用户管理操作(管理审批通过)
ExecuteUserManagement(userID int64, action string, payload map[string]interface{}) error
}
```
#### 2.3.2 用户模块调用工单模块的接口
```go
// WorkflowCreator 工单创建接口
// 由 rmdc-core 在初始化时注入,用户模块通过此接口创建工单
type WorkflowCreator interface {
// CreateRegistrationWorkflow 创建用户注册工单
CreateRegistrationWorkflow(ctx context.Context, req *RegistrationWorkflowRequest) (string, error)
// CreateManagementWorkflow 创建用户管理工单
CreateManagementWorkflow(ctx context.Context, req *ManagementWorkflowRequest) (string, error)
}
```
### 2.4 技术栈
- Gin (HTTP 路由)
- GORM (ORM依赖 `models.DatabaseConnections` 提供多库连接User/Jenkins/Core/Workflow)
- JWT (HS256)
- RSA-OAEP (2048) 前端密码加密,密钥 30 天轮换
- bcrypt (密码存储)
- 内部模块依赖:`rmdc-work-procedure`(工单)、`rmdc-jenkins-branch-dac`Jenkins 资源)、`rmdc-common`(日志、返回码、模型)
### 2.5 路由与中间件分层
- `/api/auth/*`:免 token含获取 RSA 公钥、登录、注册。
- `/api/users``/api/user``/api/permissions`:需 `AuthMiddleware(jwtSecret)` 校验 JWT部分再叠加 `RequireAdmin()`
- 中间件:
- `AuthMiddleware`:解析 Bearer JWT校验签名和用户状态为 `active`,注入用户上下文。
- `RequireAdmin`:判定角色包含 `admin` 字样superadmin/admin
### 2.6 组件关系
- Handler 层:`auth_handler``user_handler``permission_handler` 注册 HTTP 路由。
- Service 层:`AuthService``RSAService``UserService``JenkinsPermissionService``SystemConfigService`、注册/管理工单服务。
- DAO 层:`UserDao``RSAKeypairDao``JenkinsPermissionDao``BasePermissionDao``SystemConfigDao``JenkinsDao`
- 公共接口:`pkg/permission.PermissionChecker` 对外暴露权限检查,供其他模块调用。
---
## 3. 认证与登录设计
### 3.1 登录流程RSA + JWT
1. 前端调用 `GET /api/auth/rsa/public-key` 获取当前有效公钥30 天有效,过期自动轮换)。
2. 前端用 RSA-OAEP(SHA-256, 2048) 加密密码,提交 `POST /api/auth/login`,字段 `encrypted_password`
3. 后端 `RSAService` 解密,`AuthService` 用 bcrypt 校验 `users.password_hash`
4. 生成 HS256 JWT默认有效期 4hClaims 包含用户基础信息与 `status`
5. 返回 Token 与用户信息,并返回以下标识:
- `must_change_password`: 首次登录或密码重置后需强制修改密码
- `password_expire_days`: 密码剩余有效天数7 天内提示)
- `account_expire_days`: 账户剩余有效天数7 天内提示)
### 3.2 密钥与密码策略
- RSA 密钥:`rsa_keypairs` 表存储 PEM对外只暴露公钥过期自动生成新对异步清理过期数据。
- 密码哈希bcrypt 默认成本;密码有效期 3 个月(注册/重置/直改都会刷新过期时间)。
- 账户状态active/locked/disabledJWT 校验时必须 active。
- 首次登录强制改密:新注册用户或重置密码后,`must_change_password` 标识为 true。
- 登录失败计数:字段存在,逻辑留有 TODO未实现锁定策略
### 3.3 JWT 中间件行为
- 解析 Authorization: Bearer <token>,校验签名与过期。
- 校验 `claims.Status == "active"`,否则 401。
- 注入上下文键:`user_id/username/english_name/phone/group_name/role/dev_role/status`
### 3.4 缺省/限制
- 无刷新接口Token 过期需重新登录。
- 无服务器端注销接口(前端丢弃 Token
---
## 4. 用户生命周期管理
### 4.1 用户状态定义
| 状态 | 说明 | 触发条件 |
|:---|:---|:---|
| `disabled` | 待审批状态 | 用户注册后默认状态 |
| `active` | 正常激活 | 注册工单审批通过 |
| `locked` | 临时锁定 | 登录失败次数过多 / 管理员手动锁定 |
### 4.2 用户生命周期状态机
```mermaid
stateDiagram-v2
[*] --> disabled: 用户注册
disabled --> disabled: 保存草稿
disabled --> disabled: 工单打回,等待修改
disabled --> active: 注册工单审批通过
disabled --> [*]: 工单撤销,删除用户
active --> active: 正常使用
active --> active: 信息更新
active --> locked: 登录失败锁定
active --> disabled: 管理员禁用
active --> [*]: 管理员删除
locked --> active: 解锁
locked --> disabled: 管理员禁用
note right of disabled: 用户刚注册,等待审批
note right of active: 正常使用状态
note right of locked: 临时锁定,可解锁
```
### 4.3 用户有效期机制
#### 4.3.1 有效期设置规则
| 用户类型 | 有效期选项 | 默认值 | 说明 |
|:---|:---|:---|:---|
| SuperAdmin 创建的用户 | 无限制 / 自定义 | 永久 | SuperAdmin 可设置任意有效期 |
| Admin 创建的用户 | 1个月/3个月/6个月/1年 | 3个月 | 必须设置有效期 |
| Normal 创建的用户 | 1个月/3个月/6个月/1年 | 3个月 | 必须设置有效期 |
#### 4.3.2 有效期字段设计
```go
// 用户表新增字段
AccountExpiresAt *time.Time `json:"account_expires_at"` // 账户有效期
```
#### 4.3.3 有效期检查逻辑
1. **登录时检查**:若 `account_expires_at` 不为空且已过期,拒绝登录并返回账户已过期提示
2. **接口检查**JWT 中间件校验用户状态时,同时检查账户有效期
3. **提前提醒**:账户有效期剩余 7 天内,登录时返回提醒信息
### 4.4 强制修改密码机制
#### 4.4.1 触发条件
| 条件 | 字段标识 | 处理方式 |
|:---|:---|:---|
| 首次登录 | `must_change_password = true` | 登录成功后跳转改密页面 |
| 密码重置后 | `must_change_password = true` | 使用临时密码登录后强制改密 |
| 密码过期 | `password_expires_at` 已过期 | 拒绝登录,提示密码已过期 |
#### 4.4.2 密码过期时间线
```mermaid
flowchart TB
A["创建用户 / 重置密码"]
B["password_expires_at = now + 90天\nmust_change_password = true"]
C["首次登录"]
D["强制修改密码"]
E["修改密码后:\npassword_expires_at = now + 90天\nmust_change_password = false"]
F["正常使用"]
G["第83天提醒"]
H["第90天强制过期"]
A --> B --> C --> D --> E --> F --> G --> H
```
---
## 5. 用户注册与管理权限
### 5.1 角色与注册/管理权限矩阵
| 操作者角色 | 可注册角色 | 可管理范围 | 说明 |
|:---|:---|:---|:---|
| SuperAdmin | superadmin/admin/normal/third | 所有用户 | 完全权限 |
| Admin | normal/third | 自己注册的用户 | 遵循"谁注册谁管理" |
| Normal | third | 自己注册的用户 | 仅可注册三方用户 |
| Third | - | - | 无注册和管理权限 |
### 5.2 管理操作权限矩阵
| 操作 | SuperAdmin | Admin | Normal | Third |
|:---|:---|:---|:---|:---|
| 修改用户信息 | ✅ 所有用户 | ✅ 自己注册的 | ✅ 自己注册的 | ❌ |
| 启用用户 | ✅ 所有用户 | ✅ 自己注册的 | ✅ 自己注册的 | ❌ |
| 禁用用户 | ✅ 所有用户 | ✅ 自己注册的 | ✅ 自己注册的 | ❌ |
| 删除用户 | ✅ 所有用户 | ✅ 自己注册的 | ✅ 自己注册的 | ❌ |
| 重置密码 | ✅ 所有用户 | ✅ 自己注册的 | ❌ | ❌ |
| 延长有效期 | ✅ 所有用户 | ❌ | ❌ | ❌ |
### 5.3 "谁注册谁管理"原则
1. **注册关系记录**:用户表中 `registered_by_id` 记录注册人 ID
2. **权限检查规则**
- SuperAdmin可管理所有用户
- Admin/Normal只能管理 `registered_by_id == current_user_id` 的用户
3. **审批归属**:所有用户注册/管理工单由 SuperAdmin 审批
---
## 6. 用户注册工单流程
### 6.1 工单流程概述
```mermaid
sequenceDiagram
participant User as 注册发起人
participant UA as rmdc-user-auth
participant WP as rmdc-work-procedure
participant SA as SuperAdmin
User->>UA: POST /api/users (创建用户)
UA->>UA: 创建用户记录<br/>(status=disabled, 默认密码)
UA->>WP: CreateWorkflow(user_registration)
WP-->>UA: workflow_id
UA-->>User: 返回成功工单ID
WP->>SA: 通知:新用户注册待审批
alt 审批通过
SA->>WP: POST /api/workflow/approve
WP->>UA: ActivateUser(user_id)
UA->>UA: status = active
UA-->>WP: success
WP->>User: 通知:用户注册已通过
else 审批打回
SA->>WP: POST /api/workflow/return
WP->>User: 通知:请修改用户信息
User->>UA: PUT /api/users/:id (修改信息)
User->>WP: POST /api/workflow/resubmit
else 撤销
User->>WP: POST /api/workflow/revoke
WP->>UA: DeletePendingUser(user_id)
UA->>UA: DELETE users WHERE status=disabled
end
```
### 6.2 工单状态与用户状态映射
| 工单事件 | 工单状态 | 用户状态 | 说明 |
|:---|:---|:---|:---|
| create | pending_review | disabled | 创建用户并提交审核 |
| approve | approved | active | 审核通过,激活用户 |
| return | returned | disabled (保持) | 打回修改,用户状态不变 |
| resubmit | pending_review | disabled (保持) | 重新提交审核 |
| revoke | revoked | (删除) | 撤销工单,删除待审批用户 |
### 6.3 注册工单业务载荷
```go
// RegistrationWorkflowPayload 注册工单业务载荷
type RegistrationWorkflowPayload struct {
TargetUserID int64 `json:"target_user_id"` // 被注册用户ID
TargetUsername string `json:"target_username"` // 被注册用户名
TargetRole string `json:"target_role"` // 被注册用户角色
RegisteredByID int64 `json:"registered_by_id"` // 注册人ID
RegisteredByName string `json:"registered_by_name"` // 注册人姓名
AccountExpiresAt time.Time `json:"account_expires_at"` // 账户有效期
RegistrationReason string `json:"registration_reason"` // 注册原因
}
```
### 6.4 注册接口设计要点
1. **不暴露密码设置**:注册接口不接收密码参数,后端自动生成默认密码(如 `Rmdc@2026`
2. **必须设置有效期**:非 SuperAdmin 创建用户时必须选择有效期1个月/3个月/6个月/1年
3. **自动创建工单**:用户创建接口内部调用工单模块创建审批工单
4. **工单初始状态**:直接进入 `pending_review`,由 SuperAdmin 审批
---
## 7. 用户管理工单流程
### 7.1 管理操作类型
| 操作类型代码 | 操作名称 | 说明 | 需要审批 |
|:---|:---|:---|:---|
| `update` | 修改用户信息 | 修改基本信息(姓名、邮箱等) | 是 |
| `enable` | 启用用户 | 将 disabled 用户改为 active | 是 |
| `disable` | 禁用用户 | 将 active 用户改为 disabled | 是 |
| `delete` | 删除用户 | 软删除用户 | 是 |
| `reset_password` | 重置密码 | 重置为默认密码 | 是 |
| `extend_validity` | 延长有效期 | 延长账户有效期 | 是 |
### 7.2 工单流程
> **重要**:前端不允许直接调用创建工单接口,工单由用户管理接口(如 `PUT /api/users/:id`)内部自动创建。
```mermaid
sequenceDiagram
participant Operator as 操作人
participant UA as rmdc-user-auth
participant WP as rmdc-work-procedure
participant SA as SuperAdmin
Operator->>UA: PUT /api/users/:id (修改用户信息)
Note right of Operator: 或 POST /api/users/:id/enable|disable|delete
UA->>UA: CheckManagementPermission
UA->>UA: 记录原始数据快照
UA->>WP: CreateWorkflow(user_management)
WP-->>UA: workflow_id
UA-->>Operator: 返回成功工单ID
WP->>SA: 通知:用户管理待审批
alt 审批通过
SA->>WP: POST /api/workflow/approve
WP->>UA: ExecuteUserManagement(action, payload)
UA->>UA: 执行对应操作(update/enable/disable/delete)
UA-->>WP: success
WP->>Operator: 通知:管理操作已通过
else 审批打回
SA->>WP: POST /api/workflow/return
WP->>Operator: 通知:请重新提交
end
```
### 7.3 管理工单业务载荷
```go
// ManagementWorkflowPayload 管理工单业务载荷
type ManagementWorkflowPayload struct {
TargetUserID int64 `json:"target_user_id"` // 目标用户ID
TargetUsername string `json:"target_username"` // 目标用户名
ActionType string `json:"action_type"` // 操作类型
OperatorID int64 `json:"operator_id"` // 操作人ID
OperatorName string `json:"operator_name"` // 操作人姓名
OriginalData map[string]interface{} `json:"original_data"` // 原始数据快照
ModifiedData map[string]interface{} `json:"modified_data"` // 修改后数据
Reason string `json:"reason"` // 操作原因
}
```
### 7.4 管理操作执行器
```go
// UserManagementExecutor 用户管理执行器
type UserManagementExecutor interface {
// UpdateUserInfo 更新用户信息
UpdateUserInfo(userID int64, payload map[string]interface{}) error
// EnableUser 启用用户
EnableUser(userID int64) error
// DisableUser 禁用用户
DisableUser(userID int64) error
// DeleteUser 删除用户
DeleteUser(userID int64) error
// ResetPassword 重置密码
ResetPassword(userID int64) error
// ExtendValidity 延长有效期
ExtendValidity(userID int64, newExpiresAt time.Time) error
}
```
---
## 8. 权限模型
### 8.1 统一权限架构
RMDC 系统采用**专用权限表**的设计模式,针对不同业务场景使用独立的权限表结构,以满足复杂的权限控制需求。同时定义统一的权限模块枚举,便于扩展和维护。
#### 8.1.1 权限模块枚举 (PermissionModule)
```go
// PermissionModule 权限模块枚举
type PermissionModule string
const (
// 用户模块权限
ModuleUserRegister PermissionModule = "user_register" // 用户注册
ModuleUserManage PermissionModule = "user_manage" // 用户管理
ModuleUserPermission PermissionModule = "user_permission" // 用户权限管理
// 项目模块权限
ModuleProjectCreate PermissionModule = "project_create" // 项目创建
ModuleProjectView PermissionModule = "project_view" // 项目查看
ModuleProjectEdit PermissionModule = "project_edit" // 项目编辑
ModuleProjectExport PermissionModule = "project_export" // 项目导出
ModuleProjectAuth PermissionModule = "project_auth" // 项目授权管理
// Jenkins 模块权限
ModuleJenkinsView PermissionModule = "jenkins_view" // Jenkins 查看
ModuleJenkinsBuild PermissionModule = "jenkins_build" // Jenkins 构建
ModuleJenkinsManage PermissionModule = "jenkins_manage" // Jenkins 权限管理
// 微服务更新模块权限
ModuleDeliveryView PermissionModule = "delivery_view" // 微服务查看
ModuleDeliveryUpdate PermissionModule = "delivery_update" // 微服务更新
// 工单模块权限
ModuleWorkflowView PermissionModule = "workflow_view" // 工单查看
ModuleWorkflowApprove PermissionModule = "workflow_approve" // 工单审批
)
// PermissionModuleInfo 模块信息
type PermissionModuleInfo struct {
Code PermissionModule `json:"code"` // 模块代码
Name string `json:"name"` // 模块名称
Description string `json:"description"` // 模块描述
ACLTable string `json:"acl_table"` // 关联的ACL表
}
// 模块注册表
var PermissionModules = map[PermissionModule]PermissionModuleInfo{
ModuleJenkinsView: {Code: ModuleJenkinsView, Name: "Jenkins查看", ACLTable: "jenkins_acls"},
ModuleJenkinsBuild: {Code: ModuleJenkinsBuild, Name: "Jenkins构建", ACLTable: "jenkins_acls"},
ModuleProjectView: {Code: ModuleProjectView, Name: "项目查看", ACLTable: "project_acls"},
ModuleProjectEdit: {Code: ModuleProjectEdit, Name: "项目编辑", ACLTable: "project_acls"},
// ... 其他模块
}
```
#### 8.1.2 权限表概览
| 权限类型 | 权限表 | 权限粒度 | 说明 |
|:---|:---|:---|:---|
| **Jenkins 权限** | `jenkins_acls` | Org/Repo/Branch 层级 | 支持层级继承的 CI/CD 权限 |
| **项目权限** | `project_acls` | 项目模块级 | 项目信息访问权限 |
| **用户权限缓存** | `user_permission_caches` | 用户维度 | 用户所有权限的 L2 缓存树 |
#### 8.1.3 权限检查通用规则
```go
// 权限检查通用规则:
// 1. SuperAdmin 拥有所有权限,直接放行
// 2. 根据 PermissionModule 分流到对应的 ACL 表进行检查
// 3. 权限检查结果存入 L1 内存缓存和 L2 DB 缓存,提升性能
// 4. 权限变更时同时清除 L1 和 L2 缓存
```
### 8.2 Jenkins 层级权限 (jenkins_acls)
#### 8.2.1 层级结构
```
Organization (组织)
└── Repository (仓库)
└── Branch (分支)
```
#### 8.2.2 设计原则
- **层级继承**:上级权限可覆盖下级(如 Org 级权限覆盖其下所有 Repo/Branch
- **存储最小化**:一条记录可覆盖子层级,减少数据冗余
- **权限类型**`can_view`(查看,对应 `jenkins_view`)、`can_build`(构建,对应 `jenkins_build`
#### 8.2.3 权限表设计 (jenkins_acls)
| 字段 | 说明 |
|:---|:---|
| `user_id` | 用户 ID |
| `organization_folder` | 组织文件夹(必填) |
| `repository_name` | 仓库名称(可空,空表示 Org 级权限) |
| `branch_name` | 分支名称(可空,空表示 Repo 级权限) |
| `permission_level` | 权限层级org/repo/branch |
| `can_view` | 是否可查看 |
| `can_build` | 是否可构建 |
| `granted_by` | 授权人 ID |
| `granted_at` | 授权时间 |
#### 8.2.4 权限缓存机制
- **L1 内存缓存**`permissionCache`,进程内高速缓存
- **L2 DB 缓存**`user_permission_caches` 表,存储用户所有权限的缓存树 JSON
- **懒加载接口**:按 organizations → repositories → branches 逐级加载
- **缓存失效**:权限变更时同时清除 L1 和 L2 缓存
#### 8.2.5 权限检查逻辑
```go
// CheckHierarchicalPermission 检查层级权限
// 1. 先检查 Branch 级权限
// 2. 若无则检查 Repo 级权限
// 3. 若无则检查 Org 级权限
// 4. 支持 can_build 需求判断build 需要 view + build 两个权限)
```
### 8.3 BusinessInfoRegistry 注册中心
权限模块需要查询业务模块信息(如项目填写人),但不应直接依赖具体业务模块。采用**统一注册机制**解决模块依赖。
```mermaid
graph TB
subgraph rmdc_core["rmdc-core (入口层)"]
INIT[模块初始化]
end
subgraph rmdc_user_auth["rmdc-user-auth (权限层)"]
REG[BusinessInfoRegistry<br/>业务信息注册中心]
PS[ProjectPermissionService]
JS[JenkinsPermissionService]
end
subgraph business["业务模块层"]
PM[rmdc-project-management]
JB[rmdc-jenkins-branch-dac]
end
INIT -->|注册业务查询器| REG
INIT -->|注入权限检查器| PM
INIT -->|注入权限检查器| JB
PS --> REG
JS --> REG
REG -.->|查询业务信息| PM
REG -.->|查询Jenkins信息| JB
```
#### 8.3.1 注册接口定义
```go
// BusinessInfoQuerier 业务信息查询接口(通用基类)
type BusinessInfoQuerier interface {
GetModuleCode() string
}
// ProjectInfoQuerier 项目信息查询接口
type ProjectInfoQuerier interface {
BusinessInfoQuerier
GetProjectFillerID(ctx context.Context, projectID string) (int64, error)
}
// JenkinsInfoQuerier Jenkins 信息查询接口
type JenkinsInfoQuerier interface {
BusinessInfoQuerier
GetOrganizations(ctx context.Context) ([]string, error)
GetRepositories(ctx context.Context, org string) ([]string, error)
GetBranches(ctx context.Context, org, repo string) ([]string, error)
}
// ModulePermissionChecker 权限检查器接口(通用)
type ModulePermissionChecker interface {
CheckPermission(ctx context.Context, userID int64, userRole string,
resourceID, resourceType, permissionType string) (bool, error)
GetAccessibleResourceIDs(ctx context.Context, userID int64, userRole string,
resourceType string) ([]string, error)
}
```
### 8.4 项目权限ProjectACL
#### 8.4.1 设计原则
- 权限粒度:**模块级**basic_info/business_info/environment_info/middleware_info/authorization_info
- SuperAdmin 默认拥有所有权限,无需存储 ACL 记录
- 项目填写人自动获得非授权模块的 view 权限
#### 8.4.2 模块代码与 JSONB 映射
| 模块代码 | JSONB 字段 | 说明 |
|:---|:---|:---|
| `basic_info` | `projects.basic_info` | 省份、城市、联系人等 |
| `business_info` | `projects.deploy_business` | 部署人、版本、入口等 |
| `environment_info` | `projects.deploy_env` | 主机、网络、管理方式等 |
| `middleware_info` | `projects.deploy_middleware` | MySQL/Redis/EMQX 等 |
| `authorization_info` | `project_auth_configs.*` | TOTP 授权(仅 SuperAdmin |
#### 8.4.3 权限检查规则
```go
// CheckProjectModulePermission 规则:
// 1. SuperAdmin 拥有所有权限
// 2. authorization_info 模块仅 SuperAdmin 可访问
// 3. 项目填写人自动拥有非授权模块的 view 权限
// 4. 其他用户查询 project_acls 表
```
---
## 9. 数据模型
### 9.1 核心表概览
| 表 | 作用 | 关键字段 |
|:---|:---|:---|
| `users` | 账户信息 | username, english_username, password_hash, role, status, registered_by_id, password_expires_at, account_expires_at, must_change_password, failed_login_attempts, locked_until |
| `rsa_keypairs` | RSA 密钥对 | public_key/ private_key (PEM), expires_at |
| `jenkins_acls` | Jenkins 层级权限 | user_id, organization_folder, repository_name?, branch_name?, permission_level, can_view, can_build, granted_by |
| `project_acls` | 项目模块级权限 | user_id, project_id, module_code, can_view, can_export, granted_by |
| `user_permission_caches` | 用户权限 L2 缓存 | user_id, permission_tree (JSON), updated_at |
| `system_configs` | 系统配置 | key, value, description |
### 9.2 用户表 DDL (users)
```go
// User 用户表
type User struct {
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
Username string `gorm:"uniqueIndex;not null;size:50" json:"username"` // 中文真实姓名
EnglishUsername string `gorm:"size:100" json:"english_username"` // 英文用户名(昵称)
PasswordHash string `gorm:"not null;size:255" json:"-"` // 加密后的密码
AvatarID string `gorm:"size:50;default:'default_1'" json:"avatar_id"` // 头像ID
AvatarFrameID string `gorm:"size:50;default:'default'" json:"avatar_frame_id"` // 头像框ID
Gender string `gorm:"size:10;default:'male'" json:"gender"` // 性别: male/female
Email string `gorm:"uniqueIndex;size:100" json:"email"` // 邮箱
Phone string `gorm:"size:20" json:"phone"` // 手机号
ShortNumber string `gorm:"size:10" json:"short_number"` // 短号
WorkID string `gorm:"size:50" json:"work_id"` // 工号
GroupName string `gorm:"size:100" json:"group_name"` // 所属小组
Company string `gorm:"size:100" json:"company"` // 公司名称
DevRole string `gorm:"size:50;default:'unknown'" json:"dev_role"` // 开发角色
// 注册关系
RegisteredByID int64 `json:"registered_by_id"` // 注册人ID
RegisteredByName string `gorm:"size:64" json:"registered_by_name"` // 注册人姓名(冗余)
// 角色与状态
Role string `gorm:"not null;size:20;default:'normal'" json:"role"` // 系统角色
Status string `gorm:"not null;size:20;default:'disabled'" json:"status"` // 状态
// 密码策略
PasswordExpiresAt *time.Time `json:"password_expires_at"` // 密码过期时间
MustChangePassword bool `gorm:"default:true" json:"must_change_password"` // 是否需要强制改密
// 账户有效期(新增)
AccountExpiresAt *time.Time `json:"account_expires_at"` // 账户有效期
// 登录锁定
FailedLoginAttempts int `gorm:"default:0" json:"failed_login_attempts"`
LockedUntil *time.Time `json:"locked_until"`
// MFA
MFASecret string `gorm:"size:100" json:"-"`
// 审计字段
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
DeletedAt *time.Time `gorm:"index" json:"deleted_at,omitempty"`
LastLoginAt *time.Time `json:"last_login_at"`
}
```
### 9.3 用户表新增字段说明
| 字段 | 类型 | 说明 | 默认值 |
|:---|:---|:---|:---|
| `registered_by_name` | varchar(64) | 注册人姓名(冗余存储,便于展示) | NULL |
| `must_change_password` | bool | 是否需要强制修改密码 | true |
| `account_expires_at` | datetime | 账户有效期NULL表示永久有效 | NULL |
### 9.4 RSA 密钥表rsa_keypairs
- 存储 PEM 文本及过期时间;`RSAService` 负责加载、轮换、缓存与过期清理。
### 9.5 Jenkins 权限表jenkins_acls
- 层级列可空实现覆盖org=不空repo/branch 为空repo 级branch 空branch 级:三列全填。
- `permission_level` 归档实际层级,便于查询。
- 详见 [8.2 Jenkins 层级权限](#82-jenkins-层级权限-jenkins_acls)。
### 9.6 项目权限表project_acls
- 模块级权限控制user_id + project_id + module_code 组合唯一。
- 权限类型can_view查看、can_export导出
- 索引:`idx_project_acl_user`(用户维度)、`idx_project_acl_project`(项目+模块维度)。
- SuperAdmin 不存储记录,权限检查时直接放行。
---
## 10. 接口设计 (API)
### 10.1 认证
| 方法 | 路径 | 说明 | 鉴权 |
|:---|:---|:---|:---|
| GET | `/api/auth/rsa/public-key` | 获取 RSA 公钥 | 公共 |
| POST | `/api/auth/login` | RSA 加密密码登录,返回 JWT | 公共 |
| POST | `/api/auth/register` | 自助注册(创建 disabled 用户) | 公共 |
### 10.2 用户管理
| 方法 | 路径 | 说明 | 鉴权 |
|:---|:---|:---|:---|
| GET | `/api/users` | 列表,支持角色/状态/组/搜索/Scope | JWT |
| GET | `/api/users/:id` | 用户详情 + 权限 | JWT |
| POST | `/api/users` | 创建用户(自动创建注册工单) | JWT + Admin |
| PUT | `/api/users/:id` | 更新用户(仅用于保存草稿,正式修改需工单) | JWT + Admin |
| DELETE | `/api/users/:id` | 删除用户仅SuperAdmin可直接删除 | JWT + SuperAdmin |
| PUT | `/api/user/profile` | 更新本人资料(头像/密码) | JWT |
| PUT | `/api/user/password` | 本人改密(校验旧密码) | JWT |
| PUT | `/api/user/password/force-change` | 首次登录强制改密 | JWT |
### 10.3 权限Jenkins
| 方法 | 路径 | 说明 | 鉴权 |
|:---|:---|:---|:---|
| GET | `/api/permissions/jenkins/my-tree/organizations` | 我的组织列表 | JWT |
| POST | `/api/permissions/jenkins/my-tree/repositories` | 我的仓库(按 org | JWT |
| POST | `/api/permissions/jenkins/my-tree/branches` | 我的分支(按 org/repo | JWT |
| GET | `/api/permissions/jenkins/my-tree/full` | 我的权限树(缓存/重建) | JWT |
| GET | `/api/permissions/jenkins/check/:organization/:branch` | 检查分支权限 | JWT |
| GET | `/api/permissions/jenkins/tree` | 全量权限树Admin | JWT + Admin |
| GET | `/api/permissions/jenkins/users/role/:role` | 按角色查用户 | JWT + Admin |
| GET | `/api/permissions/jenkins/:userId` | 获取用户 Jenkins 权限 | JWT + Admin |
| POST | `/api/permissions/jenkins/assign` | 分配权限(层级覆盖) | JWT + Admin |
| POST | `/api/permissions/jenkins/copy` | 拷贝权限 | JWT + Admin |
| GET | `/api/permissions/jenkins/user-tree/:userId/organizations` | 懒加载组织 | JWT + Admin |
| GET | `/api/permissions/jenkins/user-tree/:userId/organizations/:org/repositories` | 懒加载仓库 | JWT + Admin |
| GET | `/api/permissions/jenkins/user-tree/:userId/organizations/:org/repositories/:repo/branches` | 懒加载分支 | JWT + Admin |
### 10.4 项目权限接口
| 方法 | 路径 | 说明 | 鉴权 |
|:---|:---|:---|:---|
| GET | `/api/permissions/projects/user/:userId/summary` | 获取用户项目权限摘要 | JWT + Admin |
| POST | `/api/permissions/projects/grant` | 授予项目模块权限 | JWT + Admin |
| POST | `/api/permissions/projects/revoke` | 撤销项目模块权限 | JWT + Admin |
> **注意**:前端不允许直接调用创建工单接口,用户注册/管理工单通过用户管理接口(如 `POST /api/users`、`PUT /api/users/:id`)内部自动创建。
### 10.5 系统配置
| 方法 | 路径 | 说明 | 鉴权 |
|:---|:---|:---|:---|
| GET | `/api/user/system-config` | 获取配置(为空时返回默认值) | JWT |
| PUT | `/api/user/system-config` | 更新配置 | JWT |
---
## 11. 业务流程
### 11.1 登录流程
```mermaid
sequenceDiagram
participant FE as 前端
participant UA as rmdc-user-auth
FE->>UA: GET /api/auth/rsa/public-key
UA-->>FE: 公钥
FE->>UA: POST /api/auth/login (encrypted_password)
UA->>UA: RSA 解密 + bcrypt 校验
UA->>UA: 检查账户状态/有效期/密码过期
UA->>UA: 生成 JWT(4h)
UA-->>FE: token + userDTO + 附加标识
Note over FE,UA: 附加标识:<br/>must_change_password<br/>password_expire_days<br/>account_expire_days
alt must_change_password = true
FE->>FE: 跳转强制改密页面
FE->>UA: PUT /api/user/password/force-change
UA->>UA: 更新密码,重置过期时间
UA-->>FE: 改密成功
end
```
### 11.2 用户注册工单
```mermaid
sequenceDiagram
participant User as 已登录用户
participant UA as rmdc-user-auth
participant WP as rmdc-work-procedure
participant SA as SuperAdmin
User->>UA: POST /api/users (创建用户)
UA->>UA: 权限检查:可注册该角色?
UA->>UA: 创建用户(status=disabled, 默认密码)
UA->>UA: 设置 account_expires_at
UA->>UA: 设置 must_change_password = true
UA->>WP: CreateWorkflow(user_registration, pending_review)
WP-->>UA: workflow_id
UA-->>User: 返回成功
WP->>SA: 通知待审批
alt 审批通过
SA->>WP: approve
WP->>UA: ActivateUser
UA->>UA: status = active
else 打回
SA->>WP: return
WP->>User: 通知修改
User->>UA: PUT /api/users/:id
User->>WP: resubmit
else 撤销
User->>WP: revoke
WP->>UA: DeletePendingUser
UA->>UA: DELETE (status=disabled)
end
```
### 11.3 用户管理工单
> **重要**:前端不允许直接调用创建工单接口,工单由用户管理接口内部自动创建。
```mermaid
sequenceDiagram
participant Operator as 操作人
participant UA as rmdc-user-auth
participant WP as rmdc-work-procedure
participant SA as SuperAdmin
Operator->>UA: PUT /api/users/:id (修改用户)
Note right of Operator: 或 POST /api/users/:id/enable|disable|delete
UA->>UA: CheckManagementPermission
UA->>UA: 记录原始数据快照
UA->>WP: CreateWorkflow(user_management)
WP-->>UA: workflow_id
UA-->>Operator: 返回成功
WP->>SA: 通知待审批
alt 审批通过
SA->>WP: approve
WP->>UA: ExecuteUserManagement
UA->>UA: 执行操作(update/enable/disable/delete)
else 打回
SA->>WP: return
WP->>Operator: 通知修改后重新提交
end
```
---
## 12. 安全与合规
- **传输安全**:登录密码必须使用 RSA-OAEP(SHA-256, 2048) 加密;禁止明文密码传输。
- **存储安全**:密码使用 bcryptRSA 私钥仅存 DB不下发前端敏感字段不外露。
- **账户状态**:仅 active 用户可通过 JWT 校验locked/disabled 一律拒绝。
- **密码策略**
- 默认 3 个月过期;修改/创建均刷新过期时间
- 注册返回 disabled需审批激活
- 首次登录或密码重置后强制修改密码
- **账户有效期**:非 SuperAdmin 创建的用户必须设置有效期,过期后无法登录
- **Token**HS2564h 过期;无刷新,需重登;超时后自动失效。
- **权限检查**SuperAdmin 全通Admin 只能管理/授权 normal/third权限分配需校验授予者是否具备权限。
- **"谁注册谁管理"**:非 SuperAdmin 只能管理自己注册的用户
- **TODO**登录失败次数与锁定策略尚未实现Watchdog 权限检查待补全。
---
## 13. 相关文档
| 文档 | 说明 |
|:---|:---|
| [用户认证PRD](1-user-auth-PRD.md) | 产品需求文档 |
| [用户权限设计](权限设计部分/3-用户部分-权限设计.md) | 用户注册管理权限设计 |
| [项目管理DDS](../4-rmdc-project-management/2-rmdc-project-management-DDS.md) | 参考文档结构(项目工单流程) |
| [工单模块DDS](../7-rmdc-work-procedure/1-rmdc-work-procedure-DDS.md) | 工单流程设计 |