Commit 6faf3c3497158854c013c394a41f237dd1a21539
1 parent
2bc3429d
chore: plan phase done
Showing
16 changed files
with
661 additions
and
627 deletions
CLAUDE.md
| ... | ... | @@ -132,7 +132,7 @@ B 阶段分两段,**全部固化到 skills**。入口:`/erp-workflow:coding- |
| 132 | 132 | |
| 133 | 133 | ### 你禁止做的 🚫 |
| 134 | 134 | |
| 135 | -1. **主会话直接 `mysql -e` 跑业务 DDL**(只读查询 / 临时本地调试除外)——业务 schema 必须走 `sql/migrations/V_n__*.sql`,详见下方 Schema 演化规约 | |
| 135 | +1. **主会话直接 `mysql -e` 跑业务 DDL**(只读查询 / 临时本地调试 / A4 `db-init` 首次 apply V1 验证除外)——业务 schema 必须走 `sql/migrations/V_n__*.sql`,详见下方 Schema 演化规约。**A4 例外**:`db-init` 在 A 阶段 setup-test-db 后会一次性手工 `mysql < V1__initial_schema.sql` 把 V1 灌入测试库,并校验 `SHOW TABLES` 行数 = docs/03 表数量,用于 DDL 自检;B 阶段(Spring Boot 启动后)Flyway 会重建 schema 并 apply 全部 migration(包括 V1),手工 apply 不会污染 Flyway 历史。 | |
| 136 | 136 | 2. **手动 Edit `docs/08 § 二/§ 三` 的 `MR:` / `整体 MR:` 字段**,必须要由 `mr-create` 自动回写 |
| 137 | 137 | |
| 138 | 138 | ### Schema 演化规约(Flyway migration) | ... | ... |
docs/01-需求清单/USR-用户管理/REQ-USR-001.md
| 1 | 1 | ### REQ-USR-001 用户登录 |
| 2 | 2 | |
| 3 | - | |
| 4 | 3 | **目标**: 用户通过用户名+密码完成身份认证,获取 JWT Token 用于后续接口鉴权 |
| 5 | 4 | |
| 6 | 5 | - **输入**: |
| ... | ... | @@ -11,12 +10,12 @@ |
| 11 | 10 | | --- | ---- | --- | ---- | ------- | ----- | --- | ----------- | |
| 12 | 11 | | 用户名 | 文本 | 是 | 手工输入 | — | — | — | — | |
| 13 | 12 | | 密码 | 文本 | 是 | 手工输入 | — | — | — | 输入显示星号 | |
| 14 | - | 版本 | 文本 | 是 | 下拉单选 | `公司表` | 页面加载时 | 标准版 | | | |
| 13 | + | 版本 | 文本 | 是 | 下拉单选 | `公司表` | 页面加载时 | — | | | |
| 15 | 14 | |
| 16 | 15 | - **输出**: 成功/失败 |
| 17 | 16 | |
| 18 | 17 | - **跨字段规则**: 校验用户名 + 密码哈希;连续失败达到阈值临时锁定账号;登录成功签发限时 token 并返回基本用户信息 |
| 19 | 18 | - **边界**: token 设置合理过期时间;接口需具备防暴力破解保护 |
| 20 | 19 | - **验收**: 正确凭据返回 Token 且可通过鉴权接口验证;错误密码返回通用错误消息(不区分用户名或密码错误);锁定账号返回锁定提示 |
| 21 | -- **依赖表**: `t_user`(认证主体 + 失败计数 + 锁定时间 + 登录时间)/ `t_company`(「版本」下拉数据源) | |
| 22 | -- **依赖接口**: `POST /api/usr/auth/login`(本 REQ 提供);后续配套 `POST /api/usr/auth/refresh`、`POST /api/usr/auth/logout` 由同模块衍生接口(不另立 REQ) | |
| 20 | +- **依赖表**: `sys_user`(读: 校验账号 / 写: 更新 `iFailedLoginCount`、`tLockUntil`、`tLastLoginDate`), `sys_company`(读: 登录页"版本"下拉) | |
| 21 | +- **依赖接口**: `POST /api/v1/auth/login`(本 REQ 提供);`GET /api/v1/companies`(登录页公司下拉,后续运营模块提供) | ... | ... |
docs/01-需求清单/USR-用户管理/REQ-USR-002.md
| ... | ... | @@ -37,5 +37,5 @@ |
| 37 | 37 | - **跨字段规则**: 用户名在系统内全局唯一;角色取值受系统配置约束 |
| 38 | 38 | - **边界**: 密码以哈希形式存储 |
| 39 | 39 | - **验收**: 提交合法数据后用户记录出现在列表;重复用户名返回错误提示;普通账号无权访问此功能 |
| 40 | -- **依赖表**: `t_user`(新建主记录)/ `t_employee`(员工名下拉 + 关联)/ `t_permission`(权限分类下拉)/ `t_user_permission`(写入权限组关联) | |
| 41 | -- **依赖接口**: `POST /api/usr/users`(本 REQ 提供);前置 `POST /api/usr/auth/login`(REQ-USR-001)获取 JWT | |
| 40 | +- **依赖表**: `sys_user`(写: 新增账号), `sys_employee`(读: 关联职员下拉), `sys_permission_category`(读: 权限分类列表), `sys_user_permission_category`(写: 写入勾选授权) | |
| 41 | +- **依赖接口**: `POST /api/v1/users`(本 REQ 提供);下游调用:`GET /api/v1/employees`(员工下拉,后续 HR 模块提供);`GET /api/v1/permission-categories`(权限分类下拉,后续运营模块提供) | ... | ... |
docs/01-需求清单/USR-用户管理/REQ-USR-003.md
| ... | ... | @@ -36,5 +36,5 @@ |
| 36 | 36 | - **跨字段规则**: 密码不在该接口修改;角色变更需具备相应权限 |
| 37 | 37 | - **边界**: 必须传入有效用户 id;字段格式与新增一致 |
| 38 | 38 | - **验收**: 修改角色或状态后立即反映在用户列表;被禁用账号无法登录并收到明确提示 |
| 39 | -- **依赖表**: `t_user`(更新主记录)/ `t_employee`(员工名下拉 + 关联)/ `t_permission`(权限分类下拉)/ `t_user_permission`(删旧 + 写新权限组关联) | |
| 40 | -- **依赖接口**: `PUT /api/usr/users/{iIncrement}`(本 REQ 提供);前置 `POST /api/usr/auth/login`(REQ-USR-001)+ 数据来源 `POST /api/usr/users`(REQ-USR-002) | |
| 39 | +- **依赖表**: `sys_user`(读 + 写: 更新非密码字段), `sys_employee`(读: 关联职员下拉), `sys_permission_category`(读: 权限分类列表), `sys_user_permission_category`(读 + 写: 增量增删授权差集) | |
| 40 | +- **依赖接口**: `PUT /api/v1/users/{userId}`(本 REQ 提供);`GET /api/v1/users/{userId}`(详情回显,可在本 REQ 实现或复用 REQ-USR-004 列表点击进入);下游调用:`GET /api/v1/employees`、`GET /api/v1/permission-categories`(同 REQ-USR-002) | ... | ... |
docs/01-需求清单/USR-用户管理/REQ-USR-004.md
| ... | ... | @@ -33,5 +33,5 @@ |
| 33 | 33 | - **跨字段规则**: - |
| 34 | 34 | - **边界**: 单页最大条数受限(默认 100);密码与敏感字段不返回;查询为只读,不产生写副作用 |
| 35 | 35 | - **验收**: 按条件筛选返回正确结果集;无匹配时返回空列表而非报错;分页参数越界时返回最后一页 |
| 36 | -- **依赖表**: `t_user`(列表主表 + 类型 / 作废 / 登录日期 / 制单人)/ `t_employee`(员工名)/ `t_department`(部门) | |
| 37 | -- **依赖接口**: `GET /api/usr/users`(本 REQ 提供);前置 `POST /api/usr/auth/login`(REQ-USR-001) | |
| 36 | +- **依赖表**: `sys_user`(读: 分页 + 筛选), `sys_employee`(读: JOIN 取员工名), `sys_department`(读: JOIN 经 employee 取部门名) | |
| 37 | +- **依赖接口**: `GET /api/v1/users`(本 REQ 提供) | ... | ... |
docs/01-需求清单/USR-用户管理/_module.md
| 1 | 1 | # USR-用户管理 |
| 2 | 2 | |
| 3 | -- **模块简述**: 提供用户全生命周期管理,含登录认证、新增、修改、查询等业务操作 | |
| 4 | -- **依赖模块**: —(基础模块) | |
| 5 | -- **涉及表**: `t_user` / `t_employee` / `t_department` / `t_permission` / `t_user_permission` / `t_company` | |
| 3 | +- **模块简述**: 用户管理模块负责用户账号的全生命周期管理,包括登录认证、新增 / 修改 / 查询用户、角色分配与账号启停 | |
| 4 | +- **依赖模块**: —(无) | |
| 5 | +- **涉及表**: `sys_user`, `sys_employee`, `sys_department`, `sys_company`, `sys_permission_category`, `sys_user_permission_category` | ... | ... |
docs/02-开发计划.md
| ... | ... | @@ -4,7 +4,7 @@ |
| 4 | 4 | |
| 5 | 5 | | 模块 ID | 模块名 | 依赖模块 | 依赖表 | |
| 6 | 6 | |---|---|---|---| |
| 7 | -| module_usr | USR-用户管理 | — | `t_user` / `t_employee` / `t_department` / `t_permission` / `t_user_permission` / `t_company` | | |
| 7 | +| module_usr | 用户管理 | — | `sys_user`, `sys_employee`, `sys_department`, `sys_company`, `sys_permission_category`, `sys_user_permission_category` | | |
| 8 | 8 | |
| 9 | 9 | ## 二、开发顺序清单(CC 分发权威) |
| 10 | 10 | |
| ... | ... | @@ -14,15 +14,16 @@ |
| 14 | 14 | |
| 15 | 15 | | # | REQ | 所属模块 | 选中理由 | 备注 | |
| 16 | 16 | |---|-----|---------|---------|------| |
| 17 | -| 1 | **REQ-USR-001** | module_usr | 所属模块无依赖,认证接口为基础设施(其余 REQ 接口需 JWT 鉴权) | — | | |
| 18 | -| 2 | **REQ-USR-002** | module_usr | 依赖 REQ-USR-001 已在前;新增用户为后续修改 / 查询提供数据 | — | | |
| 19 | -| 3 | **REQ-USR-003** | module_usr | 依赖 REQ-USR-002 已在前(先有用户才能修改) | — | | |
| 20 | -| 4 | **REQ-USR-004** | module_usr | 依赖 REQ-USR-002 已在前(先有用户才能查询) | — | | |
| 17 | +| 1 | **REQ-USR-001** | module_usr | 所属模块无依赖,登录是后续接口鉴权的基础 | — | | |
| 18 | +| 2 | **REQ-USR-002** | module_usr | 同模块,无前置 REQ 依赖;用户创建是 003/004 的数据源 | — | | |
| 19 | +| 3 | **REQ-USR-003** | module_usr | 同模块,逻辑上依赖 REQ-USR-002 已在前(修改基于已存在用户) | — | | |
| 20 | +| 4 | **REQ-USR-004** | module_usr | 同模块,无前置 REQ 依赖;列表查询用 002/003 产生的数据更易自测 | — | | |
| 21 | 21 | |
| 22 | 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 | 23 | |
| 24 | 24 | ## 三、关键说明 |
| 25 | 25 | |
| 26 | -- 本期需求只覆盖 USR 用户管理一个模块;后续若新增 PUR / SAL / FIN 等模块,需重新跑 `/erp-workflow:plan-start` 让 A5 重算依赖与顺序。 | |
| 27 | -- REQ-USR-001 排第一是「基础设施先行」策略:JWT 鉴权机制建好后,其余 REQ 才能复用 `@PreAuthorize` 与统一异常处理;初始管理员账号由 V1 后续的种子 migration 注入(或人工 SQL 直插)。 | |
| 28 | -- `t_employee` / `t_department` / `t_permission` / `t_company` 四张维表在 module_usr 内部按需访问,不另起模块;后续如演化出独立的 HR / 组织架构模块,再单独拆分。 | |
| 26 | +- 当前仅有 1 个业务模块 `module_usr`,无跨模块依赖,模块级 DAG 退化为单节点,无环。 | |
| 27 | +- REQ 内部依赖只有 003 → 002 一条边,按数字序天然满足,无需破环。 | |
| 28 | +- `sys_employee` / `sys_department` / `sys_company` / `sys_permission_category` 4 张参考字典已由 V1 建好但本模块**只读使用**,初始化数据由 B 阶段开发时通过 V2/V3 seed migration 或测试 fixture 补全(不在当前 4 个 REQ 范围内)。 | |
| 29 | +- 后端阶段所有任务限定在 `backend/module/usr/` 路径下;前端实现推迟到 `frontend-start` 派发的前端阶段,按 `prototype/*.html` 拆 FE。 | ... | ... |
docs/03-数据库设计文档.md
| ... | ... | @@ -12,7 +12,7 @@ |
| 12 | 12 | |---|---|---|---|---| |
| 13 | 13 | | `iIncrement` | int | 否 | 是 | 整数主键 ID(自增方式由实现决定:DB `AUTO_INCREMENT` 或应用 / 触发器分配) | |
| 14 | 14 | | `sId` | varchar(100) | 是 | — | 业务 ID(对外暴露的字符串标识,如 UUID / 人类可读编号) | |
| 15 | -| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离) | | |
| 15 | +| `sBrandsId` | varchar(100) | 是 | — | 母公司 ID(多租户隔离) | | |
| 16 | 16 | | `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离) | |
| 17 | 17 | | `tCreateDate` | datetime | 否 | — | 记录创建时间 | |
| 18 | 18 | |
| ... | ... | @@ -20,24 +20,37 @@ |
| 20 | 20 | |
| 21 | 21 | ## ER 关系概览 |
| 22 | 22 | |
| 23 | -USR 用户管理模块共 6 张表,围绕「用户」为核心: | |
| 23 | +``` | |
| 24 | +sys_department ◄──┐ sys_company(独立,登录页"版本"下拉) | |
| 25 | + │ | |
| 26 | + │ N:1 | |
| 27 | +sys_employee ─────┘ | |
| 28 | + ▲ | |
| 29 | + │ N:1 (可选, ON DELETE SET NULL) | |
| 30 | + │ | |
| 31 | +sys_user ────────► sys_user_permission_category ◄──── sys_permission_category | |
| 32 | + (用户 × 权限分类多对多, ON DELETE CASCADE) | |
| 33 | +``` | |
| 34 | + | |
| 35 | +- `sys_company`:登录页"版本"下拉来源;目前独立存在,不与 user 直接 FK,登录上下文记录公司选择即可。 | |
| 36 | +- `sys_department`:部门字典;被 `sys_employee` 引用。 | |
| 37 | +- `sys_employee`:职员档案;被 `sys_user` 通过 `iEmployeeId` 可选引用(员工先存在,用户后绑定)。 | |
| 38 | +- `sys_user`:用户账号(登录、权限、状态、登录追踪一体)。 | |
| 39 | +- `sys_permission_category`:权限分类字典(USR-002/003 表 2 "权限组"的来源)。 | |
| 40 | +- `sys_user_permission_category`:用户 × 权限分类多对多授权表,记录每个用户被授予哪些权限分类。 | |
| 24 | 41 | |
| 25 | -- `t_user`(用户主表)通过 `iEmployeeId` 关联 `t_employee`(职员),实现「用户号 / 员工名」一对一联动。 | |
| 26 | -- `t_employee`(职员表)通过 `iDepartmentId` 关联 `t_department`(部门表),支撑 REQ-USR-004 按部门筛选。 | |
| 27 | -- `t_user` 与 `t_permission`(权限分类字典)通过 `t_user_permission`(用户-权限关联表)建立多对多关系,实现 REQ-USR-002 / 003 的「权限组」分配。 | |
| 28 | -- `t_company`(公司版本字典)独立于 `t_user`,仅为 REQ-USR-001 登录入参「版本」下拉提供可选项,不与用户产生强引用。 | |
| 42 | +## 表清单 | |
| 29 | 43 | |
| 30 | -依赖方向:`t_user → t_employee → t_department`、`t_user ↔ t_permission`(多对多)、`t_company` 独立。 | |
| 44 | +- `sys_company` — 公司 / 版本字典,登录页下拉选择来源 | |
| 45 | +- `sys_department` — 部门字典,职员归属 | |
| 46 | +- `sys_employee` — 职员档案,员工基础信息 | |
| 47 | +- `sys_user` — 用户账号(登录认证 + 类型 + 语言 + 状态 + 登录追踪) | |
| 48 | +- `sys_permission_category` — 权限分类字典 | |
| 49 | +- `sys_user_permission_category` — 用户 × 权限分类授权关系 | |
| 31 | 50 | |
| 32 | -## 表清单 | |
| 33 | -- `t_user` — 系统用户主表,承载登录认证、用户类型、语言偏好、单据权限等基础属性 | |
| 34 | -- `t_employee` — 公司职员主档,与用户表通过 `iEmployeeId` 关联,提供姓名 / 工号 / 部门归属 | |
| 35 | -- `t_department` — 部门组织树,支撑职员归属与按部门筛选 | |
| 36 | -- `t_permission` — 权限分类字典,定义系统可分配的权限项 | |
| 37 | -- `t_user_permission` — 用户-权限分类多对多关联表 | |
| 38 | -- `t_company` — 公司 / 版本字典(标准版 / 专业版 / 旗舰版),登录时「版本」下拉数据源 | |
| 51 | +--- | |
| 39 | 52 | |
| 40 | -## `t_user` — 系统用户主表,承载登录认证与基础属性 | |
| 53 | +## `sys_company` — 公司 / 版本字典,登录页下拉选择来源 | |
| 41 | 54 | |
| 42 | 55 | ### 字段 |
| 43 | 56 | |
| ... | ... | @@ -48,38 +61,28 @@ USR 用户管理模块共 6 张表,围绕「用户」为核心: |
| 48 | 61 | | `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | |
| 49 | 62 | | `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | |
| 50 | 63 | | `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | |
| 51 | -| `sUserNo` | varchar(50) | 否 | — | 用户号;关联职员后自动同步员工号;系统内唯一 | | |
| 52 | -| `sUserName` | varchar(50) | 否 | — | 登录用户名;系统内唯一;3-50 位 | | |
| 53 | -| `iEmployeeId` | int | 是 | NULL | 关联职员 `t_employee.iIncrement`;可空(非员工账号如系统管理员) | | |
| 54 | -| `sPasswordHash` | varchar(255) | 否 | — | 密码哈希(BCrypt / Argon2);禁止明文;初始密码 `666666` 哈希后存入 | | |
| 55 | -| `sUserType` | varchar(20) | 否 | `NORMAL` | 用户类型枚举:`NORMAL`(普通用户)/ `SUPER_ADMIN`(超级管理员) | | |
| 56 | -| `sLanguage` | varchar(10) | 否 | `zh-CN` | 语言枚举:`zh-CN`(中文)/ `en-US`(英文)/ `zh-TW`(繁体) | | |
| 57 | -| `bModifyDoc` | tinyint(1) | 否 | 0 | 单据修改权限:0 否 / 1 是 | | |
| 58 | -| `bVoid` | tinyint(1) | 否 | 0 | 作废标记(软删除):0 启用 / 1 已作废 | | |
| 59 | -| `iLoginFailCount` | int | 否 | 0 | 连续登录失败次数;达到阈值触发临时锁定;登录成功后清零 | | |
| 60 | -| `tLockUntil` | datetime | 是 | NULL | 锁定截止时间;NULL 表示未锁定 | | |
| 61 | -| `tLastLoginDate` | datetime | 是 | NULL | 最近一次登录时间 | | |
| 62 | -| `sCreator` | varchar(100) | 是 | NULL | 制单人(创建该账号的操作员用户名) | | |
| 64 | +| `sCompanyName` | varchar(100) | 否 | — | 公司 / 版本名称(登录页下拉显示文本) | | |
| 65 | +| `sCompanyCode` | varchar(50) | 否 | — | 公司编码(前端唯一识别) | | |
| 66 | +| `iSortOrder` | int | 否 | 0 | 下拉列表排序权重,升序 | | |
| 67 | +| `iIsDeleted` | tinyint(1) | 否 | 0 | 软删除标记,0=正常 1=已删 | | |
| 63 | 68 | |
| 64 | 69 | ### 索引 |
| 65 | 70 | |
| 66 | -- `uk_user_username` (UNIQUE): `sUserName` | |
| 67 | -- `uk_user_userno` (UNIQUE): `sUserNo` | |
| 68 | -- `idx_user_employee` (BTREE): `iEmployeeId` | |
| 69 | -- `idx_user_tenant` (BTREE): `sBrandsId`, `sSubsidiaryId` | |
| 70 | -- `idx_user_void` (BTREE): `bVoid` | |
| 71 | +- `pk_sys_company` (PRIMARY): `iIncrement` | |
| 72 | +- `uk_sys_company_code` (UNIQUE): `sCompanyCode` | |
| 73 | +- `idx_sys_company_is_deleted` (BTREE): `iIsDeleted, iSortOrder` | |
| 71 | 74 | |
| 72 | 75 | ### 外键 |
| 73 | 76 | |
| 74 | -- `fk_user_employee`: `iEmployeeId` → `t_employee.iIncrement` (ON DELETE SET NULL / ON UPDATE RESTRICT) | |
| 77 | +(无) | |
| 75 | 78 | |
| 76 | 79 | ### 业务注记 |
| 77 | 80 | |
| 78 | -- 登录认证主体表;密码以哈希形式存储,登录失败计数与锁定时间均落库以保证服务重启不丢;亦可由 Redis 镜像加速,但 DB 字段为权威。 | |
| 79 | -- `bVoid = 1` 视为已作废账号,不可登录、不出现在默认查询结果中(REQ-USR-004 列表默认隐藏作废)。 | |
| 80 | -- 用户名 / 用户号唯一性由 `uk_user_username` / `uk_user_userno` 保证;新增 / 修改时若冲突由 DB 抛唯一约束错误,Service 转译为 `40001 用户名已存在`。 | |
| 81 | +REQ-USR-001 登录页 "版本" 字段下拉来源。当前与 `sys_user` 不直接 FK,登录会话记录用户选择的公司即可(避免单用户跨公司绑定的复杂度)。新增公司由后续运营模块管理;本模块阶段只读使用。 | |
| 82 | + | |
| 83 | +--- | |
| 81 | 84 | |
| 82 | -## `t_employee` — 公司职员主档 | |
| 85 | +## `sys_department` — 部门字典,职员归属 | |
| 83 | 86 | |
| 84 | 87 | ### 字段 |
| 85 | 88 | |
| ... | ... | @@ -90,30 +93,26 @@ USR 用户管理模块共 6 张表,围绕「用户」为核心: |
| 90 | 93 | | `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | |
| 91 | 94 | | `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | |
| 92 | 95 | | `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | |
| 93 | -| `sEmployeeNo` | varchar(50) | 否 | — | 员工号;系统内唯一 | | |
| 94 | -| `sName` | varchar(100) | 否 | — | 姓名 | | |
| 95 | -| `iDepartmentId` | int | 是 | NULL | 部门 ID,关联 `t_department.iIncrement` | | |
| 96 | -| `sPhone` | varchar(20) | 是 | NULL | 手机号 | | |
| 97 | -| `sEmail` | varchar(100) | 是 | NULL | 邮箱 | | |
| 98 | -| `bDisabled` | tinyint(1) | 否 | 0 | 是否离职:0 在职 / 1 离职 | | |
| 96 | +| `sDepartmentName` | varchar(100) | 否 | — | 部门名称 | | |
| 97 | +| `sDepartmentCode` | varchar(50) | 否 | — | 部门编码 | | |
| 98 | +| `iIsDeleted` | tinyint(1) | 否 | 0 | 软删除标记 | | |
| 99 | 99 | |
| 100 | 100 | ### 索引 |
| 101 | 101 | |
| 102 | -- `uk_employee_no` (UNIQUE): `sEmployeeNo` | |
| 103 | -- `idx_employee_dept` (BTREE): `iDepartmentId` | |
| 104 | -- `idx_employee_name` (BTREE): `sName` | |
| 105 | -- `idx_employee_tenant` (BTREE): `sBrandsId`, `sSubsidiaryId` | |
| 102 | +- `pk_sys_department` (PRIMARY): `iIncrement` | |
| 103 | +- `uk_sys_department_code` (UNIQUE): `sDepartmentCode` | |
| 106 | 104 | |
| 107 | 105 | ### 外键 |
| 108 | 106 | |
| 109 | -- `fk_employee_department`: `iDepartmentId` → `t_department.iIncrement` (ON DELETE SET NULL / ON UPDATE RESTRICT) | |
| 107 | +(无) | |
| 110 | 108 | |
| 111 | 109 | ### 业务注记 |
| 112 | 110 | |
| 113 | -- 用户表通过 `iEmployeeId` 关联本表;REQ-USR-002 / 003 选择员工时下拉数据源;REQ-USR-004 查询字段「员工名 / 部门」均联表本表。 | |
| 114 | -- 离职员工保留记录(`bDisabled = 1`),其关联用户应被自动作废(应用层逻辑,未做 DB 触发器)。 | |
| 111 | +REQ-USR-004 查询输出 "部门" 字段来源(经 `sys_employee` 关联)。本阶段是扁平字典,未来 HR 模块扩展时可加 `iParentId` 形成树状结构。 | |
| 112 | + | |
| 113 | +--- | |
| 115 | 114 | |
| 116 | -## `t_department` — 部门组织树 | |
| 115 | +## `sys_employee` — 职员档案,员工基础信息 | |
| 117 | 116 | |
| 118 | 117 | ### 字段 |
| 119 | 118 | |
| ... | ... | @@ -124,27 +123,31 @@ USR 用户管理模块共 6 张表,围绕「用户」为核心: |
| 124 | 123 | | `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | |
| 125 | 124 | | `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | |
| 126 | 125 | | `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | |
| 127 | -| `sName` | varchar(100) | 否 | — | 部门名称 | | |
| 128 | -| `sCode` | varchar(50) | 否 | — | 部门编码;系统内唯一 | | |
| 129 | -| `iParentId` | int | 是 | NULL | 上级部门 ID,NULL 表示根部门 | | |
| 130 | -| `iSortOrder` | int | 否 | 0 | 排序值,小者靠前 | | |
| 126 | +| `sEmployeeName` | varchar(50) | 否 | — | 员工姓名(2-50 字符) | | |
| 127 | +| `sEmployeeCode` | varchar(50) | 否 | — | 员工工号(系统内唯一) | | |
| 128 | +| `iDepartmentId` | int | 否 | — | 所属部门 ID(FK → sys_department.iIncrement) | | |
| 129 | +| `sPhone` | varchar(20) | 是 | NULL | 手机号 | | |
| 130 | +| `sEmail` | varchar(100) | 是 | NULL | 邮箱 | | |
| 131 | +| `iIsDeleted` | tinyint(1) | 否 | 0 | 软删除标记 | | |
| 131 | 132 | |
| 132 | 133 | ### 索引 |
| 133 | 134 | |
| 134 | -- `uk_department_code` (UNIQUE): `sCode` | |
| 135 | -- `idx_department_parent` (BTREE): `iParentId` | |
| 136 | -- `idx_department_tenant` (BTREE): `sBrandsId`, `sSubsidiaryId` | |
| 135 | +- `pk_sys_employee` (PRIMARY): `iIncrement` | |
| 136 | +- `uk_sys_employee_code` (UNIQUE): `sEmployeeCode` | |
| 137 | +- `idx_sys_employee_department` (BTREE): `iDepartmentId` | |
| 138 | +- `idx_sys_employee_name` (BTREE): `sEmployeeName` | |
| 137 | 139 | |
| 138 | 140 | ### 外键 |
| 139 | 141 | |
| 140 | -- `fk_department_parent`: `iParentId` → `t_department.iIncrement` (ON DELETE RESTRICT / ON UPDATE RESTRICT) | |
| 142 | +- `fk_sys_employee_department`: `iDepartmentId` → `sys_department.iIncrement` (ON DELETE RESTRICT, ON UPDATE CASCADE) | |
| 141 | 143 | |
| 142 | 144 | ### 业务注记 |
| 143 | 145 | |
| 144 | -- 自引用形成部门树,根部门 `iParentId IS NULL`。 | |
| 145 | -- 部门删除采用「拒绝删除有下级 / 有员工的部门」策略(应用层校验);DB 外键 RESTRICT 兜底。 | |
| 146 | +REQ-USR-002 / 003 "员工名" 下拉来源;REQ-USR-004 "员工名" / "部门" 输出来源。`iDepartmentId` 用 RESTRICT 防止删除还有员工的部门。员工先于用户存在;同一员工可对应 0 或 1 个用户账号。 | |
| 147 | + | |
| 148 | +--- | |
| 146 | 149 | |
| 147 | -## `t_permission` — 权限分类字典 | |
| 150 | +## `sys_user` — 用户账号(登录认证 + 类型 + 语言 + 状态 + 登录追踪) | |
| 148 | 151 | |
| 149 | 152 | ### 字段 |
| 150 | 153 | |
| ... | ... | @@ -155,24 +158,46 @@ USR 用户管理模块共 6 张表,围绕「用户」为核心: |
| 155 | 158 | | `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | |
| 156 | 159 | | `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | |
| 157 | 160 | | `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | |
| 158 | -| `sCode` | varchar(50) | 否 | — | 权限码,例如 `USR:ADD` / `USR:EDIT`;系统内唯一 | | |
| 159 | -| `sName` | varchar(100) | 否 | — | 权限分类名称(展示用) | | |
| 160 | -| `iSortOrder` | int | 否 | 0 | 同分类内排序 | | |
| 161 | +| `sUsername` | varchar(50) | 否 | — | 用户名(登录凭据,系统内全局唯一,3-20 位字母数字下划线) | | |
| 162 | +| `sUserCode` | varchar(50) | 否 | — | 用户号(业务展示用编码,系统内唯一) | | |
| 163 | +| `sPasswordHash` | varchar(255) | 否 | — | 密码哈希(BCrypt / Argon2,禁明文) | | |
| 164 | +| `iEmployeeId` | int | 是 | NULL | 关联职员 ID(可选;FK → sys_employee.iIncrement) | | |
| 165 | +| `sUserType` | varchar(20) | 否 | `NORMAL` | 用户类型枚举:`NORMAL`=普通用户 / `SUPER_ADMIN`=超级管理员 | | |
| 166 | +| `sLanguage` | varchar(10) | 否 | `zh-CN` | 语言:`zh-CN`=中文 / `en-US`=英文 / `zh-TW`=繁体 | | |
| 167 | +| `iCanEditDocument` | tinyint(1) | 否 | 0 | 单据修改权限:0=否 1=是 | | |
| 168 | +| `iIsDeleted` | tinyint(1) | 否 | 0 | 是否作废:0=启用 1=作废(停用) | | |
| 169 | +| `iFailedLoginCount` | int | 否 | 0 | 累计登录失败次数,达阈值锁定,登录成功清零 | | |
| 170 | +| `tLockUntil` | datetime | 是 | NULL | 锁定截止时间,NULL=未锁定,过期自动解锁 | | |
| 171 | +| `tLastLoginDate` | datetime | 是 | NULL | 最后一次成功登录时间,REQ-USR-004 "登录日期" 来源 | | |
| 172 | +| `sCreatedBy` | varchar(50) | 是 | NULL | 制单人(创建该用户的用户名),REQ-USR-002 "制单人" | | |
| 173 | +| `sUpdatedBy` | varchar(50) | 是 | NULL | 最后修改人用户名 | | |
| 174 | +| `tUpdatedDate` | datetime | 是 | NULL | 最后修改时间 | | |
| 161 | 175 | |
| 162 | 176 | ### 索引 |
| 163 | 177 | |
| 164 | -- `uk_permission_code` (UNIQUE): `sCode` | |
| 178 | +- `pk_sys_user` (PRIMARY): `iIncrement` | |
| 179 | +- `uk_sys_user_username` (UNIQUE): `sUsername` | |
| 180 | +- `uk_sys_user_code` (UNIQUE): `sUserCode` | |
| 181 | +- `idx_sys_user_employee` (BTREE): `iEmployeeId` | |
| 182 | +- `idx_sys_user_type` (BTREE): `sUserType` | |
| 183 | +- `idx_sys_user_is_deleted` (BTREE): `iIsDeleted` | |
| 184 | +- `idx_sys_user_created_by` (BTREE): `sCreatedBy` | |
| 165 | 185 | |
| 166 | 186 | ### 外键 |
| 167 | 187 | |
| 168 | -- 无 | |
| 188 | +- `fk_sys_user_employee`: `iEmployeeId` → `sys_employee.iIncrement` (ON DELETE SET NULL, ON UPDATE CASCADE) | |
| 169 | 189 | |
| 170 | 190 | ### 业务注记 |
| 171 | 191 | |
| 172 | -- 字典表,启动时由初始化脚本灌入;不允许业务删除,禁用通过软标记(暂未引入 `bVoid`,由 docs/01 补充时再加)。 | |
| 173 | -- 权限码格式 `<模块代码>:<动作>`,与后端 `@PreAuthorize('hasAuthority(...)')` 一一对应(docs/06 § 1.3)。 | |
| 192 | +USR 模块核心表。 | |
| 193 | +- **登录认证**(REQ-USR-001):`sUsername` + `sPasswordHash` 验证;连续失败累加 `iFailedLoginCount`,达 5 次写 `tLockUntil = now() + 30 分钟`;登录成功清零 + 更新 `tLastLoginDate`。 | |
| 194 | +- **作废**(`iIsDeleted = 1`):等价业务"停用",登录直接拒绝。删除用户不物理删,避免 FK 联动;`uk_sys_user_username` 与作废态共存的可能由应用层处理(作废后用户名释放或保留按运营决定,本阶段保留唯一)。 | |
| 195 | +- **职员关联**(`iEmployeeId`):可选;删除职员时此字段置 NULL(`ON DELETE SET NULL`),用户记录不丢。 | |
| 196 | +- **密码不在本接口修改**(REQ-USR-003 边界):修改用户走非密码字段;密码重置走独立流程(后续 REQ 扩展)。 | |
| 174 | 197 | |
| 175 | -## `t_user_permission` — 用户-权限分类关联表 | |
| 198 | +--- | |
| 199 | + | |
| 200 | +## `sys_permission_category` — 权限分类字典 | |
| 176 | 201 | |
| 177 | 202 | ### 字段 |
| 178 | 203 | |
| ... | ... | @@ -183,26 +208,29 @@ USR 用户管理模块共 6 张表,围绕「用户」为核心: |
| 183 | 208 | | `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | |
| 184 | 209 | | `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | |
| 185 | 210 | | `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | |
| 186 | -| `iUserId` | int | 否 | — | 用户 ID,关联 `t_user.iIncrement` | | |
| 187 | -| `iPermissionId` | int | 否 | — | 权限分类 ID,关联 `t_permission.iIncrement` | | |
| 211 | +| `sCategoryName` | varchar(100) | 否 | — | 权限分类名称(如 "采购管理" / "销售管理") | | |
| 212 | +| `sCategoryCode` | varchar(50) | 否 | — | 权限分类编码(系统内唯一,代码层引用) | | |
| 213 | +| `sCategoryDesc` | varchar(255) | 是 | NULL | 分类说明 | | |
| 214 | +| `iSortOrder` | int | 否 | 0 | 列表展示顺序 | | |
| 215 | +| `iIsDeleted` | tinyint(1) | 否 | 0 | 软删除标记 | | |
| 188 | 216 | |
| 189 | 217 | ### 索引 |
| 190 | 218 | |
| 191 | -- `uk_user_perm` (UNIQUE): `iUserId`, `iPermissionId` | |
| 192 | -- `idx_user_perm_perm` (BTREE): `iPermissionId` | |
| 219 | +- `pk_sys_permission_category` (PRIMARY): `iIncrement` | |
| 220 | +- `uk_sys_permission_category_code` (UNIQUE): `sCategoryCode` | |
| 221 | +- `idx_sys_permission_category_sort` (BTREE): `iIsDeleted, iSortOrder` | |
| 193 | 222 | |
| 194 | 223 | ### 外键 |
| 195 | 224 | |
| 196 | -- `fk_userperm_user`: `iUserId` → `t_user.iIncrement` (ON DELETE CASCADE / ON UPDATE RESTRICT) | |
| 197 | -- `fk_userperm_perm`: `iPermissionId` → `t_permission.iIncrement` (ON DELETE RESTRICT / ON UPDATE RESTRICT) | |
| 225 | +(无) | |
| 198 | 226 | |
| 199 | 227 | ### 业务注记 |
| 200 | 228 | |
| 201 | -- 关联表豁免 `sBrandsId` / `sSubsidiaryId` 的业务语义但保留列以遵守项目标准;写入时复用 `t_user` 的对应租户标识。 | |
| 202 | -- 删除用户时级联清理本表行(`ON DELETE CASCADE`);删除字典项保护已分配数据(`ON DELETE RESTRICT`)。 | |
| 203 | -- 唯一约束 `(iUserId, iPermissionId)` 确保同一用户对同一权限分类不重复授权。 | |
| 229 | +REQ-USR-002 / 003 表 2 "权限组" 的"权限分类"来源。本表是字典,分类条目由系统初始化或运营维护,**用户管理模块只读使用**。 | |
| 230 | + | |
| 231 | +--- | |
| 204 | 232 | |
| 205 | -## `t_company` — 公司 / 版本字典 | |
| 233 | +## `sys_user_permission_category` — 用户 × 权限分类授权关系 | |
| 206 | 234 | |
| 207 | 235 | ### 字段 |
| 208 | 236 | |
| ... | ... | @@ -213,18 +241,25 @@ USR 用户管理模块共 6 张表,围绕「用户」为核心: |
| 213 | 241 | | `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | |
| 214 | 242 | | `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | |
| 215 | 243 | | `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | |
| 216 | -| `sCode` | varchar(50) | 否 | — | 公司 / 版本编码;系统内唯一 | | |
| 217 | -| `sName` | varchar(100) | 否 | — | 显示名称 | | |
| 244 | +| `iUserId` | int | 否 | — | 用户 ID(FK → sys_user.iIncrement) | | |
| 245 | +| `iPermissionCategoryId` | int | 否 | — | 权限分类 ID(FK → sys_permission_category.iIncrement) | | |
| 246 | +| `sGrantedBy` | varchar(50) | 是 | NULL | 授予人用户名 | | |
| 218 | 247 | |
| 219 | 248 | ### 索引 |
| 220 | 249 | |
| 221 | -- `uk_company_code` (UNIQUE): `sCode` | |
| 250 | +- `pk_sys_user_permission_category` (PRIMARY): `iIncrement` | |
| 251 | +- `uk_sys_user_permission_category` (UNIQUE): `iUserId, iPermissionCategoryId` | |
| 252 | +- `idx_sys_user_permission_category_category` (BTREE): `iPermissionCategoryId` | |
| 222 | 253 | |
| 223 | 254 | ### 外键 |
| 224 | 255 | |
| 225 | -- 无 | |
| 256 | +- `fk_sys_upc_user`: `iUserId` → `sys_user.iIncrement` (ON DELETE CASCADE, ON UPDATE CASCADE) | |
| 257 | +- `fk_sys_upc_permission_category`: `iPermissionCategoryId` → `sys_permission_category.iIncrement` (ON DELETE CASCADE, ON UPDATE CASCADE) | |
| 226 | 258 | |
| 227 | 259 | ### 业务注记 |
| 228 | 260 | |
| 229 | -- REQ-USR-001 登录入参「版本」下拉数据源;启动时由初始化脚本插入至少一行默认公司数据;具体「版本」字段语义待后续业务确认后再补列。 | |
| 230 | -- 当前 REQ 描述不足以确认与 `t_user` 是否需要强关联,故先做独立字典;后续如需「用户绑定公司 / 版本」再于 `t_user` 增列。 | |
| 261 | +记录每个用户被授予的权限分类清单。 | |
| 262 | +- REQ-USR-002 新增用户时,根据表 2 勾选生成本表对应行。 | |
| 263 | +- REQ-USR-003 修改用户时,重新计算差集做增量增删。 | |
| 264 | +- `ON DELETE CASCADE`:用户被物理删除时联动清理(注意:业务上用户作废不会物理删除,所以联动主要面向极端场景);权限分类被物理删除时联动清理用户授权关系,避免悬挂。 | |
| 265 | +- 复合唯一键 `(iUserId, iPermissionCategoryId)` 保证同一用户同一分类只授权一次。 | ... | ... |
docs/04-技术规范.md
| ... | ... | @@ -35,117 +35,138 @@ |
| 35 | 35 | |
| 36 | 36 | ### 1.1 分层结构 |
| 37 | 37 | |
| 38 | +按 Spring Boot 3 + MyBatis-Plus 惯例分四层,每层职责单一: | |
| 39 | + | |
| 38 | 40 | | 层 | 职责 | |
| 39 | 41 | |---|---| |
| 40 | -| `controller/` | 接收 HTTP 请求、参数校验(`@Valid`)、调用 Service、组装响应;不写业务逻辑 | | |
| 41 | -| `service/` | 业务编排、事务边界、跨 Mapper 调用;接口 + `impl/` 实现 | | |
| 42 | -| `mapper/` | MyBatis-Plus 数据访问(Java 接口 + XML),仅做单表 CRUD 与简单关联查询 | | |
| 43 | -| `entity/` | 与数据库表 1:1 的实体类,字段名/类型严格对齐 docs/03 | | |
| 44 | -| `dto/` | 入参对象(前端 → 后端),含 `@NotNull` / `@Pattern` 等校验注解 | | |
| 45 | -| `vo/` | 出参对象(后端 → 前端),由 MapStruct 从 Entity 转换 | | |
| 46 | -| `config/` | Spring 配置类(Security / Redis / MyBatis-Plus / Swagger / Activiti) | | |
| 47 | -| `common/` | 全局响应包装、统一异常处理器、拦截器、工具类 | | |
| 48 | -| `security/` | JWT 生成 / 验证、`UserDetailsService` 实现、权限注解 | | |
| 42 | +| `controller` | HTTP 入口;参数校验(`@Valid`);调用 service;返回统一响应;**不写业务逻辑** | | |
| 43 | +| `service` + `service/impl` | 业务编排;事务边界;调用一个或多个 mapper;DTO ↔ Entity 转换走 converter | | |
| 44 | +| `mapper` + `resources/mapper/*.xml` | 单表 CRUD 走 MyBatis-Plus;复杂 SQL 写 XML;**不写业务逻辑** | | |
| 45 | +| `entity` / `dto` / `vo` / `converter` | 数据传输层;converter 用 MapStruct 自动生成实现 | | |
| 49 | 46 | |
| 50 | 47 | ### 1.2 命名约定 |
| 51 | 48 | |
| 52 | -- **包名**:全小写,单数。根包 `com.example.erp`。业务模块包 `<ROOT>.module.<模块代码小写>`(示例:`com.example.erp.module.usr`)。 | |
| 53 | -- **类名**:大驼峰;按层级加后缀(`UserController` / `UserServiceImpl` / `UserMapper` / `UserCreateDTO` / `UserListVO`)。 | |
| 54 | -- **方法名**:小驼峰;动词开头(`createUser` / `listUsers` / `disableUser`),与 REST 动作语义对齐。 | |
| 55 | -- **常量**:全大写下划线(`MAX_LOGIN_FAILS = 5`),放对应业务模块的 `constant/` 子包或公共 `common/constant/`。 | |
| 56 | -- **示例 1**:`com.example.erp.module.usr.controller.UserController#listUsers(UserQueryDTO)` 返回 `PageVO<UserListVO>`。 | |
| 57 | -- **示例 2**:`com.example.erp.module.usr.service.impl.UserServiceImpl#disableUser(Long id)` 写入 `t_user_audit_log` 审计。 | |
| 49 | +- **Java 包**:全小写。根包:`com.xly.erp`。业务模块:`module.<module_code_lower>`(与 docs/01 模块代码对齐)。 | |
| 50 | +- **类名**:大驼峰;后缀清晰标识层(`UserController` / `UserServiceImpl` / `UserMapper` / `UserEntity` / `CreateUserReq` / `UserVo` / `UserConverter`)。 | |
| 51 | +- **方法 / 字段**:小驼峰(`createUser` / `userName`)。 | |
| 52 | +- **常量**:全大写下划线(`MAX_LOGIN_ATTEMPTS`)。 | |
| 53 | +- **数据库表名**:小写下划线(`sys_user`);字段名同样小写下划线(`created_at`)。 | |
| 54 | + | |
| 55 | +**示例**: | |
| 56 | +```java | |
| 57 | +package com.xly.erp.module.usr.controller; | |
| 58 | + | |
| 59 | +@RestController | |
| 60 | +@RequestMapping("/api/v1/users") | |
| 61 | +public class UserController { | |
| 62 | + public Result<UserVo> createUser(@RequestBody @Valid CreateUserReq req) { ... } | |
| 63 | +} | |
| 64 | +``` | |
| 58 | 65 | |
| 59 | 66 | ### 1.3 统一响应格式 |
| 60 | 67 | |
| 61 | -后端所有 HTTP 接口返回 `Result<T>` 包装: | |
| 68 | +所有 HTTP 接口返回 `Result<T>` / `PageResult<T>`: | |
| 62 | 69 | |
| 63 | 70 | ```json |
| 64 | 71 | // 成功 |
| 65 | 72 | { "code": 0, "message": "ok", "data": { ... } } |
| 73 | + | |
| 66 | 74 | // 失败 |
| 67 | 75 | { "code": 40001, "message": "用户名已存在", "data": null } |
| 68 | 76 | ``` |
| 69 | 77 | |
| 70 | 78 | 错误码段位: |
| 71 | - | |
| 72 | -| 段位 | 含义 | | |
| 73 | -|---|---| | |
| 74 | -| `0` | 成功 | | |
| 75 | -| `1xxxx` | 系统级(参数校验、未授权、未登录) | | |
| 76 | -| `2xxxx` | 通用业务错误(资源不存在、状态非法) | | |
| 77 | -| `4xxxx` | 模块业务错误(按模块再分子段:USR=`40xxx`,PUR=`41xxx`...) | | |
| 78 | -| `5xxxx` | 第三方 / 基础设施错误(DB / Redis / 外部服务) | | |
| 79 | +- `0`:成功 | |
| 80 | +- `10xxx`:参数 / 校验类错误 | |
| 81 | +- `20xxx`:业务规则错误 | |
| 82 | +- `30xxx`:权限类错误 | |
| 83 | +- `40xxx`:资源类错误(找不到、冲突) | |
| 84 | +- `50xxx`:服务器内部错误 | |
| 85 | +- `60xxx`:第三方依赖错误 | |
| 79 | 86 | |
| 80 | 87 | ### 1.4 异常处理 |
| 81 | 88 | |
| 82 | -- **全局异常处理器**:`@RestControllerAdvice` 统一捕获,转换为 `Result` 失败结构。 | |
| 83 | -- **必须 catch**:`MethodArgumentNotValidException`(参数校验)/ `BindException` / 业务自定义 `BizException` / `AccessDeniedException`。 | |
| 84 | -- **禁止 catch**:泛 `Exception` 在业务代码中吞掉;必须让全局处理器接管。 | |
| 85 | -- **接口响应禁止回显后端异常堆栈**:返回用户友好错误码 + 文案;堆栈仅写入 logback。 | |
| 89 | +- 全局异常处理器 `GlobalExceptionHandler`(`@RestControllerAdvice`)统一捕获并转 `Result.fail(...)`。 | |
| 90 | +- 业务异常用 `BizException(int code, String message)`,service 层抛出,全局处理器捕获后转响应。 | |
| 91 | +- **禁止**在 controller / service 里 `catch (Exception e) { e.printStackTrace(); }` 后吞掉。 | |
| 92 | +- **接口响应禁止回显后端异常堆栈**:仅返回 `code` + 用户友好 `message`;堆栈写日志(含 traceId)。 | |
| 86 | 93 | |
| 87 | 94 | ### 1.5 事务 |
| 88 | 95 | |
| 89 | -- **事务边界**:放在 Service 层方法上(`@Transactional`),Controller / Mapper 禁止开事务。 | |
| 90 | -- **传播策略**:默认 `REQUIRED`;只读查询用 `@Transactional(readOnly = true)`。 | |
| 91 | -- **跨服务调用禁止开新事务嵌套**:跨进程 / 跨服务的一致性走「最终一致 + 补偿」或 Activiti 工作流,不要用分布式事务。 | |
| 96 | +- 事务边界统一在 **service 实现层**,使用 `@Transactional(rollbackFor = Exception.class)`。 | |
| 97 | +- controller / mapper 层**禁止**写 `@Transactional`。 | |
| 98 | +- 跨服务(跨进程)调用**禁止**使用本地事务跨边界;改用最终一致性(消息队列 / 补偿任务),本项目当前无跨服务场景。 | |
| 92 | 99 | |
| 93 | 100 | ### 1.6 认证 |
| 94 | 101 | |
| 95 | -- **协议**:JWT(HS256),头 `Authorization: Bearer <token>`。 | |
| 96 | -- **生命周期**:access token 8 小时,refresh token 7 天;登录返回两枚 token;access 过期前端凭 refresh 刷新一次。 | |
| 97 | -- **刷新机制**:refresh token 只能用于换 access,不能直接调业务接口;服务端可吊销(Redis 存吊销名单)。 | |
| 98 | -- **密钥管理**:JWT 签名密钥放 `.env.local` 的 `JWT_SECRET`,至少 32 字节;生产环境必须更换默认值。 | |
| 99 | -- **登录失败锁定**:连续 5 次失败锁账户 30 分钟,Redis 计数器 key `login:fail:<username>`。 | |
| 102 | +- 使用 Spring Security + JWT。 | |
| 103 | +- **Access Token**:HS256,TTL 2 小时;签发后写 Redis(key=`auth:token:<userId>:<jti>`,value=用户摘要)。 | |
| 104 | +- **Refresh Token**:TTL 7 天,单独签发;刷新时旋转 token 并使旧 token 失效。 | |
| 105 | +- 密钥(HMAC secret)放 `.env.local` 的 `JWT_SECRET`,**禁止**写入 application.yml 或代码。 | |
| 106 | +- 登出 / 改密 / 停用:把对应 `jti` 加入 Redis 黑名单直至原 TTL 过期。 | |
| 100 | 107 | |
| 101 | 108 | ## 二、前端规范 |
| 102 | 109 | |
| 103 | 110 | ### 2.1 目录约定 |
| 104 | 111 | |
| 112 | +按 Vite + React 18 + Redux Toolkit 惯例分层(详见 docs/09 § 三): | |
| 113 | + | |
| 105 | 114 | | 目录 | 职责 | |
| 106 | 115 | |---|---| |
| 107 | -| `src/api/` | Axios 实例 + 每模块一个 API 文件;**所有 HTTP 调用唯一入口** | | |
| 108 | -| `src/components/` | 跨页面通用组件(`AuthButton` / `AppTable` / `PageHeader`) | | |
| 109 | -| `src/pages/` | 业务页面,按模块组织子目录 | | |
| 110 | -| `src/store/` | Redux Toolkit slice,仅放真正的全局状态 | | |
| 111 | -| `src/hooks/` | 自定义 hook(`useAuth` / `usePagination` / `useDebounce`) | | |
| 112 | -| `src/utils/` | 纯函数工具(格式化、校验、正则) | | |
| 113 | -| `src/styles/` | `tokens.css` + 全局样式 | | |
| 114 | -| `src/router/` | 路由表 + 路由守卫 | | |
| 115 | - | |
| 116 | -> **前端禁止直接写 SQL / 操作 DB**,所有数据访问走 `api/` 层统一封装。 | |
| 116 | +| `src/api/` | Axios 实例 + 各模块接口调用函数;**所有数据访问唯一入口** | | |
| 117 | +| `src/store/` | Redux Toolkit slices;全局态(认证 / 用户信息 / 字典) | | |
| 118 | +| `src/router/` | React Router 配置 + 路由守卫 | | |
| 119 | +| `src/pages/<module>/` | 业务页面(按模块分目录) | | |
| 120 | +| `src/components/` | 跨页面通用组件(含 `Permission` 等) | | |
| 121 | +| `src/layouts/` | 框架布局 | | |
| 122 | +| `src/hooks/` | 自定义 hook | | |
| 123 | +| `src/utils/` | 工具方法(日期 / 金额 / 校验等) | | |
| 124 | +| `src/styles/` | 全局样式 + Design Tokens | | |
| 125 | +| `src/types/` | 全局 TypeScript 类型 | | |
| 126 | + | |
| 127 | +**前端禁止直接写 SQL / 操作 DB**;所有数据访问走 `api/` 层封装的 HTTP 调用。 | |
| 117 | 128 | |
| 118 | 129 | ### 2.2 状态管理 |
| 119 | 130 | |
| 120 | -- **Redux 存什么**:全局共享 + 跨页面持久(当前用户信息、权限码列表、菜单树、字典)。 | |
| 121 | -- **Redux 不存什么**:单个页面的表单值、Modal 开关、表格分页参数 → 放组件 `useState` / `useReducer`。 | |
| 122 | -- **服务端数据**:业务数据(列表、明细)不要塞 Redux,每次进入页面走 `api/` 取最新;只有字典 / 全局枚举做内存缓存(`store/slices/dictSlice`)。 | |
| 131 | +- **全局态**(用户身份 / 权限 / 全局字典 / 主题):Redux Toolkit。 | |
| 132 | +- **页面级态**(表单值 / 列表参数):组件本地 `useState` / `useReducer`。 | |
| 133 | +- **服务端数据**(列表 / 详情):直接调 `api/` 函数,配合 `useEffect` + 本地 state;**不放**全局 store,避免缓存一致性问题。 | |
| 134 | +- 跨页面共享但**非全局**的态用 React Context(如 Modal Provider)。 | |
| 123 | 135 | |
| 124 | 136 | ### 2.3 请求封装 |
| 125 | 137 | |
| 126 | -- **Axios 实例**:`src/api/http.ts` 统一创建,`baseURL` 取 `import.meta.env.VITE_API_BASE`。 | |
| 127 | -- **请求拦截器**:自动注入 `Authorization: Bearer <accessToken>`;token 临期则先刷新再发起原请求。 | |
| 128 | -- **响应拦截器**:剥 `Result.data` 给业务;`code !== 0` 时 `message.error(message)` 并 reject。 | |
| 129 | -- **超时**:默认 15s;上传 / 导出接口单独 60s。 | |
| 130 | -- **错误重试**:仅对幂等 GET 重试 1 次,POST / PUT / DELETE 禁止重试。 | |
| 138 | +`src/api/client.ts` 统一 Axios 实例: | |
| 139 | + | |
| 140 | +- baseURL 从 `import.meta.env.VITE_API_BASE_URL` 读取。 | |
| 141 | +- **请求拦截**:自动注入 `Authorization: Bearer <accessToken>`;附带 `X-Request-Id` 用于链路追踪。 | |
| 142 | +- **响应拦截**: | |
| 143 | + - HTTP 200 + `code === 0` → 返回 `data`。 | |
| 144 | + - HTTP 200 + `code !== 0` → 抛业务错误(含 message 和 code)。 | |
| 145 | + - HTTP 401 → 触发 refresh token;refresh 失败 → 清登录态 + 跳登录页。 | |
| 146 | + - HTTP 5xx → 抛网络错误。 | |
| 147 | +- 超时:默认 10s;上传 / 导出接口 60s。 | |
| 148 | +- 不做自动重试(重试由业务调用处显式控制)。 | |
| 131 | 149 | |
| 132 | 150 | ### 2.4 错误处理 |
| 133 | 151 | |
| 134 | -- **网络错误**:Axios `error.code === 'ERR_NETWORK'` → 全局 `message.error('网络异常,请检查连接')`。 | |
| 135 | -- **业务错误**:`code !== 0` 在响应拦截器统一弹 message;页面只需处理 `try/catch` 中的 reject。 | |
| 136 | -- **页面级错误**:路由级 `ErrorBoundary` 包顶层,组件 throw 时显示统一错误页(docs/06 § 1.2)。 | |
| 152 | +| 错误类型 | 处理方式 | | |
| 153 | +|---|---| | |
| 154 | +| 网络错误(断网 / 5xx) | 顶层 `message.error('网络异常,请重试')`,业务调用处可自定义重试按钮 | | |
| 155 | +| 业务错误(code !== 0) | 默认 `message.error(err.message)`,业务调用处可拦截做特殊提示 | | |
| 156 | +| 表单校验失败 | 字段下方红字(Ant Design `Form` 自带) | | |
| 157 | +| 路由级未授权 | `RequireAuth` 守卫重定向到 `/login` | | |
| 158 | +| 页面级崩溃 | 顶层 `ErrorBoundary` 显示 `Result status="500"` + 重新加载按钮 | | |
| 137 | 159 | |
| 138 | 160 | ### 2.5 样式与主题 |
| 139 | 161 | |
| 140 | 162 | - **CSS 变量命名**:`--color-<scope>-<role>-<state>` |
| 141 | - - `scope` = `form` / `table-row` / `btn` / `link` / ... | |
| 142 | - - `role` = `bg` / `fg` / `border` | |
| 143 | - - `state` = `edit` / `readonly` / `hover` / `selected` | |
| 144 | -- **文件位置**:`src/styles/tokens.css`,由 skeleton-gen 生成空骨架,色值由 docs/06 § 二锁定后填入。 | |
| 145 | -- **组件样式**:只用 `var(--color-xxx)`,禁止硬编码 hex / rgba。 | |
| 146 | -- **Ant Design 主题对接**:`<ConfigProvider theme={{ token: { colorPrimary: 'var(--color-primary)', ... } }}>` 把 tokens 注入 antd 主题。 | |
| 147 | - | |
| 148 | -具体 token 表见 docs/06 § 二。 | |
| 163 | + - `scope`:`form` / `table-row` / `tag` / `btn` 等;通用全局色无 scope(如 `--color-primary`) | |
| 164 | + - `role`:`bg` / `fg` / `border` | |
| 165 | + - `state`:`edit` / `readonly` / `hover` / `selected` / `disabled`;常态省略 | |
| 166 | +- **文件位置**:`frontend/src/styles/tokens.css`(由 skeleton-gen 生成骨架,色值由 docs/06 § 二锁定)。 | |
| 167 | +- **组件样式中只用 `var(--color-xxx)`**,禁止硬编码 hex / rgba。 | |
| 168 | +- **与 Ant Design 主题对接**:`App.tsx` 顶层包 `<ConfigProvider theme={{ token: { colorPrimary: 'var(--color-primary)', ... } }}>`;Ant Design 5 接受 token 对象映射,确保组件库色值与 tokens.css 单一来源。 | |
| 169 | +- 具体 token 表见 `docs/06 § 二`。 | |
| 149 | 170 | |
| 150 | 171 | ## 三、共同约定 |
| 151 | 172 | |
| ... | ... | @@ -153,35 +174,40 @@ |
| 153 | 174 | |
| 154 | 175 | `<type>(<scope>): <subject> REQ-XXX-NNN` |
| 155 | 176 | |
| 156 | -- `type` ∈ `feat` / `fix` / `refactor` / `test` / `docs` / `chore` / `style` | |
| 157 | -- `scope` 为模块代码小写(`usr` / `pur` / `sal`)或 `infra` | |
| 158 | -- `subject` 50 字以内动词开头 | |
| 159 | -- 末尾必须挂 `REQ-XXX-NNN`(如 `REQ-USR-001`),CI 用此挂关联 | |
| 177 | +- type:见 CLAUDE.md § 🗂️ Git 提交规范。 | |
| 178 | +- scope:模块代码小写(`usr` / `pur` / `sal`)或 `infra` / `docs`。 | |
| 179 | +- subject:祈使句,中文,<= 50 字。 | |
| 180 | +- REQ tag:业务类提交(feat / fix / test)必带。 | |
| 181 | + | |
| 182 | +**示例**:`feat(usr): 实现用户登录接口 REQ-USR-001` | |
| 160 | 183 | |
| 161 | 184 | ### 3.2 分页查询 |
| 162 | 185 | |
| 163 | -- **后端入参**:`PageQuery { pageNum: int = 1, pageSize: int = 20 }`(`pageSize` 上限 100)+ 业务过滤字段;继承基类避免重复声明。 | |
| 164 | -- **后端出参**:`PageVO<T> { records: List<T>, total: long, pageNum: int, pageSize: int }`。 | |
| 165 | -- **前端组件**:统一封装 `<AppTable />` 内置 antd `Table` + `Pagination`,受控 `current` / `pageSize` / `total`。 | |
| 166 | -- **默认排序**:按创建时间倒序;可按列点击切换。 | |
| 186 | +- **后端**:请求入参 `PageReq { page=1, size=20 }`(兜底默认值;size 上限 100);返回 `PageResult<T> { records: List<T>, total: long, page: int, size: int }`。 | |
| 187 | +- **前端**:使用 Ant Design `Table` + `Pagination`;统一组件包装在 `components/PagedTable`,对接 `PageResult`。 | |
| 188 | +- 排序参数走 `sortField` / `sortOrder`,白名单字段,禁止任意字段排序。 | |
| 167 | 189 | |
| 168 | 190 | ### 3.3 日期与金额 |
| 169 | 191 | |
| 170 | -- **后端类型**:日期 `LocalDate`,时间 `LocalDateTime`,金额 `BigDecimal`(scale=2,HALF_UP)。 | |
| 171 | -- **序列化**:Jackson 全局配置 `LocalDateTime` ↔ `yyyy-MM-dd HH:mm:ss`;`BigDecimal` 序列化为字符串避免精度丢失。 | |
| 172 | -- **前端展示**:`utils/format.ts` 提供 `formatDate(d)` / `formatMoney(n)`;金额展示固定 2 位小数 + 千分位(`¥1,234.56`)。 | |
| 192 | +- **后端类型**:日期用 `LocalDateTime` / `LocalDate`;金额用 `BigDecimal`(精度 2 位)。 | |
| 193 | +- **后端 → 前端**:日期统一序列化为 `YYYY-MM-DD HH:mm:ss` 字符串;金额序列化为 `string`(避免精度丢失)。 | |
| 194 | +- **前端展示**:日期使用 `dayjs` 格式化(`YYYY-MM-DD HH:mm`);金额使用 `utils/money.ts` 格式化为千分位。 | |
| 195 | +- **金额精度**:内部全部 `BigDecimal`,4 舍 6 入 5 成双(`HALF_EVEN`);展示截断到 2 位。 | |
| 173 | 196 | |
| 174 | 197 | ### 3.4 数据访问规约 |
| 175 | 198 | |
| 176 | -- **SELECT 字段显式列举**,禁止 `SELECT *`;Mapper XML 用 `<sql id="Base_Column_List">` 集中维护。 | |
| 177 | -- **禁止 N+1**:循环中不得执行 DB 查询;改用批量查(`IN (?,?,?)`)/ JOIN / `<foreach>`。 | |
| 178 | -- **表名 / 字段名**:Mapper XML 中通过 `<sql>` 片段或常量引用,禁止字符串拼接(防 SQL 注入 + 利于改名)。 | |
| 179 | -- **分页**:统一用 MyBatis-Plus `Page<T>` + `selectPage(...)`,禁止 LIMIT 字符串拼接。 | |
| 199 | +- **SELECT 字段显式列举**:禁止 `SELECT *`;MyBatis-Plus 用 `select(User::getId, User::getUsername)` 或 XML 显式列名。 | |
| 200 | +- **禁止 N+1 反模式**:循环中不得执行 DB 查询;改用 `selectBatchIds` / `IN` 子句 / `JOIN`。 | |
| 201 | +- **Mapper XML**:表名 / 字段名用常量或引用,禁止字符串拼接 SQL(防注入)。 | |
| 202 | +- **复杂查询走 XML**,简单 CRUD 走 LambdaQueryWrapper。 | |
| 203 | +- **写操作**:单表批量写用 `saveBatch` / `updateBatchById`;超大批量(>1000)分批 commit。 | |
| 180 | 204 | |
| 181 | 205 | ### 3.5 配置与安全 |
| 182 | 206 | |
| 183 | -- **配置**:DB 连接 / 端口 / 密钥 / 第三方 URL 等一律放 `application.yml` + `.env.local`,代码里**禁止硬编码**;`application.yml` 用 `${VAR_NAME:default}` 引用环境变量。 | |
| 207 | +- **后端配置**:DB 连接 / 端口 / 密钥 / 第三方 URL 等一律放 `application.yml` + `.env.local`。代码中**禁止硬编码**(用 `@Value` / `@ConfigurationProperties` 注入)。 | |
| 208 | +- **多环境**:`application-dev.yml` / `application-test.yml` / `application-prod.yml`,通过 `SPRING_PROFILES_ACTIVE` 切换。 | |
| 184 | 209 | - **前端安全**: |
| 185 | - - `localStorage` **不存敏感信息**(token / 身份 / 个人数据);推荐 HttpOnly Cookie 或 「内存 access token + HttpOnly refresh cookie」组合。 | |
| 186 | - - 接口响应禁止回显后端异常堆栈(与 § 1.4 一致)。 | |
| 187 | - - XSS:所有用户输入展示走 React JSX 自动转义;`dangerouslySetInnerHTML` 禁用,除非内容来源已白名单化。 | |
| 210 | + - `localStorage` **禁止**存敏感信息(token / 身份 / 个人数据)。 | |
| 211 | + - Token 推荐**内存 + HttpOnly Cookie**(refresh)或纯内存(access),Redux store 持有 access;刷新页面通过 refresh cookie 重新换取。 | |
| 212 | + - **接口响应禁止回显后端异常堆栈**(与 § 1.4 一致)。 | |
| 213 | +- **密钥来源**:所有密钥(JWT / DB 密码 / 第三方 API key)从 `.env.local` 加载;`.env.local` 在 `.gitignore`,永不入库。 | ... | ... |
docs/05-API接口契约.md
| 1 | 1 | # 05-API接口契约 |
| 2 | 2 | |
| 3 | -BasePath: `/api` | |
| 4 | -端口: `8080` | |
| 3 | +BasePath: `/api/v1` | |
| 4 | +端口: `9090` | |
| 5 | 5 | |
| 6 | 6 | ## 全局约定 |
| 7 | 7 | |
| ... | ... | @@ -22,186 +22,94 @@ BasePath: `/api` |
| 22 | 22 | |
| 23 | 23 | ### 鉴权 |
| 24 | 24 | |
| 25 | -所有业务接口走 JWT Bearer。客户端在 `Authorization: Bearer <accessToken>` 头中携带访问令牌。 | |
| 26 | - | |
| 27 | -- 登录 `POST /api/usr/auth/login` 返回 `accessToken`(8 小时)+ `refreshToken`(7 天) | |
| 28 | -- 临期前端用 refresh token 换新 access token:`POST /api/usr/auth/refresh` | |
| 29 | -- 注销 `POST /api/usr/auth/logout` 服务端把 token 加入 Redis 吊销名单 | |
| 30 | -- 未携带 / 过期 / 已吊销 → 401,对应错误码 `40101 未登录` / `40102 Token 已过期` / `40103 Token 已吊销` | |
| 25 | +除 `POST /api/v1/auth/login` 和 `GET /api/v1/companies`(登录页"版本"下拉)之外的全部接口需在请求头携带 `Authorization: Bearer <accessToken>`。Token 由 `/auth/login` 签发,TTL 2 小时,HS256 签名(密钥 `JWT_SECRET` 来自 `.env.local`)。鉴权失败统一返回 `40101 token 无效或已过期` / `40102 用户已被作废或锁定`。详见 docs/04 § 1.6。 | |
| 31 | 26 | |
| 32 | 27 | ### 分页参数 |
| 33 | 28 | |
| 34 | -统一查询入参: | |
| 29 | +请求入参: | |
| 35 | 30 | |
| 36 | -| 字段 | 类型 | 必填 | 默认 | 说明 | | |
| 31 | +| 参数 | 类型 | 必填 | 默认 | 说明 | | |
| 37 | 32 | |---|---|---|---|---| |
| 38 | -| `pageNum` | int | 否 | 1 | 当前页码,从 1 开始 | | |
| 39 | -| `pageSize` | int | 否 | 20 | 每页条数;上限 100 | | |
| 40 | -| `orderBy` | string | 否 | `tCreateDate DESC` | 排序表达式,列名 + 方向 | | |
| 33 | +| `page` | int | 否 | 1 | 页码,从 1 开始 | | |
| 34 | +| `size` | int | 否 | 20 | 每页条数,上限 100 | | |
| 35 | +| `sortField` | string | 否 | — | 排序字段,白名单内 | | |
| 36 | +| `sortOrder` | string | 否 | `desc` | `asc` / `desc` | | |
| 41 | 37 | |
| 42 | -响应包装: | |
| 38 | +响应 `data` 结构 `PageResult<T>`: | |
| 43 | 39 | |
| 44 | 40 | ```json |
| 45 | 41 | { |
| 46 | - "code": 200, "message": "ok", | |
| 47 | - "data": { | |
| 48 | - "records": [ ... ], | |
| 49 | - "total": 1234, | |
| 50 | - "pageNum": 1, | |
| 51 | - "pageSize": 20 | |
| 52 | - }, | |
| 53 | - "timestamp": 1700000000000 | |
| 42 | + "records": [], | |
| 43 | + "total": 0, | |
| 44 | + "page": 1, | |
| 45 | + "size": 20 | |
| 54 | 46 | } |
| 55 | 47 | ``` |
| 56 | 48 | |
| 57 | 49 | ## 接口清单 |
| 58 | 50 | (各模块接口段落见下方,由 `downstream-gen` 按 REQ 填入) |
| 59 | 51 | |
| 60 | -## module_usr — USR 用户管理 | |
| 52 | +## module_usr 用户管理 | |
| 61 | 53 | |
| 62 | 54 | ### REQ-USR-001 用户登录 |
| 63 | 55 | |
| 64 | 56 | - **Method**: POST |
| 65 | -- **Path**: `/api/usr/auth/login` | |
| 66 | -- **Auth**: 公开(无需 JWT) | |
| 67 | -- **请求**: | |
| 68 | - ```json | |
| 69 | - { | |
| 70 | - "userName": "admin", | |
| 71 | - "password": "P@ssw0rd", | |
| 72 | - "companyVersion": "STANDARD" | |
| 73 | - } | |
| 74 | - ``` | |
| 75 | - - `userName` (string, 必填, 3-50 位) | |
| 76 | - - `password` (string, 必填, 明文传输,由 TLS 保证机密性;后端用 BCrypt/Argon2 比对哈希) | |
| 77 | - - `companyVersion` (string, 必填, 枚举 `STANDARD` / `PRO` / `FLAGSHIP`) | |
| 78 | -- **响应**: | |
| 79 | - ```json | |
| 80 | - { | |
| 81 | - "code": 200, "message": "ok", | |
| 82 | - "data": { | |
| 83 | - "accessToken": "eyJ...", | |
| 84 | - "refreshToken": "eyJ...", | |
| 85 | - "expiresIn": 28800, | |
| 86 | - "user": { | |
| 87 | - "iIncrement": 1, | |
| 88 | - "sUserName": "admin", | |
| 89 | - "sUserType": "SUPER_ADMIN", | |
| 90 | - "sLanguage": "zh-CN", | |
| 91 | - "permissionCodes": ["USR:ADD", "USR:EDIT", "USR:DELETE", "USR:VIEW"] | |
| 92 | - } | |
| 93 | - }, | |
| 94 | - "timestamp": 1700000000000 | |
| 95 | - } | |
| 96 | - ``` | |
| 57 | +- **Path**: `/api/v1/auth/login` | |
| 58 | +- **Auth**: 无需(公开接口) | |
| 59 | +- **请求**: JSON body `LoginReq { username: string (3-20), password: string (8-20, 明文,HTTPS 传输), companyCode: string (sys_company.sCompanyCode) }` | |
| 60 | +- **响应**: JSON `LoginVo { accessToken: string (JWT), tokenType: "Bearer", expiresInSec: 7200, userInfo: { userId: int, username: string, userType: "NORMAL"|"SUPER_ADMIN", language: string, employeeName?: string, companyCode: string } }` | |
| 97 | 61 | |
| 98 | 62 | #### 错误码 |
| 99 | -- `40001` — 用户名或密码错误(不区分两者,防爆破) | |
| 100 | -- `40301` — 账号已锁定,请稍后再试 | |
| 101 | -- `40302` — 账号已作废 | |
| 102 | -- `40010` — 参数校验失败(用户名 / 密码 / 版本字段为空或格式不合法) | |
| 103 | - | |
| 104 | ---- | |
| 63 | +- `40001` — 用户名或密码格式错误 | |
| 64 | +- `40101` — 用户名或密码错误(统一文案,不区分两者) | |
| 65 | +- `40103` — 账号已被作废,禁止登录 | |
| 66 | +- `42301` — 账号已锁定,请稍后再试(HTTP 423;锁定剩余时间见 `data.lockUntil`) | |
| 67 | +- `40004` — 公司不存在或已删除 | |
| 105 | 68 | |
| 106 | 69 | ### REQ-USR-002 新增用户 |
| 107 | 70 | |
| 108 | 71 | - **Method**: POST |
| 109 | -- **Path**: `/api/usr/users` | |
| 110 | -- **Auth**: JWT;要求权限码 `USR:ADD` | |
| 111 | -- **请求**: | |
| 112 | - ```json | |
| 113 | - { | |
| 114 | - "userNo": "U10001", | |
| 115 | - "userName": "zhangsan", | |
| 116 | - "employeeId": 12, | |
| 117 | - "userType": "NORMAL", | |
| 118 | - "language": "zh-CN", | |
| 119 | - "modifyDoc": false, | |
| 120 | - "permissionIds": [3, 5, 8] | |
| 121 | - } | |
| 122 | - ``` | |
| 123 | - - `userNo` / `userName` 必填且系统内唯一 | |
| 124 | - - `employeeId` 可空(非员工账号) | |
| 125 | - - `userType` 枚举 `NORMAL` / `SUPER_ADMIN`(仅超级管理员可创建超级管理员) | |
| 126 | - - `language` 枚举 `zh-CN` / `en-US` / `zh-TW` | |
| 127 | - - `permissionIds` 权限分类 ID 数组,可为空 | |
| 128 | - - 初始密码后端固定生成(`666666`),不在请求体中 | |
| 129 | -- **响应**: | |
| 130 | - ```json | |
| 131 | - { "code": 200, "message": "ok", "data": { "userNo": "U10001", "iIncrement": 42 }, "timestamp": 1700000000000 } | |
| 132 | - ``` | |
| 72 | +- **Path**: `/api/v1/users` | |
| 73 | +- **Auth**: Bearer Token;仅 `userType=SUPER_ADMIN` 可调用 | |
| 74 | +- **请求**: JSON body `CreateUserReq { username: string (3-20), userCode: string, password: string (8-20 含大小写字母和数字), userType: "NORMAL"|"SUPER_ADMIN", language: "zh-CN"|"en-US"|"zh-TW", canEditDocument: boolean, employeeId?: int, permissionCategoryIds: int[] }` | |
| 75 | +- **响应**: JSON `UserVo { userId: int, username: string }`(HTTP 201) | |
| 133 | 76 | |
| 134 | 77 | #### 错误码 |
| 135 | -- `40001` — 用户名 / 用户号已存在 | |
| 136 | -- `40010` — 参数校验失败 | |
| 137 | -- `40310` — 普通用户无权创建超级管理员 | |
| 138 | -- `40411` — 关联职员不存在 | |
| 139 | -- `40412` — 关联权限分类不存在 | |
| 140 | - | |
| 141 | ---- | |
| 78 | +- `40001` — 必填字段缺失或格式错误 | |
| 79 | +- `40002` — 密码强度不满足(少于 8 位 / 缺大小写字母 / 缺数字) | |
| 80 | +- `40301` — 当前用户非超级管理员,无权调用 | |
| 81 | +- `40901` — 用户名已存在 | |
| 82 | +- `40902` — 用户号已存在 | |
| 83 | +- `40004` — 指定的员工 / 权限分类不存在 | |
| 142 | 84 | |
| 143 | 85 | ### REQ-USR-003 修改用户 |
| 144 | 86 | |
| 145 | 87 | - **Method**: PUT |
| 146 | -- **Path**: `/api/usr/users/{iIncrement}` | |
| 147 | -- **Auth**: JWT;要求权限码 `USR:EDIT` | |
| 148 | -- **请求**: | |
| 149 | - ```json | |
| 150 | - { | |
| 151 | - "employeeId": 12, | |
| 152 | - "userType": "NORMAL", | |
| 153 | - "language": "en-US", | |
| 154 | - "modifyDoc": true, | |
| 155 | - "void": false, | |
| 156 | - "permissionIds": [3, 5] | |
| 157 | - } | |
| 158 | - ``` | |
| 159 | - - `userName` 不可修改,故请求体不包含 | |
| 160 | - - `void = true` 等价于禁用(软删除) | |
| 161 | - - `permissionIds` 全量覆盖(先 DELETE 后 INSERT,事务内) | |
| 162 | -- **响应**: | |
| 163 | - ```json | |
| 164 | - { "code": 200, "message": "ok", "data": { "iIncrement": 42 }, "timestamp": 1700000000000 } | |
| 165 | - ``` | |
| 88 | +- **Path**: `/api/v1/users/{userId}` | |
| 89 | +- **Auth**: Bearer Token;仅 `userType=SUPER_ADMIN` 可调用 | |
| 90 | +- **请求**: Path `userId: int`;JSON body `UpdateUserReq { userCode?: string, userType?: "NORMAL"|"SUPER_ADMIN", language?: "zh-CN"|"en-US"|"zh-TW", canEditDocument?: boolean, employeeId?: int|null, isDeleted?: boolean, permissionCategoryIds?: int[] }`(username 与 password 字段不接受) | |
| 91 | +- **响应**: JSON `UserDetailVo { userId, username, userCode, userType, language, canEditDocument, isDeleted, employeeId, employeeName, permissionCategoryIds, updatedBy, updatedDate }` | |
| 166 | 92 | |
| 167 | 93 | #### 错误码 |
| 168 | -- `40404` — 目标用户不存在 | |
| 169 | -- `40310` — 不可修改超级管理员(非超级管理员调用) | |
| 170 | -- `40311` — 不可禁用 / 修改自己 | |
| 171 | -- `40411` — 关联职员不存在 | |
| 172 | -- `40412` — 关联权限分类不存在 | |
| 173 | -- `40010` — 参数校验失败 | |
| 174 | - | |
| 175 | ---- | |
| 94 | +- `40001` — 字段格式错误或试图修改 username / password | |
| 95 | +- `40301` — 当前用户非超级管理员,无权调用 | |
| 96 | +- `40302` — 试图停用当前登录用户自己 | |
| 97 | +- `40401` — 用户不存在 | |
| 98 | +- `40902` — 用户号已被占用 | |
| 99 | +- `40004` — 指定的员工 / 权限分类不存在 | |
| 176 | 100 | |
| 177 | 101 | ### REQ-USR-004 查询用户 |
| 178 | 102 | |
| 179 | 103 | - **Method**: GET |
| 180 | -- **Path**: `/api/usr/users` | |
| 181 | -- **Auth**: JWT;要求权限码 `USR:VIEW` | |
| 182 | -- **请求**: query string | |
| 183 | - - `searchField` 枚举 `userName` / `employeeName` / `userNo` / `departmentName` / `userType` / `void` / `lastLoginDate` / `creator` | |
| 184 | - - `matchMode` 枚举 `CONTAINS` / `NOT_CONTAINS` / `EQUALS` | |
| 185 | - - `searchValue` 字符串,空则不过滤 | |
| 186 | - - `includeVoid` 布尔,默认 false(不返回已作废用户) | |
| 187 | - - `pageNum` / `pageSize` / `orderBy` 通用分页参数 | |
| 188 | -- **响应**: 分页结构,`records[]` 元素: | |
| 189 | - ```json | |
| 190 | - { | |
| 191 | - "iIncrement": 42, | |
| 192 | - "sUserName": "zhangsan", | |
| 193 | - "employeeName": "张三", | |
| 194 | - "sUserNo": "U10001", | |
| 195 | - "departmentName": "技术部", | |
| 196 | - "sUserType": "NORMAL", | |
| 197 | - "sLanguage": "zh-CN", | |
| 198 | - "bVoid": false, | |
| 199 | - "tLastLoginDate": "2026-05-13 09:12:33", | |
| 200 | - "sCreator": "admin", | |
| 201 | - "tCreateDate": "2025-12-01 10:00:00" | |
| 202 | - } | |
| 203 | - ``` | |
| 204 | - - 不返回 `sPasswordHash` / `iLoginFailCount` / `tLockUntil` | |
| 104 | +- **Path**: `/api/v1/users` | |
| 105 | +- **Auth**: Bearer Token;仅 `userType=SUPER_ADMIN` 可调用 | |
| 106 | +- **请求**: Query 参数 `page=1&size=20&sortField=tCreateDate&sortOrder=desc&queryField=username&matchMode=contains&queryValue=张&userType=NORMAL&isDeleted=false` | |
| 107 | + - `queryField`: 枚举 `username|employeeName|userCode|departmentName|userType|isDeleted|lastLoginDate|createdBy` | |
| 108 | + - `matchMode`: 枚举 `contains|notContains|equals`,缺省 `contains` | |
| 109 | + - `queryValue`: 字符串,空字符串表示不限 | |
| 110 | +- **响应**: `PageResult<UserListItemVo>`,`UserListItemVo { userId, username, employeeName, userCode, departmentName, userType, language, isDeleted, lastLoginDate, createdBy, createdDate }`(密码字段不返回) | |
| 205 | 111 | |
| 206 | 112 | #### 错误码 |
| 207 | -- `40010` — 参数校验失败(`pageSize > 100` / `matchMode` 不合法 / `searchField` 不合法) | |
| 113 | +- `40001` — 分页参数越界或类型错误 | |
| 114 | +- `40003` — `queryField` / `matchMode` 不在白名单 | |
| 115 | +- `40301` — 当前用户非超级管理员,无权调用 | ... | ... |
docs/06-UI交互规范.md
| ... | ... | @@ -6,88 +6,95 @@ |
| 6 | 6 | |
| 7 | 7 | ### 1.1 操作反馈 |
| 8 | 8 | |
| 9 | -- **成功提示**:使用 Ant Design `message.success()`,默认 3s 自动消失;新增 / 修改 / 删除等写操作必须给反馈。 | |
| 10 | -- **失败提示**:使用 `message.error()` 显示后端返回的 `message` 字段;表单字段级错误用 Form 的 `validateStatus="error"` + `help` 同步显示。 | |
| 11 | -- **危险操作二次确认**:删除 / 禁用 / 重置密码等不可逆操作必须用 `Modal.confirm({ okType: 'danger' })` 二次确认,按钮文案使用业务动词(如「确认禁用」),不用「确定」。 | |
| 12 | -- **长耗时按钮 loading**:提交类按钮在请求未返回前必须置为 `loading=true` 并禁用,避免重复提交;查询类操作在表格组件上启用 `loading` 属性即可。 | |
| 9 | +- **成功提示**:使用 Ant Design `message.success`,3 秒自动消失;不阻塞操作。 | |
| 10 | +- **失败提示**:使用 `message.error` 显示后端 `Result.message`;4.5 秒消失;保留可复制。 | |
| 11 | +- **危险操作**(删除 / 停用 / 重置密码):必须 `Modal.confirm` 二次确认,标题写明动作,正文写明影响对象 + 不可逆性。 | |
| 12 | +- **长耗时按钮**(>300ms):按钮 `loading` 态 + 禁用,避免重复提交;提交完成后恢复。 | |
| 13 | +- **表单校验失败**:字段下方红字提示;首个错误字段自动聚焦并滚入视口。 | |
| 13 | 14 | |
| 14 | 15 | ### 1.2 数据展示 |
| 15 | 16 | |
| 16 | -- **空状态**:列表 / 表格无数据时使用 `<Empty description="暂无数据" />`,明细页无数据时附加「返回列表」按钮。 | |
| 17 | -- **加载状态**:页面级加载用 `<Spin spinning>` 包裹主区域;表格 / 卡片局部加载用组件自带 `loading` 属性。 | |
| 18 | -- **异常状态**:接口 5xx 或网络异常时显示 `<Result status="error" title="加载失败" extra={<Button>重试</Button>} />`;403 时显示「无权限」并提供「返回首页」按钮。 | |
| 17 | +- **空状态**:使用 `Empty` 组件,图标 + 一句话说明(如 `暂无用户`)+ 主操作按钮(如 `新增用户`)。 | |
| 18 | +- **加载中**:列表用 `Skeleton`;单组件用 `Spin`;全屏首次加载允许使用 `Spin` 覆盖。 | |
| 19 | +- **异常态**:网络 / 5xx 错误使用 `Result status="error"` 显示,附 `重试` 按钮。 | |
| 20 | +- **分页**:默认 20 条 / 页,可选 10 / 20 / 50 / 100;显示总数 + 当前页 / 总页数。 | |
| 21 | +- **表格列**:必显列固定在左侧;操作列固定在右侧;时间列统一格式 `YYYY-MM-DD HH:mm`。 | |
| 19 | 22 | |
| 20 | 23 | ### 1.3 权限控制(前端) |
| 21 | 24 | |
| 22 | -- **菜单级**:登录后由后端返回菜单权限码列表,前端 Layout 按权限码过滤后渲染左侧菜单。 | |
| 23 | -- **按钮级**:包装 `<AuthButton code="USR:ADD">新增</AuthButton>`,权限码不在用户列表时直接不渲染(避免显示再禁用)。 | |
| 24 | -- **路由级**:在 React Router 外层包 `<AuthRoute />`,路由元数据声明 `meta.code`,无权限重定向 403 页面。 | |
| 25 | -- **关联后端 RBAC**:权限码格式 `<模块代码>:<动作>`(如 `USR:ADD` / `USR:EDIT` / `USR:DELETE`),与后端 Spring Security `@PreAuthorize("hasAuthority('USR:ADD')")` 一一对应。 | |
| 25 | +- **菜单级**:Redux `auth.user.permissions` 驱动;菜单项无权限直接不渲染。 | |
| 26 | +- **路由级**:`router/index.tsx` 用 `RequireAuth` + `RequireRole` 高阶守卫;未授权重定向到登录或 403 页。 | |
| 27 | +- **按钮级**:`<Permission code="...">` 包裹按钮,无权限时不渲染(不显示 disabled 灰按钮)。 | |
| 28 | +- **后端 RBAC 同源**:前端权限码与后端 `Permission` 表严格对齐(codename 一致);前端只做展示控制,最终鉴权由后端兜底。 | |
| 26 | 29 | |
| 27 | 30 | ## 二、Design Tokens |
| 28 | 31 | |
| 29 | -> 所有色值统一以 CSS 变量定义于 `src/styles/tokens.css`;命名规范见 docs/04 § 2.5。 | |
| 32 | +> 所有色值统一以 CSS 变量定义于 `frontend/src/styles/tokens.css`;命名规范见 docs/04 § 2.5。组件样式只引用 `var(--color-xxx)`,禁止硬编码 hex / rgba。 | |
| 30 | 33 | |
| 31 | 34 | ### 2.1 全局调色板 |
| 32 | 35 | |
| 33 | -与 Ant Design 5 主题对齐,统一通过 `<ConfigProvider theme={{ token: {...} }}>` 注入。 | |
| 36 | +与 Ant Design 5 默认主题对齐,按语义分组。 | |
| 34 | 37 | |
| 35 | 38 | | 语义 | 变量名 | 默认值 | 用途 | |
| 36 | 39 | |---|---|---|---| |
| 37 | -| 主色 | `--color-primary` | `#1677ff` | 主操作按钮 / 链接 / 选中态 | | |
| 38 | -| 成功 | `--color-success` | `#52c41a` | 成功消息 / 启用状态标签 | | |
| 39 | -| 警告 | `--color-warning` | `#faad14` | 警告消息 / 待处理状态 | | |
| 40 | -| 错误 | `--color-error` | `#ff4d4f` | 错误消息 / 危险按钮 / 禁用状态 | | |
| 41 | -| 主文字 | `--color-text-primary` | `rgba(0, 0, 0, 0.88)` | 正文 / 表格内容 | | |
| 42 | -| 次文字 | `--color-text-secondary` | `rgba(0, 0, 0, 0.65)` | 辅助说明 / 占位 | | |
| 43 | -| 边框 | `--color-border` | `#d9d9d9` | 表单输入 / 卡片边界 | | |
| 44 | -| 背景 | `--color-bg-base` | `#ffffff` | 页面主背景 | | |
| 45 | -| 弱背景 | `--color-bg-layout` | `#f5f5f5` | 页面 layout 间隙 / 灰底 | | |
| 40 | +| 主色 | `--color-primary` | `#1677ff` | 主按钮、链接、激活态边框 | | |
| 41 | +| 主色-悬浮 | `--color-primary-hover` | `#4096ff` | 主色控件 hover | | |
| 42 | +| 主色-激活 | `--color-primary-active` | `#0958d9` | 主色控件 active / pressed | | |
| 43 | +| 成功 | `--color-success` | `#52c41a` | 成功提示、积极状态标签 | | |
| 44 | +| 警告 | `--color-warning` | `#faad14` | 警告提示、需注意状态 | | |
| 45 | +| 错误 | `--color-error` | `#ff4d4f` | 错误提示、危险按钮 | | |
| 46 | +| 信息 | `--color-info` | `#1677ff` | 普通信息提示 | | |
| 47 | +| 主文字 | `--color-text` | `rgba(0,0,0,0.88)` | 标题、正文 | | |
| 48 | +| 次文字 | `--color-text-secondary` | `rgba(0,0,0,0.65)` | 辅助说明 | | |
| 49 | +| 禁用文字 | `--color-text-disabled` | `rgba(0,0,0,0.25)` | 禁用态 | | |
| 50 | +| 边框 | `--color-border` | `#d9d9d9` | 输入框 / 卡片边框 | | |
| 51 | +| 分割线 | `--color-split` | `#f0f0f0` | 列表分割线 | | |
| 52 | +| 背景-页面 | `--color-bg-page` | `#f5f5f5` | 页面底色 | | |
| 53 | +| 背景-容器 | `--color-bg-container` | `#ffffff` | 卡片 / 表单 / Modal | | |
| 54 | +| 背景-禁用 | `--color-bg-disabled` | `#f5f5f5` | 禁用控件 | | |
| 46 | 55 | |
| 47 | 56 | ### 2.2 组件级状态色 |
| 48 | 57 | |
| 49 | -| 序号 | 组件 | 编辑bg | 只读bg | 悬浮bg | 编辑fg | 只读fg | 悬浮fg | 备注 | | |
| 50 | -|---|---|---|---|---|---|---|---|---| | |
| 51 | -| 1 | 表单输入框 | `var(--color-bg-base)` | `var(--color-bg-layout)` | `var(--color-bg-base)` | `var(--color-text-primary)` | `var(--color-text-secondary)` | `var(--color-text-primary)` | 只读态边框使用 `transparent` | | |
| 52 | -| 2 | 下拉单选 | `var(--color-bg-base)` | `var(--color-bg-layout)` | `var(--color-bg-base)` | `var(--color-text-primary)` | `var(--color-text-secondary)` | `var(--color-text-primary)` | — | | |
| 53 | -| 3 | 表格行 | — | `var(--color-bg-base)` | `var(--color-bg-row-hover)` | — | `var(--color-text-primary)` | `var(--color-text-primary)` | 编辑态走 Modal,行内不编辑 | | |
| 54 | -| 4 | 主按钮 | `var(--color-primary)` | `var(--color-bg-layout)` | `var(--color-primary-hover)` | `#ffffff` | `var(--color-text-secondary)` | `#ffffff` | 危险按钮替换 `--color-error` | | |
| 55 | -| 5 | 链接 | — | — | `var(--color-primary-hover)` | `var(--color-primary)` | — | `var(--color-primary-hover)` | — | | |
| 58 | +按场景 × 状态映射,单元格写 token 引用形式(`var(--color-xxx)`)。`—` 表示该状态不适用。 | |
| 56 | 59 | |
| 57 | -**Token 默认值**: | |
| 60 | +| # | 组件 | 编辑bg | 只读bg | 悬浮bg | 编辑fg | 只读fg | 悬浮fg | 备注 | | |
| 61 | +|---|---|---|---|---|---|---|---|---| | |
| 62 | +| 1 | Input | `var(--color-bg-container)` | `var(--color-bg-disabled)` | `var(--color-bg-container)` | `var(--color-text)` | `var(--color-text-secondary)` | `var(--color-text)` | 只读用 `readOnly`,禁用用 `disabled` | | |
| 63 | +| 2 | Select | `var(--color-bg-container)` | `var(--color-bg-disabled)` | `var(--color-bg-container)` | `var(--color-text)` | `var(--color-text-secondary)` | `var(--color-text)` | 同 Input | | |
| 64 | +| 3 | Button-Primary | `var(--color-primary)` | — | `var(--color-primary-hover)` | `#fff` | — | `#fff` | 主操作 | | |
| 65 | +| 4 | Button-Default | `var(--color-bg-container)` | — | `var(--color-bg-container)` | `var(--color-text)` | — | `var(--color-primary)` | 次操作 | | |
| 66 | +| 5 | Button-Danger | `var(--color-error)` | — | `#ff7875` | `#fff` | — | `#fff` | 删除 / 停用 | | |
| 67 | +| 6 | Table-Row | `var(--color-bg-container)` | — | `#fafafa` | `var(--color-text)` | — | `var(--color-text)` | 斑马纹关闭 | | |
| 68 | +| 7 | Tag-Success | `#f6ffed` | — | — | `var(--color-success)` | — | — | 正常 / 启用状态 | | |
| 69 | +| 8 | Tag-Error | `#fff2f0` | — | — | `var(--color-error)` | — | — | 停用 / 失败状态 | | |
| 58 | 70 | |
| 59 | -| Token | 默认值 | | |
| 60 | -|---|---| | |
| 61 | -| `--color-primary-hover` | `#4096ff` | | |
| 62 | -| `--color-bg-row-hover` | `#fafafa` | | |
| 71 | +**Token 默认值**(与 § 2.1 完整一致;新增 token 时此表与 tokens.css 同步更新)。 | |
| 63 | 72 | |
| 64 | 73 | ### 2.3 引用约定 |
| 65 | 74 | |
| 66 | -- 组件样式只用 `var(--color-xxx)`,禁止硬编码 hex / rgba。 | |
| 67 | -- 新增 token 须先登记到 § 2.1 / 2.2 再补 `tokens.css`。 | |
| 68 | -- 修改色值只改 `tokens.css` 一处,不允许组件覆盖。 | |
| 75 | +- 组件样式只用 `var(--color-xxx)`,**禁止**直接写 hex / rgb / rgba。 | |
| 76 | +- 新增 token 必须先登记到 § 2.1 / 2.2,再写入 `tokens.css`。 | |
| 77 | +- 修改色值只改 `tokens.css` 一处;组件层不允许覆盖(如 `.ant-btn-primary { background: ... }` 之类禁止)。 | |
| 69 | 78 | |
| 70 | 79 | ## 三、页面清单 |
| 80 | +(由 `downstream-gen` 按模块追加段落) | |
| 71 | 81 | |
| 72 | -### module_usr USR-用户管理 | |
| 82 | +### module_usr 用户管理 | |
| 73 | 83 | |
| 74 | 84 | - **登录页** (`/login`) |
| 75 | 85 | - 类型: 表单页 |
| 76 | 86 | - 对应 REQ: REQ-USR-001 |
| 77 | - - 入口菜单: 无(未登录态唯一可达页面) | |
| 78 | - - 主要交互: 表单输入 用户名 / 密码 / 版本 → 提交 → 成功跳首页 / 失败留页并显示通用错误消息;账号锁定时显示锁定提示 | |
| 79 | -- **用户列表页** (`/usr/users`) | |
| 87 | + - 入口菜单: 未登录访问任何路径均重定向至此 | |
| 88 | + - 主要交互: 公司/版本下拉(页面加载时拉取 `GET /api/v1/companies`)→ 输入用户名 / 密码(密码星号显示)→ 提交按钮带 loading 态防重复提交;连续 5 次失败显示锁定剩余时间;成功后写 access token 到内存并跳转默认主页 | |
| 89 | + | |
| 90 | +- **用户列表** (`/users`) | |
| 80 | 91 | - 类型: 列表页 |
| 81 | - - 对应 REQ: REQ-USR-004 | |
| 82 | - - 入口菜单: 系统管理 → 用户管理 → 用户列表 | |
| 83 | - - 主要交互: 查询字段 + 匹配方式 + 查询值 三段筛选 → 表格分页展示 → 行内「编辑」「禁用 / 启用」按钮(权限码 `USR:EDIT`);顶部「新增用户」按钮(权限码 `USR:ADD`)跳新增表单 | |
| 84 | -- **用户新增页** (`/usr/users/new`) | |
| 85 | - - 类型: 表单页 | |
| 86 | - - 对应 REQ: REQ-USR-002 | |
| 87 | - - 入口菜单: 系统管理 → 用户管理 → 用户列表 → 新增按钮 | |
| 88 | - - 主要交互: 员工名(下拉单选)选择后自动回填用户号 / 用户名;类型 / 语言下拉;权限组表格(多选);提交校验失败显示字段级 `validateStatus="error"`;成功 message.success 后回列表页 | |
| 89 | -- **用户编辑页** (`/usr/users/:id/edit`) | |
| 90 | - - 类型: 表单页 | |
| 91 | - - 对应 REQ: REQ-USR-003 | |
| 92 | - - 入口菜单: 用户列表 → 行内「编辑」按钮 | |
| 93 | - - 主要交互: 复用新增表单组件;用户名只读;提供「重置密码」按钮(二次确认);提供「禁用 / 启用」开关;禁用自己时按钮置灰 | |
| 92 | + - 对应 REQ: REQ-USR-002("新增"入口)、REQ-USR-003("编辑"行内按钮)、REQ-USR-004(列表分页 + 筛选) | |
| 93 | + - 入口菜单: 系统管理 → 用户管理 | |
| 94 | + - 主要交互: 顶部筛选区(查询字段下拉 / 匹配方式下拉 / 查询值输入框 / 查询按钮)+ 右上"新增用户"按钮 + Ant Design Table(含序号 / 用户名 / 员工名 / 用户号 / 部门 / 用户类型 / 语言 / 作废标签 / 登录日期 / 制单人 / 制单日期 / 操作列)+ 分页器(10/20/50/100);编辑按钮打开 Modal 复用新增表单组件(按 REQ-USR-003 字段差异置只读 / 禁用);不允许停用当前登录账号自己(按钮 disabled + tooltip 解释) | |
| 95 | + | |
| 96 | +- **用户表单**(嵌入式 Modal,无独立路由) | |
| 97 | + - 类型: 表单页(Modal) | |
| 98 | + - 对应 REQ: REQ-USR-002 / REQ-USR-003 | |
| 99 | + - 入口菜单: 由用户列表的"新增" / "编辑"按钮触发 | |
| 100 | + - 主要交互: 左侧基础信息区(员工名下拉 / 用户号 / 用户名 / 类型下拉 / 语言下拉 / 单据修改权限复选框 / 作废复选框(仅编辑))+ 右侧权限组区(按 `sys_permission_category` 渲染勾选列表)+ 底部"保存"/"取消";保存调 `POST /api/v1/users`(新增)或 `PUT /api/v1/users/{userId}`(编辑),成功后关闭 Modal 并刷新列表 | ... | ... |
docs/07-环境配置.md
| ... | ... | @@ -4,43 +4,46 @@ |
| 4 | 4 | |
| 5 | 5 | | 层 | 依赖 | 版本 | 说明 | |
| 6 | 6 | |---|---|---|---| |
| 7 | -| 运行时 | Java (JDK) | 17 / 21 | Spring Boot 3 推荐版本 | | |
| 8 | -| 运行时 | MySQL | 8.x | 关系数据库 | | |
| 9 | -| 运行时 | Redis | 最新稳定版 | 缓存 / 会话 | | |
| 10 | -| 运行时 | Node.js | 20.x LTS | 前端构建 + 本地 dev server | | |
| 11 | -| 构建(后端) | Maven | 3.9.x | Java 依赖与构建工具 | | |
| 12 | -| 构建(前端) | pnpm | 8.x(或 npm 10.x) | 前端依赖管理 | | |
| 13 | -| 构建(前端) | Vite | 最新稳定版 | 前端开发与打包 | | |
| 14 | -| 容器 | Docker | 最新稳定版 | 容器化部署 | | |
| 15 | -| 容器 | Docker Compose | 最新稳定版 | 本地一键启依赖(MySQL + Redis) | | |
| 16 | -| 反向代理 | Nginx | 最新稳定版 | 前端静态托管 / 反向代理 | | |
| 17 | -| CLI 工具 | git | 2.30+ | 代码版本控制 | | |
| 18 | -| CLI 工具 | mysql client | 8.x | 本地执行 SQL / 验证 migration | | |
| 19 | -| CLI 工具 | glab(可选) | 最新稳定版 | GitLab MR 创建(亦可直接走 Web) | | |
| 7 | +| 运行时 | Java JDK | 17 或 21 | Spring Boot 3.x 运行环境 | | |
| 8 | +| 运行时 | Node.js | 20.x LTS | Vite 5 / React 18 构建运行 | | |
| 9 | +| 运行时 | MySQL | 8.x | 关系型数据库 | | |
| 10 | +| 运行时 | Redis | 最新稳定 | 缓存 / 会话 | | |
| 11 | +| 运行时 | Nginx | 最新稳定 | 反向代理、前端静态托管(生产) | | |
| 12 | +| 构建 | Maven | 3.9.x | 后端依赖管理与构建 | | |
| 13 | +| 构建 | npm(或 pnpm) | 与 Node 配套 | 前端包管理器 | | |
| 14 | +| 构建 | Flyway core + mysql | 10.x | Spring Boot 启动时自动 apply migration | | |
| 15 | +| 构建 | Vite | 最新稳定 | 前端打包 | | |
| 16 | +| 容器 | Docker | 最新稳定 | 镜像构建与本地编排 | | |
| 17 | +| 容器 | docker-compose | 最新稳定 | 本地 mysql / redis 编排 | | |
| 18 | +| CLI 工具 | git | ≥ 2.40 | 版本控制 | | |
| 19 | +| CLI 工具 | mysql client | 8.x | `scripts/setup-test-db.sh` 调用 | | |
| 20 | +| CLI 工具 | curl / jq | 最新稳定 | API 调试、脚本辅助 | | |
| 20 | 21 | |
| 21 | 22 | ## 二、端口约定 |
| 22 | 23 | |
| 23 | 24 | | 服务 | 端口 | 说明 | |
| 24 | 25 | |---|---|---| |
| 25 | -| 后端 HTTP | 8080 | Spring Boot 默认端口(`server.port`) | | |
| 26 | -| 前端 dev server | 5173 | Vite 默认端口(`vite --port`) | | |
| 27 | -| MySQL | 3306 | 默认端口,本地开发可用 Docker Compose 暴露 | | |
| 28 | -| Redis | 6379 | 默认端口 | | |
| 29 | -| Nginx | 80 / 443 | 生产部署反向代理入口 | | |
| 26 | +| 后端 HTTP | 9090 | 项目锁定(避开常见 8080 冲突) | | |
| 27 | +| 前端 dev server | 5173 | Vite 默认 | | |
| 28 | +| MySQL | 3306 | 主库 | | |
| 29 | +| MySQL(测试) | 3307 | 本地独立测试库(避免与开发库冲突,可选) | | |
| 30 | +| Redis | 6379 | 默认 | | |
| 31 | +| Nginx | 80 / 443 | 生产反代 | | |
| 30 | 32 | |
| 31 | 33 | ## 三、环境变量 |
| 32 | 34 | |
| 33 | -运行时凭据(数据库连接、JWT 密钥等)全部放在仓库根的 `.env.local`,不入 git。 | |
| 34 | -字段清单与占位符见该文件,真实值由开发者本地填写。 | |
| 35 | +运行时凭据(数据库连接、JWT 密钥、Redis 密码等)全部放在仓库根的 `.env.local`,**不入 git**(由 `.gitignore` 强制忽略)。 | |
| 36 | + | |
| 37 | +字段清单与占位符见 `.env.local`,真实值由开发者本地填写。CC 不读取 `.env.local` 的真实值;任何引用都用 `${VAR_NAME}` 形式。 | |
| 35 | 38 | |
| 36 | 39 | ## 四、常用命令 |
| 37 | 40 | |
| 38 | 41 | | 命令 | 说明 | |
| 39 | 42 | |---|---| |
| 40 | -| `./mvnw spring-boot:run` | 本地启动后端(含 Flyway 自动 apply migration) | | |
| 41 | -| `pnpm dev` | 本地启动前端 dev server(默认 5173) | | |
| 42 | -| `./mvnw clean package -DskipTests` | 后端打包生成 jar | | |
| 43 | -| `pnpm build` | 前端打包到 `dist/` | | |
| 44 | -| `bash scripts/test.sh` | 执行后端 + 前端测试组合(lint / unit / e2e) | | |
| 45 | -| `bash scripts/setup-test-db.sh` | 重置本地测试数据库(DROP + CREATE + apply V1) | | |
| 46 | -| `glab mr create` | 推送当前分支并创建 GitLab MR(亦可走 Web 端) | | |
| 43 | +| `cd backend && ./mvnw spring-boot:run` | 启动后端服务(监听 8080) | | |
| 44 | +| `cd frontend && npm run dev` | 启动前端 dev server(监听 5173) | | |
| 45 | +| `cd backend && ./mvnw clean package` | 后端打包(生成 jar) | | |
| 46 | +| `cd frontend && npm run build` | 前端打包(生成 dist/) | | |
| 47 | +| `bash scripts/test.sh` | 跑后端 + 前端全部测试(test-gate 入口) | | |
| 48 | +| `bash scripts/setup-test-db.sh` | DROP+CREATE 测试库(Flyway V1 会在测试启动时 apply) | | |
| 49 | +| `glab mr create` 或 `gh pr create` | 创建 MR / PR(mr-create skill 调用) | | ... | ... |
docs/08-模块任务管理.md
docs/09-项目目录结构.md
| ... | ... | @@ -4,93 +4,108 @@ |
| 4 | 4 | |
| 5 | 5 | ``` |
| 6 | 6 | . |
| 7 | -├── CLAUDE.md # 项目级规范与流程指令 | |
| 8 | -├── README.md # 项目说明(可选) | |
| 9 | -├── .env.local # 本地凭据(不入 git) | |
| 7 | +├── CLAUDE.md # Claude Code 主指令 | |
| 8 | +├── README.md # 项目说明 | |
| 9 | +├── .env.local # 本地凭据(不入 git) | |
| 10 | 10 | ├── .gitignore |
| 11 | -├── .githooks/ | |
| 12 | -│ └── pre-push # 推送前自动跑 scripts/test.sh | |
| 13 | -├── scripts/ | |
| 14 | -│ ├── test.sh # 后端 + 前端测试组合入口 | |
| 15 | -│ └── setup-test-db.sh # 重置本地测试数据库 | |
| 11 | +├── .githooks/ # 仓库级 git hooks(core.hooksPath 指向) | |
| 12 | +│ └── pre-push | |
| 13 | +├── scripts/ # 项目脚本(test.sh / setup-test-db.sh 等) | |
| 14 | +├── docs/ # 全部规划与设计文档 | |
| 15 | +├── prototype/ # 前端 HTML mockup(前端阶段的布局权威) | |
| 16 | 16 | ├── sql/ |
| 17 | -│ └── migrations/ # Flyway V*__*.sql 文件 | |
| 18 | -├── docs/ # 全量项目文档(见 § 四) | |
| 19 | -├── prototype/ # 静态 HTML mockup(前端实现权威) | |
| 20 | -├── backend/ # 后端工程(Spring Boot + Maven) | |
| 21 | -└── frontend/ # 前端工程(Vite + React) | |
| 17 | +│ └── migrations/ # Flyway V_n__*.sql | |
| 18 | +├── backend/ # Spring Boot 服务 | |
| 19 | +│ ├── pom.xml | |
| 20 | +│ └── src/ | |
| 21 | +└── frontend/ # Vite + React 应用 | |
| 22 | + ├── package.json | |
| 23 | + ├── vite.config.ts | |
| 24 | + └── src/ | |
| 22 | 25 | ``` |
| 23 | 26 | |
| 24 | 27 | ## 二、后端目录 |
| 25 | 28 | |
| 29 | +后端按 Spring Boot 3 + MyBatis-Plus 的惯例组织;业务代码按 docs/01 模块索引落到 `module/<module_code_lower>/`。 | |
| 30 | + | |
| 26 | 31 | ``` |
| 27 | 32 | backend/ |
| 28 | 33 | ├── pom.xml |
| 29 | -└── src/ | |
| 30 | - ├── main/ | |
| 31 | - │ ├── java/ | |
| 32 | - │ │ └── com.example.erp/ | |
| 33 | - │ │ ├── ErpApplication.java | |
| 34 | - │ │ ├── common/ # 全局响应 / 异常 / 拦截器 / 工具 | |
| 35 | - │ │ │ ├── result/ | |
| 36 | - │ │ │ ├── exception/ | |
| 37 | - │ │ │ ├── interceptor/ | |
| 38 | - │ │ │ └── util/ | |
| 39 | - │ │ ├── config/ # Spring 配置类(Security / Redis / MyBatis-Plus / Swagger / Activiti) | |
| 40 | - │ │ ├── module/ # 业务模块(按 docs/01 索引拆分) | |
| 41 | - │ │ │ └── usr/ # USR 用户管理 | |
| 42 | - │ │ │ ├── controller/ | |
| 43 | - │ │ │ ├── service/ | |
| 44 | - │ │ │ │ └── impl/ | |
| 45 | - │ │ │ ├── mapper/ | |
| 46 | - │ │ │ ├── entity/ | |
| 47 | - │ │ │ ├── dto/ | |
| 48 | - │ │ │ └── vo/ | |
| 49 | - │ │ └── security/ # 认证 / 鉴权 / JWT | |
| 50 | - │ └── resources/ | |
| 51 | - │ ├── application.yml | |
| 52 | - │ ├── application-dev.yml | |
| 53 | - │ ├── application-prod.yml | |
| 54 | - │ ├── mapper/ # MyBatis XML(按模块再分子目录) | |
| 55 | - │ └── logback-spring.xml | |
| 56 | - └── test/ | |
| 57 | - └── java/ | |
| 58 | - └── com.example.erp/ | |
| 59 | - └── module/ | |
| 60 | - └── usr/ # 与 main/ 镜像 | |
| 34 | +├── src/ | |
| 35 | +│ ├── main/ | |
| 36 | +│ │ ├── java/ | |
| 37 | +│ │ │ └── com/xly/erp/ | |
| 38 | +│ │ │ ├── Application.java # Spring Boot 启动类 | |
| 39 | +│ │ │ ├── common/ # 跨模块基础组件 | |
| 40 | +│ │ │ │ ├── response/ # 统一响应包装(Result / PageResult) | |
| 41 | +│ │ │ │ ├── exception/ # 全局异常处理 + 业务异常 | |
| 42 | +│ │ │ │ ├── security/ # Spring Security / JWT 配置 | |
| 43 | +│ │ │ │ ├── config/ # 通用配置(CORS / Swagger / MyBatis-Plus 等) | |
| 44 | +│ │ │ │ └── util/ # 工具类 | |
| 45 | +│ │ │ └── module/ | |
| 46 | +│ │ │ └── usr/ # 用户管理(REQ-USR-*) | |
| 47 | +│ │ │ ├── controller/ | |
| 48 | +│ │ │ ├── service/ | |
| 49 | +│ │ │ ├── service/impl/ | |
| 50 | +│ │ │ ├── mapper/ | |
| 51 | +│ │ │ ├── entity/ | |
| 52 | +│ │ │ ├── dto/ # Request DTO | |
| 53 | +│ │ │ ├── vo/ # Response VO | |
| 54 | +│ │ │ └── converter/ # MapStruct DTO/VO/Entity 转换 | |
| 55 | +│ │ └── resources/ | |
| 56 | +│ │ ├── application.yml # 主配置 | |
| 57 | +│ │ ├── application-dev.yml # 开发环境 | |
| 58 | +│ │ ├── application-test.yml # 测试环境 | |
| 59 | +│ │ ├── mapper/ # MyBatis XML(与 module 子目录对应) | |
| 60 | +│ │ │ └── usr/ | |
| 61 | +│ │ └── logback-spring.xml | |
| 62 | +│ └── test/ | |
| 63 | +│ └── java/ | |
| 64 | +│ └── com/xly/erp/ | |
| 65 | +│ └── module/ | |
| 66 | +│ └── usr/ # 单元测试 + 集成测试 | |
| 67 | +└── target/ # Maven 构建产物(gitignore) | |
| 61 | 68 | ``` |
| 62 | 69 | |
| 70 | +> Flyway migration 不在 backend/ 下,统一在仓库根 `sql/migrations/`,由 Spring Boot 启动时自动 apply(见 docs/04 § Schema 演化规约)。 | |
| 71 | + | |
| 63 | 72 | ## 三、前端目录 |
| 64 | 73 | |
| 74 | +前端按 Vite + React 18 + Ant Design 5 + Redux Toolkit + React Router v6 的惯例组织;业务页面按模块分组放在 `pages/<module>/`。 | |
| 75 | + | |
| 65 | 76 | ``` |
| 66 | 77 | frontend/ |
| 67 | 78 | ├── package.json |
| 68 | 79 | ├── vite.config.ts |
| 69 | 80 | ├── tsconfig.json |
| 70 | 81 | ├── index.html |
| 71 | -└── src/ | |
| 72 | - ├── main.tsx # 入口(挂载 React + Router + Store + ConfigProvider) | |
| 73 | - ├── App.tsx # 顶层路由 + 全局 Layout | |
| 74 | - ├── api/ # Axios 实例 + 各模块 API(数据访问统一入口) | |
| 75 | - │ └── usr.ts # USR 用户管理 API | |
| 76 | - ├── components/ # 跨页面通用组件(AuthButton / AppTable / PageHeader 等) | |
| 77 | - ├── pages/ # 按业务模块组织页面 | |
| 78 | - │ └── usr/ # USR 用户管理 | |
| 79 | - │ ├── UserList.tsx | |
| 80 | - │ ├── UserEdit.tsx | |
| 81 | - │ └── Login.tsx | |
| 82 | - ├── store/ # Redux Toolkit slices(全局状态) | |
| 83 | - │ └── slices/ | |
| 84 | - ├── hooks/ # 自定义 hook(useAuth / usePagination 等) | |
| 85 | - ├── utils/ # 工具函数(formatDate / formatMoney / regex 等) | |
| 86 | - ├── styles/ | |
| 87 | - │ ├── tokens.css # Design Tokens(docs/06 § 二) | |
| 88 | - │ └── global.css # 全局样式 reset / typography | |
| 89 | - ├── router/ # 路由表(含 meta.code 权限码) | |
| 90 | - └── assets/ # 静态资源(图片 / 字体) | |
| 82 | +├── src/ | |
| 83 | +│ ├── main.tsx # 入口 | |
| 84 | +│ ├── App.tsx # 根组件 + 路由 | |
| 85 | +│ ├── api/ # Axios 实例 + 接口调用层 | |
| 86 | +│ │ ├── client.ts # axios 实例 + 拦截器(401 / 错误统一处理) | |
| 87 | +│ │ └── usr.ts # 用户管理接口(按模块拆分) | |
| 88 | +│ ├── store/ # Redux Toolkit | |
| 89 | +│ │ ├── index.ts # configureStore | |
| 90 | +│ │ └── slices/ | |
| 91 | +│ │ └── auth.ts # 登录态 slice | |
| 92 | +│ ├── router/ # React Router 配置 | |
| 93 | +│ │ └── index.tsx | |
| 94 | +│ ├── pages/ # 页面(按模块分组) | |
| 95 | +│ │ ├── login/ # FE-NN 登录页 | |
| 96 | +│ │ └── usr/ # 用户管理页面 | |
| 97 | +│ ├── components/ # 跨页面通用组件 | |
| 98 | +│ ├── layouts/ # 框架布局(侧栏 / 顶栏 / 内容区) | |
| 99 | +│ ├── hooks/ # 自定义 hook | |
| 100 | +│ ├── utils/ # 工具方法 | |
| 101 | +│ ├── styles/ | |
| 102 | +│ │ └── tokens.css # Design Tokens(docs/06 § 二) | |
| 103 | +│ └── types/ # 全局 TypeScript 类型 | |
| 104 | +├── tests/ # Playwright E2E | |
| 105 | +└── dist/ # Vite 构建产物(gitignore) | |
| 91 | 106 | ``` |
| 92 | 107 | |
| 93 | -> 注:仓库根 `src/styles/tokens.css` 由 skeleton-gen 创建,作为 Design Tokens 的「上游」源;前端工程化前可先保留在根 `src/styles/` 下,前端工程初始化时迁入 `frontend/src/styles/`(同名同内容)。 | |
| 108 | +> Vitest 组件测试与对应源码同级(`*.test.tsx`)。 | |
| 94 | 109 | |
| 95 | 110 | ## 四、docs/ 结构 |
| 96 | 111 | |
| ... | ... | @@ -111,14 +126,37 @@ docs/ |
| 111 | 126 | |
| 112 | 127 | ## 五、命名与放置约定 |
| 113 | 128 | |
| 114 | -- **后端根包**:`com.example.erp`,下文统称 `<ROOT>`;新增业务模块时落到 `<ROOT>.module.<模块代码小写>`(如 `<ROOT>.module.usr`)。 | |
| 115 | -- **Controller**:`<ROOT>.module.<m>.controller.<Module>Controller`,文件名首字母大写驼峰,URI 前缀 `/api/<模块代码小写>`。 | |
| 116 | -- **Service**:接口 `<Module>Service` + 实现 `<Module>ServiceImpl`,实现类放 `service/impl/`。 | |
| 117 | -- **Mapper**:`<Module>Mapper`(Java 接口)+ `resources/mapper/<m>/<Module>Mapper.xml`(XML 同名)。 | |
| 118 | -- **Entity / DTO / VO**: | |
| 119 | - - `entity/` 数据库实体(与表 1:1,字段类型同 docs/03) | |
| 120 | - - `dto/` 入参(前端 → 后端的请求体) | |
| 121 | - - `vo/` 出参(后端 → 前端的响应体) | |
| 122 | -- **前端组件**:通用组件放 `frontend/src/components/`,文件名首字母大写驼峰(`AuthButton.tsx`);样式同名 `.module.css`(启用 CSS Modules)。 | |
| 123 | -- **前端页面**:放 `frontend/src/pages/<模块代码小写>/`,文件名按业务功能命名(`UserList.tsx` / `UserEdit.tsx`),路由 path 前缀 `/<模块代码小写>`。 | |
| 124 | -- **API 客户端**:每个模块一个文件 `frontend/src/api/<模块代码小写>.ts`,导出该模块所有接口函数;禁止在组件里直接 `axios.xxx`。 | |
| 129 | +### 5.1 根包 / 命名空间 | |
| 130 | + | |
| 131 | +- **Java 根包**: `com.xly.erp` | |
| 132 | +- **前端 npm 包名**: `xly-erp-frontend` | |
| 133 | + | |
| 134 | +### 5.2 后端文件放置 | |
| 135 | + | |
| 136 | +| 类型 | 路径 | 命名 | | |
| 137 | +|---|---|---| | |
| 138 | +| Controller | `module/<m>/controller/<Entity>Controller.java` | 大驼峰 + `Controller` 后缀 | | |
| 139 | +| Service 接口 | `module/<m>/service/<Entity>Service.java` | 大驼峰 + `Service` 后缀 | | |
| 140 | +| Service 实现 | `module/<m>/service/impl/<Entity>ServiceImpl.java` | 大驼峰 + `ServiceImpl` 后缀 | | |
| 141 | +| Mapper 接口 | `module/<m>/mapper/<Entity>Mapper.java` | 大驼峰 + `Mapper` 后缀 | | |
| 142 | +| Mapper XML | `resources/mapper/<m>/<Entity>Mapper.xml` | 与接口同名 | | |
| 143 | +| Entity | `module/<m>/entity/<Entity>.java` | 大驼峰,业务实体名 | | |
| 144 | +| DTO(请求) | `module/<m>/dto/<Action><Entity>Req.java` | 动词 + 实体 + `Req`,如 `CreateUserReq` | | |
| 145 | +| VO(响应) | `module/<m>/vo/<Entity>Vo.java` 或 `<Entity>DetailVo.java` | 大驼峰 + `Vo` 后缀 | | |
| 146 | +| Converter | `module/<m>/converter/<Entity>Converter.java` | MapStruct 接口 | | |
| 147 | + | |
| 148 | +### 5.3 前端文件放置 | |
| 149 | + | |
| 150 | +| 类型 | 路径 | 命名 | | |
| 151 | +|---|---|---| | |
| 152 | +| 页面 | `pages/<module>/<PageName>.tsx` | 大驼峰,与路由对齐 | | |
| 153 | +| 通用组件 | `components/<ComponentName>/index.tsx` | 大驼峰 | | |
| 154 | +| 接口调用 | `api/<module>.ts` | 小写模块名 | | |
| 155 | +| Redux slice | `store/slices/<feature>.ts` | 小驼峰 | | |
| 156 | +| 测试 | 与源文件同级 `<X>.test.tsx`(组件) / `tests/<X>.spec.ts`(E2E) | 与被测对象对齐 | | |
| 157 | + | |
| 158 | +### 5.4 通用规则 | |
| 159 | + | |
| 160 | +- 所有 Java 包名小写;类名大驼峰;方法 / 字段小驼峰;常量全大写下划线。 | |
| 161 | +- TypeScript / TSX 文件用大驼峰命名组件,其他用小驼峰。 | |
| 162 | +- 业务模块代码与 docs/01 模块代码(USR / PUR / ...)保持一致,小写下划线作为目录名(`module/usr/`)。 | ... | ... |
scripts/test.sh
| ... | ... | @@ -20,19 +20,19 @@ echo "[test.sh] 1/6 setup test db" |
| 20 | 20 | ./scripts/setup-test-db.sh |
| 21 | 21 | |
| 22 | 22 | echo "[test.sh] 2/6 build" |
| 23 | -if [ $HAS_BACKEND -eq 1 ]; then (cd backend && ./mvnw -B -DskipTests package); else echo "[test.sh] skip backend build"; fi | |
| 24 | -if [ $HAS_FRONTEND -eq 1 ]; then (cd frontend && pnpm install --frozen-lockfile && pnpm build); else echo "[test.sh] skip frontend build"; fi | |
| 23 | +if [ $HAS_BACKEND -eq 1 ]; then (cd backend && ./mvnw -B -DskipTests clean package); else echo "[test.sh] skip backend build"; fi | |
| 24 | +if [ $HAS_FRONTEND -eq 1 ]; then (cd frontend && npm ci && npm run build); else echo "[test.sh] skip frontend build"; fi | |
| 25 | 25 | |
| 26 | 26 | echo "[test.sh] 3/6 lint" |
| 27 | -if [ $HAS_BACKEND -eq 1 ]; then (cd backend && ./mvnw -B -q -P lint verify -DskipTests || ./mvnw -B -q checkstyle:check spotbugs:check || :); else echo "[test.sh] skip backend lint"; fi | |
| 28 | -if [ $HAS_FRONTEND -eq 1 ]; then (cd frontend && pnpm lint); else echo "[test.sh] skip frontend lint"; fi | |
| 27 | +if [ $HAS_BACKEND -eq 1 ]; then (cd backend && ./mvnw -B -q checkstyle:check || ./mvnw -B -q spotless:check || echo "[test.sh] backend lint skipped (no plugin configured)"); 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 | 29 | |
| 30 | 30 | echo "[test.sh] 4/6 unit + integration" |
| 31 | 31 | if [ $HAS_BACKEND -eq 1 ]; then (cd backend && ./mvnw -B test); else echo "[test.sh] skip backend test"; fi |
| 32 | -if [ $HAS_FRONTEND -eq 1 ]; then (cd frontend && pnpm test -- --run); else echo "[test.sh] skip frontend test"; fi | |
| 32 | +if [ $HAS_FRONTEND -eq 1 ]; then (cd frontend && npm run test -- --run); else echo "[test.sh] skip frontend test"; fi | |
| 33 | 33 | |
| 34 | 34 | echo "[test.sh] 5/6 E2E" |
| 35 | -if [ $HAS_FRONTEND -eq 1 ] && [ -f frontend/playwright.config.ts ]; then (cd frontend && pnpm exec playwright test); else echo "[test.sh] e2e 略"; fi | |
| 35 | +if [ $HAS_FRONTEND -eq 1 ]; then (cd frontend && npx playwright test); else echo "[test.sh] e2e 略 (no frontend)"; fi | |
| 36 | 36 | |
| 37 | 37 | echo "[test.sh] 6/6 reset test db" |
| 38 | 38 | ./scripts/setup-test-db.sh | ... | ... |
sql/migrations/V1__initial_schema.sql
| 1 | 1 | -- Flyway migration V1 — initial schema for 小羚羊 |
| 2 | --- Generated: 2026-05-14T01:37:50Z | |
| 2 | +-- Generated: 2026-05-14T15:46:57Z | |
| 3 | 3 | -- Source: 由 A4 db-init 从 docs/03-数据库设计文档.md 翻译生成(schema SSoT 是 docs/03) |
| 4 | 4 | -- This is the FIRST migration; subsequent schema changes must be written as new files sql/migrations/V2__<desc>.sql, V3__... etc. |
| 5 | 5 | -- Apply: Flyway runs this automatically at Spring Boot startup. |
| 6 | 6 | -- Do not hand-edit this file after it is committed; write a new migration instead. |
| 7 | 7 | |
| 8 | --- =========================================================================== | |
| 9 | --- t_user — 系统用户主表,承载登录认证与基础属性 | |
| 10 | --- =========================================================================== | |
| 11 | -CREATE TABLE `t_user` ( | |
| 12 | - `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | |
| 13 | - `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', | |
| 14 | - `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '品牌 ID(多租户隔离,标准列)', | |
| 15 | - `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', | |
| 16 | - `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | |
| 17 | - `sUserNo` VARCHAR(50) NOT NULL COMMENT '用户号;关联职员后自动同步员工号;系统内唯一', | |
| 18 | - `sUserName` VARCHAR(50) NOT NULL COMMENT '登录用户名;系统内唯一;3-50 位', | |
| 19 | - `iEmployeeId` INT NULL DEFAULT NULL COMMENT '关联职员 t_employee.iIncrement;可空(非员工账号如系统管理员)', | |
| 20 | - `sPasswordHash` VARCHAR(255) NOT NULL COMMENT '密码哈希(BCrypt / Argon2);禁止明文;初始密码 666666 哈希后存入', | |
| 21 | - `sUserType` VARCHAR(20) NOT NULL DEFAULT 'NORMAL' COMMENT '用户类型枚举:NORMAL(普通用户)/ SUPER_ADMIN(超级管理员)', | |
| 22 | - `sLanguage` VARCHAR(10) NOT NULL DEFAULT 'zh-CN' COMMENT '语言枚举:zh-CN(中文)/ en-US(英文)/ zh-TW(繁体)', | |
| 23 | - `bModifyDoc` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '单据修改权限:0 否 / 1 是', | |
| 24 | - `bVoid` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '作废标记(软删除):0 启用 / 1 已作废', | |
| 25 | - `iLoginFailCount` INT NOT NULL DEFAULT 0 COMMENT '连续登录失败次数;达到阈值触发临时锁定;登录成功后清零', | |
| 26 | - `tLockUntil` DATETIME NULL DEFAULT NULL COMMENT '锁定截止时间;NULL 表示未锁定', | |
| 27 | - `tLastLoginDate` DATETIME NULL DEFAULT NULL COMMENT '最近一次登录时间', | |
| 28 | - `sCreator` VARCHAR(100) NULL DEFAULT NULL COMMENT '制单人(创建该账号的操作员用户名)', | |
| 8 | +-- ============================================================================= | |
| 9 | +-- Table: sys_company — 公司 / 版本字典,登录页下拉选择来源 | |
| 10 | +-- ============================================================================= | |
| 11 | +CREATE TABLE `sys_company` ( | |
| 12 | + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | |
| 13 | + `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', | |
| 14 | + `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '母公司 ID(多租户隔离,标准列)', | |
| 15 | + `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', | |
| 16 | + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | |
| 17 | + `sCompanyName` VARCHAR(100) NOT NULL COMMENT '公司 / 版本名称(登录页下拉显示文本)', | |
| 18 | + `sCompanyCode` VARCHAR(50) NOT NULL COMMENT '公司编码(前端唯一识别)', | |
| 19 | + `iSortOrder` INT NOT NULL DEFAULT 0 COMMENT '下拉列表排序权重,升序', | |
| 20 | + `iIsDeleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记,0=正常 1=已删', | |
| 29 | 21 | PRIMARY KEY (`iIncrement`), |
| 30 | - UNIQUE KEY `uk_user_username` (`sUserName`), | |
| 31 | - UNIQUE KEY `uk_user_userno` (`sUserNo`), | |
| 32 | - KEY `idx_user_employee` (`iEmployeeId`), | |
| 33 | - KEY `idx_user_tenant` (`sBrandsId`, `sSubsidiaryId`), | |
| 34 | - KEY `idx_user_void` (`bVoid`) | |
| 35 | -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统用户主表,承载登录认证与基础属性'; | |
| 36 | - | |
| 37 | --- =========================================================================== | |
| 38 | --- t_employee — 公司职员主档 | |
| 39 | --- =========================================================================== | |
| 40 | -CREATE TABLE `t_employee` ( | |
| 41 | - `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | |
| 42 | - `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', | |
| 43 | - `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '品牌 ID(多租户隔离,标准列)', | |
| 44 | - `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', | |
| 45 | - `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | |
| 46 | - `sEmployeeNo` VARCHAR(50) NOT NULL COMMENT '员工号;系统内唯一', | |
| 47 | - `sName` VARCHAR(100) NOT NULL COMMENT '姓名', | |
| 48 | - `iDepartmentId` INT NULL DEFAULT NULL COMMENT '部门 ID,关联 t_department.iIncrement', | |
| 49 | - `sPhone` VARCHAR(20) NULL DEFAULT NULL COMMENT '手机号', | |
| 50 | - `sEmail` VARCHAR(100) NULL DEFAULT NULL COMMENT '邮箱', | |
| 51 | - `bDisabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否离职:0 在职 / 1 离职', | |
| 22 | + UNIQUE KEY `uk_sys_company_code` (`sCompanyCode`), | |
| 23 | + KEY `idx_sys_company_is_deleted` (`iIsDeleted`, `iSortOrder`) | |
| 24 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='公司 / 版本字典,登录页下拉选择来源'; | |
| 25 | + | |
| 26 | + | |
| 27 | +-- ============================================================================= | |
| 28 | +-- Table: sys_department — 部门字典,职员归属 | |
| 29 | +-- ============================================================================= | |
| 30 | +CREATE TABLE `sys_department` ( | |
| 31 | + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | |
| 32 | + `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', | |
| 33 | + `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '母公司 ID(多租户隔离,标准列)', | |
| 34 | + `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', | |
| 35 | + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | |
| 36 | + `sDepartmentName` VARCHAR(100) NOT NULL COMMENT '部门名称', | |
| 37 | + `sDepartmentCode` VARCHAR(50) NOT NULL COMMENT '部门编码', | |
| 38 | + `iIsDeleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记', | |
| 52 | 39 | PRIMARY KEY (`iIncrement`), |
| 53 | - UNIQUE KEY `uk_employee_no` (`sEmployeeNo`), | |
| 54 | - KEY `idx_employee_dept` (`iDepartmentId`), | |
| 55 | - KEY `idx_employee_name` (`sName`), | |
| 56 | - KEY `idx_employee_tenant` (`sBrandsId`, `sSubsidiaryId`) | |
| 57 | -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='公司职员主档'; | |
| 58 | - | |
| 59 | --- =========================================================================== | |
| 60 | --- t_department — 部门组织树 | |
| 61 | --- =========================================================================== | |
| 62 | -CREATE TABLE `t_department` ( | |
| 63 | - `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | |
| 64 | - `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', | |
| 65 | - `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '品牌 ID(多租户隔离,标准列)', | |
| 66 | - `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', | |
| 67 | - `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | |
| 68 | - `sName` VARCHAR(100) NOT NULL COMMENT '部门名称', | |
| 69 | - `sCode` VARCHAR(50) NOT NULL COMMENT '部门编码;系统内唯一', | |
| 70 | - `iParentId` INT NULL DEFAULT NULL COMMENT '上级部门 ID,NULL 表示根部门', | |
| 71 | - `iSortOrder` INT NOT NULL DEFAULT 0 COMMENT '排序值,小者靠前', | |
| 40 | + UNIQUE KEY `uk_sys_department_code` (`sDepartmentCode`) | |
| 41 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='部门字典,职员归属'; | |
| 42 | + | |
| 43 | + | |
| 44 | +-- ============================================================================= | |
| 45 | +-- Table: sys_employee — 职员档案,员工基础信息 | |
| 46 | +-- ============================================================================= | |
| 47 | +CREATE TABLE `sys_employee` ( | |
| 48 | + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | |
| 49 | + `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', | |
| 50 | + `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '母公司 ID(多租户隔离,标准列)', | |
| 51 | + `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', | |
| 52 | + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | |
| 53 | + `sEmployeeName` VARCHAR(50) NOT NULL COMMENT '员工姓名(2-50 字符)', | |
| 54 | + `sEmployeeCode` VARCHAR(50) NOT NULL COMMENT '员工工号(系统内唯一)', | |
| 55 | + `iDepartmentId` INT NOT NULL COMMENT '所属部门 ID(FK → sys_department.iIncrement)', | |
| 56 | + `sPhone` VARCHAR(20) NULL DEFAULT NULL COMMENT '手机号', | |
| 57 | + `sEmail` VARCHAR(100) NULL DEFAULT NULL COMMENT '邮箱', | |
| 58 | + `iIsDeleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记', | |
| 72 | 59 | PRIMARY KEY (`iIncrement`), |
| 73 | - UNIQUE KEY `uk_department_code` (`sCode`), | |
| 74 | - KEY `idx_department_parent` (`iParentId`), | |
| 75 | - KEY `idx_department_tenant` (`sBrandsId`, `sSubsidiaryId`) | |
| 76 | -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='部门组织树'; | |
| 77 | - | |
| 78 | --- =========================================================================== | |
| 79 | --- t_permission — 权限分类字典 | |
| 80 | --- =========================================================================== | |
| 81 | -CREATE TABLE `t_permission` ( | |
| 82 | - `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | |
| 83 | - `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', | |
| 84 | - `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '品牌 ID(多租户隔离,标准列)', | |
| 85 | - `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', | |
| 86 | - `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | |
| 87 | - `sCode` VARCHAR(50) NOT NULL COMMENT '权限码,例如 USR:ADD / USR:EDIT;系统内唯一', | |
| 88 | - `sName` VARCHAR(100) NOT NULL COMMENT '权限分类名称(展示用)', | |
| 89 | - `iSortOrder` INT NOT NULL DEFAULT 0 COMMENT '同分类内排序', | |
| 60 | + UNIQUE KEY `uk_sys_employee_code` (`sEmployeeCode`), | |
| 61 | + KEY `idx_sys_employee_department` (`iDepartmentId`), | |
| 62 | + KEY `idx_sys_employee_name` (`sEmployeeName`) | |
| 63 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='职员档案,员工基础信息'; | |
| 64 | + | |
| 65 | + | |
| 66 | +-- ============================================================================= | |
| 67 | +-- Table: sys_user — 用户账号(登录认证 + 类型 + 语言 + 状态 + 登录追踪) | |
| 68 | +-- ============================================================================= | |
| 69 | +CREATE TABLE `sys_user` ( | |
| 70 | + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | |
| 71 | + `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', | |
| 72 | + `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '母公司 ID(多租户隔离,标准列)', | |
| 73 | + `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', | |
| 74 | + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | |
| 75 | + `sUsername` VARCHAR(50) NOT NULL COMMENT '用户名(登录凭据,系统内全局唯一,3-20 位字母数字下划线)', | |
| 76 | + `sUserCode` VARCHAR(50) NOT NULL COMMENT '用户号(业务展示用编码,系统内唯一)', | |
| 77 | + `sPasswordHash` VARCHAR(255) NOT NULL COMMENT '密码哈希(BCrypt / Argon2,禁明文)', | |
| 78 | + `iEmployeeId` INT NULL DEFAULT NULL COMMENT '关联职员 ID(可选;FK → sys_employee.iIncrement)', | |
| 79 | + `sUserType` VARCHAR(20) NOT NULL DEFAULT 'NORMAL' COMMENT '用户类型枚举:NORMAL=普通用户 / SUPER_ADMIN=超级管理员', | |
| 80 | + `sLanguage` VARCHAR(10) NOT NULL DEFAULT 'zh-CN' COMMENT '语言:zh-CN=中文 / en-US=英文 / zh-TW=繁体', | |
| 81 | + `iCanEditDocument` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '单据修改权限:0=否 1=是', | |
| 82 | + `iIsDeleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否作废:0=启用 1=作废(停用)', | |
| 83 | + `iFailedLoginCount` INT NOT NULL DEFAULT 0 COMMENT '累计登录失败次数,达阈值锁定,登录成功清零', | |
| 84 | + `tLockUntil` DATETIME NULL DEFAULT NULL COMMENT '锁定截止时间,NULL=未锁定,过期自动解锁', | |
| 85 | + `tLastLoginDate` DATETIME NULL DEFAULT NULL COMMENT '最后一次成功登录时间,REQ-USR-004 登录日期来源', | |
| 86 | + `sCreatedBy` VARCHAR(50) NULL DEFAULT NULL COMMENT '制单人(创建该用户的用户名),REQ-USR-002 制单人', | |
| 87 | + `sUpdatedBy` VARCHAR(50) NULL DEFAULT NULL COMMENT '最后修改人用户名', | |
| 88 | + `tUpdatedDate` DATETIME NULL DEFAULT NULL COMMENT '最后修改时间', | |
| 90 | 89 | PRIMARY KEY (`iIncrement`), |
| 91 | - UNIQUE KEY `uk_permission_code` (`sCode`) | |
| 92 | -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='权限分类字典'; | |
| 90 | + UNIQUE KEY `uk_sys_user_username` (`sUsername`), | |
| 91 | + UNIQUE KEY `uk_sys_user_code` (`sUserCode`), | |
| 92 | + KEY `idx_sys_user_employee` (`iEmployeeId`), | |
| 93 | + KEY `idx_sys_user_type` (`sUserType`), | |
| 94 | + KEY `idx_sys_user_is_deleted` (`iIsDeleted`), | |
| 95 | + KEY `idx_sys_user_created_by` (`sCreatedBy`) | |
| 96 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户账号(登录认证 + 类型 + 语言 + 状态 + 登录追踪)'; | |
| 97 | + | |
| 93 | 98 | |
| 94 | --- =========================================================================== | |
| 95 | --- t_user_permission — 用户-权限分类关联表 | |
| 96 | --- =========================================================================== | |
| 97 | -CREATE TABLE `t_user_permission` ( | |
| 98 | - `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | |
| 99 | - `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', | |
| 100 | - `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '品牌 ID(多租户隔离,标准列)', | |
| 101 | - `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', | |
| 102 | - `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | |
| 103 | - `iUserId` INT NOT NULL COMMENT '用户 ID,关联 t_user.iIncrement', | |
| 104 | - `iPermissionId` INT NOT NULL COMMENT '权限分类 ID,关联 t_permission.iIncrement', | |
| 99 | +-- ============================================================================= | |
| 100 | +-- Table: sys_permission_category — 权限分类字典 | |
| 101 | +-- ============================================================================= | |
| 102 | +CREATE TABLE `sys_permission_category` ( | |
| 103 | + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | |
| 104 | + `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', | |
| 105 | + `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '母公司 ID(多租户隔离,标准列)', | |
| 106 | + `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', | |
| 107 | + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | |
| 108 | + `sCategoryName` VARCHAR(100) NOT NULL COMMENT '权限分类名称(如 采购管理 / 销售管理)', | |
| 109 | + `sCategoryCode` VARCHAR(50) NOT NULL COMMENT '权限分类编码(系统内唯一,代码层引用)', | |
| 110 | + `sCategoryDesc` VARCHAR(255) NULL DEFAULT NULL COMMENT '分类说明', | |
| 111 | + `iSortOrder` INT NOT NULL DEFAULT 0 COMMENT '列表展示顺序', | |
| 112 | + `iIsDeleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记', | |
| 105 | 113 | PRIMARY KEY (`iIncrement`), |
| 106 | - UNIQUE KEY `uk_user_perm` (`iUserId`, `iPermissionId`), | |
| 107 | - KEY `idx_user_perm_perm` (`iPermissionId`) | |
| 108 | -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户-权限分类关联表'; | |
| 109 | - | |
| 110 | --- =========================================================================== | |
| 111 | --- t_company — 公司 / 版本字典 | |
| 112 | --- =========================================================================== | |
| 113 | -CREATE TABLE `t_company` ( | |
| 114 | - `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | |
| 115 | - `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', | |
| 116 | - `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '品牌 ID(多租户隔离,标准列)', | |
| 117 | - `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', | |
| 118 | - `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | |
| 119 | - `sCode` VARCHAR(50) NOT NULL COMMENT '公司 / 版本编码;系统内唯一', | |
| 120 | - `sName` VARCHAR(100) NOT NULL COMMENT '显示名称', | |
| 114 | + UNIQUE KEY `uk_sys_permission_category_code` (`sCategoryCode`), | |
| 115 | + KEY `idx_sys_permission_category_sort` (`iIsDeleted`, `iSortOrder`) | |
| 116 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='权限分类字典'; | |
| 117 | + | |
| 118 | + | |
| 119 | +-- ============================================================================= | |
| 120 | +-- Table: sys_user_permission_category — 用户 × 权限分类授权关系 | |
| 121 | +-- ============================================================================= | |
| 122 | +CREATE TABLE `sys_user_permission_category` ( | |
| 123 | + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | |
| 124 | + `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', | |
| 125 | + `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '母公司 ID(多租户隔离,标准列)', | |
| 126 | + `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', | |
| 127 | + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | |
| 128 | + `iUserId` INT NOT NULL COMMENT '用户 ID(FK → sys_user.iIncrement)', | |
| 129 | + `iPermissionCategoryId` INT NOT NULL COMMENT '权限分类 ID(FK → sys_permission_category.iIncrement)', | |
| 130 | + `sGrantedBy` VARCHAR(50) NULL DEFAULT NULL COMMENT '授予人用户名', | |
| 121 | 131 | PRIMARY KEY (`iIncrement`), |
| 122 | - UNIQUE KEY `uk_company_code` (`sCode`) | |
| 123 | -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='公司 / 版本字典'; | |
| 132 | + UNIQUE KEY `uk_sys_user_permission_category` (`iUserId`, `iPermissionCategoryId`), | |
| 133 | + KEY `idx_sys_user_permission_category_category` (`iPermissionCategoryId`) | |
| 134 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户 × 权限分类授权关系'; | |
| 124 | 135 | |
| 125 | --- =========================================================================== | |
| 126 | --- 外键约束(统一在最后追加,避免建表顺序依赖) | |
| 127 | --- =========================================================================== | |
| 128 | -ALTER TABLE `t_user` | |
| 129 | - ADD CONSTRAINT `fk_user_employee` FOREIGN KEY (`iEmployeeId`) REFERENCES `t_employee` (`iIncrement`) ON DELETE SET NULL ON UPDATE RESTRICT; | |
| 130 | 136 | |
| 131 | -ALTER TABLE `t_employee` | |
| 132 | - ADD CONSTRAINT `fk_employee_department` FOREIGN KEY (`iDepartmentId`) REFERENCES `t_department` (`iIncrement`) ON DELETE SET NULL ON UPDATE RESTRICT; | |
| 137 | +-- ============================================================================= | |
| 138 | +-- Foreign keys | |
| 139 | +-- ============================================================================= | |
| 140 | +ALTER TABLE `sys_employee` | |
| 141 | + ADD CONSTRAINT `fk_sys_employee_department` | |
| 142 | + FOREIGN KEY (`iDepartmentId`) REFERENCES `sys_department` (`iIncrement`) | |
| 143 | + ON DELETE RESTRICT ON UPDATE CASCADE; | |
| 133 | 144 | |
| 134 | -ALTER TABLE `t_department` | |
| 135 | - ADD CONSTRAINT `fk_department_parent` FOREIGN KEY (`iParentId`) REFERENCES `t_department` (`iIncrement`) ON DELETE RESTRICT ON UPDATE RESTRICT; | |
| 145 | +ALTER TABLE `sys_user` | |
| 146 | + ADD CONSTRAINT `fk_sys_user_employee` | |
| 147 | + FOREIGN KEY (`iEmployeeId`) REFERENCES `sys_employee` (`iIncrement`) | |
| 148 | + ON DELETE SET NULL ON UPDATE CASCADE; | |
| 136 | 149 | |
| 137 | -ALTER TABLE `t_user_permission` | |
| 138 | - ADD CONSTRAINT `fk_userperm_user` FOREIGN KEY (`iUserId`) REFERENCES `t_user` (`iIncrement`) ON DELETE CASCADE ON UPDATE RESTRICT; | |
| 150 | +ALTER TABLE `sys_user_permission_category` | |
| 151 | + ADD CONSTRAINT `fk_sys_upc_user` | |
| 152 | + FOREIGN KEY (`iUserId`) REFERENCES `sys_user` (`iIncrement`) | |
| 153 | + ON DELETE CASCADE ON UPDATE CASCADE; | |
| 139 | 154 | |
| 140 | -ALTER TABLE `t_user_permission` | |
| 141 | - ADD CONSTRAINT `fk_userperm_perm` FOREIGN KEY (`iPermissionId`) REFERENCES `t_permission` (`iIncrement`) ON DELETE RESTRICT ON UPDATE RESTRICT; | |
| 155 | +ALTER TABLE `sys_user_permission_category` | |
| 156 | + ADD CONSTRAINT `fk_sys_upc_permission_category` | |
| 157 | + FOREIGN KEY (`iPermissionCategoryId`) REFERENCES `sys_permission_category` (`iIncrement`) | |
| 158 | + ON DELETE CASCADE ON UPDATE CASCADE; | ... | ... |