- 新增小红书前端模块(启动服务、内容分析、爆款创作、发布记录、技能卡片、拟人设置、AI 助手) - 新增 xhs-agent-desktop 独立桌面 Agent,对接 OMS 任务调度 - 登录页重构,整合微信登录与 OMS JWT 认证 - 订单、渠道、用户中心等页面与 API 增强 Co-authored-by: Cursor <cursoragent@cursor.com>
40 KiB
RBAC 与统一认证 — 产品需求文档(PRD)
代码基线:
C:\Users\wilso\Desktop\SimpleAdmin.Api-master(后端)+E:\oms0414(前端 OpenAPI 客户端)
RBAC 内核:NuGet 包Fake.Rbac.Application/Fake.Rbac.Infrastructurev8.0.5.11
API 前缀:/rbac/*(RBAC 管理)、/customer/*(OMS 客户资料,含 RBAC 联动)
导读
这份文档解决什么问题?
系统当前实现要搞清楚四件事:
- 谁能登录 — 内部用户(管理端)与客户(OMS 端)共用 RBAC
user表;客户登录凭证存t_customer,通过user.userid与 RBAC 关联。 - 登录之后凭什么进业务 — JWT Access Token + Refresh Token;Claims 含
mapId(OMS 整型用户编号)与loginType(0=内部用户,1=客户)。 - 能操作哪些功能 — 用户有效权限 = 其所有角色在
role_permission中权限码的并集;子客户通过专属角色CustomerSub_{userId}收窄权限;菜单按权限码裁剪展示,不替代 API 鉴权。 - 客户与 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 关联关系图
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.InfrastructureEF 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):
- 取用户有效权限码集合
P。 - 选出
menu.PermissionCode ∈ P的节点。 - 递归补齐所有祖先节点(保证树结构完整)。
- 按
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)
不支持微信登录。管理端仅提供账号 + 密码方式,登录页不展示微信扫码入口。
POST /rbac/auth/login,body:{ account, password, type: 0 }AccountManager.ValidateCredentialsAsync校验user.Account+ 哈希密码。- 生成 Access Token + Refresh Token。
- 前端调用
GET /rbac/auth/get-current-user拉取用户信息、角色、权限、菜单。
4.3.2 客户(type=1,客户端 /login)
客户端支持 账号密码 与 微信扫码 两种方式(见 4.3.4)。
POST /rbac/auth/login,body:{ account, password, type: 1 }- 按手机 → 邮箱 → 客户编号顺序查找
t_customer。 - 校验客户状态(子客户还需校验主客户状态)。
- 比对
t_customer.Password(MD5)。 - 通过
user.userid查 RBAC GUID,生成 Token(loginType=1)。 - 更新
LastLoginIp、LastLoginTime。
4.3.3 刷新令牌
POST /rbac/auth/refresh-token,body:{ refreshToken }- 校验 Refresh Token 有效性,解析
loginType。 - 客户刷新:通过
mapId重新查 GUID 并签发新 Token。 - 内部用户刷新:通过 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 倒计时),待对接后端 |
微信开放平台配置(网站应用扫码登录)
采用 微信开放平台 · 网站应用 · 微信登录 方案:
| 配置项 | 说明 |
|---|---|
AppID / AppSecret |
网站应用凭证,存 Consul / 环境变量,禁止写入前端 |
| 授权回调域 | 与 OMS 客户门户同域,如 oms.example.com |
| 业务 scope | snsapi_login(扫码确认后换取 OpenID) |
端到端流程
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
步骤说明:
- 申请二维码:前端调用
GET /rbac/auth/wechat/qrcode,后端生成唯一ticket(即 OAuthstate),返回可嵌入<img>的qrUrl(微信showqrcode或自建二维码图)。 - 用户扫码:微信 App 打开授权页,用户确认登录。
- 服务端回调:微信重定向
GET /rbac/auth/wechat/callback?code&state;后端用code换openid(及昵称、头像)。 - 客户匹配:
openid→t_customer.WechatOpenid;命中则走与密码登录相同的 Token 签发逻辑(mapId = user.userid,loginType = 1)。 - 前端轮询:
poll接口在 ticket 变为success时返回 Token 对;前端复用现有rbac-userstore 落盘与跳转逻辑。 - 刷新二维码:ticket 过期或
cancelled时,前端重新调用qrcode接口。
待实现 API 契约
1. 获取扫码 ticket
GET /rbac/auth/wechat/qrcode
响应:
{
"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
{
"account": "admin",
"password": "123456",
"type": 0
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| account | string | 是 | 内部用户=账号;客户=手机/邮箱/客户编号 |
| password | string | 是 | 明文密码 |
| type | int | 否,默认 0 | 0=内部用户,1=客户 |
响应 LoginResultDto
{
"accessToken": "eyJ...",
"refreshToken": "eyJ...",
"tokenType": "Bearer",
"expiresIn": 3600,
"userId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
业务逻辑
- 按
type分流到LoginAdminAsync或LoginCustomerAsync。 - 客户登录失败统一提示「客户账号或密码错误」(不暴露具体原因)。
- 子客户登录额外校验主客户未禁用/冻结。
异常
| 场景 | 错误信息 |
|---|---|
| 不支持的 type | 不支持的登录类型 |
| 客户凭证错误 | 客户账号或密码错误 |
| 客户已禁用/冻结 | 客户已禁用 / 客户已冻结 |
| 子客户已禁用/冻结 | 子客户已禁用 / 子客户已冻结 |
| 未关联 RBAC 用户 | 客户未关联用户 |
8.1.2 POST /rbac/auth/refresh-token
请求体
{ "refreshToken": "eyJ..." }
响应:同 LoginResultDto。
8.1.3 GET /rbac/auth/get-current-user
响应 UserInfoDto
{
"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
{ "oldPassword": "123456", "newPassword": "654321" }
- 内部用户:走
AccountManager.UpdatePasswordAsync。 - 客户:校验
t_customerMD5 旧密码后更新。
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
{
"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
{
"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
{
"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
{
"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[]
[
{
"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
{
"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) | 是 | 手机号,全局唯一 |
| string(64) | 否 | 邮箱,全局唯一 | |
| password | string | 是,≥6 位 | 登录密码(OMS 侧 MD5 存储) |
| status | int | 否,默认 1 | 0/1/2 |
| balance / totalQuota / usedQuota | decimal | 否 | 额度 |
| salesman / customerService | string | 否 | 销售/客服 |
| customerGroup | int | 否 | 客户分组 |
核心流程
- 校验客户编号、手机、邮箱唯一性。
UserService.CreateAsync:创建 RBAC 用户(Account=phone,Name=customerName)。UserService.AssignRolesAsync:分配Customer角色。- 读取
user.userid写入t_customer.UserId。 - 插入
t_customer(ParentCustomerId=null)。 - 任一步失败则回滚已创建的 RBAC 用户。
8.7.2 POST /customer/sub/create — 创建子客户
请求体:CreateSubCustomerInput = CreateCustomerInput + permissions: string[]
核心流程
- 当前登录主体必须是主客户(
ParentCustomerId IS NULL)。 - 校验主客户状态正常。
- 校验
permissions中每个码 ⊆ 主客户当前权限;否则「子客户权限不能超出当前客户权限」。 - 创建 RBAC 用户。
- 创建角色
CustomerSub_{userId},名称子客户-{customerName},写入permissions。 - 分配该专属角色给用户。
- 插入
t_customer(ParentCustomerId=主客户Id)。 - 失败时回滚角色和用户。
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(临时放开菜单权限)。
恢复时应改回:
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 保持同步重新生成。