- 新增小红书前端模块(启动服务、内容分析、爆款创作、发布记录、技能卡片、拟人设置、AI 助手) - 新增 xhs-agent-desktop 独立桌面 Agent,对接 OMS 任务调度 - 登录页重构,整合微信登录与 OMS JWT 认证 - 订单、渠道、用户中心等页面与 API 增强 Co-authored-by: Cursor <cursoragent@cursor.com>
1147 lines
40 KiB
Markdown
1147 lines
40 KiB
Markdown
# 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 保持同步重新生成。
|