front/docs/rbac系统设计-060801.md
zzh 5a26ae0048 feat: 小红书模块迁移、登录重构与 xhs-agent-desktop 桌面 Agent
- 新增小红书前端模块(启动服务、内容分析、爆款创作、发布记录、技能卡片、拟人设置、AI 助手)

- 新增 xhs-agent-desktop 独立桌面 Agent,对接 OMS 任务调度

- 登录页重构,整合微信登录与 OMS JWT 认证

- 订单、渠道、用户中心等页面与 API 增强

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 16:13:13 +08:00

1147 lines
40 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# RBAC 与统一认证 — 产品需求文档(PRD)
> **代码基线**:`C:\Users\wilso\Desktop\SimpleAdmin.Api-master`(后端)+ `E:\oms0414`(前端 OpenAPI 客户端)
> **RBAC 内核**:NuGet 包 `Fake.Rbac.Application` / `Fake.Rbac.Infrastructure` **v8.0.5.11**
> **API 前缀**:`/rbac/*`(RBAC 管理)、`/customer/*`(OMS 客户资料,含 RBAC 联动)
---
## 导读
### 这份文档解决什么问题?
系统当前实现要搞清楚四件事:
1. **谁能登录** — 内部用户(管理端)与客户(OMS 端)共用 RBAC `user` 表;客户登录凭证存 `t_customer`,通过 `user.userid` 与 RBAC 关联。
2. **登录之后凭什么进业务** — JWT Access Token + Refresh Token;Claims 含 `mapId`(OMS 整型用户编号)与 `loginType`(0=内部用户,1=客户)。
3. **能操作哪些功能** — 用户有效权限 = 其所有角色在 `role_permission` 中权限码的**并集**;子客户通过**专属角色** `CustomerSub_{userId}` 收窄权限;菜单按权限码裁剪展示,**不替代** API 鉴权。
4. **客户与 RBAC 如何联动** — 创建客户时同步创建 RBAC 用户、分配 `Customer` 角色;创建子客户时创建专属角色并校验权限不超过主客户。
### 建议阅读路径
| 角色 | 建议路径 |
| --- | --- |
| 后端 / DBA | **2 概念模型** → **3 表结构** → **8 接口详细设计** |
| 前端 / BFF | **4 认证与令牌** → **7 接口速查** → **9 前端集成** |
| 产品 / 测试 | **2** → **5 权限模型** → **7** → **8.7 客户接口** |
### 章节一览
| 章 | 内容 |
| --- | --- |
| **1** | 系统架构 |
| **2** | 概念模型 |
| **3** | 数据表结构 |
| **4** | 认证与令牌 |
| **5** | 权限模型 |
| **6** | 枚举与常量 |
| **7** | 接口路径速查 |
| **8** | 接口详细设计 |
| **9** | 前端集成 |
| **10** | 种子数据与默认账号 |
---
## 1 系统架构
### 1.1 整体分层
```
SimpleAdmin.Api(宿主)
├── Fake.Rbac.Application # User/Role/Menu/Permission/Organization 服务 + DTO + JWT
├── Fake.Rbac.Infrastructure # FakeRbacDbContext、EF 配置、仓储、种子数据
├── Fake.Rbac.Domain # 聚合根、AccountManager、权限定义 Provider
└── Oms.Application # CustomerService:客户资料 + RBAC 用户/角色生命周期
```
- **API 生成方式**:继承 `ApplicationService` 的服务由 Fake 框架自动映射为 Controller(`ApplicationServices2Controller`)。
- **RBAC 路由根路径**:`rbac`(见 `SimpleAdminApiModule.cs`)。
- **自定义认证**:宿主项目 `AuthService` **替换**包内默认 `AuthService`,补充 OMS 双端登录。
- **数据库**:RBAC 表由 EF Core 管理(MySQL);OMS 业务表由 SqlSugar 管理(`db.sql`)。
### 1.2 双 ID 体系(关键)
| 标识 | 类型 | 用途 |
| --- | --- | --- |
| `user.Id` | `GUID` | RBAC 主键;JWT `sub`;服务层用户标识 |
| `user.userid` | `INT` 自增 | OMS 桥接 ID;`t_customer.UserId` 外键;JWT Claim `mapId` |
- 内部 API 操作用户时传 **GUID**(`id` 查询参数)。
- OMS 业务侧(订单、客户上下文)用 **`CurrentUser.GetId<int>()`** 读取 `mapId`。
### 1.3 中间件管道
```
UseForwardedHeaders → UsePathBase → UseFakeSwagger → UseFakeExceptionHandling
→ UseCors → UseStaticFiles → UseRouting → UseAuthentication → UseAuthorization → MapControllers
```
- 当前**无**细粒度权限拦截器;接口层主要校验 **Bearer Token 是否有效**(`[Authorize]`)。
- 细粒度 `[Authorize(Permission = "...")]` 在包内标记为待实现。
---
## 2 概念模型
### 2.1 三层分工
| 层次 | 回答的问题 | 核心对象 |
| --- | --- | --- |
| **身份(Authentication)** | 你是谁?凭证是否有效? | `user`(内部用户)/ `t_customer`(客户登录凭证) |
| **授权(Authorization)** | 你被允许执行哪些 PermissionCode? | `user_role` → `role` → `role_permission` |
| **展示(UI)** | 控制台显示哪些菜单? | `menu`(每节点绑定 `PermissionCode`,按有效权限裁剪) |
### 2.2 有效权限码集合 P 的计算
记用户绑定的所有角色,其 `role_permission.PermissionCode` 并集为 **P**:
- **内部用户**:`P = ⋃ role_permission`(该用户所有角色)。
- **客户主账号**:创建时分配 `Customer` 角色;`P` 由该角色(及后续分配的其他角色)决定。
- **子客户**:创建时新建角色 `CustomerSub_{userId}`,创建入参 `permissions[]` 直接写入该角色的 `role_permission`;保存前校验 **子客户权限 ⊆ 主客户当前权限**。
> **注意**:当前实现**没有** `customer_sub_permission` 白名单表;子客户权限通过**专属角色**表达,而非与角色权限求交。
### 2.3 登录主体类型
| `OmsLoginType` | 值 | 说明 |
| --- | --- | --- |
| `User` | 0 | 内部用户;校验 RBAC `user.Account` + 哈希密码 |
| `Customer` | 1 | 客户;校验 `t_customer` 手机/邮箱/客户编号 + MD5 密码,再映射到 RBAC GUID |
### 2.4 关联关系图
```mermaid
flowchart LR
subgraph RBAC
U[user]
UR[user_role]
R[role]
RP[role_permission]
M[menu]
O[organization]
end
subgraph OMS
C[t_customer]
end
U -->|userid INT| C
U --> UR --> R --> RP
RP --> P["有效权限码 P"]
P --> API["API 鉴权(当前主要为登录态)"]
P --> M
U -->|OrganizationId| O
```
---
## 3 数据表结构
> RBAC 表名在 MySQL 中为**小写**;由 `Fake.Rbac.Infrastructure` EF Migration 创建。
> OMS 客户表在 `db.sql` 中定义,表名保留 `t_` 前缀。
### 3.1 `user` — RBAC 用户表
| 字段名 | 类型 | 约束 | 说明 |
| --- | --- | --- | --- |
| `Id` | CHAR(36) | PK | GUID 主键 |
| `userid` | INT | 自增,唯一 | OMS 桥接编号,对应 `t_customer.UserId` |
| `Name` | VARCHAR | NOT NULL | 显示名称 |
| `Account` | VARCHAR | NOT NULL,唯一索引 `IX_user_Account` | 登录账号 |
| `Password` | VARCHAR | NOT NULL | 密码哈希(`EncryptPassword` 值对象) |
| `Salt` | VARCHAR | NOT NULL | 密码盐 |
| `Email` | VARCHAR | 可空,唯一索引 `IX_user_Email` | 邮箱 |
| `Avatar` | VARCHAR | 可空 | 头像 URL |
| `OrganizationId` | CHAR(36) | 可空,索引 `IX_user_OrganizationId` | 所属组织 |
| `ConcurrencyStamp` | CHAR(36) | NOT NULL | 乐观并发 |
| `CreateUserId` | INT | NOT NULL | 创建人 |
| `CreateTime` | DATETIME | NOT NULL | 创建时间 |
| `UpdateUserId` | INT | 可空 | 更新人 |
| `UpdateTime` | DATETIME | 可空 | 更新时间 |
| `IsDeleted` | TINYINT | NOT NULL,默认 0 | 软删除 |
**说明**:
- 内部用户:账号、密码、姓名、邮箱均存此表。
- 客户:RBAC 用户行由 `CustomerService` 创建(`Account` 默认用手机号),登录密码以 `t_customer.Password`(MD5)为准。
### 3.2 `role` — 角色表
| 字段名 | 类型 | 约束 | 说明 |
| --- | --- | --- | --- |
| `Id` | CHAR(36) | PK | GUID 主键 |
| `Name` | VARCHAR | NOT NULL | 角色名称,如「客户」「子客户-xxx」 |
| `Code` | VARCHAR | NOT NULL,唯一索引 `IX_role_Code` | 角色编码,如 `Customer`、`CustomerSub_123` |
| `ConcurrencyStamp` | CHAR(36) | NOT NULL | 乐观并发 |
| `CreateUserId` / `CreateTime` | — | 审计 | |
| `UpdateUserId` / `UpdateTime` | — | 可空 | |
| `IsDeleted` | TINYINT | 默认 0 | 软删除 |
**内置角色**:
| Code | Name | 来源 |
| --- | --- | --- |
| `Customer` | 客户 | `CustomerRoleSeedContributor` 启动种子 |
| `CustomerSub_{userId}` | 子客户-{名称} | `CustomerService.CreateSubCustomerAsync` 动态创建 |
### 3.3 `user_role` — 用户角色关联表
| 字段名 | 类型 | 约束 | 说明 |
| --- | --- | --- | --- |
| `Id` | CHAR(36) | PK | |
| `UserId` | CHAR(36) | FK → `user.Id` | |
| `RoleId` | CHAR(36) | FK → `role.Id` | |
| `CreateUserId` | INT | | 审计 |
| `CreateTime` | DATETIME | | 审计 |
唯一索引:`IX_user_role_UserId_RoleId`
### 3.4 `role_permission` — 角色权限关联表
| 字段名 | 类型 | 约束 | 说明 |
| --- | --- | --- | --- |
| `Id` | CHAR(36) | PK | |
| `RoleId` | CHAR(36) | FK → `role.Id` | |
| `PermissionCode` | VARCHAR | NOT NULL | 权限码字符串 |
| `CreateUserId` | INT | | 审计 |
| `CreateTime` | DATETIME | | 审计 |
唯一索引:`IX_role_permission_RoleId_PermissionCode`
> **无** `permission_definition` 数据库表。权限码权威定义在代码 `PermissionDefinitionProvider` 中,分配时以字符串存入本表。
### 3.5 `menu` — 菜单表
| 字段名 | 类型 | 约束 | 说明 |
| --- | --- | --- | --- |
| `Id` | CHAR(36) | PK | |
| `PId` | CHAR(36) | NOT NULL | 父菜单 ID;`00000000-0000-0000-0000-000000000000` 表示根 |
| `Name` | VARCHAR | NOT NULL | 菜单名称 |
| `PermissionCode` | VARCHAR | 可空,索引 `IX_menu_PermissionCode` | 关联权限码 |
| `Type` | INT | NOT NULL | `MenuType`:1=菜单,2=按钮 |
| `Icon` | VARCHAR | 可空 | 图标 |
| `Route` | VARCHAR | 可空 | 前端路由 path |
| `Component` | VARCHAR | 可空 | 前端组件路径 |
| `Order` | INT | NOT NULL | 排序 |
| `IsHidden` | TINYINT | 默认 0 | 是否隐藏 |
| `IsCached` | TINYINT | 默认 0 | 是否缓存(keep-alive) |
| `Description` | VARCHAR | 可空 | 描述 |
| 审计 + `IsDeleted` | — | | 标准软删除 |
**用户可见菜单算法**(`AuthService` / `MenuService`):
1. 取用户有效权限码集合 `P`。
2. 选出 `menu.PermissionCode ∈ P` 的节点。
3. 递归补齐所有祖先节点(保证树结构完整)。
4. 按 `Order` 构建树返回。
### 3.6 `organization` — 组织表
| 字段名 | 类型 | 约束 | 说明 |
| --- | --- | --- | --- |
| `Id` | CHAR(36) | PK | |
| `ParentId` | CHAR(36) | 可空 | 父组织;空或零 GUID 为根 |
| `Name` | VARCHAR | NOT NULL | 组织名称 |
| `Code` | VARCHAR | NOT NULL,唯一索引 `IX_organization_Code` | 组织编码 |
| `Type` | INT | NOT NULL | `OrganizationType`:1/2/3 |
| `LeaderId` | CHAR(36) | 可空 | 负责人用户 GUID |
| `Order` | INT | | 排序 |
| `Description` | VARCHAR | 可空 | 描述 |
| `IsEnabled` | TINYINT | 默认 1 | 是否启用 |
| 审计 + `IsDeleted` | — | | |
**说明**:组织仅关联内部用户 `user.OrganizationId`;客户不挂组织树。组织**不是**权限码,用于人事与数据范围等业务查询。
### 3.7 `t_customer` — OMS 客户表(桥接 RBAC)
| 字段名 | 类型 | 约束 | 说明 |
| --- | --- | --- | --- |
| `Id` | INT | PK,自增 | 客户主键 |
| `ParentCustomerId` | INT | 可空 | NULL=主客户;有值=子客户 |
| `UserId` | INT | NOT NULL,唯一 `uk_cus_user` | 关联 `user.userid` |
| `CustomerCode` | VARCHAR(32) | NOT NULL,唯一 | 客户编号 |
| `CustomerName` | VARCHAR(128) | NOT NULL | 客户名称 |
| `Phone` | VARCHAR(16) | NOT NULL,唯一 | 登录账号(之一) |
| `Email` | VARCHAR(64) | 可空,唯一 | 登录账号(之一) |
| `Password` | VARCHAR(64) | NOT NULL | MD5 加密,客户登录用 |
| `WechatAvatar` / `WechatNickname` | VARCHAR | 可空 | 微信资料 |
| `WechatOpenid` | VARCHAR(64) | 可空,唯一 | 微信 OpenID |
| `Balance` / `TotalQuota` / `UsedQuota` | DECIMAL(18,2) | | 账户额度 |
| `Status` | TINYINT | 默认 1 | 0=禁用,1=正常,2=冻结 |
| `CustomerGroup` | TINYINT | 可空 | 0=普通,1~5=VIP1~VIP5 |
| `Salesman` / `CustomerService` | VARCHAR(32) | 可空 | 销售/客服 |
| `LastLoginIp` / `LastLoginTime` | — | 可空 | 最后登录 |
| `Remark` | VARCHAR(512) | 可空 | 备注 |
| 审计 + `IsDeleted` + `ConcurrencyStamp` | — | | |
**主/子客户判定**:
- `ParentCustomerId IS NULL` → 主客户,可调用 `/customer/sub/create` 创建子客户。
- `ParentCustomerId IS NOT NULL` → 子客户;登录时同时校验子客户与主客户状态。
---
## 4 认证与令牌
### 4.1 JWT 配置项
| 配置键 | 说明 | 示例 |
| --- | --- | --- |
| `Jwt:SecretKey` | 签名密钥 | 生产从 Consul 加载 |
| `Jwt:Issuer` | 签发者 | |
| `Jwt:Audience` | 受众 | |
| `Jwt:ExpiryInMinutes` | Access Token 有效期(分钟) | 60 |
| `Jwt:RefreshExpiryInDays` | Refresh Token 有效期(天) | 7 |
| `Jwt:ClockSkewMinutes` | 时钟偏移容忍(分钟) | 5 |
### 4.2 JWT Claims
| Claim | 类型 | 说明 |
| --- | --- | --- |
| 标准 RBAC Claims | — | 由 `JwtService.GenerateClaimsByUserIdAsync` 生成(用户 ID、角色等) |
| `mapId` | int 字符串 | `user.userid`,OMS 业务上下文用 |
| `loginType` | int 字符串 | `0`=内部用户,`1`=客户 |
自定义实现:`SimpleJwtService` 在生成 Claims 时注入 `mapId`;`AuthService` 在客户登录时覆盖 `loginType=1`。
### 4.3 登录流程
#### 4.3.1 内部用户(`type=0`,管理端 `/admin`)
> **不支持微信登录**。管理端仅提供账号 + 密码方式,登录页不展示微信扫码入口。
1. `POST /rbac/auth/login`,body:`{ account, password, type: 0 }`
2. `AccountManager.ValidateCredentialsAsync` 校验 `user.Account` + 哈希密码。
3. 生成 Access Token + Refresh Token。
4. 前端调用 `GET /rbac/auth/get-current-user` 拉取用户信息、角色、权限、菜单。
#### 4.3.2 客户(`type=1`,客户端 `/login`)
> 客户端支持 **账号密码** 与 **微信扫码** 两种方式(见 [4.3.4](#434-客户微信扫码登录仅客户端))。
1. `POST /rbac/auth/login`,body:`{ account, password, type: 1 }`
2. 按手机 → 邮箱 → 客户编号顺序查找 `t_customer`。
3. 校验客户状态(子客户还需校验主客户状态)。
4. 比对 `t_customer.Password`(MD5)。
5. 通过 `user.userid` 查 RBAC GUID,生成 Token(`loginType=1`)。
6. 更新 `LastLoginIp`、`LastLoginTime`。
#### 4.3.3 刷新令牌
1. `POST /rbac/auth/refresh-token`,body:`{ refreshToken }`
2. 校验 Refresh Token 有效性,解析 `loginType`。
3. 客户刷新:通过 `mapId` 重新查 GUID 并签发新 Token。
4. 内部用户刷新:通过 GUID 查用户并签发新 Token。
#### 4.3.4 客户微信扫码登录(仅客户端)
##### 适用范围
| 登录页 | 路由 | 账号密码 | 微信扫码 |
| --- | --- | --- | --- |
| 管理端 | `/admin`(`adminLogin`) | ✅ | ❌ **不支持** |
| 客户端 | `/login`(`login`) | ✅ | ✅ |
前端实现:`login/index.vue` 根据路由 `loginPortal` 判定主体;仅 `OmsLoginType.Customer` 时渲染 `WechatLogin` 组件及密码页底部的微信入口按钮。
##### 现状与依赖
| 层级 | 状态 |
| --- | --- |
| 数据表 | `t_customer` 已有 `WechatOpenid`(唯一)、`WechatAvatar`、`WechatNickname` |
| 仓储 | `ICustomerRepository.GetByWechatOpenidAsync` 已实现 |
| 后端认证 | **未实现** 微信 OAuth / 扫码登录接口;`AuthService` 仅支持密码登录 |
| 前端 | `wechat-login.vue` 为占位 UI(假二维码 + 120s 倒计时),待对接后端 |
##### 微信开放平台配置(网站应用扫码登录)
采用 [微信开放平台 · 网站应用 · 微信登录](https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html) 方案:
| 配置项 | 说明 |
| --- | --- |
| `AppID` / `AppSecret` | 网站应用凭证,存 Consul / 环境变量,**禁止**写入前端 |
| 授权回调域 | 与 OMS 客户门户同域,如 `oms.example.com` |
| 业务 scope | `snsapi_login`(扫码确认后换取 OpenID) |
##### 端到端流程
```mermaid
sequenceDiagram
participant U as 客户浏览器
participant F as OMS 前端 /login
participant B as SimpleAdmin.Api
participant W as 微信开放平台
U->>F: 打开客户端登录页,切换「微信扫码」
F->>B: GET /rbac/auth/wechat/qrcode
B->>B: 生成 scene/ticket,写入 Redis(TTL 120s)
B->>W: 拼装 oauth2/authorize URL(state=ticket)
B-->>F: { ticket, qrUrl, expiresIn }
F->>F: 渲染二维码(qrUrl)
loop 每 2s 轮询,最长 120s
F->>B: GET /rbac/auth/wechat/poll?ticket=xxx
alt 未扫码 / 已扫码未确认
B-->>F: { status: pending }
else 用户取消
B-->>F: { status: cancelled }
else 扫码成功
B-->>F: { status: success, accessToken, refreshToken, expiresIn }
F->>F: 写入 Token,跳转 /user/profile
end
end
Note over W,B: 用户扫码确认后,微信回调 B 的 redirect_uri
W->>B: GET /rbac/auth/wechat/callback?code=xxx&state=ticket
B->>W: code 换 access_token + openid
B->>B: GetByWechatOpenidAsync(openid)
alt OpenID 已绑定客户
B->>B: 校验客户/主客户状态,签发 JWT(loginType=1)
B->>B: 更新 LastLoginIp/Time、WechatAvatar/Nickname
B->>B: 将 ticket 标记为 success + Token
else OpenID 未绑定
B->>B: ticket 标记为 unbound
end
```
**步骤说明**:
1. **申请二维码**:前端调用 `GET /rbac/auth/wechat/qrcode`,后端生成唯一 `ticket`(即 OAuth `state`),返回可嵌入 `<img>` 的 `qrUrl`(微信 `showqrcode` 或自建二维码图)。
2. **用户扫码**:微信 App 打开授权页,用户确认登录。
3. **服务端回调**:微信重定向 `GET /rbac/auth/wechat/callback?code&state`;后端用 `code` 换 `openid`(及昵称、头像)。
4. **客户匹配**:`openid` → `t_customer.WechatOpenid`;命中则走与密码登录相同的 Token 签发逻辑(`mapId = user.userid`,`loginType = 1`)。
5. **前端轮询**:`poll` 接口在 ticket 变为 `success` 时返回 Token 对;前端复用现有 `rbac-user` store 落盘与跳转逻辑。
6. **刷新二维码**:ticket 过期或 `cancelled` 时,前端重新调用 `qrcode` 接口。
##### 待实现 API 契约
**1. 获取扫码 ticket**
```
GET /rbac/auth/wechat/qrcode
```
响应:
```json
{
"ticket": "a1b2c3d4-...",
"qrUrl": "https://open.weixin.qq.com/connect/qrconnect?appid=...&redirect_uri=...&response_type=code&scope=snsapi_login&state=a1b2c3d4-...#wechat_redirect",
"expiresIn": 120
}
```
**2. 轮询扫码结果**
```
GET /rbac/auth/wechat/poll?ticket={ticket}
```
| status | 含义 | 前端行为 |
| --- | --- | --- |
| `pending` | 等待扫码或确认 | 继续轮询 |
| `cancelled` | 用户取消授权 | 提示后展示刷新按钮 |
| `unbound` | OpenID 未绑定任何客户 | 提示「请联系管理员绑定微信」 |
| `disabled` | 客户或主客户已禁用/冻结 | 提示账号不可用 |
| `expired` | ticket 超时 | 自动刷新二维码 |
| `success` | 登录成功 | 取 `accessToken` / `refreshToken` 完成登录 |
`success` 时响应体与 `POST /rbac/auth/login` 一致(含 `accessToken`、`refreshToken`、`expiresIn`)。
**3. 微信 OAuth 回调(浏览器重定向,非前端直接调用)**
```
GET /rbac/auth/wechat/callback?code={code}&state={ticket}
```
- 成功:更新 Redis ticket 状态,返回简单 HTML「登录成功,请关闭此页」或 302 回 `/login?wechat=ok`。
- 失败:ticket 标记 `unbound` / `error`,记录日志。
##### 绑定与账号规则
| 规则 | 说明 |
| --- | --- |
| 绑定主体 | **仅客户**(`t_customer`);内部 `user` 表不存 `WechatOpenid` |
| 绑定时机 | 管理员在「客户管理」创建/编辑客户时写入 `WechatOpenid`;或客户首次密码登录后在个人中心「绑定微信」(二期) |
| 唯一性 | 一个 `WechatOpenid` 只能绑定一个客户;解绑需管理员操作 |
| 子客户 | 子客户可有独立 `WechatOpenid`;登录时同样校验子客户与主客户 `Status` |
| 资料同步 | 扫码成功后可选更新 `WechatAvatar`、`WechatNickname`(以微信侧为准) |
| 无密码登录 | 微信登录**不校验** `t_customer.Password`;禁用客户不得仅凭历史 OpenID 登录 |
##### 安全要求
- `AppSecret` 仅存服务端;`code` 换 `openid` 必须在后端完成。
- `ticket` / `state` 一次性使用,Redis TTL ≤ 120s,防重放。
- 回调域与 `redirect_uri` 严格匹配微信开放平台配置。
- 轮询接口限流(建议每 IP 每 ticket ≤ 1 次/2s)。
- 日志记录 `openid` 哈希或末四位,避免明文落盘。
##### 前端集成要点(`oms0414`)
| 文件 | 职责 |
| --- | --- |
| `views/login/index.vue` | 仅 `login` 路由展示 `WechatLogin`;`adminLogin` 强制 `activeKey=pwd` |
| `views/login/components/pwd-login/index.vue` | 仅 `OmsLoginType.Customer` 显示底部微信图标 |
| `views/login/components/wechat-login.vue` | 对接 `qrcode` + `poll`;二维码用 `qrUrl` 或 `qrcode` 库渲染 |
| `store/modules/rbac-user` | `success` 后复用 `setToken` + `fetchCurrentUser` + 跳转 `/user/profile` |
建议新增 `src/api/rbac/wechat-auth.ts` 封装上述两个 GET 接口。
##### 实施阶段建议
| 阶段 | 内容 |
| --- | --- |
| P0(当前) | 管理端隐藏微信入口;客户端保留占位 UI;本文档定稿 |
| P1 | 后端实现 `qrcode` / `callback` / `poll` + Redis;管理端录入 `WechatOpenid` |
| P2 | 前端 `wechat-login.vue` 实装轮询与错误态;个人中心「绑定/解绑微信」 |
### 4.4 前端 Token 策略(`rbac-user` store)
- Access Token 存 `localStorage`(`rbac_access_token`)。
- 到期前 5 分钟触发刷新(`expiresIn - 300s`)。
- 1 小时无用户交互则停止自动续期(`KEEP_ALIVE_IDLE_MS`)。
- 401 / 刷新失败 → 清除本地状态并跳转登录页。
### 4.5 密码策略
| 主体 | 存储 | 修改接口 |
| --- | --- | --- |
| 内部用户 | RBAC 哈希 + 盐 | `POST /rbac/auth/change-password`(本人)/ `PUT /rbac/user/update-password`(管理员重置) |
| 客户 | `t_customer.Password` MD5 | `POST /rbac/auth/change-password`(本人)/ `POST /customer/reset-password`(管理员重置) |
客户改密:`AccountManager.ValidatePasswordStrength` 校验新密码强度。
---
## 5 权限模型
### 5.1 权限定义方式
- 权限码在 **代码** 中通过 `RbacPermissionDefinitionProvider` 静态注册。
- 运行时通过 `GET /rbac/permission/get-all-permissions` 或 `get-permission-tree` 获取目录。
- 角色分配时调用 `POST /rbac/role/assign-permissions`,权限码写入 `role_permission`。
- **不存在**权限定义数据库表。
### 5.2 权限码命名规范
**格式**:`{Module}.{Resource}.{Action}`(PascalCase,点分隔)
#### RBAC 管理模块(包内定义)
| 权限码 | 含义 |
| --- | --- |
| `Rbac.Users` | 用户模块 |
| `Rbac.Users.Query` | 查询用户 |
| `Rbac.Users.Create` | 创建用户 |
| `Rbac.Users.Update` | 更新用户 |
| `Rbac.Users.Delete` | 删除用户 |
| `Rbac.Users.AssignRoles` | 分配角色 |
| `Rbac.Users.ResetPassword` | 重置密码 |
| `Rbac.Roles` / `.Query` / `.Create` / `.Update` / `.Delete` / `.AssignPermissions` | 角色 CRUD + 分配权限 |
| `Rbac.Menus` / `.Query` / `.Create` / `.Update` / `.Delete` / `.Move` | 菜单 CRUD + 移动 |
| `Rbac.Organizations` / `.Query` / `.Create` / `.Update` / `.Delete` / `.Move` | 组织 CRUD + 移动 |
#### 业务模块(前端路由已使用,须在 Provider 中注册后方能分配)
| 模块前缀 | 示例 |
| --- | --- |
| `Api.*` | `Api.Interfaces.Query`、`Api.Applications.Create` |
| `Track.*` | `Track.Orders.Query`、`Track.Kpi.Query` |
| `Home.*` | `Home.Monitor` |
| `System.*` | `System.BatchEmail.Use` |
完整列表以 `GET /rbac/permission/get-permission-tree` 返回为准。
### 5.3 鉴权现状
| 机制 | 状态 |
| --- | --- |
| `[Authorize]`(需登录) | ✅ 已启用 |
| `[Authorize(Permission = "...")]` 细粒度 | ❌ 包内待实现 |
| `POST /rbac/permission/check-permission` | ✅ API 可用,供显式校验 |
| `IUserService.HasPermissionAsync` | ✅ 服务方法可用 |
| 前端 `hasPermission()` | ⚠️ 当前临时返回 `true`(`src/utils/rbac-permission.ts`) |
### 5.4 菜单类型
| `MenuType` | 值 | 说明 |
| --- | --- | --- |
| Menu | 1 | 页面菜单 |
| Button | 2 | 按钮权限 |
---
## 6 枚举与常量
### 6.1 OMS 集成(代码内)
| 名称 | 值 | 文件 |
| --- | --- | --- |
| `OmsLoginType.User` | 0 | `AuthRequestDtos.cs` |
| `OmsLoginType.Customer` | 1 | `AuthRequestDtos.cs` |
| `SimpleOmsClaimTypes.MapId` | `"mapId"` | `SimpleOmsClaimTypes.cs` |
| `SimpleOmsClaimTypes.LoginType` | `"loginType"` | `SimpleOmsClaimTypes.cs` |
| `CustomerRoleCode` | `"Customer"` | `CustomerRoleSeedContributor.cs` |
| `CustomerRoleName` | `"客户"` | `CustomerRoleSeedContributor.cs` |
| 子客户角色 Code | `CustomerSub_{userId}` | `CustomerService.cs` |
| `CustomerStatus.Disabled` | 0 | |
| `CustomerStatus.Normal` | 1 | |
| `CustomerStatus.Frozen` | 2 | |
| `CustomerGroup.Normal` | 0 | |
| `CustomerGroup.Vip1~Vip5` | 1~5 | |
| `OrganizationType` | 1 / 2 / 3 | OpenAPI 生成 |
| `MenuType.Menu` | 1 | |
| `MenuType.Button` | 2 | |
---
## 7 接口路径速查
### 7.1 认证 `/rbac/auth/*`
| 方法 | 路径 | 鉴权 | 说明 |
| --- | --- | --- | --- |
| POST | `/rbac/auth/login` | 匿名 | 用户/客户登录 |
| POST | `/rbac/auth/refresh-token` | 匿名 | 刷新令牌 |
| GET | `/rbac/auth/get-current-user` | Bearer | 当前用户信息(含权限、菜单) |
| POST | `/rbac/auth/change-password` | Bearer | 当前用户改密 |
| PUT | `/rbac/auth/update-profile` | Bearer(仅内部用户) | 更新姓名、邮箱 |
| POST | `/rbac/auth/upload-avatar` | Bearer(仅内部用户) | 上传头像 |
### 7.2 用户 `/rbac/user/*`
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/rbac/user/get-list` | 分页列表 |
| GET | `/rbac/user/get` | 详情 |
| POST | `/rbac/user/create` | 创建 |
| PUT | `/rbac/user/update` | 更新 |
| DELETE | `/rbac/user/delete` | 删除 |
| DELETE | `/rbac/user/delete-batch` | 批量删除 |
| POST | `/rbac/user/assign-roles` | 分配角色 |
| GET | `/rbac/user/get-user-roles` | 用户角色列表 |
| GET | `/rbac/user/get-user-permissions` | 用户权限码列表 |
| GET | `/rbac/user/get-users-by-role` | 按角色查用户 |
| PUT | `/rbac/user/update-password` | 重置密码 |
| PUT | `/rbac/user/update-avatar` | 更新头像 |
### 7.3 角色 `/rbac/role/*`
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/rbac/role/get-list` | 分页列表 |
| GET | `/rbac/role/get-all-roles` | 全部角色(简表) |
| GET | `/rbac/role/get` | 详情 |
| POST | `/rbac/role/create` | 创建(可带初始 permissions) |
| PUT | `/rbac/role/update` | 更新名称 |
| DELETE | `/rbac/role/delete` | 删除 |
| POST | `/rbac/role/assign-permissions` | 分配权限码 |
| GET | `/rbac/role/get-role-permissions` | 角色权限码列表 |
| GET | `/rbac/role/get-role-users` | 角色下用户 |
| GET | `/rbac/role/get-role-user-count` | 角色用户数量 |
### 7.4 菜单 `/rbac/menu/*`
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/rbac/menu/get-menu-tree` | 完整菜单树 |
| GET | `/rbac/menu/get-user-menus` | 指定用户可见菜单 |
| GET | `/rbac/menu/get` | 菜单详情 |
| POST | `/rbac/menu/create` | 创建 |
| PUT | `/rbac/menu/update` | 更新 |
| PUT | `/rbac/menu/update-order` | 更新排序 |
| POST | `/rbac/menu/move-menu` | 移动节点 |
| DELETE | `/rbac/menu/delete` | 删除 |
### 7.5 权限 `/rbac/permission/*`
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/rbac/permission/get-all-permissions` | 全部权限定义(树形) |
| GET | `/rbac/permission/get-permission-tree` | 按分组返回权限树 |
| POST | `/rbac/permission/check-permission` | 单权限校验 |
| POST | `/rbac/permission/check-permissions` | 批量权限校验 |
### 7.6 组织 `/rbac/organization/*`
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/rbac/organization/get-list` | 组织列表 |
| GET | `/rbac/organization/get-tree` | 组织树 |
| GET | `/rbac/organization/get` | 详情 |
| POST | `/rbac/organization/create` | 创建 |
| PUT | `/rbac/organization/update` | 更新 |
| DELETE | `/rbac/organization/delete` | 删除 |
| POST | `/rbac/organization/move` | 移动节点 |
### 7.7 客户(OMS)`/customer/*`
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/customer/list` | 客户分页列表 |
| GET | `/customer/get` | 客户详情 |
| POST | `/customer/create` | 创建主客户(联动 RBAC) |
| POST | `/customer/sub/create` | 主客户创建子客户(联动 RBAC + 专属角色) |
| POST | `/customer/update` | 更新客户资料 |
| POST | `/customer/delete` | 删除客户 |
| POST | `/customer/reset-password` | 重置客户密码 |
---
## 8 接口详细设计
> 请求/响应字段以 OpenAPI 生成客户端 `E:\oms0414\src\api\rbac\generated` 为准。
> 分页接口统一返回 `{ total, items }`;单条删除/变更成功通常无 body 或返回更新后 DTO。
### 8.1 认证接口
#### 8.1.1 `POST /rbac/auth/login`
**请求体 `OmsLoginInput`**
```json
{
"account": "admin",
"password": "123456",
"type": 0
}
```
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| account | string | 是 | 内部用户=账号;客户=手机/邮箱/客户编号 |
| password | string | 是 | 明文密码 |
| type | int | 否,默认 0 | 0=内部用户,1=客户 |
**响应 `LoginResultDto`**
```json
{
"accessToken": "eyJ...",
"refreshToken": "eyJ...",
"tokenType": "Bearer",
"expiresIn": 3600,
"userId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```
**业务逻辑**
1. 按 `type` 分流到 `LoginAdminAsync` 或 `LoginCustomerAsync`。
2. 客户登录失败统一提示「客户账号或密码错误」(不暴露具体原因)。
3. 子客户登录额外校验主客户未禁用/冻结。
**异常**
| 场景 | 错误信息 |
| --- | --- |
| 不支持的 type | 不支持的登录类型 |
| 客户凭证错误 | 客户账号或密码错误 |
| 客户已禁用/冻结 | 客户已禁用 / 客户已冻结 |
| 子客户已禁用/冻结 | 子客户已禁用 / 子客户已冻结 |
| 未关联 RBAC 用户 | 客户未关联用户 |
#### 8.1.2 `POST /rbac/auth/refresh-token`
**请求体**
```json
{ "refreshToken": "eyJ..." }
```
**响应**:同 `LoginResultDto`。
#### 8.1.3 `GET /rbac/auth/get-current-user`
**响应 `UserInfoDto`**
```json
{
"id": "guid",
"name": "管理员",
"account": "admin",
"email": "admin@example.com",
"avatar": "/uploads/avatars/xxx.jpg",
"organizationId": "guid",
"organizationName": "总公司",
"roles": [{ "id": "guid", "name": "超级管理员", "code": "Admin" }],
"permissions": ["Rbac.Users.Query", "Rbac.Roles.Query"],
"menus": [{ "id": "guid", "pId": "00000000-0000-0000-0000-000000000000", "name": "系统管理", "permissionCode": "Rbac.Users", "type": 1, "route": "/system", "children": [] }],
"createdAt": "2026-01-01T00:00:00Z",
"createdBy": "system",
"updatedAt": null,
"updatedBy": null
}
```
#### 8.1.4 `POST /rbac/auth/change-password`
**请求体 `OmsChangePasswordInput`**
```json
{ "oldPassword": "123456", "newPassword": "654321" }
```
- 内部用户:走 `AccountManager.UpdatePasswordAsync`。
- 客户:校验 `t_customer` MD5 旧密码后更新。
#### 8.1.5 `PUT /rbac/auth/update-profile`
**请求体 `OmsUpdateProfileInput`**:`{ name?, email? }`
- 仅内部用户可用;客户调用返回「当前登录主体暂不支持此操作」。
#### 8.1.6 `POST /rbac/auth/upload-avatar`
- Content-Type:`multipart/form-data`,字段 `file`。
- 支持 jpg/jpeg/png/gif/webp,最大 10MB,自动缩放至 200px。
- 返回头像 URL 字符串,如 `/uploads/avatars/{userId}_{timestamp}.jpg`。
---
### 8.2 用户接口
#### 8.2.1 `GET /rbac/user/get-list`
**Query 参数**
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| Keyword | string | 搜索名称或账号 |
| RoleId | guid | 按角色筛选 |
| Page | int | 页码,从 1 开始 |
| PageSize | int | 每页条数 |
| OrderBy | string | 排序字段 |
| Descending | bool | 是否降序 |
**响应 `UserDtoPagedResultDto`**
```json
{
"total": 100,
"items": [
{
"id": "guid",
"name": "张三",
"account": "zhangsan",
"email": "zs@example.com",
"avatar": null,
"roles": [{ "id": "guid", "name": "运营", "code": "Operator" }],
"createdAt": "...",
"createdBy": "...",
"updatedAt": null,
"updatedBy": null
}
]
}
```
#### 8.2.2 `POST /rbac/user/create`
**请求体 `UserCreateDto`**
```json
{
"name": "张三",
"account": "zhangsan",
"password": "123456",
"email": "zs@example.com",
"avatar": null,
"roleIds": ["role-guid-1"]
}
```
#### 8.2.3 `PUT /rbac/user/update`
- Query:`id`(用户 GUID)
- Body `UserUpdateDto`:`{ name?, email?, avatar? }`
#### 8.2.4 `POST /rbac/user/assign-roles`
- Query:`userId`(GUID)
- Body:`string[]`(角色 GUID 列表)
#### 8.2.5 `PUT /rbac/user/update-password`
- Query:`id`
- Body `UpdatePasswordDto`:`{ oldPassword, newPassword }`
#### 8.2.6 `DELETE /rbac/user/delete` / `delete-batch`
- 单删 Query:`id`
- 批删 Query:`ids`(数组)
---
### 8.3 角色接口
#### 8.3.1 `POST /rbac/role/create`
**请求体 `RoleCreateDto`**
```json
{
"name": "运营专员",
"code": "Operator",
"permissions": ["Api.Interfaces.Query", "Api.Logs.Query"]
}
```
#### 8.3.2 `PUT /rbac/role/update`
- Query:`id`
- Body `RoleUpdateDto`:`{ name? }`(仅可改名称)
#### 8.3.3 `POST /rbac/role/assign-permissions`
- Query:`roleId`
- Body:`string[]`(权限码列表,全量覆盖式分配)
#### 8.3.4 `GET /rbac/role/get-role-permissions`
- Query:`roleId`
- 响应:`string[]`
---
### 8.4 菜单接口
#### 8.4.1 `POST /rbac/menu/create`
**请求体 `MenuCreateDto`**
```json
{
"pId": "00000000-0000-0000-0000-000000000000",
"name": "用户管理",
"permissionCode": "Rbac.Users.Query",
"type": 1,
"icon": "icon-user",
"route": "/system/users",
"component": "views/system/users/index",
"order": 10,
"isHidden": false,
"isCached": true,
"description": null
}
```
#### 8.4.2 `PUT /rbac/menu/update`
- Query:`id`
- Body `MenuUpdateDto`:`{ name?, permissionCode?, icon?, route?, component?, isHidden?, isCached?, description?, order? }`
#### 8.4.3 `POST /rbac/menu/move-menu`
- Query:`menuId`、`targetParentId`
---
### 8.5 权限接口
#### 8.5.1 `GET /rbac/permission/get-permission-tree`
**响应 `PermissionGroupDto[]`**
```json
[
{
"code": "Rbac",
"name": "权限管理",
"permissions": [
{
"code": "Rbac.Users",
"name": "用户管理",
"parentCode": null,
"description": null,
"children": [
{ "code": "Rbac.Users.Query", "name": "查询", "parentCode": "Rbac.Users", "children": [] }
]
}
]
}
]
```
#### 8.5.2 `POST /rbac/permission/check-permissions`
- Query:`userId`
- Body:`string[]`(待检查的权限码)
- 响应:`Record<string, boolean>`,如 `{ "Rbac.Users.Query": true, "Rbac.Users.Delete": false }`
---
### 8.6 组织接口
#### 8.6.1 `POST /rbac/organization/create`
**请求体 `OrganizationCreateDto`**
```json
{
"parentId": null,
"name": "技术部",
"code": "TECH",
"type": 1,
"leaderId": null,
"order": 1,
"description": null,
"isEnabled": true
}
```
#### 8.6.2 `POST /rbac/organization/move`
- Query:`id`
- Body `OrganizationMoveDto`:目标父节点信息
---
### 8.7 客户接口(OMS + RBAC 联动)
#### 8.7.1 `POST /customer/create` — 创建主客户
**请求体 `CreateCustomerInput`(主要字段)**
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| customerCode | string(32) | 是 | 客户编号,全局唯一 |
| customerName | string(128) | 是 | 客户名称 |
| phone | string(16) | 是 | 手机号,全局唯一 |
| email | string(64) | 否 | 邮箱,全局唯一 |
| password | string | 是,≥6 位 | 登录密码(OMS 侧 MD5 存储) |
| status | int | 否,默认 1 | 0/1/2 |
| balance / totalQuota / usedQuota | decimal | 否 | 额度 |
| salesman / customerService | string | 否 | 销售/客服 |
| customerGroup | int | 否 | 客户分组 |
**核心流程**
1. 校验客户编号、手机、邮箱唯一性。
2. `UserService.CreateAsync`:创建 RBAC 用户(`Account=phone`,`Name=customerName`)。
3. `UserService.AssignRolesAsync`:分配 `Customer` 角色。
4. 读取 `user.userid` 写入 `t_customer.UserId`。
5. 插入 `t_customer`(`ParentCustomerId=null`)。
6. 任一步失败则回滚已创建的 RBAC 用户。
#### 8.7.2 `POST /customer/sub/create` — 创建子客户
**请求体**:`CreateSubCustomerInput` = `CreateCustomerInput` + `permissions: string[]`
**核心流程**
1. 当前登录主体必须是**主客户**(`ParentCustomerId IS NULL`)。
2. 校验主客户状态正常。
3. 校验 `permissions` 中每个码 ⊆ 主客户当前权限;否则「子客户权限不能超出当前客户权限」。
4. 创建 RBAC 用户。
5. 创建角色 `CustomerSub_{userId}`,名称 `子客户-{customerName}`,写入 `permissions`。
6. 分配该专属角色给用户。
7. 插入 `t_customer`(`ParentCustomerId=主客户Id`)。
8. 失败时回滚角色和用户。
#### 8.7.3 `GET /customer/list`
**Query `CustomerPaginatedInput`**
| 参数 | 说明 |
| --- | --- |
| page | 页码,默认 1 |
| pageSize | 每页条数,默认 20 |
| keyword | 匹配编号/名称/手机/邮箱 |
| status | 状态筛选 |
| customerGroup | 分组筛选 |
| salesman / customerService | 销售/客服筛选 |
**响应**:`{ items: CustomerPaginatedOutput[], total: number }`
#### 8.7.4 `POST /customer/reset-password`
- Body:`{ id, newPassword }`
- 直接更新 `t_customer.Password`(MD5),不影响 RBAC 密码。
---
## 9 前端集成
### 9.1 API 客户端
- 路径:`src/api/rbac/generated/`(由 OpenAPI 自动生成)。
- 统一前缀:`/rbac/*`;Bearer Token 由 `OpenAPI.TOKEN` 注入。
### 9.2 状态管理(`src/store/modules/rbac-user.ts`)
| 状态 | 说明 |
| --- | --- |
| `accessToken` / `refreshToken` | JWT |
| `userInfo` | `UserInfoDto`,含 roles、permissions、menus |
| `loginPortalType` | 0=管理端,1=客户端 |
| `permissions` | 计算属性,来自 `userInfo.permissions` |
| `menus` | 计算属性,来自 `userInfo.menus` |
**登录调用**:`AuthService.postRbacAuthLogin({ account, password, type })`
**拉用户信息**:`AuthService.getRbacAuthGetCurrentUser()`
### 9.3 路由权限(`meta.permission`)
前端路由在 `src/router/routes/newModules/` 配置 `meta.permission`,示例:
| 路由模块 | permission 示例 |
| --- | --- |
| 系统管理 | `Rbac.Users.Query`、`Rbac.Roles.Query` |
| 接口管理 | `Api.Interfaces.Query` |
| 轨迹中心 | `Track.Orders.Query` |
| 首页 | `Home.Monitor` |
路由守卫 `src/router/guard/permission.ts` 读取 `meta.permission` 并调用 `hasPermission()`。
### 9.4 权限校验函数现状
`src/utils/rbac-permission.ts` 中 `hasPermission` / `hasAnyPermission` / `hasAllPermissions` **当前固定返回 `true`**(临时放开菜单权限)。
恢复时应改回:
```typescript
return useRbacUserStore().permissions.includes(permission)
```
### 9.5 动态菜单
- 登录后 `get-current-user` 返回的 `menus` 即为服务端按权限裁剪后的菜单树。
- 亦可调用 `MenuService.getRbacMenuGetUserMenus(userId)` 单独获取。
---
## 10 种子数据与默认账号
### 10.1 RBAC 种子(Fake.Rbac.Infrastructure)
- 自动创建默认管理员:账号 `admin`,密码 `123456`,角色「超级管理员」。
- 初始化系统管理菜单(组织、用户、角色、菜单等)。
### 10.2 OMS 客户角色种子(`CustomerRoleSeedContributor`)
- 启动时若不存在则创建角色:`Code=Customer`,`Name=客户`,`Permissions=[]`。
- 客户主账号创建时分配此角色;具体业务权限需后续通过 `assign-permissions` 配置。
### 10.3 生产环境注意
- 部署后立即修改默认管理员密码。
- JWT `SecretKey` 通过 Consul(`idwms/admin`)或本地配置注入,禁止硬编码弱密钥。
---
## 附录 A 关键源码文件索引
| 用途 | 路径 |
| --- | --- |
| 宿主模块 / RBAC 注册 | `SimpleAdmin.Api/SimpleAdminApiModule.cs` |
| 自定义认证(双端登录) | `SimpleAdmin.Api/Auth/AuthService.cs` |
| 认证 DTO / 登录类型 | `SimpleAdmin.Api/Auth/AuthRequestDtos.cs` |
| JWT Claims 扩展 | `SimpleAdmin.Api/SimpleJwtService.cs` |
| OMS Claim 类型 | `SimpleAdmin.Api/SimpleOmsClaimTypes.cs` |
| 客户角色种子 | `SimpleAdmin.Api/Auth/CustomerRoleSeedContributor.cs` |
| 客户 ↔ RBAC 联动 | `Oms.Application/Services/Customers/CustomerService.cs` |
| 子客户权限入参 | `Oms.Application/Dtos/Customers/CreateSubCustomerInput.cs` |
| OMS 客户表 SQL | `db.sql` → `t_customer` |
| 前端 OpenAPI 客户端 | `oms0414/src/api/rbac/generated/` |
| 前端用户 Store | `oms0414/src/store/modules/rbac-user.ts` |
| 前端权限工具 | `oms0414/src/utils/rbac-permission.ts` |
## 附录 B Swagger 分组
| 分组名 | 内容 |
| --- | --- |
| RBAC | 认证、用户、角色、菜单、权限、组织 |
| OMS-Customer | 客户资料 CRUD + 子客户创建 |
本地调试可访问 Fake Swagger UI 查看完整 OpenAPI 契约;前端 `src/api/rbac/generated` 应与 Swagger 保持同步重新生成。