Commit f20b8d2644f43d31b9626cf80192e69733642a6c
0 parents
chore: plan phase done
Showing
24 changed files
with
1920 additions
and
0 deletions
.githooks/pre-push
0 → 100755
.gitignore
0 → 100644
| 1 | +++ a/.gitignore | ||
| 1 | +# ==== ERP 插件推荐忽略项(skeleton-gen 追加) ==== | ||
| 2 | +# 本地运行时配置(含真实凭据,严禁入库) | ||
| 3 | +.env.local | ||
| 4 | +.env.*.local | ||
| 5 | + | ||
| 6 | +# Java / Maven | ||
| 7 | +target/ | ||
| 8 | +*.class | ||
| 9 | + | ||
| 10 | +# Node / 前端构建产物 | ||
| 11 | +node_modules/ | ||
| 12 | +dist/ | ||
| 13 | +build/ | ||
| 14 | +coverage/ | ||
| 15 | + | ||
| 16 | +# IDE | ||
| 17 | +.idea/ | ||
| 18 | +.vscode/ | ||
| 19 | +*.iml | ||
| 20 | + | ||
| 21 | +# OS | ||
| 22 | +.DS_Store | ||
| 23 | +Thumbs.db | ||
| 24 | + | ||
| 25 | +# 日志 | ||
| 26 | +*.log | ||
| 27 | +logs/ | ||
| 28 | + | ||
| 29 | +# 插件运行时临时文件 | ||
| 30 | +.tmp/ | ||
| 31 | +*.raw | ||
| 32 | +# ==== 结束 ==== |
CLAUDE.md
0 → 100644
| 1 | +++ a/CLAUDE.md | ||
| 1 | +# CLAUDE.md — ERP项目 Claude Code 主指令文件 | ||
| 2 | + | ||
| 3 | +> 本文件是 Claude Code 的"操作手册"。Claude Code 启动时会自动读取此文件。 | ||
| 4 | + | ||
| 5 | +--- | ||
| 6 | + | ||
| 7 | +## 🎯 项目概述 | ||
| 8 | + | ||
| 9 | +- **项目名称**: 小羚羊 | ||
| 10 | +- **项目简述**: 测试ERP | ||
| 11 | +- **目标用户**: 企业内部管理人员 | ||
| 12 | +- **部署方式**: 私有化部署 | ||
| 13 | + | ||
| 14 | +--- | ||
| 15 | + | ||
| 16 | +## 🔄 B 阶段开发流程(后端模块循环 → 前端整体阶段) | ||
| 17 | + | ||
| 18 | +B 阶段分两段,**全部固化到 skills**。入口:`/erp-workflow:coding-start`。 | ||
| 19 | + | ||
| 20 | +### 阶段路由(coding-start 内,只做分发) | ||
| 21 | + | ||
| 22 | +`coding-start` 每次入口做两段完成性检查后真值表派发: | ||
| 23 | + | ||
| 24 | +- 后端完成性检查 → `backend_done`(扫 docs/08 § 二 + GitLab state) | ||
| 25 | +- 前端完成性检查 → `frontend_done`(扫 docs/08 § 三 整体 MR + state) | ||
| 26 | + | ||
| 27 | +| `backend_done` | `frontend_done` | 派发 | | ||
| 28 | +|---|---|---| | ||
| 29 | +| `false` | 任意 | `module-start`(写后端) | | ||
| 30 | +| `true` | `false` | `frontend-start`(写前端) | | ||
| 31 | +| `true` | `true` | "所有阶段已完成" | | ||
| 32 | + | ||
| 33 | +前端阶段前置(prototype/ 门禁)由 `frontend-start` 自带,不在 coding-start。`module-start` 与 `frontend-start` **互不感知对方**。 | ||
| 34 | + | ||
| 35 | +### 后端阶段(每模块一个 MR) | ||
| 36 | + | ||
| 37 | +- **模块循环(外)**:`module-start` → `test-gate(phase=backend)` → `module-report` → `mr-create` → 人工 Approve MR → 用户重跑 coding-start → coding-start 再次路由 | ||
| 38 | +- **功能循环(内,每 REQ-XXX-NNN 一遍)**:`feature-brainstorm` → `feature-plan` → `feature-tdd` → `feature-verify` → `feature-review` | ||
| 39 | +- 后端阶段任务严格落在 `backend/` 路径下;docs/01 REQ 卡片的 UI 描述在此阶段忽略,UI 推迟到前端阶段。 | ||
| 40 | + | ||
| 41 | +### 前端阶段(整体一个 MR,所有后端模块 merged 后启动) | ||
| 42 | + | ||
| 43 | +- **FE 清单(AI 自主推导,无审阅断点)**:`frontend-start` 进入时扫 prototype + docs/01 + docs/05 → AI 自主推导 FE 业务功能清单写入 `docs/08 § 三`(已有则加载)。**FE 是业务功能粒度,与 prototype HTML 文件数无关**——一个 HTML 可拆多个 FE,多个 HTML 也可合成一个 FE。FE 清单的合理性由 fe-feature-review / mr-create 在整体 MR 提交时一并校核(人工只在 MR Approve+Merge 处介入)。 | ||
| 44 | +- **FE 循环(外)**:`frontend-start` → fe-feature 循环 → `test-gate(phase=frontend)` → `module-report(phase=frontend)` → `mr-create`(分支 `frontend-phase`,docs/08 § 三 整体 MR)。 | ||
| 45 | +- **FE 功能循环(内,每个 FE-NN 一遍)**:`fe-feature-brainstorm` → `fe-feature-plan` → `fe-feature-tdd` → `fe-feature-verify` → `fe-feature-review`(专用 `fe-code-reviewer` agent,硬编码 7 维 review checklist) | ||
| 46 | +- 前端阶段任务严格落在 `frontend/` 路径下;布局以 `prototype/` 为权威。 | ||
| 47 | + | ||
| 48 | +### MR 前测试闸门 | ||
| 49 | + | ||
| 50 | +- `test-gate`:后端阶段子会话跑 `scripts/test.sh`(含本模块新增 + 已合并模块回归);前端阶段子会话跑 vitest + playwright。 | ||
| 51 | +- `.githooks/pre-push` 兜底;`git push --no-verify` 被 `deny-no-verify.sh` 硬拦。 | ||
| 52 | + | ||
| 53 | +--- | ||
| 54 | + | ||
| 55 | +## ✅ 阶段完成判定规则 | ||
| 56 | + | ||
| 57 | +`docs/08-模块任务管理.md` 分两段: | ||
| 58 | +- `§ 二`:后端模块元数据表(每个模块一行 bullet,记录依赖 / 路径 / MR iid / 功能子项) | ||
| 59 | +- `§ 三`:前端阶段元数据(整体 MR + FE 子项清单,由 `frontend-start` 在所有后端模块 merged 后填入) | ||
| 60 | + | ||
| 61 | +**阶段完成判定**统一以 `MR:` 字段(§ 二 各模块) / `整体 MR:` 字段(§ 三)+ `GitLab API state=merged` 判定;子项勾选只作可视化进度,不参与完成判定。 | ||
| 62 | + | ||
| 63 | +### 后端模块格式 | ||
| 64 | + | ||
| 65 | +每个后端模块在 docs/08 § 二 中长这样: | ||
| 66 | + | ||
| 67 | +```markdown | ||
| 68 | +- module_0 系统管理 | ||
| 69 | + - 依赖: — | ||
| 70 | + - 路径: backend/module/sys/ | ||
| 71 | + - MR: — | ||
| 72 | + - 功能: | ||
| 73 | + - [ ] REQ-SYS-001 用户登录 | ||
| 74 | + - [ ] REQ-SYS-002 用户注册 | ||
| 75 | +``` | ||
| 76 | + | ||
| 77 | +- `MR:` 字段由 `mr-create` 在创建 MR 时从 `—` 改为 `!<iid>`。 | ||
| 78 | +- 每个 `REQ-*` 子项由 `feature-review` 在 `verdict=approve` 时自动勾选为 `[x]`。 | ||
| 79 | +- 路径限定为后端目录(如 `backend/module/sys/`);前端代码不在此阶段产生。 | ||
| 80 | + | ||
| 81 | +### 前端阶段格式(§ 三) | ||
| 82 | + | ||
| 83 | +```markdown | ||
| 84 | +- 整体 MR: — | ||
| 85 | +- 功能: | ||
| 86 | + - [ ] FE-01 用户登录与注册 | 关联 REQ:REQ-SYS-001, REQ-SYS-002 | 关联原型:prototype/auth.html | ||
| 87 | + - [ ] FE-02 仪表盘总览 | 关联 REQ:REQ-DASH-001 | 关联原型:prototype/dashboard.html | ||
| 88 | +``` | ||
| 89 | + | ||
| 90 | +- `整体 MR:` 字段由 `mr-create` 在创建前端 MR 时从 `—` 改为 `!<iid>`。 | ||
| 91 | +- "功能:" 列表由 `frontend-start` 进入时由 AI 自主推导写入(无人工审阅断点)。FE 是业务功能粒度,与 prototype HTML 文件数无关;合理性由 fe-feature-review / 整体 MR 时统一校核。 | ||
| 92 | +- 每个 `FE-NN` 子项由 `fe-feature-review` 在 `verdict=approve` 时自动勾选为 `[x]`。 | ||
| 93 | +- 进入前端阶段前 `frontend-start` 步骤 1 自带 prototype/ 门禁,强制检查项目根 `prototype/` 至少含 1 个 `*.html` mockup。 | ||
| 94 | + | ||
| 95 | +### 状态语义(后端模块 + 前端阶段共用) | ||
| 96 | + | ||
| 97 | +| `MR:` 字段 | `GitLab API state` | 含义 | 你(Claude Code)的行为 | | ||
| 98 | +|---|---|---|---| | ||
| 99 | +| `—` | — | 该阶段未开始(未创建 MR) | ✅ 开始该阶段开发 | | ||
| 100 | +| `!<iid>` | `opened` / `closed` | 阶段开发中 / 打回 | ✅ 继续推进该阶段 | | ||
| 101 | +| `!<iid>` | `merged` | 阶段**已完成** | 🟢 后端:进入下一未完成模块;后端全完 → 前端阶段;前端 merged → 全部完成 | | ||
| 102 | + | ||
| 103 | +### 模块完成报告 | ||
| 104 | + | ||
| 105 | +由 `module-report` skill 产出,模板位于 由 module-report skill 持有(12 节标准化,含跨模块改动等 CLAUDE.md 软规则映射节)。CC 不手写模块报告,仅填模板。 | ||
| 106 | + | ||
| 107 | +--- | ||
| 108 | + | ||
| 109 | +## 🏷️ 占位符统一约定 | ||
| 110 | + | ||
| 111 | +项目文档里有 **2 种填写占位** + **1 种提示占位**: | ||
| 112 | + | ||
| 113 | +| 格式 | 谁填 | 使用阶段 | 说明 | | ||
| 114 | +|------|-----|---------|------| | ||
| 115 | +| `【人工填写:<简短说明>】` | 人 | 仅 A 阶段文档 | 密钥 / 账密 / 包名 / 命名约定 / 小版本号等人工才能决定的值;B 阶段 plan/spec 禁止出现,查不到真值时用 `AskUserQuestion` 问用户 | | ||
| 116 | +| `TBD(<责任人>)` | CC 自动 | A 或 B | 后缀附带责任方(如 `TBD(A3 自动补)` / `TBD(A5 自动补)`);由对应 skill 就地补填,`module-report` § ⑦ 检查 `TBD(CC 补)` 残留 | | ||
| 117 | + | ||
| 118 | +**HTML 注释 `<!-- ... -->`**:提示占位,是**插件内部大纲模板**里给 LLM 的**填空提示 / 章节引导**,指引 LLM 按结构填实际内容。skill 生成时会**剥除**这些注释,最终产物里注释不会保留。 | ||
| 119 | + | ||
| 120 | +--- | ||
| 121 | + | ||
| 122 | +## 📐 编码行为约束 | ||
| 123 | + | ||
| 124 | +### 你必须做的 ✅ | ||
| 125 | + | ||
| 126 | +1. **严格遵循** `docs/04-技术规范.md`——命名 / 编码 / 统一响应 / 异常处理 / 数据访问 / 配置与安全 等项目专属技术规约全部在此 | ||
| 127 | +2. **严格遵循** `docs/09-项目目录结构.md`——文件放对位置 | ||
| 128 | +3. **每个后端接口** 必须先在 `docs/05-API接口契约.md` 定义,再编码实现 | ||
| 129 | +4. **每个功能可追溯到 `REQ-XXX-NNN`**——commit tag + 代码注释(如 `// REQ-SYS-001: 用户登录`)+ plan/spec 文件名均用此 tag | ||
| 130 | +5. **遇到跨模块改动**(动到非当前模块的代码)——按 § 🟡 软规则 **S2** 执行(允许改,但必须留痕) | ||
| 131 | +6. **遇到技术栈外组件引入**(`docs/04 § 零` 技术栈表外的框架 / 中间件 / 关键库),按 § 🟡 软规则 **S1** 执行(允许引入,但必须先 AskUserQuestion) | ||
| 132 | + | ||
| 133 | +### 你禁止做的 🚫 | ||
| 134 | + | ||
| 135 | +1. **主会话直接 `mysql -e` 跑业务 DDL**(只读查询 / 临时本地调试除外)——业务 schema 必须走 `sql/migrations/V_n__*.sql`,详见下方 Schema 演化规约 | ||
| 136 | +2. **手动 Edit `docs/08 § 二/§ 三` 的 `MR:` / `整体 MR:` 字段**,必须要由 `mr-create` 自动回写 | ||
| 137 | + | ||
| 138 | +### Schema 演化规约(Flyway migration) | ||
| 139 | + | ||
| 140 | +1. **文件命名**:`sql/migrations/V<n>__<snake_case_desc>.sql`,例:`V5__add_user_email_unique_index.sql` | ||
| 141 | +2. **版本号分配**:建文件前 `ls sql/migrations/V*.sql` 查当前最大 n,新文件 `n_max + 1` | ||
| 142 | +3. **Apply 方式**:Spring Boot 启动 / 测试启动时 Flyway 自动 apply(项目必须在 `pom.xml` 声明 `flyway-core` + `flyway-mysql` 依赖)。`scripts/setup-test-db.sh` 只负责清空库,不做 apply | ||
| 143 | +4. **已合并的 migration 永不修改**:发现错了写一个补救 migration(如 `V7__fix_V5_index_name.sql`),旧 `V_n.sql` | ||
| 144 | +5. **临时调试 DDL**:临时在本地试字段/索引可手动 `mysql -e`,但不写 migration;下次 `setup-test-db.sh` 会 drop+create 清掉 | ||
| 145 | +6. **A4 生成的 V1**:`V1__initial_schema.sql` 是 A 阶段由 `db-init` 从 `docs/03-数据库设计文档.md`(A3 正向设计的 schema SSoT)翻译生成的初始版本;后续 V2/V3/... 由 B 阶段每个 REQ 按需写入,**同时**反向同步更新 docs/03 对应表小节以保持 SSoT 一致 | ||
| 146 | + | ||
| 147 | +--- | ||
| 148 | + | ||
| 149 | +## 🗂️ Git 提交规范 | ||
| 150 | + | ||
| 151 | +每次提交必须遵循以下格式: | ||
| 152 | + | ||
| 153 | +``` | ||
| 154 | +<type>(<scope>): <subject> | ||
| 155 | +``` | ||
| 156 | + | ||
| 157 | +- `scope`: 模块名,如 `user` / `inventory` / `order` | ||
| 158 | +- `subject`: 简短描述;业务类(feat / fix / test)必须带 `REQ-XXX-NNN` 后缀 | ||
| 159 | + | ||
| 160 | +`type` 含义: | ||
| 161 | + | ||
| 162 | +| type | 看到它意味着 | | ||
| 163 | +|-----|-------------| | ||
| 164 | +| `feat` | **新能力上线**——用户多了一个功能、接口、页面或业务规则 | | ||
| 165 | +| `fix` | **修 bug**——原来行为错了,这次改对 | | ||
| 166 | +| `refactor` | **重构**——外部行为不变,只改代码结构 / 命名 / 抽象 | | ||
| 167 | +| `docs` | **文档改动**——只动 Markdown / 代码注释,不动实现 | | ||
| 168 | +| `style` | **格式调整**——空白 / 缩进 / import 顺序,逻辑 0 变化 | | ||
| 169 | +| `test` | **只动测试代码**——补用例 / 修 fixture,不碰实现 | | ||
| 170 | +| `chore` | **流程维护**——构建 / 依赖 / 工具 / 证据档案 / MR 元数据等非业务动作 | | ||
| 171 | + | ||
| 172 | +--- | ||
| 173 | + | ||
| 174 | +## 🚩 中断机制 | ||
| 175 | + | ||
| 176 | +功能循环(每个功能 REQ-XXX 的 Brainstorm → Plan → TDD → Verify → AI 自审)默认 **静默编程**,但触发以下任何一条必须**立刻停下、记录原因、等人决策**,不得自行绕过: | ||
| 177 | + | ||
| 178 | +| # | 中断 | 例子 | | ||
| 179 | +| - | --- | --- | | ||
| 180 | +| 1 | **测试反复失败** | 同一测试同一功能内连续 **10 次**修复失败 | | ||
| 181 | +| 2 | **要改密钥 / 账密 / 包名** | `docs/07-环境配置.md` 里由人工标注必须填的字段 | | ||
| 182 | +| 3 | **外部接口不可达** | 第三方 API 无法连接、证书失效等环境问题,并无法自行解决 | | ||
| 183 | + | ||
| 184 | +> 其余需要人类判断的场景一律走普通 `AskUserQuestion` Q&A,不中断、不写 Blocker 文件。 | ||
| 185 | + | ||
| 186 | +**触发中断时的固定动作:** | ||
| 187 | + | ||
| 188 | +1. 在当前功能的 plan 文件里追加一节 `## 🚩 Blocker`(报告格式由 `interrupt-check` 的 `interrupt-block-template.md` 持有) | ||
| 189 | +2. 停止后续所有功能的静默执行 | ||
| 190 | +3. 在主会话输出一句话摘要 + 指向 blocker 文件的路径,等人回复 | ||
| 191 | + | ||
| 192 | +--- | ||
| 193 | + | ||
| 194 | +## 🟡 软规则(允许继续,但有强制后续动作) | ||
| 195 | + | ||
| 196 | +以下情况 **不触发中断**,CC 可自行继续推进,但必须在约定位置留痕,模块完成时统一审计。 | ||
| 197 | + | ||
| 198 | +| # | 软规则 | 允许动作 | 强制后续 | | ||
| 199 | +| - | ----- | ------- | ------- | | ||
| 200 | +| S1 | **技术栈外组件引入** | 用 `AskUserQuestion` 给用户三选一:接受引入 / 换方案 / 拒绝 | ① **接受** → 同会话直接在 `docs/04 § 零` 追加一行 → 继续流程 ② **换方案 / 拒绝** → 视为常规歧义澄清,继续 Q&A 收敛 ③ 不写 Blocker、不中断流程 | | ||
| 201 | +| S2 | **跨模块改动** | **默认不改**,仅为当前模块实现所必需时允许修改 | ① hook `log-cross-module.sh` 自动落存根 ② `module-report` 一次性调用 `cross-module-log` skill 批量补齐「原因 / 影响评估」+ 「跨模块改动」节完整贴入《模块完成报告》 | | ||
| 202 | + | ||
| 203 | +--- | ||
| 204 | + | ||
| 205 | +## 🧭 通用工作准则(General Principles) | ||
| 206 | + | ||
| 207 | +### 1. Think Before Coding | ||
| 208 | + | ||
| 209 | +**Don't assume. Don't hide confusion. Surface tradeoffs.** | ||
| 210 | + | ||
| 211 | +Before implementing: | ||
| 212 | +- State your assumptions explicitly. If uncertain, ask. | ||
| 213 | +- If multiple interpretations exist, present them - don't pick silently. | ||
| 214 | +- If a simpler approach exists, say so. Push back when warranted. | ||
| 215 | +- If something is unclear, stop. Name what's confusing. Ask. | ||
| 216 | + | ||
| 217 | +### 2. Simplicity First | ||
| 218 | + | ||
| 219 | +**Minimum code that solves the problem. Nothing speculative.** | ||
| 220 | + | ||
| 221 | +- No features beyond what was asked. | ||
| 222 | +- No abstractions for single-use code. | ||
| 223 | +- No "flexibility" or "configurability" that wasn't requested. | ||
| 224 | +- No error handling for impossible scenarios. | ||
| 225 | +- If you write 200 lines and it could be 50, rewrite it. | ||
| 226 | + | ||
| 227 | +Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. | ||
| 228 | + | ||
| 229 | +### 3. Surgical Changes | ||
| 230 | + | ||
| 231 | +**Touch only what you must. Clean up only your own mess.** | ||
| 232 | + | ||
| 233 | +When editing existing code: | ||
| 234 | +- Don't "improve" adjacent code, comments, or formatting. | ||
| 235 | +- Don't refactor things that aren't broken. | ||
| 236 | +- Match existing style, even if you'd do it differently. | ||
| 237 | +- If you notice unrelated dead code, mention it - don't delete it. | ||
| 238 | + | ||
| 239 | +When your changes create orphans: | ||
| 240 | +- Remove imports/variables/functions that YOUR changes made unused. | ||
| 241 | +- Don't remove pre-existing dead code unless asked. | ||
| 242 | + | ||
| 243 | +The test: Every changed line should trace directly to the user's request. | ||
| 244 | + | ||
| 245 | +### 4. Goal-Driven Execution | ||
| 246 | + | ||
| 247 | +**Define success criteria. Loop until verified.** | ||
| 248 | + | ||
| 249 | +Transform tasks into verifiable goals: | ||
| 250 | +- "Add validation" → "Write tests for invalid inputs, then make them pass" | ||
| 251 | +- "Fix the bug" → "Write a test that reproduces it, then make it pass" | ||
| 252 | +- "Refactor X" → "Ensure tests pass before and after" | ||
| 253 | + | ||
| 254 | +For multi-step tasks, state a brief plan: | ||
| 255 | +``` | ||
| 256 | +1. [Step] → verify: [check] | ||
| 257 | +2. [Step] → verify: [check] | ||
| 258 | +3. [Step] → verify: [check] | ||
| 259 | +``` | ||
| 260 | + | ||
| 261 | +Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification. |
docs/01-需求清单/USR-用户管理/REQ-USR-001.md
0 → 100644
| 1 | +++ a/docs/01-需求清单/USR-用户管理/REQ-USR-001.md | ||
| 1 | +### REQ-USR-001 增加用户 | ||
| 2 | + | ||
| 3 | +**目标**: 管理员在后台新建用户账号,指定用户名、密码及角色,账号立即生效可用 | ||
| 4 | + | ||
| 5 | +- **输入**: | ||
| 6 | + | ||
| 7 | + - **表1**: | ||
| 8 | + | ||
| 9 | + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 预加载 | 默认值 | 业务规则 | | ||
| 10 | + | -------- | ---- | --- | ---- | ----------------- | --------- | --------- | ------------------- | | ||
| 11 | + | 创建时间 | 日期时间 | — | 系统生成 | — | 页面加载时 | 当前日期 | 保存后自动生成;只读 | | ||
| 12 | + | 制单人 | 文本 | — | 系统生成 | — | 页面加载时 | 当前登录用户 | 保存后自动生成;只读 | | ||
| 13 | + | 员工名 | 文本 | 否 | 下拉单选 | `职员表` | 用户操作时 | — | 关联职员(可选) | | ||
| 14 | + | 用户号 | 文本 | 是 | 手工输入 | — | 用户操作时 | — | 关联职员选择后自动输入员工姓名 | | ||
| 15 | + | 用户名 | 文本 | 是 | 手工输入 | — | 用户操作时 | — | 关联职员选择后自动输入员工姓名 | | ||
| 16 | + | 类型 | 文本 | 是 | 下拉单选 | 普通用户/超级管理员 | 页面加载时 | 普通用户 | — | | ||
| 17 | + | 语言 | 文本 | 是 | 下拉单选 | 中文/英文/繁体 | 页面加载时 | — | — | | ||
| 18 | + | 单据修改权限 | 布尔 | 否 | 复选框 | — | — | 否 | — | | ||
| 19 | + | 密码 | 文本 | — | 系统生成 | 不显示 | — | 666666 | 保存后自动设为初始化 | | ||
| 20 | + | ||
| 21 | + - **表2** - 权限组: | ||
| 22 | + | ||
| 23 | + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 预加载 | 默认值 | 业务规则 | | ||
| 24 | + | -------- | ---- | --- | ---- | ----------------- | --------- | --------- | ------------------- | | ||
| 25 | + | 复选框 | 布尔 | 否 | 复选框 | — | — | 否 | 是否选择当前行权限 | | ||
| 26 | + | 权限分类 | 文本 | — | — | — | 页面加载时 | — | — | | ||
| 27 | + | ||
| 28 | + | ||
| 29 | +- **输出**: | ||
| 30 | + | ||
| 31 | + - **表1**: | ||
| 32 | + | ||
| 33 | + | 字段 | 类型 | 显示来源 | | ||
| 34 | + | --- | --- | --- | | ||
| 35 | + | 用户号 | 文本 | — | | ||
| 36 | + | ||
| 37 | +- **跨字段规则**: 用户名在系统内全局唯一;角色取值受系统配置约束 | ||
| 38 | +- **边界**: 密码以哈希形式存储 | ||
| 39 | +- **验收**: 提交合法数据后用户记录出现在列表;重复用户名返回错误提示;普通账号无权访问此功能 | ||
| 40 | +- **依赖表**: `tUser`(写入新用户主记录)、`tEmployee`(读取员工下拉)、`tPermission`(读取权限分类下拉)、`tUserPermission`(写入权限组勾选关系) | ||
| 41 | +- **依赖接口**: `POST /api/usr/user`(本 REQ 自身接口) |
docs/01-需求清单/USR-用户管理/REQ-USR-002.md
0 → 100644
| 1 | +++ a/docs/01-需求清单/USR-用户管理/REQ-USR-002.md | ||
| 1 | +### REQ-USR-002 修改用户 | ||
| 2 | + | ||
| 3 | +**目标**: 管理员可更新已有用户的基本信息(姓名、角色、状态等),修改实时生效 | ||
| 4 | + | ||
| 5 | +- **输入**: 选中目标 | ||
| 6 | + | ||
| 7 | + - **表1**: | ||
| 8 | + | ||
| 9 | + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 预加载 | 默认值 | 业务规则 | | ||
| 10 | + | -------- | ---- | --- | ---- | ----------------- | --------- | --------- | ------------------- | | ||
| 11 | + | 创建时间 | 日期时间 | — | 系统生成 | — | 页面加载时 | 原值 | 保存后自动生成;只读 | | ||
| 12 | + | 制单人 | 文本 | — | 系统生成 | — | 页面加载时 | 原值 | 保存后自动生成;只读 | | ||
| 13 | + | 员工名 | 文本 | 否 | 下拉单选 | `职员表` | 页面加载时 | 原值 | 关联职员(可选) | | ||
| 14 | + | 用户号 | 文本 | 是 | 手工输入 | — | 页面加载时 | 原值 | 关联职员选择后自动输入员工姓名 | | ||
| 15 | + | 用户名 | 文本 | 是 | 手工输入 | — | 页面加载时 | 原值 | 关联职员选择后自动输入员工姓名 | | ||
| 16 | + | 类型 | 文本 | 是 | 下拉单选 | 普通用户/超级管理员 | 页面加载时 | 原值 | — | | ||
| 17 | + | 语言 | 文本 | 是 | 下拉单选 | 中文/英文/繁体 | 页面加载时 | 原值 | — | | ||
| 18 | + | 单据修改权限 | 布尔 | 否 | 复选框 | — | 页面加载时 | 原值 | — | | ||
| 19 | + | 密码 | 文本 | — | 系统生成 | 不显示 | 页面加载时 | 原值 | 保存后自动设为初始化 | | ||
| 20 | + | ||
| 21 | + - **表2** - 权限组: | ||
| 22 | + | ||
| 23 | + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 预加载 | 默认值 | 业务规则 | | ||
| 24 | + | -------- | ---- | --- | ---- | ----------------- | --------- | --------- | ------------------- | | ||
| 25 | + | 复选框 | 布尔 | 否 | 复选框 | — | 页面加载时 | 原值 | 是否选择当前行的权限 | | ||
| 26 | + | 权限分类 | 文本 | — | — | — | 页面加载时 | — | — | | ||
| 27 | + | ||
| 28 | +- **输出**: | ||
| 29 | + | ||
| 30 | + - **表1**: | ||
| 31 | + | ||
| 32 | + | 字段 | 类型 | 显示来源 | | ||
| 33 | + | --- | --- | --- | | ||
| 34 | + | 用户 id | 文本 | `职员表` | | ||
| 35 | + | ||
| 36 | +- **跨字段规则**: 密码不在该接口修改;角色变更需具备相应权限 | ||
| 37 | +- **边界**: 必须传入有效用户 id;字段格式与新增一致 | ||
| 38 | +- **验收**: 修改角色或状态后立即反映在用户列表;被禁用账号无法登录并收到明确提示 | ||
| 39 | +- **依赖表**: `tUser`(更新用户主记录)、`tEmployee`(读取员工下拉)、`tPermission`(读取权限分类下拉)、`tUserPermission`(重写权限组勾选关系) | ||
| 40 | +- **依赖接口**: `PUT /api/usr/user/{userId}`(本 REQ 自身接口) |
docs/01-需求清单/USR-用户管理/REQ-USR-003.md
0 → 100644
| 1 | +++ a/docs/01-需求清单/USR-用户管理/REQ-USR-003.md | ||
| 1 | +### REQ-USR-003 查询用户 | ||
| 2 | + | ||
| 3 | +**目标**: 管理员可按用户名、角色或状态筛选并分页浏览用户列表 | ||
| 4 | + | ||
| 5 | +- **输入**: | ||
| 6 | + | ||
| 7 | + - **表1**: | ||
| 8 | + | ||
| 9 | + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 预加载 | 默认值 | 业务规则 | | ||
| 10 | + | ---- | ---- | --- | ---- | ----------------------------------------------- | ----- | ------- | --------------- | | ||
| 11 | + | 查询字段 | 文本 | 否 | 下拉单选 | 用户名/员工名/用户号/部门/用户类型/作废/登录日期/制单人 | 页面加载时 | 用户名 | — | | ||
| 12 | + | 匹配方式 | 文本 | 否 | 下拉单选 | 包含/不包含/等于 | 页面加载时 | 包含 | — | | ||
| 13 | + | 查询值 | 文本 | 否 | 手工输入 | — | — | — | 与「查询字段」配合使用,空为选择全部 | | ||
| 14 | + | ||
| 15 | +- **输出**: | ||
| 16 | + | ||
| 17 | + - **表1**: | ||
| 18 | + | ||
| 19 | + | 字段 | 类型 | 显示来源 | | ||
| 20 | + | ---- | ---- | ----- | | ||
| 21 | + | 序号 | 数字 | 系统生成 | | ||
| 22 | + | 用户名 | 文本 | `用户表` | | ||
| 23 | + | 员工名 | 文本 | `职员表` | | ||
| 24 | + | 用户号 | 文本 | `用户表` | | ||
| 25 | + | 部门 | 文本 | `职员表` | | ||
| 26 | + | 用户类型 | 文本 | `用户表` | | ||
| 27 | + | 语言 | 文本 | `用户表` | | ||
| 28 | + | 作废 | 布尔 | `用户表` | | ||
| 29 | + | 登录日期 | 日期时间 | `用户表` | | ||
| 30 | + | 制单人 | 文本 | `用户表` | | ||
| 31 | + | 制单日期 | 日期时间 | `用户表` | | ||
| 32 | + | ||
| 33 | +- **跨字段规则**: - | ||
| 34 | +- **边界**: 单页最大条数受限(默认 100);密码与敏感字段不返回;查询为只读,不产生写副作用 | ||
| 35 | +- **验收**: 按条件筛选返回正确结果集;无匹配时返回空列表而非报错;分页参数越界时返回最后一页 | ||
| 36 | +- **依赖表**: `tUser`(主查询表)、`tEmployee`(LEFT JOIN 取员工名 / 部门) | ||
| 37 | +- **依赖接口**: `GET /api/usr/user`(本 REQ 自身接口) |
docs/01-需求清单/USR-用户管理/REQ-USR-004.md
0 → 100644
| 1 | +++ a/docs/01-需求清单/USR-用户管理/REQ-USR-004.md | ||
| 1 | +### REQ-USR-004 用户登录 | ||
| 2 | + | ||
| 3 | +**目标**: 用户通过用户名+密码完成身份认证,获取 JWT Token 用于后续接口鉴权 | ||
| 4 | + | ||
| 5 | +- **输入**: | ||
| 6 | + | ||
| 7 | + - **表1**: | ||
| 8 | + | ||
| 9 | + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 预加载 | 默认值 | 业务规则 | | ||
| 10 | + | --- | ---- | --- | ---- | ------- | ----- | --- | ----------- | | ||
| 11 | + | 用户名 | 文本 | 是 | 手工输入 | — | — | — | — | | ||
| 12 | + | 密码 | 文本 | 是 | 手工输入 | — | — | — | 输入显示星号 | | ||
| 13 | + | 版本 | 文本 | 是 | 下拉单选 | `公司表` | 页面加载时 | 标准版 | | | ||
| 14 | + | ||
| 15 | +- **输出**: 成功/失败 | ||
| 16 | + | ||
| 17 | +- **跨字段规则**: 校验用户名 + 密码哈希;连续失败达到阈值临时锁定账号;登录成功签发限时 token 并返回基本用户信息 | ||
| 18 | +- **边界**: token 设置合理过期时间;接口需具备防暴力破解保护 | ||
| 19 | +- **验收**: 正确凭据返回 Token 且可通过鉴权接口验证;错误密码返回通用错误消息(不区分用户名或密码错误);锁定账号返回锁定提示 | ||
| 20 | +- **依赖表**: `tUser`(按用户名查询 + 校验密码 + 更新登录态 / 失败计数 / 锁定时间)、`tCompany`(读取版本下拉) | ||
| 21 | +- **依赖接口**: `POST /api/auth/login`(本 REQ 自身接口) |
docs/01-需求清单/USR-用户管理/_module.md
0 → 100644
docs/01-需求清单/index.md
0 → 100644
| 1 | +++ a/docs/01-需求清单/index.md | ||
| 1 | +# 需求清单 | ||
| 2 | + | ||
| 3 | +> 本目录按模块组织所有功能需求。每个模块一个子目录,含 `_module.md`(模块头)和 `REQ-XXX-NNN.md`(每张 REQ 卡片一个文件)。下方核心功能点供 CC 拆分出 REQ 编号 + 标题 + 草拟规则;卡片内输入 / 输出的简述句和 N 张字段表由人工编辑。 | ||
| 4 | + | ||
| 5 | +## 模块索引 | ||
| 6 | + | ||
| 7 | +| 模块代码 | 模块名称 | 核心功能点(简要) | | ||
| 8 | +| ---- | ---- | ------------------- | | ||
| 9 | +| USR | 用户管理 | 增加用户,修改用户,查询用户,登录用户 | | ||
| 10 | + | ||
| 11 | +## 填写说明 | ||
| 12 | + | ||
| 13 | +1. 每个模块占一行,`模块代码` 用大写英文缩写(如 SYS / PUR / INV / SAL / FIN / HR) | ||
| 14 | +2. `核心功能点` 只需列关键词,CC 会基于此拆分出 N 张 REQ 卡片骨架(卡片内输入 / 输出的简述句和字段表仍由人工编辑) | ||
| 15 | +3. 填完后运行 `/erp-workflow:plan-start`,CC 会自动检测并进入需求生成阶段 |
docs/02-开发计划.md
0 → 100644
| 1 | +++ a/docs/02-开发计划.md | ||
| 1 | +# 02-开发计划 | ||
| 2 | + | ||
| 3 | +## 一、模块依赖表 | ||
| 4 | + | ||
| 5 | +| 模块 ID | 模块名 | 依赖模块 | 依赖表 | | ||
| 6 | +|---|---|---|---| | ||
| 7 | +| module_usr | 用户管理 | — | `tUser`, `tEmployee`, `tPermission`, `tUserPermission`, `tCompany` | | ||
| 8 | + | ||
| 9 | +## 二、开发顺序清单(CC 分发权威) | ||
| 10 | + | ||
| 11 | +> 本清单由 A5 `downstream-gen` 一次性生成。**每行是一个 REQ**,不是模块。CC 按表格行序从上到下扫描,对每个 REQ 所属模块查 `docs/08 § 二` 的 `MR:` 字段 + GitLab API `state`:`merged` 跳过,其他(`—` / opened / closed / 查不到)选为当前模块;`module-start` 会把该模块的所有 REQ 一次做完。 | ||
| 12 | +> | ||
| 13 | +> **约束**:同一模块的所有 REQ 必须**连续排列**。允许打破依赖拓扑(如环依赖、业务必须先做),但必须在「备注」列写明原因。 | ||
| 14 | + | ||
| 15 | +| # | REQ | 所属模块 | 选中理由 | 备注 | | ||
| 16 | +|---|-----|---------|---------|------| | ||
| 17 | +| 1 | **REQ-USR-001** | module_usr | 所属模块无依赖,基础模块;本 REQ 创建用户主记录,是后续修改 / 查询 / 登录的前置 | — | | ||
| 18 | +| 2 | **REQ-USR-002** | module_usr | 依赖 REQ-USR-001 已在前(修改基于已存在的用户记录) | — | | ||
| 19 | +| 3 | **REQ-USR-003** | module_usr | 依赖 REQ-USR-001 已在前(查询基于已存在的用户记录) | — | | ||
| 20 | +| 4 | **REQ-USR-004** | module_usr | 依赖 REQ-USR-001 已在前(登录校验已存在的用户名 + 密码);同模块尾位以承接前 3 个的用户数据 | — | | ||
| 21 | + | ||
| 22 | +> **后端模块全部 merged 后**:用户重跑 `/erp-workflow:coding-start` → coding-start 检测到 `backend_done=true && frontend_done=false` → 派发 `frontend-start`。`frontend-start` 步骤 1 自带 prototype/ 门禁(≥ 1 个 `*.html` mockup,缺失则 AskUserQuestion 提示用户补齐)。前端阶段以业务功能(不是 HTML 文件数)为粒度拆分 FE,每个 FE 跑一次 feature 循环(fe-feature-*),最后整个阶段合 1 个 MR(分支 `frontend-phase`,记录在 `docs/08 § 三 整体 MR`)。 | ||
| 23 | + | ||
| 24 | +## 三、关键说明 | ||
| 25 | + | ||
| 26 | +- **模块粒度合 MR**:本项目当前只有一个后端模块 `module_usr`,4 个 REQ 在同一分支(如 `module-usr`)里依次实现,最后合 1 个 MR。 | ||
| 27 | +- **REQ 实现顺序**:在同模块内允许 CC 根据实际依赖局部调整(如先做骨架接口再做业务规则),但完成后所有 4 个 REQ 的子项必须勾选并通过 `feature-review`,否则 `test-gate` 不放行。 | ||
| 28 | +- **种子数据**:`tEmployee` / `tPermission` / `tCompany` 三张字典表在 REQ-USR-001 实现前需要少量种子(通过 Flyway `R__seed.sql` 或集成测试 `@Sql` 注入),便于 REQ-USR-001 的"员工名/版本/权限组"下拉测试。具体种子内容由 feature-tdd 落到测试 fixture,不进 V_n migration。 | ||
| 29 | +- **登录失败锁定**:REQ-USR-004 的"连续失败阈值"与"锁定时长"由 `application.yml` 配置项 `app.security.max-login-fail` / `app.security.lock-minutes` 注入,默认 5 / 30。 |
docs/03-数据库设计文档 copy.md
0 → 100644
| 1 | +++ a/docs/03-数据库设计文档 copy.md | ||
| 1 | +# 03-数据库设计文档 | ||
| 2 | + | ||
| 3 | +- **Schema**: `xlyweberp_vibe_erp_test` | ||
| 4 | +- **Migration 清单**: `sql/migrations/V*.sql`(由 Flyway 顺序 apply) | ||
| 5 | +- **生成方式**: 由 A3 `db-design-gen` 基于 `docs/01-需求清单/<module>/REQ-*.md` REQ 卡片正向设计生成(schema SSoT)。 | ||
| 6 | + | ||
| 7 | +## 项目标准列约定 | ||
| 8 | + | ||
| 9 | +下文每张业务表的字段清单都自动包含以下 5 个标准列(匈牙利前缀 `i` int / `s` varchar / `t` datetime)。渲染时由 `docs-03-table-template.md` 模板内置原样输出。 | ||
| 10 | + | ||
| 11 | +| 列名 | 类型 | 可空 | 主键 | 说明 | | ||
| 12 | +|---|---|---|---|---| | ||
| 13 | +| `iIncrement` | int | 否 | 是 | 整数主键 ID(自增方式由实现决定:DB `AUTO_INCREMENT` 或应用 / 触发器分配) | | ||
| 14 | +| `sId` | varchar(100) | 是 | — | 业务 ID(对外暴露的字符串标识,如 UUID / 人类可读编号) | | ||
| 15 | +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离) | | ||
| 16 | +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离) | | ||
| 17 | +| `tCreateDate` | datetime | 否 | — | 记录创建时间 | | ||
| 18 | + | ||
| 19 | +字典 / 辅助表如有豁免,在该表业务注记里注明豁免原因。 | ||
| 20 | + | ||
| 21 | +## ER 关系概览 | ||
| 22 | + | ||
| 23 | +``` | ||
| 24 | +usr_user(用户主表) | ||
| 25 | + └─< usr_user_permission(用户权限关联,多对多中间表)>─ usr_permission_group(权限分类表) | ||
| 26 | + | ||
| 27 | +跨模块引用(当前阶段记录,对应表由后续模块设计): | ||
| 28 | + usr_user.sEmployeeId → 职员表.sId(跨模块,可空,ON DELETE SET NULL) | ||
| 29 | +``` | ||
| 30 | + | ||
| 31 | +## 表清单 | ||
| 32 | + | ||
| 33 | +- `usr_user` — 用户账户主表,存储登录信息、类型、语言偏好及安全控制字段 | ||
| 34 | +- `tStaff` — 职员维度(员工名 / 部门 / 编号) | ||
| 35 | +- `usr_permission_group` — 权限分类/权限组定义表,每行对应一个可分配给用户的权限项 | ||
| 36 | +- `usr_user_permission` — 用户与权限组的多对多关联表 | ||
| 37 | +- `brand` — 公司/品牌主表,登录账号前缀来源 | ||
| 38 | + | ||
| 39 | +--- | ||
| 40 | + | ||
| 41 | +## `usr_user` — 用户账户主表,存储登录信息、类型、语言偏好及安全控制字段 | ||
| 42 | + | ||
| 43 | +### 字段 | ||
| 44 | + | ||
| 45 | +| 字段 | 类型 | Nullable | 默认 | 业务含义 | | ||
| 46 | +|---|---|---|---|---| | ||
| 47 | +| `iIncrement` | int | 否 | — | 整数主键 ID(标准列) | | ||
| 48 | +| `sId` | varchar(100) | 是 | — | 业务 ID(标准列) | | ||
| 49 | +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离,标准列) | | ||
| 50 | +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离,标准列) | | ||
| 51 | +| `tCreateDate` | datetime | 否 | — | 创建时间(标准列) | | ||
| 52 | +| `sUserCode` | varchar(50) | 否 | — | 用户号(业务编号,人类可读唯一标识) | | ||
| 53 | +| `sUsername` | varchar(100) | 否 | — | 用户名(登录标识,全局唯一,不可修改) | | ||
| 54 | +| `sPasswordHash` | varchar(255) | 否 | — | BCrypt 哈希密码,禁止存储明文 | | ||
| 55 | +| `sUserType` | varchar(20) | 否 | `普通用户` | 用户类型:`普通用户` / `超级管理员` | | ||
| 56 | +| `sLanguage` | varchar(20) | 否 | `中文` | 界面语言:`中文` / `英文` / `繁体` | | ||
| 57 | +| `bCanEditDoc` | tinyint(1) | 否 | `0` | 单据修改权限:0=否,1=是 | | ||
| 58 | +| `bIsDisabled` | tinyint(1) | 否 | `0` | 是否作废/禁用:0=正常,1=禁用 | | ||
| 59 | +| `sEmployeeId` | varchar(100) | 是 | NULL | 关联职员 ID(跨模块引用,职员未关联时为 NULL) | | ||
| 60 | +| `sCreatorUsername` | varchar(100) | 是 | NULL | 制单人用户名(冗余字段,便于列表展示) | | ||
| 61 | +| `tLastLoginDate` | datetime | 是 | NULL | 最后登录时间 | | ||
| 62 | +| `iLoginFailCount` | int | 否 | `0` | 连续登录失败次数,用于防暴力破解 | | ||
| 63 | +| `tLockUntil` | datetime | 是 | NULL | 账号锁定截止时间,NULL 表示未锁定 | | ||
| 64 | + | ||
| 65 | +### 索引 | ||
| 66 | + | ||
| 67 | +- `uk_usr_user_username_tenant` (UNIQUE): `(sUsername, sBrandsId)` — 用户名在同一 brand 内唯一(V2 迁移:原全局唯一改为多租户复合唯一) | ||
| 68 | +- `uk_usr_user_usercode` (UNIQUE): `sUserCode` — 用户号唯一约束 | ||
| 69 | +- `idx_usr_user_tenant` (INDEX): `sBrandsId, sSubsidiaryId` — 多租户隔离查询 | ||
| 70 | +- `idx_usr_user_type` (INDEX): `sUserType` — 按用户类型过滤 | ||
| 71 | +- `idx_usr_user_disabled` (INDEX): `bIsDisabled` — 按状态过滤 | ||
| 72 | + | ||
| 73 | +### 外键 | ||
| 74 | + | ||
| 75 | +- 无(`sEmployeeId` 为跨模块引用,职员表由其他模块管理,当前阶段不建强约束) | ||
| 76 | + | ||
| 77 | +### 业务注记 | ||
| 78 | + | ||
| 79 | +用户登录标识为 `sUsername`(全局唯一),`sUserCode` 为业务编号(可为工号等人类可读标识)。密码以 BCrypt 哈希存储于 `sPasswordHash`,禁止回显。`iLoginFailCount` 达到阈值(默认 5 次)时写入 `tLockUntil`(当前时间 +30 分钟),解锁后计数归零。`sEmployeeId` 软关联职员表,职员表由后续模块设计;`sCreatorUsername` 为冗余字段,记录新建时的操作人用户名。 | ||
| 80 | + | ||
| 81 | +--- | ||
| 82 | + | ||
| 83 | +## `tStaff` — 职员维度(员工名 / 部门 / 编号) | ||
| 84 | + | ||
| 85 | +### 字段 | ||
| 86 | + | ||
| 87 | +| 字段 | 类型 | Nullable | 默认 | 业务含义 | | ||
| 88 | +|---|---|---|---|---| | ||
| 89 | +| `iIncrement` | int | 否 | — | 整数主键 ID(标准列) | | ||
| 90 | +| `sId` | varchar(100) | 是 | — | 业务 ID(标准列) | | ||
| 91 | +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离,标准列) | | ||
| 92 | +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离,标准列) | | ||
| 93 | +| `tCreateDate` | datetime | 否 | — | 创建时间(标准列) | | ||
| 94 | +| `sStaffNo` | varchar(50) | 是 | — | 职员编号;系统内唯一 | | ||
| 95 | +| `sStaffName` | varchar(50) | 否 | — | 职员姓名 | | ||
| 96 | +| `sDepartment` | varchar(100) | 是 | NULL | 所属部门(本期暂用字符串,未来如需独立 `tDepartment` 字典表再另行重构) | | ||
| 97 | +| `sCreatedBy` | varchar(50) | 是 | — | 制单人 | | ||
| 98 | +| `bDeleted` | bit(1) | 否 | 0 | 软删除标记 | | ||
| 99 | +| `tDeletedDate` | datetime | 是 | NULL | 软删除时间 | | ||
| 100 | +| `sDeletedBy` | varchar(50) | 是 | NULL | 软删除操作人 | | ||
| 101 | + | ||
| 102 | +### 索引 | ||
| 103 | + | ||
| 104 | +- `uk_staff_no` (UNIQUE): (`sStaffNo`) | ||
| 105 | +- `idx_staff_name` (NORMAL): (`sStaffName`) | ||
| 106 | +- `idx_department` (NORMAL): (`sDepartment`) | ||
| 107 | + | ||
| 108 | +### 外键 | ||
| 109 | + | ||
| 110 | +(无) | ||
| 111 | + | ||
| 112 | +### 业务注记 | ||
| 113 | + | ||
| 114 | +- USR 模块通过 `tUser.iStaffId` 引用本表;REQ-USR-003 列表的「员工名 / 部门」均来自本表。 | ||
| 115 | +- 部门当前以字符串保存,便于本期快速落地;后续若需独立部门字典表(如审批流引用部门),改为 `iDepartmentId` 外键。 | ||
| 116 | + | ||
| 117 | +--- | ||
| 118 | + | ||
| 119 | +## `usr_permission_group` — 权限分类/权限组定义表,每行对应一个可分配给用户的权限项 | ||
| 120 | + | ||
| 121 | +### 字段 | ||
| 122 | + | ||
| 123 | +| 字段 | 类型 | Nullable | 默认 | 业务含义 | | ||
| 124 | +|---|---|---|---|---| | ||
| 125 | +| `iIncrement` | int | 否 | — | 整数主键 ID(标准列) | | ||
| 126 | +| `sId` | varchar(100) | 是 | — | 业务 ID(标准列) | | ||
| 127 | +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离,标准列) | | ||
| 128 | +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离,标准列) | | ||
| 129 | +| `tCreateDate` | datetime | 否 | — | 创建时间(标准列) | | ||
| 130 | +| `sGroupCode` | varchar(100) | 否 | — | 权限代码(如 `usr:create`、`usr:edit`),全局唯一 | | ||
| 131 | +| `sGroupName` | varchar(200) | 否 | — | 权限显示名称(如"新增用户"、"修改用户") | | ||
| 132 | +| `sCategory` | varchar(100) | 是 | NULL | 权限分类标签,用于前端权限分组展示 | | ||
| 133 | + | ||
| 134 | +### 索引 | ||
| 135 | + | ||
| 136 | +- `uk_usr_perm_group_code` (UNIQUE): `sGroupCode` — 权限代码唯一约束 | ||
| 137 | +- `idx_usr_perm_group_tenant` (INDEX): `sBrandsId, sSubsidiaryId` — 多租户隔离查询 | ||
| 138 | + | ||
| 139 | +### 外键 | ||
| 140 | + | ||
| 141 | +- 无 | ||
| 142 | + | ||
| 143 | +### 业务注记 | ||
| 144 | + | ||
| 145 | +权限分类表存储系统内所有可分配权限项的定义,`sGroupCode` 即前端 `PermButton` 校验的权限码字符串。`sCategory` 用于将权限归组(如"用户管理"分类下含 `usr:create`、`usr:edit`、`usr:query` 等),对应前端 REQ-USR-001/002 权限组表格的"权限分类"列。 | ||
| 146 | + | ||
| 147 | +--- | ||
| 148 | + | ||
| 149 | +## `usr_user_permission` — 用户与权限组的多对多关联表 | ||
| 150 | + | ||
| 151 | +### 字段 | ||
| 152 | + | ||
| 153 | +| 字段 | 类型 | Nullable | 默认 | 业务含义 | | ||
| 154 | +|---|---|---|---|---| | ||
| 155 | +| `iIncrement` | int | 否 | — | 整数主键 ID(标准列) | | ||
| 156 | +| `sId` | varchar(100) | 是 | — | 业务 ID(标准列) | | ||
| 157 | +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离,标准列) | | ||
| 158 | +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离,标准列) | | ||
| 159 | +| `tCreateDate` | datetime | 否 | — | 创建时间(标准列) | | ||
| 160 | +| `sUserId` | varchar(100) | 否 | — | 关联 `usr_user.sId` | | ||
| 161 | +| `sPermGroupId` | varchar(100) | 否 | — | 关联 `usr_permission_group.sId` | | ||
| 162 | + | ||
| 163 | +### 索引 | ||
| 164 | + | ||
| 165 | +- `uk_usr_user_perm` (UNIQUE): `sUserId, sPermGroupId` — 防止重复授权 | ||
| 166 | +- `idx_usr_user_perm_user` (INDEX): `sUserId` — 按用户查询其所有权限 | ||
| 167 | +- `idx_usr_user_perm_group` (INDEX): `sPermGroupId` — 按权限组查询持有该权限的用户 | ||
| 168 | + | ||
| 169 | +### 外键 | ||
| 170 | + | ||
| 171 | +- `fk_usr_user_perm_user`: `sUserId` → `usr_user.sId` (ON DELETE CASCADE ON UPDATE CASCADE) | ||
| 172 | +- `fk_usr_user_perm_group`: `sPermGroupId` → `usr_permission_group.sId` (ON DELETE CASCADE ON UPDATE CASCADE) | ||
| 173 | + | ||
| 174 | +### 业务注记 | ||
| 175 | + | ||
| 176 | +多对多中间表,记录每个用户持有的权限组。新增用户时(REQ-USR-001)保存权限组复选框选中项,修改用户时(REQ-USR-002)先删除该用户旧记录再批量插入新记录(replace 策略)。用户被删除或权限组被删除时,对应关联记录级联删除。 | ||
| 177 | + | ||
| 178 | +--- | ||
| 179 | + | ||
| 180 | +## `brand` — 公司表 | ||
| 181 | + | ||
| 182 | +### 字段 | ||
| 183 | + | ||
| 184 | +| 字段 | 类型 | Nullable | 默认 | 业务含义 | | ||
| 185 | +|---|---|---|---|---| | ||
| 186 | +| `iIncrement` | int | 否 | — | 整数主键 ID(标准列) | | ||
| 187 | +| `sId` | varchar(100) | 是 | — | 业务 ID(标准列) | | ||
| 188 | +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离,标准列) | | ||
| 189 | +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离,标准列) | | ||
| 190 | +| `tCreateDate` | datetime | 否 | — | 创建时间(标准列) | | ||
| 191 | +| `sName` | varchar(100) | 是 | — | 公司名称 | | ||
| 192 | +| `sShortName` | varchar(100) | 是 | — | 公司简称 | | ||
| 193 | +| `sNo` | varchar(100) | 是 | — | 单位编号(登录账号根据单位编号作为前缀) | | ||
| 194 | + | ||
| 195 | +### 索引 | ||
| 196 | + | ||
| 197 | +- `uk_brand_no` (UNIQUE): `sNo` — 单位编号唯一约束(登录账号前缀来源,必须唯一) | ||
| 198 | +- `idx_brand_name` (INDEX): `sName` — 按公司名称查询 | ||
| 199 | + | ||
| 200 | +### 外键 | ||
| 201 | + | ||
| 202 | +- 无 | ||
| 203 | + | ||
| 204 | +### 业务注记 | ||
| 205 | + | ||
| 206 | +公司/品牌主表,记录系统内每个租户的基础信息。`sNo` 为单位编号,登录用户的账号以该编号为前缀生成(参见 `usr_user.sUsername` 的命名规则);因此 `sNo` 必须全局唯一。`sShortName` 为公司简称,用于前端列表 / 抬头等空间受限位置展示。本表为多租户体系的根表,其他业务表的 `sBrandsId` 软关联到本表 `sId`,当前阶段不建强外键约束。 |
docs/03-数据库设计文档.md
0 → 100644
| 1 | +++ a/docs/03-数据库设计文档.md | ||
| 1 | +# 03-数据库设计文档 | ||
| 2 | + | ||
| 3 | +- **Schema**: `xlyweberp_vibe_erp_test` | ||
| 4 | +- **Migration 清单**: `sql/migrations/V*.sql`(由 Flyway 顺序 apply) | ||
| 5 | +- **生成方式**: 由 A3 `db-design-gen` 基于 `docs/01-需求清单/<module>/REQ-*.md` REQ 卡片正向设计生成(schema SSoT)。 | ||
| 6 | + | ||
| 7 | +## 项目标准列约定 | ||
| 8 | + | ||
| 9 | +下文每张业务表的字段清单都自动包含以下 5 个标准列(匈牙利前缀 `i` int / `s` varchar / `t` datetime)。渲染时由 `docs-03-table-template.md` 模板内置原样输出。 | ||
| 10 | + | ||
| 11 | +| 列名 | 类型 | 可空 | 主键 | 说明 | | ||
| 12 | +|---|---|---|---|---| | ||
| 13 | +| `iIncrement` | int | 否 | 是 | 整数主键 ID(自增方式由实现决定:DB `AUTO_INCREMENT` 或应用 / 触发器分配) | | ||
| 14 | +| `sId` | varchar(100) | 是 | — | 业务 ID(对外暴露的字符串标识,如 UUID / 人类可读编号) | | ||
| 15 | +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离) | | ||
| 16 | +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离) | | ||
| 17 | +| `tCreateDate` | datetime | 否 | — | 记录创建时间 | | ||
| 18 | + | ||
| 19 | +字典 / 辅助表如有豁免,在该表业务注记里注明豁免原因。 | ||
| 20 | + | ||
| 21 | +## ER 关系概览 | ||
| 22 | + | ||
| 23 | +``` | ||
| 24 | +tCompany (公司/版本字典) | ||
| 25 | + ▲ | ||
| 26 | + │ 登录时仅作为下拉来源,无 FK 引用 | ||
| 27 | + | ||
| 28 | +tEmployee (职员) | ||
| 29 | + ▲ | ||
| 30 | + │ iEmployeeId(可空) | ||
| 31 | + │ | ||
| 32 | +tUser ────────────────────────► tUserPermission ◄──── tPermission | ||
| 33 | + (用户) N:M (经关联表) (权限分类字典) | ||
| 34 | +``` | ||
| 35 | + | ||
| 36 | +- `tUser` 通过 `iEmployeeId` 可选关联 `tEmployee`(员工名/部门 等显示属性的来源)。 | ||
| 37 | +- `tUser` 与 `tPermission` 经 `tUserPermission` 关联表实现多对多权限分配。 | ||
| 38 | +- `tCompany` 在登录场景作为「版本」下拉值的来源,业务上独立,登录时不强约束 FK(避免账号跨公司复用受阻)。 | ||
| 39 | + | ||
| 40 | +## 表清单 | ||
| 41 | + | ||
| 42 | +- `tUser` — 系统用户主表(账号 / 类型 / 语言 / 密码哈希 / 登录态) | ||
| 43 | +- `tEmployee` — 职员字典(员工名 / 部门),被用户表可选引用 | ||
| 44 | +- `tPermission` — 权限分类字典 | ||
| 45 | +- `tUserPermission` — 用户 ↔ 权限 多对多关联表 | ||
| 46 | +- `tCompany` — 公司及其版本字典,用于登录页「版本」下拉 | ||
| 47 | + | ||
| 48 | +--- | ||
| 49 | + | ||
| 50 | +## `tUser` — 系统用户主表 | ||
| 51 | + | ||
| 52 | +### 字段 | ||
| 53 | + | ||
| 54 | +| 字段 | 类型 | Nullable | 默认 | 业务含义 | | ||
| 55 | +|---|---|---|---|---| | ||
| 56 | +| `iIncrement` | int | 否 | — | 整数主键 ID(标准列) | | ||
| 57 | +| `sId` | varchar(100) | 是 | — | 业务 ID(标准列) | | ||
| 58 | +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离,标准列) | | ||
| 59 | +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离,标准列) | | ||
| 60 | +| `tCreateDate` | datetime | 否 | CURRENT_TIMESTAMP | 创建时间(标准列) | | ||
| 61 | +| `sUserCode` | varchar(32) | 否 | — | 用户号,业务唯一编码 | | ||
| 62 | +| `sUserName` | varchar(50) | 否 | — | 用户名(登录标识),全局唯一 | | ||
| 63 | +| `iEmployeeId` | int | 是 | NULL | 关联 `tEmployee.iIncrement`,可选;选填后页面回显员工名/部门 | | ||
| 64 | +| `sUserType` | varchar(20) | 否 | 'NORMAL' | 用户类型:`NORMAL`=普通用户,`ADMIN`=超级管理员 | | ||
| 65 | +| `sLanguage` | varchar(16) | 否 | 'zh-CN' | 界面语言:`zh-CN` 中文,`en` 英文,`zh-TW` 繁体 | | ||
| 66 | +| `iCanEditDoc` | tinyint(1) | 否 | 0 | 单据修改权限:0=否,1=是 | | ||
| 67 | +| `sPasswordHash` | varchar(100) | 否 | — | 密码哈希(BCrypt 或同强度算法),初始值由系统生成;REQ-USR-001 边界要求"以哈希形式存储" | | ||
| 68 | +| `iIsDisabled` | tinyint(1) | 否 | 0 | 作废标志:0=有效,1=已作废(被作废后不可登录) | | ||
| 69 | +| `tLastLoginDate` | datetime | 是 | NULL | 最近一次登录时间,登录成功时更新 | | ||
| 70 | +| `iLoginFailCount` | int | 否 | 0 | 连续登录失败计数,登录成功清零(用于阈值锁定) | | ||
| 71 | +| `tLockedUntil` | datetime | 是 | NULL | 临时锁定截止时间;非空且大于当前时刻视为锁定(REQ-USR-004 防暴破) | | ||
| 72 | +| `sCreatedBy` | varchar(50) | 是 | NULL | 制单人(创建该用户的操作员用户名) | | ||
| 73 | +| `tUpdateDate` | datetime | 否 | CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 修改时间 | | ||
| 74 | + | ||
| 75 | +### 索引 | ||
| 76 | + | ||
| 77 | +- `uk_tUser_sUserName` (UNIQUE): `sUserName` | ||
| 78 | +- `uk_tUser_sUserCode` (UNIQUE): `sUserCode` | ||
| 79 | +- `idx_tUser_iEmployeeId` (NORMAL): `iEmployeeId` | ||
| 80 | +- `idx_tUser_tenant_status` (NORMAL): `sBrandsId, sSubsidiaryId, iIsDisabled` | ||
| 81 | +- `idx_tUser_tLastLoginDate` (NORMAL): `tLastLoginDate` | ||
| 82 | +- `idx_tUser_sUserType` (NORMAL): `sUserType` | ||
| 83 | + | ||
| 84 | +### 外键 | ||
| 85 | + | ||
| 86 | +- `fk_tUser_iEmployeeId`: `iEmployeeId` → `tEmployee.iIncrement` (ON DELETE SET NULL, ON UPDATE CASCADE) | ||
| 87 | + | ||
| 88 | +### 业务注记 | ||
| 89 | + | ||
| 90 | +- 用户名 `sUserName` 全局唯一,对应 REQ-USR-001 跨字段规则"用户名在系统内全局唯一"。 | ||
| 91 | +- `sUserType` 取值受系统配置约束:当前仅 `NORMAL` / `ADMIN` 两类,由应用层枚举校验。 | ||
| 92 | +- 密码字段只存哈希,禁止存明文;REQ-USR-001 初始密码 `666666` 由应用层哈希后写入。 | ||
| 93 | +- `iLoginFailCount` + `tLockedUntil` 配合实现 REQ-USR-004 的"连续失败临时锁定账号",具体阈值由 `application.yml` 配置(如 5 次失败锁 30 分钟)。 | ||
| 94 | +- 作废用 `iIsDisabled` 软删除,禁止物理 DELETE;REQ-USR-002 中"被禁用账号无法登录"由 Service 层在登录时校验本字段。 | ||
| 95 | + | ||
| 96 | +--- | ||
| 97 | + | ||
| 98 | +## `tEmployee` — 职员字典 | ||
| 99 | + | ||
| 100 | +### 字段 | ||
| 101 | + | ||
| 102 | +| 字段 | 类型 | Nullable | 默认 | 业务含义 | | ||
| 103 | +|---|---|---|---|---| | ||
| 104 | +| `iIncrement` | int | 否 | — | 整数主键 ID(标准列) | | ||
| 105 | +| `sId` | varchar(100) | 是 | — | 业务 ID(标准列) | | ||
| 106 | +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离,标准列) | | ||
| 107 | +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离,标准列) | | ||
| 108 | +| `tCreateDate` | datetime | 否 | CURRENT_TIMESTAMP | 创建时间(标准列) | | ||
| 109 | +| `sEmployeeCode` | varchar(32) | 否 | — | 员工号,业务唯一编码 | | ||
| 110 | +| `sEmployeeName` | varchar(50) | 否 | — | 员工姓名 | | ||
| 111 | +| `sDepartment` | varchar(100) | 是 | NULL | 所属部门名 | | ||
| 112 | +| `iIsDisabled` | tinyint(1) | 否 | 0 | 作废标志:0=有效,1=已作废 | | ||
| 113 | +| `tUpdateDate` | datetime | 否 | CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 修改时间 | | ||
| 114 | + | ||
| 115 | +### 索引 | ||
| 116 | + | ||
| 117 | +- `uk_tEmployee_sEmployeeCode` (UNIQUE): `sEmployeeCode` | ||
| 118 | +- `idx_tEmployee_sEmployeeName` (NORMAL): `sEmployeeName` | ||
| 119 | +- `idx_tEmployee_sDepartment` (NORMAL): `sDepartment` | ||
| 120 | +- `idx_tEmployee_tenant` (NORMAL): `sBrandsId, sSubsidiaryId` | ||
| 121 | + | ||
| 122 | +### 外键 | ||
| 123 | + | ||
| 124 | +(无外向引用;本表作为字典被 `tUser.iEmployeeId` 反向引用) | ||
| 125 | + | ||
| 126 | +### 业务注记 | ||
| 127 | + | ||
| 128 | +- 字典表,提供 REQ-USR-001/002 中"员工名"下拉来源以及 REQ-USR-003 列表中"员工名 / 部门"的显示数据。 | ||
| 129 | +- 部门字段当前为字符串扁平存储;如后续部门需要层级管理,再拆 `tDepartment` 表并将本字段改为外键。 | ||
| 130 | +- 员工被作废(`iIsDisabled=1`)后,引用了该员工的 `tUser` 记录通过 `iEmployeeId` 仍可保留旧值;查询时需 LEFT JOIN 并过滤展示。 | ||
| 131 | + | ||
| 132 | +--- | ||
| 133 | + | ||
| 134 | +## `tPermission` — 权限分类字典 | ||
| 135 | + | ||
| 136 | +### 字段 | ||
| 137 | + | ||
| 138 | +| 字段 | 类型 | Nullable | 默认 | 业务含义 | | ||
| 139 | +|---|---|---|---|---| | ||
| 140 | +| `iIncrement` | int | 否 | — | 整数主键 ID(标准列) | | ||
| 141 | +| `sId` | varchar(100) | 是 | — | 业务 ID(标准列) | | ||
| 142 | +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离,标准列) | | ||
| 143 | +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离,标准列) | | ||
| 144 | +| `tCreateDate` | datetime | 否 | CURRENT_TIMESTAMP | 创建时间(标准列) | | ||
| 145 | +| `sCategory` | varchar(100) | 否 | — | 权限分类编码(如 `usr:user:create`) | | ||
| 146 | +| `sCategoryName` | varchar(100) | 否 | — | 权限分类中文名(用于权限组复选框展示) | | ||
| 147 | +| `sDescription` | varchar(255) | 是 | NULL | 权限说明 | | ||
| 148 | +| `iIsDisabled` | tinyint(1) | 否 | 0 | 作废标志:0=有效,1=已作废 | | ||
| 149 | +| `tUpdateDate` | datetime | 否 | CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 修改时间 | | ||
| 150 | + | ||
| 151 | +### 索引 | ||
| 152 | + | ||
| 153 | +- `uk_tPermission_sCategory` (UNIQUE): `sCategory` | ||
| 154 | +- `idx_tPermission_sCategoryName` (NORMAL): `sCategoryName` | ||
| 155 | + | ||
| 156 | +### 外键 | ||
| 157 | + | ||
| 158 | +(无) | ||
| 159 | + | ||
| 160 | +### 业务注记 | ||
| 161 | + | ||
| 162 | +- 提供 REQ-USR-001/002 表 2「权限组」复选框的下拉数据;新增 / 修改用户时配套写入 `tUserPermission`。 | ||
| 163 | +- `sCategory` 作为程序内 RBAC 校验的字符串编码,规范见 `docs/04 § 1.6`;新增权限须同步发布到后端枚举与前端 `<AuthButton>`。 | ||
| 164 | +- 字典表,行数预期 < 1000;不做按租户隔离查询的复合索引。 | ||
| 165 | + | ||
| 166 | +--- | ||
| 167 | + | ||
| 168 | +## `tUserPermission` — 用户 ↔ 权限多对多关联表 | ||
| 169 | + | ||
| 170 | +### 字段 | ||
| 171 | + | ||
| 172 | +| 字段 | 类型 | Nullable | 默认 | 业务含义 | | ||
| 173 | +|---|---|---|---|---| | ||
| 174 | +| `iIncrement` | int | 否 | — | 整数主键 ID(标准列) | | ||
| 175 | +| `sId` | varchar(100) | 是 | — | 业务 ID(标准列) | | ||
| 176 | +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离,标准列) | | ||
| 177 | +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离,标准列) | | ||
| 178 | +| `tCreateDate` | datetime | 否 | CURRENT_TIMESTAMP | 创建时间(标准列) | | ||
| 179 | +| `iUserId` | int | 否 | — | 用户主键,关联 `tUser.iIncrement` | | ||
| 180 | +| `iPermissionId` | int | 否 | — | 权限主键,关联 `tPermission.iIncrement` | | ||
| 181 | +| `sGrantedBy` | varchar(50) | 是 | NULL | 授权操作员用户名(审计用) | | ||
| 182 | + | ||
| 183 | +### 索引 | ||
| 184 | + | ||
| 185 | +- `uk_tUserPermission_user_perm` (UNIQUE): `iUserId, iPermissionId` | ||
| 186 | +- `idx_tUserPermission_iPermissionId` (NORMAL): `iPermissionId` | ||
| 187 | + | ||
| 188 | +### 外键 | ||
| 189 | + | ||
| 190 | +- `fk_tUserPermission_iUserId`: `iUserId` → `tUser.iIncrement` (ON DELETE CASCADE, ON UPDATE CASCADE) | ||
| 191 | +- `fk_tUserPermission_iPermissionId`: `iPermissionId` → `tPermission.iIncrement` (ON DELETE RESTRICT, ON UPDATE CASCADE) | ||
| 192 | + | ||
| 193 | +### 业务注记 | ||
| 194 | + | ||
| 195 | +- 关联表,承载 REQ-USR-001/002 表 2「权限组」的勾选结果。 | ||
| 196 | +- 用户被删除时级联清理本表行;权限分类被删除时阻止(需先解除全部用户授权)。 | ||
| 197 | +- 唯一索引 `(iUserId, iPermissionId)` 防止重复授权。 | ||
| 198 | +- 字典关联表不带 `tUpdateDate` / `iIsDisabled`:授权关系采用"存在即生效,删除即撤销"语义,无需软删。 | ||
| 199 | + | ||
| 200 | +--- | ||
| 201 | + | ||
| 202 | +## `tCompany` — 公司及版本字典 | ||
| 203 | + | ||
| 204 | +### 字段 | ||
| 205 | + | ||
| 206 | +| 字段 | 类型 | Nullable | 默认 | 业务含义 | | ||
| 207 | +|---|---|---|---|---| | ||
| 208 | +| `iIncrement` | int | 否 | — | 整数主键 ID(标准列) | | ||
| 209 | +| `sId` | varchar(100) | 是 | — | 业务 ID(标准列) | | ||
| 210 | +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离,标准列) | | ||
| 211 | +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离,标准列) | | ||
| 212 | +| `tCreateDate` | datetime | 否 | CURRENT_TIMESTAMP | 创建时间(标准列) | | ||
| 213 | +| `sCompanyName` | varchar(100) | 否 | — | 公司名称 | | ||
| 214 | +| `sEdition` | varchar(32) | 否 | '标准版' | 版本:`标准版` / `专业版` / `企业版` 等 | | ||
| 215 | +| `iIsDisabled` | tinyint(1) | 否 | 0 | 作废标志 | | ||
| 216 | +| `tUpdateDate` | datetime | 否 | CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 修改时间 | | ||
| 217 | + | ||
| 218 | +### 索引 | ||
| 219 | + | ||
| 220 | +- `idx_tCompany_sCompanyName` (NORMAL): `sCompanyName` | ||
| 221 | +- `idx_tCompany_sEdition` (NORMAL): `sEdition` | ||
| 222 | + | ||
| 223 | +### 外键 | ||
| 224 | + | ||
| 225 | +(无) | ||
| 226 | + | ||
| 227 | +### 业务注记 | ||
| 228 | + | ||
| 229 | +- 字典表,提供 REQ-USR-004 登录页「版本」下拉数据。 | ||
| 230 | +- 不与 `tUser` 建外键:账号可在多公司 / 多版本之间复用;登录时仅用作上下文选择,由应用层校验"用户是否被允许进入该公司版本"。 | ||
| 231 | +- 当 `sEdition` 的取值集合稳定后,可改为受控字典并迁移到 `tDictionary` 类公共字典表;当前阶段独立维护。 |
docs/04-技术规范.md
0 → 100644
| 1 | +++ a/docs/04-技术规范.md | ||
| 1 | +# 04-技术规范 | ||
| 2 | + | ||
| 3 | +## 零、技术栈总览 | ||
| 4 | + | ||
| 5 | +| 分层模块 | 技术 | 版本要求 | 说明 | | ||
| 6 | +|---|---|---|---| | ||
| 7 | +| 前端基础框架 | React | 18.x | 构建前端应用 | | ||
| 8 | +| 前端 UI 组件 | Ant Design | 5.x | 页面组件与交互控件 | | ||
| 9 | +| 前端状态管理 | Redux Toolkit | 最新稳定版 | 管理全局状态 | | ||
| 10 | +| 前端路由管理 | React Router | v6 | 页面路由与导航 | | ||
| 11 | +| 前端工程化构建 | Vite | 最新稳定版 | 前端开发与打包构建 | | ||
| 12 | +| 前端接口通信 | Axios | 最新稳定版 | 调用后端 API | | ||
| 13 | +| 后端基础框架 | Spring Boot | 3.x | 构建后端服务 | | ||
| 14 | +| 后端数据访问 | MyBatis-Plus | 最新稳定版 | 数据库访问与 ORM 增强 | | ||
| 15 | +| 工作流引擎 | Activiti | 6.x | 审批流、流程流转 | | ||
| 16 | +| 缓存服务 | Redis | 最新稳定版 | 缓存、会话、分布式能力 | | ||
| 17 | +| 报表打印 | JXLS | 2.8.1 | 基于 Excel 模板生成报表 | | ||
| 18 | +| Excel 导入导出 | EasyExcel | 4.0.3 | Excel 数据导入导出 | | ||
| 19 | +| 关系型数据库 | MySQL | 8.x | 核心业务数据存储 | | ||
| 20 | +| 数据库 schema 迁移 | Flyway (`flyway-core` + `flyway-mysql`) | 10.x / 最新稳定版 | `sql/migrations/V_n__*.sql` 顺序 apply;Spring Boot 启动时自动应用 | | ||
| 21 | +| 接口风格 | RESTful API | 统一规范 | 前后端接口设计规范 | | ||
| 22 | +| 权限认证 | Spring Security / JWT | 最新稳定版 | 登录认证、权限控制 | | ||
| 23 | +| API 文档 | OpenAPI / Swagger | 最新稳定版 | 接口文档与调试 | | ||
| 24 | +| 项目构建管理 | Maven | 3.9.x | Java 项目依赖与构建 | | ||
| 25 | +| JDK 运行环境 | Java | 17 / 21 | Spring Boot 3 推荐版本 | | ||
| 26 | +| 部署容器 | Docker | 最新稳定版 | 容器化部署 | | ||
| 27 | +| Web 服务器 / 反向代理 | Nginx | 最新稳定版 | 前端托管、反向代理、负载分发 | | ||
| 28 | +| 日志管理 | Logback | 默认集成 / 最新稳定版 | 应用日志输出 | | ||
| 29 | +| 对象映射工具 | MapStruct | 最新稳定版 | DTO / VO / Entity 转换 | | ||
| 30 | +| 工具类库 | Hutool / Apache Commons | 最新稳定版 | 常用工具方法支持 | | ||
| 31 | + | ||
| 32 | +> 本表由 scope-lock 锁定。后续所有规范基于此表推导。 | ||
| 33 | + | ||
| 34 | +## 一、后端规范 | ||
| 35 | + | ||
| 36 | +### 1.1 分层结构 | ||
| 37 | + | ||
| 38 | +| 层 | 职责 | | ||
| 39 | +|---|---| | ||
| 40 | +| controller | 解析 HTTP 入参 / 校验请求体格式 / 返回 `Result<T>`;不写业务逻辑 | | ||
| 41 | +| service | 业务流程编排、事务边界、跨 Mapper 组合;接口 + impl 拆开 | | ||
| 42 | +| mapper | 数据访问,对应一张表;XML 写 SQL,禁止跨表 JOIN 之外的业务判断 | | ||
| 43 | +| entity | 与数据库表一一映射的 PO;不带业务方法 | | ||
| 44 | +| dto / vo | 入参(DTO) / 出参(VO)专用结构;通过 MapStruct 与 entity 转换 | | ||
| 45 | +| common | 跨模块基础设施(异常 / 响应 / 配置 / 工具 / 安全) | | ||
| 46 | + | ||
| 47 | +调用方向严格自上而下,禁止反向依赖(Controller 不能被 Service import)。 | ||
| 48 | + | ||
| 49 | +### 1.2 命名约定 | ||
| 50 | + | ||
| 51 | +- **包名**:根包`com.xly.test4`,全小写点分隔;模块代码小写作为子包(`USR` → `usr`)。 | ||
| 52 | +- **类名**:大驼峰,按职责加后缀:`UserController` / `UserService` / `UserServiceImpl` / `UserMapper` / `UserCreateDTO` / `UserVO`。 | ||
| 53 | +- **方法名 / 变量**:小驼峰;布尔以 `is` / `has` / `can` 开头。 | ||
| 54 | +- **常量**:`UPPER_SNAKE_CASE`,定义在 `common.constant.<Module>Constants`。 | ||
| 55 | + | ||
| 56 | +示例: | ||
| 57 | + | ||
| 58 | +```java | ||
| 59 | +// 示例 1:Controller | ||
| 60 | +@RestController | ||
| 61 | +@RequestMapping("/api/usr/user") | ||
| 62 | +public class UserController { | ||
| 63 | + public Result<Long> createUser(@Valid @RequestBody UserCreateDTO dto) { ... } | ||
| 64 | +} | ||
| 65 | + | ||
| 66 | +// 示例 2:常量 | ||
| 67 | +public final class UserConstants { | ||
| 68 | + public static final int MAX_LOGIN_FAIL_TIMES = 5; | ||
| 69 | +} | ||
| 70 | +``` | ||
| 71 | + | ||
| 72 | +### 1.3 统一响应格式 | ||
| 73 | + | ||
| 74 | +所有 Controller 返回 `Result<T>`,结构: | ||
| 75 | + | ||
| 76 | +```json | ||
| 77 | +// 成功 | ||
| 78 | +{ "code": "0", "msg": "ok", "data": { ... }, "traceId": "..." } | ||
| 79 | + | ||
| 80 | +// 失败 | ||
| 81 | +{ "code": "USR-001", "msg": "用户名已存在", "data": null, "traceId": "..." } | ||
| 82 | +``` | ||
| 83 | + | ||
| 84 | +错误码段位: | ||
| 85 | + | ||
| 86 | +| 段位 | 含义 | | ||
| 87 | +|---|---| | ||
| 88 | +| `0` | 成功 | | ||
| 89 | +| `1xxx` | 通用框架错误(参数校验失败、权限不足、未登录) | | ||
| 90 | +| `<MOD>-<NNN>` | 业务错误,MOD 与模块代码一致(如 `USR-001`) | | ||
| 91 | +| `5xxx` | 系统内部错误(DB 异常、第三方调用失败) | | ||
| 92 | + | ||
| 93 | +### 1.4 异常处理 | ||
| 94 | + | ||
| 95 | +- 使用 `@RestControllerAdvice` 全局异常处理器,统一捕获并转 `Result.fail(...)`。 | ||
| 96 | +- 业务异常用自定义 `BusinessException(code, msg)` 抛出,不要 catch 后吞掉。 | ||
| 97 | +- 参数校验异常(`MethodArgumentNotValidException`)→ `1001` + 首个字段错误信息。 | ||
| 98 | +- 未预期异常 → `5000` + 通用文案"系统繁忙,请稍后重试";**接口响应禁止回显堆栈**,堆栈只进 Logback。 | ||
| 99 | +- traceId 在 Filter 层注入 MDC,错误响应中回填便于排查。 | ||
| 100 | + | ||
| 101 | +### 1.5 事务 | ||
| 102 | + | ||
| 103 | +- `@Transactional` 仅打在 Service 实现类方法上,默认 `rollbackFor = Exception.class`。 | ||
| 104 | +- Controller 层禁止打事务注解。 | ||
| 105 | +- 一个 Service 方法只能跨当前模块的 Mapper;调用另一模块必须走对方 Service 的公共方法。 | ||
| 106 | +- 跨服务(跨进程)调用 **禁止**包在事务里;如有最终一致性需求走异步 + 补偿。 | ||
| 107 | + | ||
| 108 | +### 1.6 认证 | ||
| 109 | + | ||
| 110 | +- 使用 Spring Security + JWT;登录接口签发 `accessToken`(24h)+ `refreshToken`(7 天)。 | ||
| 111 | +- token 放 Authorization header,格式 `Bearer <token>`。 | ||
| 112 | +- 密钥从 `.env.local` → `JWT_SECRET` 注入到 `application.yml`,**代码里禁止硬编码**。 | ||
| 113 | +- 刷新机制:accessToken 过期前 30 分钟前端用 refreshToken 换新;refreshToken 一次性使用,签发新 access 时同时滚动新 refresh。 | ||
| 114 | + | ||
| 115 | +## 二、前端规范 | ||
| 116 | + | ||
| 117 | +### 2.1 目录约定 | ||
| 118 | + | ||
| 119 | +| 目录 | 职责 | | ||
| 120 | +|---|---| | ||
| 121 | +| `api/` | 后端接口封装;每模块一文件;导出函数对应 REQ 接口 | | ||
| 122 | +| `components/` | 跨页面通用组件;无业务耦合 | | ||
| 123 | +| `pages/` | 业务页面;按模块代码分子目录 | | ||
| 124 | +| `store/` | Redux Toolkit slice;按模块拆分 | | ||
| 125 | +| `hooks/` | 自定义 hooks | | ||
| 126 | +| `utils/` | 纯函数工具(日期、金额、字符串等) | | ||
| 127 | +| `styles/` | 全局样式 + Design Tokens | | ||
| 128 | + | ||
| 129 | +**前端禁止直接写 SQL / 操作 DB / 直连数据库连接**;所有数据访问走 `api/` 层 axios 封装。 | ||
| 130 | + | ||
| 131 | +### 2.2 状态管理 | ||
| 132 | + | ||
| 133 | +- **服务端数据**:默认不存 Redux;通过 `useQuery` 风格 hook 或就近 `useState + useEffect` 拉取;如确实需跨页面共享(如当前用户信息),存 `store/` 对应 slice。 | ||
| 134 | +- **全局状态**:登录态、当前用户、权限列表、菜单 → Redux。 | ||
| 135 | +- **页面级状态**:表单字段、过滤条件、分页 → `useState` 或 `useReducer`,不进 Redux。 | ||
| 136 | + | ||
| 137 | +### 2.3 请求封装 | ||
| 138 | + | ||
| 139 | +- 单例 `axios` 实例,`baseURL` 由环境变量注入。 | ||
| 140 | +- 请求拦截器:从 Redux 取 accessToken 注入 Authorization header。 | ||
| 141 | +- 响应拦截器: | ||
| 142 | + - `code === '0'` → resolve `data` | ||
| 143 | + - 401 → 自动调 refresh 接口尝试续期;失败则清空登录态跳 `/login` | ||
| 144 | + - 其他失败 → reject `Result`,由调用方决定弹窗 | ||
| 145 | +- 默认超时 15s;GET 类请求允许业务侧覆盖至 60s(如导出大数据量列表)。 | ||
| 146 | +- 重试:默认不重试;幂等查询接口(GET)业务侧可显式配置 1 次重试。 | ||
| 147 | + | ||
| 148 | +### 2.4 错误处理 | ||
| 149 | + | ||
| 150 | +- **网络错误**(无 response):响应拦截器统一 `notification.error`「网络异常,请检查连接」。 | ||
| 151 | +- **业务错误**(`code !== '0'`):reject 给调用方;调用方根据场景决定 `message.error`(inline)或 `notification.error`(异步任务)。 | ||
| 152 | +- **页面级错误**(路由懒加载失败 / 组件渲染异常):用 React `ErrorBoundary` 兜底,展示 `Result` 错误页 + "重试"按钮。 | ||
| 153 | + | ||
| 154 | +### 2.5 样式与主题 | ||
| 155 | + | ||
| 156 | +- CSS 变量命名格式:`--color-<scope>-<role>-<state>` | ||
| 157 | + - `<scope>`:`form` / `table-row` / `button` / 其他组件域名 | ||
| 158 | + - `<role>`:`bg` / `fg` / `border` | ||
| 159 | + - `<state>`:`edit` / `readonly` / `hover` / `selected` / `disabled` | ||
| 160 | +- 文件位置:`src/styles/tokens.css`,由 skeleton-gen 生成空骨架,色值由 docs/06 § 二锁定后填入。 | ||
| 161 | +- 组件样式中只允许 `var(--color-xxx)`,**禁止硬编码 hex / rgba**。 | ||
| 162 | +- 与 Ant Design 5 对接:在 App 根层用 `ConfigProvider`,将 token 值绑定到 `theme.token.colorPrimary` 等。 | ||
| 163 | +- 具体 token 表见 docs/06 § 二。 | ||
| 164 | + | ||
| 165 | +## 三、共同约定 | ||
| 166 | + | ||
| 167 | +### 3.1 Git 提交 | ||
| 168 | + | ||
| 169 | +`<type>(<scope>): <subject> REQ-XXX-NNN` | ||
| 170 | + | ||
| 171 | +具体规则见 `CLAUDE.md § 🗂️ Git 提交规范`。 | ||
| 172 | + | ||
| 173 | +### 3.2 分页查询 | ||
| 174 | + | ||
| 175 | +- **后端入参**:`PageQuery { pageNum: int=1, pageSize: int=20, ... }`,pageSize 上限 100。 | ||
| 176 | +- **后端出参**:`PageResult<T> { records: List<T>, total: long, pageNum: int, pageSize: int }`。 | ||
| 177 | +- **前端**:Ant Design `Table` + `pagination` 双向绑定 `pageNum` / `pageSize`;切换时清空选中行。 | ||
| 178 | +- 排序与过滤条件并入 PageQuery 子类(如 `UserPageQuery`),避免接口爆炸。 | ||
| 179 | + | ||
| 180 | +### 3.3 日期与金额 | ||
| 181 | + | ||
| 182 | +- **后端类型**:日期一律 `LocalDateTime`(含时分秒)或 `LocalDate`(仅日期);JSON 序列化用 ISO-8601 字符串(`yyyy-MM-dd'T'HH:mm:ss`)。 | ||
| 183 | +- **金额类型**:`BigDecimal`,标度(scale)= 2(保留 2 位小数),所有运算用 `BigDecimal.divide(..., RoundingMode.HALF_UP)` 避免 `double` 精度丢失。 | ||
| 184 | +- **前端展示**: | ||
| 185 | + - 日期用 `dayjs` 统一格式化,列表 `YYYY-MM-DD HH:mm`,详情 `YYYY-MM-DD HH:mm:ss`。 | ||
| 186 | + - 金额展示用千分位 + 2 位小数;展示前必须 `Number.toFixed(2)`。 | ||
| 187 | + | ||
| 188 | +### 3.4 数据访问规约 | ||
| 189 | + | ||
| 190 | +- **SELECT 字段必须显式列举**,禁止 `SELECT *`;新增字段时同步更新 Mapper XML 与 entity。 | ||
| 191 | +- **禁止循环中执行 DB 查询**(N+1 反模式);改用: | ||
| 192 | + - 批量查询:`Mapper.selectBatchIds(ids)` | ||
| 193 | + - IN 子句:`Mapper.selectByIds(ids)` 一次拉取 | ||
| 194 | + - JOIN:在 XML 内 JOIN 子表 | ||
| 195 | +- Mapper XML 内字段与表名建议用 `<sql>` 复用片段或常量;禁止字符串拼接 SQL 片段。 | ||
| 196 | +- MyBatis-Plus 的 `LambdaQueryWrapper` 优先于硬编码字符串列名。 | ||
| 197 | + | ||
| 198 | +### 3.5 配置与安全 | ||
| 199 | + | ||
| 200 | +- **配置外置**:DB 连接 / 端口 / 密钥 / 第三方 URL 等一律放 `application.yml` + `.env.local`; | ||
| 201 | + - `application.yml` 写占位 `${JWT_SECRET}` | ||
| 202 | + - 真实值放 `.env.local`(不入 git) | ||
| 203 | + - 代码里**禁止硬编码**任何凭据。 | ||
| 204 | +- **前端安全**: | ||
| 205 | + - `localStorage` 不存敏感信息(token / 身份 / 个人数据)。 | ||
| 206 | + - 推荐 HttpOnly Cookie 存 refreshToken;accessToken 放内存(Redux)+ 刷新机制兜底。 | ||
| 207 | + - 接口响应**禁止回显后端异常堆栈**(与 § 1.4 一致),错误码 + 文案足够。 |
docs/05-API接口契约.md
0 → 100644
| 1 | +++ a/docs/05-API接口契约.md | ||
| 1 | +# 05-API接口契约 | ||
| 2 | + | ||
| 3 | +BasePath: `/api` | ||
| 4 | +端口: `8080` | ||
| 5 | + | ||
| 6 | +## 全局约定 | ||
| 7 | + | ||
| 8 | +### 响应格式 | ||
| 9 | +```json | ||
| 10 | +{"code": 200, "message": "操作成功", "data": {}, "timestamp": 1700000000000} | ||
| 11 | +``` | ||
| 12 | + | ||
| 13 | +### 错误码 | ||
| 14 | +| 范围 | 含义 | | ||
| 15 | +|---|---| | ||
| 16 | +| 200 | 成功 | | ||
| 17 | +| 400xx | 客户端参数错误 | | ||
| 18 | +| 401xx | 认证/授权错误 | | ||
| 19 | +| 403xx | 权限不足 | | ||
| 20 | +| 404xx | 资源不存在 | | ||
| 21 | +| 500xx | 服务端内部错误 | | ||
| 22 | + | ||
| 23 | +### 鉴权 | ||
| 24 | + | ||
| 25 | +- 所有 `/api/**` 接口默认要求 JWT;登录、版本下拉等少数白名单接口(见各接口 `Auth: 否`)匿名访问。 | ||
| 26 | +- 客户端将登录返回的 `accessToken` 放入请求头:`Authorization: Bearer <accessToken>`。 | ||
| 27 | +- accessToken 默认有效期 24h;过期前 30 分钟前端可用 `refreshToken` 续期(独立接口,本契约暂不展开)。 | ||
| 28 | +- 锁定中(`tUser.tLockedUntil` 未过期)的账号即使密码正确也无法登录,返回 `40110`。 | ||
| 29 | + | ||
| 30 | +### 分页参数 | ||
| 31 | + | ||
| 32 | +请求查询参数(GET)或 body(POST 列表)统一: | ||
| 33 | + | ||
| 34 | +``` | ||
| 35 | +pageNum: int = 1 // 页码,从 1 开始 | ||
| 36 | +pageSize: int = 20 // 每页条数,上限 100 | ||
| 37 | +``` | ||
| 38 | + | ||
| 39 | +响应 `data` 结构: | ||
| 40 | + | ||
| 41 | +```json | ||
| 42 | +{ | ||
| 43 | + "records": [ ... ], | ||
| 44 | + "total": 123, | ||
| 45 | + "pageNum": 1, | ||
| 46 | + "pageSize": 20 | ||
| 47 | +} | ||
| 48 | +``` | ||
| 49 | + | ||
| 50 | +`pageSize > 100` 时后端自动截断为 100,并在响应 `message` 中提示 `pageSize 已截断到 100`。 | ||
| 51 | + | ||
| 52 | +## 接口清单 | ||
| 53 | + | ||
| 54 | +### REQ-USR-001 增加用户 | ||
| 55 | + | ||
| 56 | +- **Method**: POST | ||
| 57 | +- **Path**: `/api/usr/user` | ||
| 58 | +- **Auth**: 是 | ||
| 59 | +- **Permission**: `usr:user:create`(仅超级管理员或被显式授权者) | ||
| 60 | +- **请求**: JSON body,字段包括 `userCode`、`userName`、`employeeId`(可空)、`userType`(NORMAL/ADMIN)、`language`(zh-CN/en/zh-TW)、`canEditDoc`(bool)、`password`(明文,后端哈希后存储,缺省则使用配置项 `app.security.default-password`,默认 `666666`)、`permissionIds`(int[],权限分类主键数组,对应 `tUserPermission` 关联)。 | ||
| 61 | +- **响应**: `data` = `{ "userId": <int>, "userCode": <string> }`,对应 REQ-USR-001 输出表 1「用户号」。 | ||
| 62 | + | ||
| 63 | +#### 错误码 | ||
| 64 | +- `40001` — 必填字段缺失或格式不合法 | ||
| 65 | +- `40002` — `userName` 已存在(违反 `uk_tUser_sUserName`) | ||
| 66 | +- `40003` — `userCode` 已存在(违反 `uk_tUser_sUserCode`) | ||
| 67 | +- `40004` — `employeeId` 非法(在 `tEmployee` 中不存在或已作废) | ||
| 68 | +- `40005` — `permissionIds` 中某个权限分类不存在或已作废 | ||
| 69 | +- `40301` — 当前账号无 `usr:user:create` 权限 | ||
| 70 | + | ||
| 71 | +--- | ||
| 72 | + | ||
| 73 | +### REQ-USR-002 修改用户 | ||
| 74 | + | ||
| 75 | +- **Method**: PUT | ||
| 76 | +- **Path**: `/api/usr/user/{userId}` | ||
| 77 | +- **Auth**: 是 | ||
| 78 | +- **Permission**: `usr:user:update`(管理员)或本人改本人非角色字段 | ||
| 79 | +- **请求**: 路径参数 `userId` + JSON body,可选字段 `employeeId` / `userType` / `language` / `canEditDoc` / `iIsDisabled` / `permissionIds`。`userName` / `userCode` / `password` 不在本接口修改。 | ||
| 80 | +- **响应**: `data` = `{ "userId": <int> }`,对应 REQ-USR-002 输出表 1「用户 id」。 | ||
| 81 | + | ||
| 82 | +#### 错误码 | ||
| 83 | +- `40001` — 字段格式不合法 | ||
| 84 | +- `40006` — `userType` 变更需 `usr:user:assign-role` 权限 | ||
| 85 | +- `40004` — `employeeId` 非法(不存在或已作废) | ||
| 86 | +- `40005` — `permissionIds` 中某个权限分类不存在或已作废 | ||
| 87 | +- `40402` — 目标 `userId` 不存在 | ||
| 88 | +- `40301` — 当前账号无 `usr:user:update` 权限(且非本人) | ||
| 89 | + | ||
| 90 | +--- | ||
| 91 | + | ||
| 92 | +### REQ-USR-003 查询用户 | ||
| 93 | + | ||
| 94 | +- **Method**: GET | ||
| 95 | +- **Path**: `/api/usr/user` | ||
| 96 | +- **Auth**: 是 | ||
| 97 | +- **Permission**: `usr:user:list`(管理员) | ||
| 98 | +- **请求**: 查询参数 `queryField`(用户名/员工名/用户号/部门/用户类型/作废/登录日期/制单人)+ `matchMode`(包含/不包含/等于)+ `queryValue`(可空,空时不过滤)+ `pageNum` + `pageSize`。 | ||
| 99 | +- **响应**: 分页结构,`records[]` 项对应 REQ-USR-003 输出表 1 全部字段:`index` / `userName` / `employeeName` / `userCode` / `department` / `userType` / `language` / `isDisabled` / `lastLoginDate` / `createdBy` / `createDate`。`employeeName` / `department` 由 LEFT JOIN `tEmployee` 取得。密码字段任何场景下都不返回。 | ||
| 100 | + | ||
| 101 | +#### 错误码 | ||
| 102 | +- `40001` — `queryField` / `matchMode` 取值非法 | ||
| 103 | +- `40301` — 当前账号无 `usr:user:list` 权限 | ||
| 104 | + | ||
| 105 | +--- | ||
| 106 | + | ||
| 107 | +### REQ-USR-004 用户登录 | ||
| 108 | + | ||
| 109 | +- **Method**: POST | ||
| 110 | +- **Path**: `/api/auth/login` | ||
| 111 | +- **Auth**: 否(匿名) | ||
| 112 | +- **Permission**: 无 | ||
| 113 | +- **请求**: JSON body `{ "userName": <string>, "password": <string>, "companyId": <int> }`,`companyId` 对应 `tCompany.iIncrement`(前端「版本」下拉所选项)。 | ||
| 114 | +- **响应**: `data` = `{ "accessToken": <string>, "tokenType": "Bearer", "expiresIn": 86400, "user": { "userId": ..., "userName": ..., "userType": ..., "language": ..., "canEditDoc": ..., "permissions": [<sCategory>...] } }`。 | ||
| 115 | + | ||
| 116 | +#### 错误码 | ||
| 117 | +- `40001` — `userName` / `password` / `companyId` 字段缺失 | ||
| 118 | +- `40101` — 用户名或密码错误(统一返回,不区分具体哪个错;同时 `tUser.iLoginFailCount += 1`) | ||
| 119 | +- `40110` — 账号已锁定(`tLockedUntil > NOW()`),返回锁定截止时间 | ||
| 120 | +- `40111` — 账号已作废(`tUser.iIsDisabled = 1`) | ||
| 121 | +- `40404` — `companyId` 对应公司不存在或已作废 |
docs/06-UI交互规范.md
0 → 100644
| 1 | +++ a/docs/06-UI交互规范.md | ||
| 1 | +# 06-UI交互规范 | ||
| 2 | + | ||
| 3 | +> 本项目所有页面布局以项目根 `prototype/` 目录下的静态 HTML mockup 为权威。前端阶段(fe-feature-*)实现时直接以 prototype/ HTML 推导组件树与样式。本文件仅承载跨页面通用规则与 Design Tokens。 | ||
| 4 | + | ||
| 5 | +## 一、通用交互规则 | ||
| 6 | + | ||
| 7 | +### 1.1 操作反馈 | ||
| 8 | + | ||
| 9 | +- **成功反馈**:表单提交 / 数据操作成功后,统一使用 Ant Design `message.success`(顶部居中,2 秒自动消失),文案以"操作动词 + 成功"格式,例如"保存成功""删除成功""登录成功"。 | ||
| 10 | +- **失败反馈**:业务错误使用 `message.error`(4 秒),文案直接展示后端返回的 `msg` 字段;网络异常或 5xx 使用 `notification.error`(右上角,需手动关闭),标题统一"操作失败",描述给出可复制的 traceId(便于排查)。 | ||
| 11 | +- **危险操作二次确认**:删除、禁用、批量更新等不可逆或影响范围大的动作,统一使用 `Modal.confirm`(图标 `ExclamationCircleOutlined`),确认按钮使用 danger 主题,按钮文案"确认删除/确认禁用",禁止默认聚焦确认按钮。 | ||
| 12 | +- **长耗时按钮 loading 态**:所有提交按钮在请求未返回前必须置 `loading=true` 并禁用其他相关按钮;超过 10 秒未返回,前端在 button 上叠加文案"处理中…"提示用户耐心等待,不主动中断。 | ||
| 13 | + | ||
| 14 | +### 1.2 数据展示 | ||
| 15 | + | ||
| 16 | +- **空状态**:列表无数据统一使用 Ant Design `Empty` 组件,描述文案"暂无数据";带筛选条件时改为"未找到匹配的记录,请调整筛选条件"。 | ||
| 17 | +- **加载态**:列表 / 详情页统一使用 `Skeleton`(结构化骨架屏),不允许使用纯 spinner 遮罩整页;表格内联加载使用 Table 自带 `loading` 属性。 | ||
| 18 | +- **异常态**:接口失败导致页面无法渲染时,使用 `Result` 组件(`status="error"`),标题"加载失败",描述展示业务错误码与可复制 traceId,并提供"重试"主按钮。 | ||
| 19 | + | ||
| 20 | +### 1.3 权限控制(前端) | ||
| 21 | + | ||
| 22 | +- **路由级**:在 React Router 外层封装 `<RequireAuth>`,未登录跳 `/login`,已登录但权限不足跳 `/403`;权限定义由后端 RBAC 接口返回当前用户的 `permissions` 字符串数组(如 `usr:user:create`)。 | ||
| 23 | +- **菜单级**:左侧菜单根据 `permissions` 过滤;用户看不到的模块完全不出现,不做置灰处理。 | ||
| 24 | +- **按钮级**:封装 `<AuthButton perm="usr:user:create">` 组件,无权限时直接不渲染(而非禁用)。后端接口必须独立做权限校验,前端权限隐藏只是用户体验层。 | ||
| 25 | + | ||
| 26 | +## 二、Design Tokens | ||
| 27 | + | ||
| 28 | +### 2.1 全局调色板 | ||
| 29 | + | ||
| 30 | +所有色值统一以 CSS 变量定义于 `src/styles/tokens.css`,与 Ant Design 5 的 `ConfigProvider.theme.token` 双向对齐。 | ||
| 31 | + | ||
| 32 | +| 语义 | 变量名 | 默认值 | 用途 | | ||
| 33 | +|---|---|---|---| | ||
| 34 | +| 主色 | `--color-primary` | `#1677ff` | 主按钮、链接、选中态;映射 AntD `colorPrimary` | | ||
| 35 | +| 成功 | `--color-success` | `#52c41a` | 成功态、有效状态指示 | | ||
| 36 | +| 警告 | `--color-warning` | `#faad14` | 警告 tag / message | | ||
| 37 | +| 错误 | `--color-error` | `#ff4d4f` | 错误 message、危险按钮、必填校验失败 | | ||
| 38 | +| 主文字 | `--color-text` | `rgba(0,0,0,0.88)` | 标题、正文 | | ||
| 39 | +| 次文字 | `--color-text-secondary` | `rgba(0,0,0,0.65)` | 表单 label、辅助说明 | | ||
| 40 | +| 三级文字 | `--color-text-tertiary` | `rgba(0,0,0,0.45)` | placeholder、禁用文字 | | ||
| 41 | +| 边框 | `--color-border` | `#d9d9d9` | 输入框、卡片、表格分割线 | | ||
| 42 | +| 背景 | `--color-bg` | `#ffffff` | 卡片、模态框背景 | | ||
| 43 | +| 页面背景 | `--color-bg-layout` | `#f5f5f5` | 整页底色 | | ||
| 44 | + | ||
| 45 | +### 2.2 组件级状态色 | ||
| 46 | + | ||
| 47 | +| 序号 | 组件 | 编辑 bg | 只读 bg | 悬浮 bg | 编辑 fg | 只读 fg | 悬浮 fg | 备注 | | ||
| 48 | +|---|---|---|---|---|---|---|---|---| | ||
| 49 | +| 1 | 表单输入框 | `var(--color-bg)` | `var(--color-bg-layout)` | `var(--color-bg)` | `var(--color-text)` | `var(--color-text-secondary)` | `var(--color-text)` | 只读使用 Input 的 readOnly 属性 | | ||
| 50 | +| 2 | 表格行 | `var(--color-bg)` | — | `var(--color-row-hover)` | `var(--color-text)` | — | `var(--color-text)` | 表格 hover 高亮 | | ||
| 51 | +| 3 | 选中行 | — | — | `var(--color-row-selected)` | — | — | `var(--color-primary)` | 单选 / 多选 | | ||
| 52 | +| 4 | 主按钮 | `var(--color-primary)` | `var(--color-text-tertiary)` | `var(--color-primary-hover)` | `#fff` | `#fff` | `#fff` | 禁用态走只读列 | | ||
| 53 | +| 5 | 危险按钮 | `var(--color-error)` | — | `var(--color-error-hover)` | `#fff` | — | `#fff` | 删除 / 禁用类操作 | | ||
| 54 | + | ||
| 55 | +Token 默认值(在 `src/styles/tokens.css` 定义): | ||
| 56 | + | ||
| 57 | +| 变量名 | 默认值 | | ||
| 58 | +|---|---| | ||
| 59 | +| `--color-row-hover` | `#fafafa` | | ||
| 60 | +| `--color-row-selected` | `#e6f4ff` | | ||
| 61 | +| `--color-primary-hover` | `#4096ff` | | ||
| 62 | +| `--color-error-hover` | `#ff7875` | | ||
| 63 | + | ||
| 64 | +### 2.3 引用约定 | ||
| 65 | + | ||
| 66 | +- 组件样式只用 `var(--color-xxx)`,禁止硬编码 hex / rgba。 | ||
| 67 | +- 新增 token 须先登记到 § 2.1 / 2.2 再补 `tokens.css`,禁止"先写代码后补登记"。 | ||
| 68 | +- 修改色值只改 `tokens.css` 一处,不允许组件层 `style` / CSS Module 覆盖;如确实业务需要新色,走"新增 token"流程。 | ||
| 69 | + | ||
| 70 | +## 三、页面清单 | ||
| 71 | + | ||
| 72 | +### module_usr 用户管理 | ||
| 73 | + | ||
| 74 | +- **登录页** (`/login`) | ||
| 75 | + - 类型: 表单页 | ||
| 76 | + - 对应 REQ: REQ-USR-004 | ||
| 77 | + - 入口菜单: 系统外(匿名访问入口) | ||
| 78 | + - 主要交互: 输入用户名 / 密码 / 选择版本(公司)→ 提交 → 成功跳 `/`;失败统一弹「用户名或密码错误」(不区分),账号锁定时弹锁定截止时间。 | ||
| 79 | + | ||
| 80 | +- **用户列表页** (`/usr/user`) | ||
| 81 | + - 类型: 列表页 | ||
| 82 | + - 对应 REQ: REQ-USR-003 | ||
| 83 | + - 入口菜单: 系统管理 → 用户管理 | ||
| 84 | + - 主要交互: 顶部筛选区(查询字段下拉 + 匹配方式下拉 + 查询值输入)+ 表格(分页 / 排序)+ 操作列(编辑 / 作废)+ 顶部「新增」按钮跳新增页。 | ||
| 85 | + | ||
| 86 | +- **用户新增页** (`/usr/user/new`) | ||
| 87 | + - 类型: 表单页 | ||
| 88 | + - 对应 REQ: REQ-USR-001 | ||
| 89 | + - 入口菜单: 用户列表页「新增」按钮 | ||
| 90 | + - 主要交互: 用户号 / 用户名手工输入,员工名下拉(来自 tEmployee),类型 / 语言下拉,单据修改权限复选,权限组复选框列表(来自 tPermission)。提交成功提示「保存成功」并返回列表。 | ||
| 91 | + | ||
| 92 | +- **用户编辑页** (`/usr/user/:userId/edit`) | ||
| 93 | + - 类型: 表单页 | ||
| 94 | + - 对应 REQ: REQ-USR-002 | ||
| 95 | + - 入口菜单: 用户列表页「编辑」操作 | ||
| 96 | + - 主要交互: 表单字段同新增,但用户号 / 用户名 / 密码不可编辑(置 readOnly 灰底);权限组勾选反映当前授权状态。提交成功提示「保存成功」并返回列表。 | ||
| 97 | + |
docs/07-环境配置.md
0 → 100644
| 1 | +++ a/docs/07-环境配置.md | ||
| 1 | +# 07-环境配置 | ||
| 2 | + | ||
| 3 | +## 一、依赖清单 | ||
| 4 | + | ||
| 5 | +| 层 | 依赖 | 版本 | 说明 | | ||
| 6 | +|---|---|---|---| | ||
| 7 | +| 运行时 | JDK | 17 或 21 | Spring Boot 3 推荐 | | ||
| 8 | +| 运行时 | Node.js | ≥ 18 LTS | Vite 5 / React 18 构建运行 | | ||
| 9 | +| 运行时 | MySQL Server | 8.x | 业务数据存储 | | ||
| 10 | +| 运行时 | Redis | 最新稳定版 | 缓存、会话 | | ||
| 11 | +| 运行时 | Nginx | 最新稳定版 | 前端静态托管 + 反向代理 | | ||
| 12 | +| 构建 | Maven | 3.9.x | 后端依赖管理与打包 | | ||
| 13 | +| 构建 | npm 或 pnpm | npm ≥ 9 / pnpm ≥ 8 | 前端包管理;项目统一二选一 | | ||
| 14 | +| 构建 | Flyway | 10.x | 数据库 migration(随 Spring Boot 启动 apply) | | ||
| 15 | +| 容器 | Docker | 最新稳定版 | 容器化部署 | | ||
| 16 | +| 容器 | Docker Compose | 最新稳定版 | 本地多服务编排 | | ||
| 17 | +| CLI 工具 | git | ≥ 2.30 | 版本管理 | | ||
| 18 | +| CLI 工具 | mysql-client | 8.x | `scripts/setup-test-db.sh` 需要 | | ||
| 19 | +| CLI 工具 | glab 或 gh | 最新稳定版 | 创建 MR / PR | | ||
| 20 | + | ||
| 21 | +## 二、端口约定 | ||
| 22 | + | ||
| 23 | +| 服务 | 端口 | 说明 | | ||
| 24 | +|---|---|---| | ||
| 25 | +| 后端 HTTP | 8080 | Spring Boot 内嵌 Tomcat | | ||
| 26 | +| 后端管理端点 | 8081 | Spring Boot Actuator(如启用,独立端口) | | ||
| 27 | +| 前端 dev 服务器 | 5173 | Vite dev server | | ||
| 28 | +| MySQL | 3306 | 业务数据库 | | ||
| 29 | +| MySQL(测试库) | 3307 | 隔离的测试库;可与主库同实例不同 schema | | ||
| 30 | +| Redis | 6379 | 缓存 / 会话 | | ||
| 31 | +| Nginx | 80 / 443 | 生产反向代理(80 → 443 强跳) | | ||
| 32 | + | ||
| 33 | +## 三、环境变量 | ||
| 34 | + | ||
| 35 | +运行时凭据(数据库连接、JWT 密钥等)全部放在仓库根的 `.env.local`,不入 git。 | ||
| 36 | +字段清单与占位符见该文件,真实值由开发者本地填写。 | ||
| 37 | + | ||
| 38 | +## 四、常用命令 | ||
| 39 | + | ||
| 40 | +| 命令 | 说明 | | ||
| 41 | +|---|---| | ||
| 42 | +| `cd backend && mvn spring-boot:run` | 本地启动后端(自动 apply Flyway migration) | | ||
| 43 | +| `cd frontend && npm run dev` | 本地启动前端 dev server(监听 5173) | | ||
| 44 | +| `cd backend && mvn clean package -DskipTests` | 打后端 jar | | ||
| 45 | +| `cd frontend && npm run build` | 打前端静态资源到 `dist/` | | ||
| 46 | +| `bash scripts/test.sh` | 一键跑全部测试(后端 build/lint/test + 前端 build/lint/test + e2e) | | ||
| 47 | +| `bash scripts/setup-test-db.sh` | 重置测试库(DROP + CREATE,不 apply migration;migration 由 Spring Boot 启动时 Flyway 自动 apply) | | ||
| 48 | +| `glab mr create` 或 `gh pr create` | 推送当前分支后创建 MR / PR | |
docs/08-模块任务管理.md
0 → 100644
| 1 | +++ a/docs/08-模块任务管理.md | ||
| 1 | +# 08-工作流进度 | ||
| 2 | + | ||
| 3 | +> 全流程进度跟踪。CC 每完成一项产出就勾选一项。 | ||
| 4 | +> - **§ 一 Plan(A0~A5)**:`plan-start` 找第一个未勾 A 子项分发到对应 skill | ||
| 5 | +> - **§ 二 Coding(模块)**:分发以 `docs/02-开发计划.md § 二 开发顺序清单` 为准;`coding-start` 按 docs/02 顺序扫描,对每个 REQ 所属模块查询本 § 二的 `MR:` 字段 + GitLab API `state`,找第一个非 merged 模块分发。本 § 二 行序无语义,仅作模块元数据表 | ||
| 6 | + | ||
| 7 | +## 一、Plan 阶段(一次性) | ||
| 8 | + | ||
| 9 | +- [x] A0 项目初始化 — project-init | ||
| 10 | + - [x] 依赖检查通过 | ||
| 11 | + - [x] 项目文件骨架已创建(CLAUDE.md + docs/01-需求清单/index.md + docs/04-技术规范.md) | ||
| 12 | + - [x] Git 已初始化 | ||
| 13 | + | ||
| 14 | +- [x] A1 范围锁定 — scope-lock | ||
| 15 | + - [x] 项目概述已填写(CLAUDE.md § 🎯 项目概述) | ||
| 16 | + - [x] 技术栈已确认(docs/04 § 零) | ||
| 17 | + - [x] 需求清单索引已填写(docs/01-需求清单/index.md) | ||
| 18 | + - [x] REQ 卡片骨架已生成(docs/01-需求清单/<module>/REQ-*.md,业务内容留待人工填写) | ||
| 19 | + | ||
| 20 | +- [x] A2 骨架生成 — skeleton-gen | ||
| 21 | + - [x] 架构文档已生成(docs/04 § 一+、docs/06、docs/07、docs/09) | ||
| 22 | + - [x] 工具脚本已生成(scripts/*.sh、.githooks/pre-push、.env.local) | ||
| 23 | + - [x] .gitignore 已配置 | ||
| 24 | + | ||
| 25 | +- [x] A3 DB 设计 + REQ 回填 — db-design-gen | ||
| 26 | + - [x] docs/03-数据库设计文档.md 已生成 | ||
| 27 | + - [x] docs/01 各 REQ 卡片"依赖表" + 模块头"涉及表" 已回填 | ||
| 28 | + | ||
| 29 | +- [x] A4 DB 初始化 — db-init | ||
| 30 | + - [x] sql/migrations/V1__initial_schema.sql 已生成 | ||
| 31 | + - [x] DDL 与 docs/03 全量一致 | ||
| 32 | + - [x] .env.local 凭据已验证(mysql -e "SELECT 1" OK) | ||
| 33 | + - [x] setup-test-db.sh 防护通过 + DROP+CREATE + apply V1 已执行 | ||
| 34 | + - [x] SHOW TABLES 行数 == docs/03 表数量 | ||
| 35 | + | ||
| 36 | +- [x] A5 下游文档生成 — downstream-gen | ||
| 37 | + - [x] docs/02 开发计划已生成 | ||
| 38 | + - [x] docs/05 API 契约已生成 | ||
| 39 | + - [x] docs/06 § 三 页面清单已填入 | ||
| 40 | + - [x] docs/10 验收清单已生成 | ||
| 41 | + - [x] 下方模块列表已填入 | ||
| 42 | + - [x] REQ 卡片依赖接口已回填 | ||
| 43 | + | ||
| 44 | +## 二、Coding 阶段(后端模块循环) | ||
| 45 | + | ||
| 46 | +(A5 填入后,每行一个后端模块。每个模块的 `MR:` 字段在 `—` 和 `!<iid>` 之间变化,完成由 GitLab API `state=merged` 判定。`coding-start` 每次按 docs/02 REQ 序扫每模块的 MR state 决定派发。后端模块全部 merged 后自动进入 § 三 前端阶段。) | ||
| 47 | + | ||
| 48 | +<!-- 模块格式示例(由 A5 downstream-gen 追加;功能子项由 feature-review 在 approve 时勾选): | ||
| 49 | +- module_0 系统管理 | ||
| 50 | + - 依赖: — | ||
| 51 | + - 路径: backend/module/sys/ | ||
| 52 | + - MR: — | ||
| 53 | + - 功能: | ||
| 54 | + - [ ] REQ-SYS-001 用户登录 | ||
| 55 | + - [ ] REQ-SYS-002 用户注册 | ||
| 56 | +--> | ||
| 57 | + | ||
| 58 | +- module_usr 用户管理 | ||
| 59 | + - 依赖: — | ||
| 60 | + - 路径: backend/src/main/java/com/xly/test4/module/usr/ | ||
| 61 | + - MR: — | ||
| 62 | + - 功能: | ||
| 63 | + - [ ] REQ-USR-001 增加用户 | ||
| 64 | + - [ ] REQ-USR-002 修改用户 | ||
| 65 | + - [ ] REQ-USR-003 查询用户 | ||
| 66 | + - [ ] REQ-USR-004 用户登录 | ||
| 67 | + | ||
| 68 | +## 三、Coding 阶段(前端整体) | ||
| 69 | + | ||
| 70 | +(`frontend-start` 进入时扫 prototype/ + docs/01 + docs/05 → AI 自主推导 FE 业务功能清单写到下方"功能:"项(无人工审阅断点;合理性由整体 MR 时统一校核)。已有清单则直接加载。整个前端阶段 1 个 MR,分支 `frontend-phase`。) | ||
| 71 | + | ||
| 72 | +- 整体 MR: — | ||
| 73 | +- 功能: | ||
| 74 | + <!-- AI 进入时按以下行格式写入(每行 1 个 FE,可关联多个 REQ / 多份原型): | ||
| 75 | + - [ ] FE-NN 功能名 | 关联 REQ:REQ-A, REQ-B | 关联原型:prototype/<file>.html, prototype/<other>.html | ||
| 76 | + | ||
| 77 | + 示例: | ||
| 78 | + - [ ] FE-01 用户登录与注册 | 关联 REQ:REQ-SYS-001, REQ-SYS-002 | 关联原型:prototype/auth.html | ||
| 79 | + - [ ] FE-02 仪表盘总览 | 关联 REQ:REQ-DASH-001 | 关联原型:prototype/dashboard.html | ||
| 80 | + --> |
docs/09-项目目录结构.md
0 → 100644
| 1 | +++ a/docs/09-项目目录结构.md | ||
| 1 | +# 09-项目目录结构 | ||
| 2 | + | ||
| 3 | +> 根包名 / 命名空间占位由人工补填一次,其他文档复用: | ||
| 4 | +> - **后端根包名**:`com.xly.test4` | ||
| 5 | + | ||
| 6 | +## 一、仓库顶层 | ||
| 7 | + | ||
| 8 | +``` | ||
| 9 | +<repo-root>/ | ||
| 10 | +├── CLAUDE.md # Claude Code 主指令 | ||
| 11 | +├── README.md # 项目说明(可选) | ||
| 12 | +├── .env.local # 本地凭据(不入 git) | ||
| 13 | +├── .env.example # 凭据模板(可选) | ||
| 14 | +├── .gitignore | ||
| 15 | +├── .githooks/ | ||
| 16 | +│ └── pre-push # MR 前测试闸门兜底 | ||
| 17 | +├── scripts/ | ||
| 18 | +│ ├── test.sh # 一键测试 | ||
| 19 | +│ └── setup-test-db.sh # 重置测试库 | ||
| 20 | +├── docs/ # 项目文档(结构见 § 四) | ||
| 21 | +├── sql/ | ||
| 22 | +│ └── migrations/ # Flyway V_n__*.sql | ||
| 23 | +├── prototype/ # 前端原型 HTML mockup(前端阶段权威布局) | ||
| 24 | +├── backend/ # Spring Boot 后端工程(结构见 § 二) | ||
| 25 | +└── frontend/ # Vite + React 前端工程(结构见 § 三) | ||
| 26 | +``` | ||
| 27 | + | ||
| 28 | +## 二、后端目录 | ||
| 29 | + | ||
| 30 | +``` | ||
| 31 | +backend/ | ||
| 32 | +├── pom.xml | ||
| 33 | +├── src/ | ||
| 34 | +│ ├── main/ | ||
| 35 | +│ │ ├── java/ | ||
| 36 | +│ │ │ └── com/xly/test4/ | ||
| 37 | +│ │ │ ├── Application.java # @SpringBootApplication 入口 | ||
| 38 | +│ │ │ ├── common/ # 跨模块基础设施 | ||
| 39 | +│ │ │ │ ├── config/ # Spring 配置类 | ||
| 40 | +│ │ │ │ ├── exception/ # 全局异常处理器 + 业务异常类 | ||
| 41 | +│ │ │ │ ├── response/ # 统一响应包装 Result<T> | ||
| 42 | +│ │ │ │ ├── security/ # Spring Security + JWT | ||
| 43 | +│ │ │ │ └── util/ # 工具类 | ||
| 44 | +│ │ │ └── module/ # 业务模块(按 docs/01 index) | ||
| 45 | +│ │ │ └── usr/ # USR 用户管理 | ||
| 46 | +│ │ │ ├── controller/ | ||
| 47 | +│ │ │ ├── service/ | ||
| 48 | +│ │ │ ├── mapper/ | ||
| 49 | +│ │ │ ├── entity/ | ||
| 50 | +│ │ │ ├── dto/ | ||
| 51 | +│ │ │ └── vo/ | ||
| 52 | +│ │ └── resources/ | ||
| 53 | +│ │ ├── application.yml | ||
| 54 | +│ │ ├── application-dev.yml | ||
| 55 | +│ │ └── mapper/ # MyBatis-Plus XML | ||
| 56 | +│ │ └── usr/ | ||
| 57 | +│ └── test/ | ||
| 58 | +│ └── java/ | ||
| 59 | +│ └── com/xly/test4/ | ||
| 60 | +│ └── module/usr/ # 与 main 同包结构 | ||
| 61 | +``` | ||
| 62 | + | ||
| 63 | +业务模块按 `docs/01-需求清单/index.md` 的模块代码(小写)落到 `module/<code>/` 下;本项目当前模块: | ||
| 64 | + | ||
| 65 | +| 模块代码 | 后端目录 | | ||
| 66 | +|---|---| | ||
| 67 | +| USR | `backend/src/main/java/com/xly/test4/module/usr/` | | ||
| 68 | + | ||
| 69 | +## 三、前端目录 | ||
| 70 | + | ||
| 71 | +``` | ||
| 72 | +frontend/ | ||
| 73 | +├── package.json | ||
| 74 | +├── vite.config.ts | ||
| 75 | +├── tsconfig.json | ||
| 76 | +├── index.html | ||
| 77 | +└── src/ | ||
| 78 | + ├── main.tsx # 入口 | ||
| 79 | + ├── App.tsx # 顶层路由 | ||
| 80 | + ├── api/ # 后端 API 客户端(按模块分文件) | ||
| 81 | + │ └── usr.ts | ||
| 82 | + ├── components/ # 跨页面通用组件 | ||
| 83 | + ├── pages/ # 业务页面(按模块分子目录) | ||
| 84 | + │ └── usr/ | ||
| 85 | + │ ├── LoginPage.tsx | ||
| 86 | + │ └── UserListPage.tsx | ||
| 87 | + ├── store/ # Redux Toolkit slice | ||
| 88 | + │ └── usrSlice.ts | ||
| 89 | + ├── hooks/ # 自定义 hooks | ||
| 90 | + ├── utils/ # 纯工具函数 | ||
| 91 | + ├── styles/ | ||
| 92 | + │ └── tokens.css # Design Tokens(docs/06 § 二) | ||
| 93 | + └── types/ # 全局 TS 类型 | ||
| 94 | +``` | ||
| 95 | + | ||
| 96 | +业务模块按 `docs/01-需求清单/index.md` 的模块代码(小写)落到 `pages/<code>/`、`api/<code>.ts`、`store/<code>Slice.ts`。 | ||
| 97 | + | ||
| 98 | +## 四、docs/ 结构 | ||
| 99 | + | ||
| 100 | +``` | ||
| 101 | +docs/ | ||
| 102 | +├── 01-需求清单/ # 每模块一子目录(_module.md 模块头 + REQ-*.md 卡片) | ||
| 103 | +├── 02-开发计划.md | ||
| 104 | +├── 03-数据库设计文档.md | ||
| 105 | +├── 04-技术规范.md | ||
| 106 | +├── 05-API接口契约.md | ||
| 107 | +├── 06-UI交互规范.md | ||
| 108 | +├── 07-环境配置.md | ||
| 109 | +├── 08-模块任务管理.md | ||
| 110 | +├── 09-项目目录结构.md | ||
| 111 | +├── 10-验收检查清单.md | ||
| 112 | +└── superpowers/ # CC 运行时产物 | ||
| 113 | +``` | ||
| 114 | + | ||
| 115 | +## 五、命名与放置约定 | ||
| 116 | + | ||
| 117 | +### 5.1 后端 | ||
| 118 | + | ||
| 119 | +- **根包名**:`com.xly.test4`(顶部已声明,本节作引用)。 | ||
| 120 | +- **包路径**:全小写、点分隔;模块代码作为子包名转小写(`USR` → `usr`)。 | ||
| 121 | +- **Controller**:`<根包>.module.<code>.controller.<Entity>Controller`,仅处理 HTTP 参数解析与响应包装,禁止写业务逻辑。 | ||
| 122 | +- **Service**:`<根包>.module.<code>.service.<Entity>Service` + 实现 `impl/<Entity>ServiceImpl`,业务逻辑与事务边界落在此层。 | ||
| 123 | +- **Mapper**:`<根包>.module.<code>.mapper.<Entity>Mapper` + `resources/mapper/<code>/<Entity>Mapper.xml`,仅数据访问,禁止跨模块查询。 | ||
| 124 | +- **Entity**:`<根包>.module.<code>.entity.<Entity>`,与数据库表一一对应。 | ||
| 125 | +- **DTO** / **VO**:`dto/<Entity>CreateDTO` 等用于入参,`vo/<Entity>VO` 用于出参;DTO → Entity → VO 转换走 MapStruct。 | ||
| 126 | +- **类名**:大驼峰;**方法名 / 变量**:小驼峰;**常量**:全大写下划线分隔;**布尔变量/方法**:`is` / `has` / `can` 前缀。 | ||
| 127 | + | ||
| 128 | +### 5.2 前端 | ||
| 129 | + | ||
| 130 | +- **页面组件**:大驼峰 + `Page` 后缀,如 `UserListPage.tsx`。 | ||
| 131 | +- **通用组件**:大驼峰,如 `AuthButton.tsx`。 | ||
| 132 | +- **api 文件**:模块代码小写 + `.ts`,如 `usr.ts`;导出函数名小驼峰,如 `loginUser`、`createUser`。 | ||
| 133 | +- **store slice**:模块代码小写 + `Slice.ts`。 | ||
| 134 | +- **hooks**:`use` 前缀小驼峰,如 `useCurrentUser.ts`。 | ||
| 135 | +- **样式 token**:变量名 `--color-<scope>-<role>-<state>`(见 docs/04 § 2.5)。 | ||
| 136 | + | ||
| 137 | +### 5.3 跨层禁止事项 | ||
| 138 | + | ||
| 139 | +- 前端 **禁止** 直接写 SQL / 访问数据库;所有数据访问走 `api/` 层封装。 | ||
| 140 | +- Controller **禁止** 注入 Mapper;必须通过 Service 中转。 | ||
| 141 | +- 跨模块调用 **禁止** 直接 import 另一模块的 Mapper / Entity;必须通过对方模块暴露的 Service 公共方法。 |
docs/10-验收检查清单.md
0 → 100644
| 1 | +++ a/docs/10-验收检查清单.md | ||
| 1 | +# 10-验收检查清单 | ||
| 2 | + | ||
| 3 | +通用验收项(全项目适用): | ||
| 4 | + | ||
| 5 | +- [ ] `scripts/test.sh` 本地全绿 | ||
| 6 | +- [ ] 所有 schema 改动都有对应 `sql/migrations/V_n__<desc>.sql` | ||
| 7 | +- [ ] 所有新接口在 `docs/05` 中有契约定义 | ||
| 8 | +- [ ] 所有新功能代码注释含 REQ-XXX-NNN | ||
| 9 | +- [ ] 统一响应格式 `{code, message, data, timestamp}` | ||
| 10 | +- [ ] 异常走全局处理器,不暴露堆栈到前端 | ||
| 11 | +- [ ] 前端不存敏感信息到 localStorage | ||
| 12 | + | ||
| 13 | +> 本文档仅维护项目级验收 SOP。粒度更细的验收信息分散在: | ||
| 14 | +> - **每 REQ 的业务验收点**:`docs/01-需求清单/<module>/<req_id>.md § 验收` | ||
| 15 | +> - **每模块的实测验收(数据 / UI / 自动化用例位置)**:由 B 阶段 `module-report` 在该模块完成时填入模块完成报告 | ||
| 16 | +> - **REQ 开发进度(feature-review approve 状态)**:`docs/08 § 二` |
scripts/setup-test-db.sh
0 → 100755
| 1 | +++ a/scripts/setup-test-db.sh | ||
| 1 | +#!/usr/bin/env bash | ||
| 2 | +# scripts/setup-test-db.sh — 数据库重置脚本:drop + create 空库。 | ||
| 3 | +# schema apply 由 Flyway 在 Spring Boot 启动时自动处理(见 docs/04 技术栈 + sql/migrations/V*.sql)。 | ||
| 4 | +# seed 数据由测试框架负责(Spring @Sql / Flyway R__seed.sql / data.sql)。 | ||
| 5 | +# | ||
| 6 | +# 使用场景: | ||
| 7 | +# - scripts/test.sh 开头:清空库,让 Spring 启动时 Flyway 从 V1 开始重放所有 migration | ||
| 8 | +# - scripts/test.sh 结尾:清空库,避免测试遗留污染下次运行 | ||
| 9 | +# - 手动调试时:reset 到零状态 | ||
| 10 | +# | ||
| 11 | +# 防护:本脚本只允许在本地 host + 测试库名上执行;非预期目标会被拒绝, | ||
| 12 | +# 避免 .env.local 误指向 staging/prod 时触发不可逆 DROP。 | ||
| 13 | + | ||
| 14 | +set -euo pipefail | ||
| 15 | + | ||
| 16 | +ENV_FILE="$(dirname "$0")/../.env.local" | ||
| 17 | +[ -f "$ENV_FILE" ] || { echo "[setup-test-db] ⚠️ .env.local 不存在($ENV_FILE)" >&2; exit 1; } | ||
| 18 | + | ||
| 19 | +# 用 set -a 加载,让 KEY=VALUE 导出为环境变量;密码中含特殊字符时 .env.local 请用单引号包裹 | ||
| 20 | +set -a; . "$ENV_FILE"; set +a | ||
| 21 | + | ||
| 22 | +# 防护 1:默认只允许本地 host(localhost / 127.0.0.1 / ::1)。 | ||
| 23 | +# 若要为本项目额外允许某些远程 host(如公司测试 MySQL),在 .env.local 里设: | ||
| 24 | +# TEST_DB_ALLOWED_HOSTS="118.178.19.35 test-mysql.internal" # 空格或逗号分隔 | ||
| 25 | +# 被列入者可直接 DROP CREATE,不再需要 TEST_DB_ALLOW_REMOTE=1。 | ||
| 26 | +ALLOWED_HOSTS="localhost 127.0.0.1 ::1 ${TEST_DB_ALLOWED_HOSTS//,/ }" | ||
| 27 | +host_allowed=0 | ||
| 28 | +for h in $ALLOWED_HOSTS; do | ||
| 29 | + [ "${DB_HOST:-}" = "$h" ] && { host_allowed=1; break; } | ||
| 30 | +done | ||
| 31 | +if [ "$host_allowed" -ne 1 ]; then | ||
| 32 | + echo "[setup-test-db] ⚠️ 拒绝在非白名单 host (${DB_HOST}) 上执行 DROP DATABASE" >&2 | ||
| 33 | + echo " 当前白名单:${ALLOWED_HOSTS}" >&2 | ||
| 34 | + echo " 加入 host:在 .env.local 追加 TEST_DB_ALLOWED_HOSTS=\"<host1> <host2>\"" >&2 | ||
| 35 | + echo " 一次性绕过:TEST_DB_ALLOW_REMOTE=1 $0" >&2 | ||
| 36 | + [ "${TEST_DB_ALLOW_REMOTE:-0}" = "1" ] || exit 1 | ||
| 37 | +fi | ||
| 38 | + | ||
| 39 | +# 防护 2:schema 名需像测试/开发库(含 test / _dev / _local),否则要求显式确认 | ||
| 40 | +case "${DB_SCHEMA:-}" in | ||
| 41 | + *test*|*_dev|*_local|*_ci) | ||
| 42 | + ;; | ||
| 43 | + *) | ||
| 44 | + echo "[setup-test-db] ⚠️ schema '${DB_SCHEMA}' 不像测试库(期望命名含 test / _dev / _local / _ci)" >&2 | ||
| 45 | + echo " 如确为期望行为,请显式声明:TEST_DB_ALLOW_PROD_NAME=1 $0" >&2 | ||
| 46 | + [ "${TEST_DB_ALLOW_PROD_NAME:-0}" = "1" ] || exit 1 | ||
| 47 | + ;; | ||
| 48 | +esac | ||
| 49 | + | ||
| 50 | +# 防护 3:显式 banner,让人看见自己在 drop 什么;远程 host 额外提示白名单内容 | ||
| 51 | +echo "[setup-test-db] 即将 DROP + CREATE \`${DB_SCHEMA}\` on ${DB_HOST}:${DB_PORT}" | ||
| 52 | +case "${DB_HOST:-}" in | ||
| 53 | + localhost|127.0.0.1|::1) ;; | ||
| 54 | + *) | ||
| 55 | + echo "[setup-test-db] ⚠️ 目标是 **远程** host(已在 TEST_DB_ALLOWED_HOSTS 白名单中,每次 test.sh 都会 DROP)" | ||
| 56 | + echo "[setup-test-db] 当前白名单: ${ALLOWED_HOSTS}" | ||
| 57 | + echo "[setup-test-db] 若不希望每次自动 DROP,从 .env.local 的 TEST_DB_ALLOWED_HOSTS 删掉此 host" | ||
| 58 | + ;; | ||
| 59 | +esac | ||
| 60 | + | ||
| 61 | +MYSQL_CMD="mysql -h${DB_HOST} -P${DB_PORT} -u${DB_USER} -p${DB_PASSWORD}" | ||
| 62 | + | ||
| 63 | +$MYSQL_CMD -e "DROP DATABASE IF EXISTS \`${DB_SCHEMA}\`; CREATE DATABASE \`${DB_SCHEMA}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" | ||
| 64 | + | ||
| 65 | +echo "[setup-test-db] done — schema will be applied by Flyway when Spring Boot starts" |
scripts/test.sh
0 → 100755
| 1 | +++ a/scripts/test.sh | ||
| 1 | +#!/usr/bin/env bash | ||
| 2 | +# scripts/test.sh —— 合并到默认分支(main / master)前的测试闸门。 | ||
| 3 | +# 顺序:detect → setup-db → build → lint → unit+integration → e2e → reset-db | ||
| 4 | +# 由 .githooks/pre-push 和 test-gate skill(通过子会话)调用。 | ||
| 5 | + | ||
| 6 | +set -euo pipefail | ||
| 7 | + | ||
| 8 | +PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)" | ||
| 9 | +cd "$PROJECT_ROOT" | ||
| 10 | + | ||
| 11 | +# Stack detection (runtime, mode-agnostic) | ||
| 12 | +HAS_BACKEND=0; [ -d backend ] && HAS_BACKEND=1 | ||
| 13 | +HAS_FRONTEND=0; [ -d frontend ] && HAS_FRONTEND=1 | ||
| 14 | +if [ $HAS_BACKEND -eq 0 ] && [ $HAS_FRONTEND -eq 0 ]; then | ||
| 15 | + echo "[test.sh] FATAL: neither backend/ nor frontend/ exists" >&2 | ||
| 16 | + exit 1 | ||
| 17 | +fi | ||
| 18 | + | ||
| 19 | +echo "[test.sh] 1/6 setup test db" | ||
| 20 | +./scripts/setup-test-db.sh | ||
| 21 | + | ||
| 22 | +echo "[test.sh] 2/6 build" | ||
| 23 | +if [ $HAS_BACKEND -eq 1 ]; then (cd backend && mvn -B clean package -DskipTests); else echo "[test.sh] skip backend build"; fi | ||
| 24 | +if [ $HAS_FRONTEND -eq 1 ]; then (cd frontend && npm run build); else echo "[test.sh] skip frontend build"; fi | ||
| 25 | + | ||
| 26 | +echo "[test.sh] 3/6 lint" | ||
| 27 | +if [ $HAS_BACKEND -eq 1 ]; then (cd backend && mvn -B compile); else echo "[test.sh] skip backend lint"; fi | ||
| 28 | +if [ $HAS_FRONTEND -eq 1 ]; then (cd frontend && npm run lint); else echo "[test.sh] skip frontend lint"; fi | ||
| 29 | + | ||
| 30 | +echo "[test.sh] 4/6 unit + integration" | ||
| 31 | +if [ $HAS_BACKEND -eq 1 ]; then (cd backend && mvn -B test); else echo "[test.sh] skip backend test"; fi | ||
| 32 | +if [ $HAS_FRONTEND -eq 1 ]; then (cd frontend && npm run test); else echo "[test.sh] skip frontend test"; fi | ||
| 33 | + | ||
| 34 | +echo "[test.sh] 5/6 E2E" | ||
| 35 | +echo "[test.sh] e2e 略" | ||
| 36 | + | ||
| 37 | +echo "[test.sh] 6/6 reset test db" | ||
| 38 | +./scripts/setup-test-db.sh | ||
| 39 | + | ||
| 40 | +echo "[test.sh] GREEN" |
sql/migrations/.gitkeep
0 → 100644
| 1 | +++ a/sql/migrations/.gitkeep |
sql/migrations/V1__initial_schema.sql
0 → 100644
| 1 | +++ a/sql/migrations/V1__initial_schema.sql | ||
| 1 | +-- Flyway migration V1 — initial schema for 小羚羊 | ||
| 2 | +-- Generated: 2026-05-13T07:35:36Z | ||
| 3 | +-- Source: 由 A4 db-init 从 docs/03-数据库设计文档.md 翻译生成(schema SSoT 是 docs/03) | ||
| 4 | +-- This is the FIRST migration; subsequent schema changes must be written as new files sql/migrations/V2__<desc>.sql, V3__... etc. | ||
| 5 | +-- Apply: Flyway runs this automatically at Spring Boot startup. | ||
| 6 | +-- Do not hand-edit this file after it is committed; write a new migration instead. | ||
| 7 | + | ||
| 8 | +SET NAMES utf8mb4; | ||
| 9 | +SET FOREIGN_KEY_CHECKS = 0; | ||
| 10 | + | ||
| 11 | +-- ============================================================ | ||
| 12 | +-- tUser — 系统用户主表 | ||
| 13 | +-- ============================================================ | ||
| 14 | +CREATE TABLE `tUser` ( | ||
| 15 | + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | ||
| 16 | + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)', | ||
| 17 | + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)', | ||
| 18 | + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)', | ||
| 19 | + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | ||
| 20 | + `sUserCode` VARCHAR(32) NOT NULL COMMENT '用户号,业务唯一编码', | ||
| 21 | + `sUserName` VARCHAR(50) NOT NULL COMMENT '用户名(登录标识),全局唯一', | ||
| 22 | + `iEmployeeId` INT NULL DEFAULT NULL COMMENT '关联 tEmployee.iIncrement,可选;选填后页面回显员工名/部门', | ||
| 23 | + `sUserType` VARCHAR(20) NOT NULL DEFAULT 'NORMAL' COMMENT '用户类型:NORMAL=普通用户,ADMIN=超级管理员', | ||
| 24 | + `sLanguage` VARCHAR(16) NOT NULL DEFAULT 'zh-CN' COMMENT '界面语言:zh-CN 中文,en 英文,zh-TW 繁体', | ||
| 25 | + `iCanEditDoc` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '单据修改权限:0=否,1=是', | ||
| 26 | + `sPasswordHash` VARCHAR(100) NOT NULL COMMENT '密码哈希(BCrypt 或同强度算法),初始值由系统生成;以哈希形式存储', | ||
| 27 | + `iIsDisabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '作废标志:0=有效,1=已作废(被作废后不可登录)', | ||
| 28 | + `tLastLoginDate` DATETIME NULL DEFAULT NULL COMMENT '最近一次登录时间,登录成功时更新', | ||
| 29 | + `iLoginFailCount` INT NOT NULL DEFAULT 0 COMMENT '连续登录失败计数,登录成功清零(用于阈值锁定)', | ||
| 30 | + `tLockedUntil` DATETIME NULL DEFAULT NULL COMMENT '临时锁定截止时间;非空且大于当前时刻视为锁定', | ||
| 31 | + `sCreatedBy` VARCHAR(50) NULL DEFAULT NULL COMMENT '制单人(创建该用户的操作员用户名)', | ||
| 32 | + `tUpdateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', | ||
| 33 | + PRIMARY KEY (`iIncrement`), | ||
| 34 | + UNIQUE KEY `uk_tUser_sUserName` (`sUserName`), | ||
| 35 | + UNIQUE KEY `uk_tUser_sUserCode` (`sUserCode`), | ||
| 36 | + KEY `idx_tUser_iEmployeeId` (`iEmployeeId`), | ||
| 37 | + KEY `idx_tUser_tenant_status` (`sBrandsId`, `sSubsidiaryId`, `iIsDisabled`), | ||
| 38 | + KEY `idx_tUser_tLastLoginDate` (`tLastLoginDate`), | ||
| 39 | + KEY `idx_tUser_sUserType` (`sUserType`) | ||
| 40 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统用户主表'; | ||
| 41 | + | ||
| 42 | +-- ============================================================ | ||
| 43 | +-- tEmployee — 职员字典 | ||
| 44 | +-- ============================================================ | ||
| 45 | +CREATE TABLE `tEmployee` ( | ||
| 46 | + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | ||
| 47 | + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)', | ||
| 48 | + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)', | ||
| 49 | + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)', | ||
| 50 | + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | ||
| 51 | + `sEmployeeCode` VARCHAR(32) NOT NULL COMMENT '员工号,业务唯一编码', | ||
| 52 | + `sEmployeeName` VARCHAR(50) NOT NULL COMMENT '员工姓名', | ||
| 53 | + `sDepartment` VARCHAR(100) NULL DEFAULT NULL COMMENT '所属部门名', | ||
| 54 | + `iIsDisabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '作废标志:0=有效,1=已作废', | ||
| 55 | + `tUpdateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', | ||
| 56 | + PRIMARY KEY (`iIncrement`), | ||
| 57 | + UNIQUE KEY `uk_tEmployee_sEmployeeCode` (`sEmployeeCode`), | ||
| 58 | + KEY `idx_tEmployee_sEmployeeName` (`sEmployeeName`), | ||
| 59 | + KEY `idx_tEmployee_sDepartment` (`sDepartment`), | ||
| 60 | + KEY `idx_tEmployee_tenant` (`sBrandsId`, `sSubsidiaryId`) | ||
| 61 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='职员字典'; | ||
| 62 | + | ||
| 63 | +-- ============================================================ | ||
| 64 | +-- tPermission — 权限分类字典 | ||
| 65 | +-- ============================================================ | ||
| 66 | +CREATE TABLE `tPermission` ( | ||
| 67 | + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | ||
| 68 | + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)', | ||
| 69 | + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)', | ||
| 70 | + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)', | ||
| 71 | + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | ||
| 72 | + `sCategory` VARCHAR(100) NOT NULL COMMENT '权限分类编码(如 usr:user:create)', | ||
| 73 | + `sCategoryName` VARCHAR(100) NOT NULL COMMENT '权限分类中文名(用于权限组复选框展示)', | ||
| 74 | + `sDescription` VARCHAR(255) NULL DEFAULT NULL COMMENT '权限说明', | ||
| 75 | + `iIsDisabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '作废标志:0=有效,1=已作废', | ||
| 76 | + `tUpdateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', | ||
| 77 | + PRIMARY KEY (`iIncrement`), | ||
| 78 | + UNIQUE KEY `uk_tPermission_sCategory` (`sCategory`), | ||
| 79 | + KEY `idx_tPermission_sCategoryName` (`sCategoryName`) | ||
| 80 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='权限分类字典'; | ||
| 81 | + | ||
| 82 | +-- ============================================================ | ||
| 83 | +-- tUserPermission — 用户 ↔ 权限多对多关联表 | ||
| 84 | +-- ============================================================ | ||
| 85 | +CREATE TABLE `tUserPermission` ( | ||
| 86 | + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | ||
| 87 | + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)', | ||
| 88 | + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)', | ||
| 89 | + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)', | ||
| 90 | + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | ||
| 91 | + `iUserId` INT NOT NULL COMMENT '用户主键,关联 tUser.iIncrement', | ||
| 92 | + `iPermissionId` INT NOT NULL COMMENT '权限主键,关联 tPermission.iIncrement', | ||
| 93 | + `sGrantedBy` VARCHAR(50) NULL DEFAULT NULL COMMENT '授权操作员用户名(审计用)', | ||
| 94 | + PRIMARY KEY (`iIncrement`), | ||
| 95 | + UNIQUE KEY `uk_tUserPermission_user_perm` (`iUserId`, `iPermissionId`), | ||
| 96 | + KEY `idx_tUserPermission_iPermissionId` (`iPermissionId`) | ||
| 97 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户 ↔ 权限多对多关联表'; | ||
| 98 | + | ||
| 99 | +-- ============================================================ | ||
| 100 | +-- tCompany — 公司及版本字典 | ||
| 101 | +-- ============================================================ | ||
| 102 | +CREATE TABLE `tCompany` ( | ||
| 103 | + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | ||
| 104 | + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)', | ||
| 105 | + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)', | ||
| 106 | + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)', | ||
| 107 | + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | ||
| 108 | + `sCompanyName` VARCHAR(100) NOT NULL COMMENT '公司名称', | ||
| 109 | + `sEdition` VARCHAR(32) NOT NULL DEFAULT '标准版' COMMENT '版本:标准版 / 专业版 / 企业版 等', | ||
| 110 | + `iIsDisabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '作废标志', | ||
| 111 | + `tUpdateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', | ||
| 112 | + PRIMARY KEY (`iIncrement`), | ||
| 113 | + KEY `idx_tCompany_sCompanyName` (`sCompanyName`), | ||
| 114 | + KEY `idx_tCompany_sEdition` (`sEdition`) | ||
| 115 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='公司及版本字典'; | ||
| 116 | + | ||
| 117 | +-- ============================================================ | ||
| 118 | +-- 外键 | ||
| 119 | +-- ============================================================ | ||
| 120 | +ALTER TABLE `tUser` | ||
| 121 | + ADD CONSTRAINT `fk_tUser_iEmployeeId` | ||
| 122 | + FOREIGN KEY (`iEmployeeId`) REFERENCES `tEmployee` (`iIncrement`) | ||
| 123 | + ON DELETE SET NULL ON UPDATE CASCADE; | ||
| 124 | + | ||
| 125 | +ALTER TABLE `tUserPermission` | ||
| 126 | + ADD CONSTRAINT `fk_tUserPermission_iUserId` | ||
| 127 | + FOREIGN KEY (`iUserId`) REFERENCES `tUser` (`iIncrement`) | ||
| 128 | + ON DELETE CASCADE ON UPDATE CASCADE; | ||
| 129 | + | ||
| 130 | +ALTER TABLE `tUserPermission` | ||
| 131 | + ADD CONSTRAINT `fk_tUserPermission_iPermissionId` | ||
| 132 | + FOREIGN KEY (`iPermissionId`) REFERENCES `tPermission` (`iIncrement`) | ||
| 133 | + ON DELETE RESTRICT ON UPDATE CASCADE; | ||
| 134 | + | ||
| 135 | +SET FOREIGN_KEY_CHECKS = 1; |
src/styles/tokens.css
0 → 100644
| 1 | +++ a/src/styles/tokens.css | ||
| 1 | +/* | ||
| 2 | + * src/styles/tokens.css — Design Tokens | ||
| 3 | + * 命名规范见 docs/04-技术规范.md § 2.5 | ||
| 4 | + * 色值锁定见 docs/06-UI交互规范.md § 四 | ||
| 5 | + * | ||
| 6 | + * 命名格式:--color-<scope>-<role>-<state> | ||
| 7 | + * <scope> 组件域:form / table-row / table-header / ... | ||
| 8 | + * <role> 作用:bg(背景)/ fg(前景/字体)/ border | ||
| 9 | + * <state> 状态:edit / readonly / hover / selected(无状态时省略) | ||
| 10 | + * | ||
| 11 | + * 约束: | ||
| 12 | + * - 组件样式中只用 var(--color-xxx),禁止硬编码 hex / rgba | ||
| 13 | + * - 修改色值只改本文件,不允许在组件级覆盖 | ||
| 14 | + * - 新增 token 须先登记到 docs/06 § 4.1 / 4.2,再补到此处 | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +:root { | ||
| 18 | + /* === 1. 全局调色板(与 Ant Design 主题对齐) === */ | ||
| 19 | + --color-primary: #1890ff; | ||
| 20 | + --color-success: #52c41a; | ||
| 21 | + --color-warning: #faad14; | ||
| 22 | + --color-error: #ff4d4f; | ||
| 23 | + --color-text: rgba(0, 0, 0, 0.85); | ||
| 24 | + --color-text-secondary: rgba(0, 0, 0, 0.45); | ||
| 25 | + --color-border: #d9d9d9; | ||
| 26 | + --color-bg-base: #f0f2f5; | ||
| 27 | + | ||
| 28 | + /* === 2. 组件级状态色(与 docs/06 § 4.2 一一对应) === */ | ||
| 29 | + | ||
| 30 | + /* form:输入框 / 备注框 / 时间框 / 下拉框共用 */ | ||
| 31 | + --color-form-bg-edit: #ffffff; | ||
| 32 | + --color-form-bg-readonly: #f1f2f8; | ||
| 33 | + --color-form-bg-hover: #f5f5f5; /* 仅下拉框使用 */ | ||
| 34 | + --color-form-fg: #000000; | ||
| 35 | + | ||
| 36 | + /* table */ | ||
| 37 | + --color-table-row-bg-selected: #86d5fb; | ||
| 38 | + --color-table-row-bg-hover: #fff7e6; | ||
| 39 | + --color-table-row-bg-readonly: #f1f2f8; /* = rgb(241, 242, 248) */ | ||
| 40 | + --color-table-row-fg: #000000; | ||
| 41 | + --color-table-header-bg: #f5f5f5; | ||
| 42 | + --color-table-header-fg: rgba(0, 0, 0, 0.85); /* = #000000D9 */ | ||
| 43 | +} |