107 KiB
RBAC 与统一认证 — 规范说明
导读
这份文档解决什么问题?
用直白话说,系统要搞清楚四件事:
- 谁能登录(内部员工、客户主账号、客户子账号)——账号与凭证放在哪张表、怎么区分类型。
- 登录之后凭什么进业务——访问令牌、刷新令牌,以及改权限、改密码后旧令牌如何作废。
- 能操作哪些功能——有效权限码来自
t_principal_role的角色并集;客户子账号还可由主账号在t_customer_sub_permission配可选白名单,与角色并集求交再收窄(无任何白名单行时等于仅用角色)。用意是:同一角色模板下仍可把某个子账号缩到更少能力,而不必为每人微调就新建一堆细角色;菜单由同一套有效权限码推导,并非与白名单并列的另一套「来源」。菜单只负责界面展示,不能代替接口鉴权。 - 出了问题怎么追溯——RBAC 关键动作与普通业务增删改,统一落在
t_operation_log(支持字段级、JSON 快照或 RBAC 动作行)。
更细的表字段、接口示例见后文各节。
正文层级怎么读?
编号用 点分十进制,层级越深小数位越多(与 Markdown 标题 ## → ### → #### → ##### 对应):
| 层级 | 示例 | 说明 |
|---|---|---|
| 章 | 1、2、… 5 | 五大块(表结构、令牌、接口清单、详细设计、客户域) |
| 节 | 1.1、2.1、4.2 | 章内分节(###) |
| 条 | 1.1.1、3.1.3、4.3.2 | 更细条目或接口分组(####) |
| 款 | 4.2.1.1、4.2.1.2 | 单个接口下的「请求示例」「业务逻辑」(#####) |
| 目 | 4.2.1.2.1、4.2.1.2.2 | 「核心流程」「校验与异常」等(######) |
五章一览:1 表结构 · 2 令牌与鉴权 · 3 接口路径速查 · 4 接口详细设计 · 5 客户域接口详细设计(5.1 OMS-Customer 资料 6 接口 · 5.2 主/子账号 RBAC 目标 8 接口,结构与 4 相同)。
建议怎么读?
| 你是谁 | 建议路径 |
|---|---|
| 后端 / DBA | 1.1 概念与 1.1.6 落地自检 → 1.2 起各表;需要时看 4 |
| 前端 / BFF | 2 → 3 → 4(管理端 RBAC);客户资料与 5.1、客户 RBAC 目标 5.2;参数与异常看对应章节 |
| 产品 / 测试 | 1.1 前几段 + 2.2 客户子账号表 + 5(5.1 客户资料 OpenAPI + 5.2 RBAC 设计项) |
第一次通读:1(概念 + 流程图 + 1.1.6)→ 2 → 再按需打开 3~5。
1 表结构(按链路自上而下)
本章导读:先读 1.1「概念模型」四件事和流程图,建立「登录账号 → 主体 → 角色 → 权限码」;再按需翻到 1.2 起各数据表。三张枢纽表:
t_auth_account、t_principal_role、role_permission。
1.1 RBAC 概念模型与设计原则(主体·组织·角色·权限·菜单)
本段回答四件事:谁在登录、凭什么进系统、能做什么(权限从哪来)、菜单与 API 的分工。以下与 2、2.2 一致。
1.1.1 设计目标
- 统一身份:所有可登录主体(内部用户、客户主账号、客户子账号)各对应
t_auth_account一行(PrincipalType+PrincipalId唯一),登录凭证(手机、邮箱、口令、微信等)只落在认证表,不散落在业务主体表。 - 统一授权:主体与角色的绑定只通过
t_principal_role;角色拥有的权限码只通过role_permission;权限码的合法集合由t_permission_definition定义。API 与后台任务不得以未注册的字符串当作权限码。 - 可吊销、可版本化:授权变更、改密、全员登出等通过
PermissionRevision/SessionVersion与令牌中的permission_rev/session_ver对齐(详见 2),避免「改权限后旧令牌长期有效」。 - 可审计:RBAC 关键动作与任意业务对数据的增删改均落
t_operation_log(见 1.13):业务侧可选 字段级、快照(OldSnapshotJson/NewSnapshotJson,前端 diff);RBAC 动作用Action为动作码 +DetailJson/TargetPrincipal*/RoleId等,与订单/客户等共用同一追溯模型。
1.1.2 三层分工(身份 / 授权 / 展示)
| 层次 | 回答的问题 | 核心对象 |
|---|---|---|
| 身份(Authentication) | 你是谁?凭证是否有效? | t_auth_account → 映射到业务主体 |
| 授权(Authorization) | 你被允许执行哪些 PermissionCode? | t_principal_role → role → role_permission → ⊆ t_permission_definition |
| 展示(UI,非鉴权替代) | 控制台显示哪些菜单项? | menu(每节点绑定一个 PermissionCode,用于裁剪,不能替代 API 侧校验) |
1.1.3 有效权限码集合 P 怎么算
记某主体通过 t_principal_role 绑定的所有角色,其 role_permission 中权限码的并集为 (P_{\text{role}})(均在 t_permission_definition 中有定义且未删除)。
-
PrincipalType = user或customer(主账号)
(P = P_{\text{role}})。 -
PrincipalType = customer_sub(子账号)
先算 (P_{\text{role}})。若t_customer_sub_permission对该子账号存在未删除白名单行,则 (P = P_{\text{role}} \cap \text{白名单码集});若无白名单行,则 (P = P_{\text{role}})。
风控约束:子账号权限必须不超过其主客户主账号的权限集合((P \subseteq P_{\text{主}})),保存时事务内强校验;细节与接口见 2.2、5。
1.1.4 组织、字典、客户域在 RBAC 中的位置
- 组织
organization:仅与内部用户user.OrganizationId关联,用于人事与行级数据范围等业务查询;不是权限码,也不替代PermissionCode。客户主账号、子账号不挂组织树。 - 字典
t_dict:存放岗位、学历、客户类型、结算方式等可配置枚举(亦可供其他模块扩展DictType,如notice_type、warehouse_type);业务表存DictCode,按DictType+DictCode联查DictName,须IsDeleted=0;列表可按DictSort排序(见 1.14)。 - 令牌
org_id:对客户类主体通常为空;内部用户可按实现携带,用于 BFF 做数据范围过滤(与权限码校验正交)。
1.1.5 分层职责一览表
| 层级 | 对象 | 职责 |
|---|---|---|
| 认证 | t_auth_account |
手机 + 邮箱注册必填;口令;微信(可选);PermissionRevision/SessionVersion;PrincipalType + PrincipalId → 业务主体 |
| 主体 | user / t_customer / t_customer_sub_account |
业务专有资料;不存口令/手机/邮箱、微信头像/微信昵称(见 t_auth_account);user 另存员工档案(岗位/学历等为 t_dict 编码等);t_customer 另存工商+执照影像、客户类型/结算方式(字典)、应收财务等;user.Avatar 仅为本地上传等非微信展示;不直接存权限集合 |
| 组织 | organization |
仅 user.OrganizationId;数据范围/人事,不是权限码 |
| 权限目录 | t_permission_definition |
权限码权威库;API / menu / role_permission 的 Code 须存在且未删 |
| 授权 | t_principal_role → role → role_permission |
PermissionCode 鉴权,码集 ⊆ 权限目录 |
| 菜单 | menu |
按 PermissionCode 裁剪前端路由/菜单,不替代 API 校验 |
| 审计与操作日志 | t_operation_log |
全系统统一表:RBAC 动作(一行一动作,Action 为动作码)与 业务数据 ADD/UPDATE/DELETE(字段级或 快照);业务与定时任务、登录主体均可写入 |
关联小结:主体 → 角色 只经 t_principal_role;客户子账号可再经 t_customer_sub_permission 做交集收紧。下图与上表一致,从登录身份走到「权限码集合 (P)」再分流到 API 与菜单。
flowchart LR
A[t_auth_account] --> B["主体 user / customer / customer_sub"]
B --> C[t_principal_role]
C --> D[role]
D --> E[role_permission]
E --> F["有效权限码集合 P"]
E -.-> I[t_permission_definition]
W[t_customer_sub_permission] -.->|"子账号:与白名单求交(可选)"| F
F --> G["API:校验 PermissionCode"]
F --> H["menu:按 PermissionCode 过滤"]
H -.-> I
1.1.6 落地自检(实现/联调)
- 任意受保护 API 的 PermissionCode 必须 ∈
t_permission_definition且未删除;menu.PermissionCode列长度与定义表一致(1.11)。 - 写入
t_principal_role或子账号t_customer_sub_permission成功后,对应主体PermissionRevision+1(与 2 令牌策略一致)。 - 分配角色前校验
role.AllowedPrincipalTypes与主体类型匹配。 - 子账号保存角色/白名单时 (P_{\text{子}}\subseteq P_{\text{主}}) 强校验;违反则整单 400(见 2.2)。
- 关键写操作(含 RBAC 与业务数据)写
t_operation_log;改密、吊销会话等SessionVersion规则见 2。 - 业务数据 ADD/UPDATE/DELETE 按
t_operation_log规则落库(字段级或快照二选一为主、批量不合并、系统操作OpPrincipalType=system等,见 1.13)。
1.2 统一认证账号表 t_auth_account
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|---|---|---|---|---|---|
| Id | BIGINT | - | 主键 | 是 | 自增主键 |
| Phone | VARCHAR | 16 | - | 是 | 注册必填;全局唯一 |
| VARCHAR | 64 | - | 是 | 注册必填;全局唯一 | |
| PasswordHash | VARCHAR | 256 | - | 否 | 密码哈希;可与微信并存;纯微信快捷登录且无本地口令时可空(须已完成手机+邮箱注册并绑定微信) |
| PasswordSalt | VARCHAR | 128 | - | 否 | 密码盐值;无本地口令时可为空 |
| WechatOpenid | VARCHAR | 64 | - | 否 | 可选;便捷登录,非开户凭证;绑定前须已有手机+邮箱注册行 |
| WechatUnionid | VARCHAR | 64 | - | 否 | 微信 UnionID(跨应用识别,可选) |
| WechatAvatar | VARCHAR | 255 | - | 否 | 微信头像 URL(OAuth/授权回调同步;各主体认证行均可有值) |
| WechatNickname | VARCHAR | 64 | - | 否 | 微信昵称(展示;与 OpenID 同源更新) |
| PrincipalType | VARCHAR | 32 | - | 是 | 主体类型:user(内部用户)/ customer(客户主账号)/ customer_sub(客户子账号) |
| PrincipalId | BIGINT | - | - | 是 | 主体业务主键:分别关联 user.Id、t_customer.Id、t_customer_sub_account.Id(均为 BIGINT,非 GUID) |
| Status | TINYINT | - | - | 是 | 状态:0-禁用,1-启用 |
| LastLoginTime | DATETIME | - | - | 否 | 最后登录时间 |
| LastLoginIp | VARCHAR | 64 | - | 否 | 最后登录 IP |
| PermissionRevision | INT | - | - | 是 | 该主体 t_principal_role 或(customer_sub)t_customer_sub_permission 白名单变更时 +1;access token permission_rev 与库一致(见 2) |
| SessionVersion | INT | - | - | 是 | 改密、全部登出、冻结等 +1;refresh 内 session_ver 对齐 |
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
| CreateUserId | INT | - | - | 是 | 创建人 |
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
| UpdateUserId | INT | - | - | 否 | 更新人 |
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
注册:
Phone、WechatAvatar/WechatNickname同源;业务表不重复存手机/邮箱。IsDeleted=0时PrincipalType+PrincipalId唯一(每主体一行认证账号)。先扫码绑微信再开户 / 先开户再补绑 见 2.3。
Id与PrincipalId:本表Id为认证行主键;对外主体标识(登录响应principalId、令牌 claims)使用PrincipalId(业务主体主键),二者勿混用;请求参数约定见 4.1.1。
1.3 用户表 user
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|---|---|---|---|---|---|
| Id | BIGINT | - | 主键 | 是 | 自增主键 |
| Name | VARCHAR | 32 | - | 是 | 用户姓名(展示) |
| Avatar | VARCHAR | 128 | - | 否 | 本地上传等非微信头像 URL;微信头像见 t_auth_account.WechatAvatar |
| PositionCode | VARCHAR | 64 | - | 否 | 岗位:存 t_dict.DictCode,DictType=user_position;联查 DictName(IsDeleted=0);下拉可按 DictSort |
| Age | TINYINT | - | - | 否 | 年龄(周岁);可与 Birthday 冗余或仅维护其一 |
| IdCardNo | VARCHAR | 18 | - | 否 | 身份证号;敏感须脱敏;建议 IsDeleted=0 时全局唯一 |
| Birthday | DATE | - | - | 否 | 生日 |
| EducationCode | VARCHAR | 64 | - | 否 | 学历:存 t_dict.DictCode,DictType=user_education;联查 DictName(IsDeleted=0);下拉可按 DictSort |
| Address | VARCHAR | 255 | - | 否 | 住址 |
| GraduateSchool | VARCHAR | 128 | - | 否 | 毕业学校 |
| EmergencyContact | VARCHAR | 64 | - | 否 | 紧急联系人姓名 |
| EmergencyContactPhone | VARCHAR | 32 | - | 否 | 紧急联系电话 |
| HireDate | DATE | - | - | 否 | 入职时间 |
| LeaveDate | DATE | - | - | 否 | 离职时间;在职为空 |
| WarehouseId | BIGINT | - | 外键(t_warehouse_address.Id) |
否 | 归属/作业仓库 |
| ConcurrencyStamp | INT | - | - | 是 | 乐观并发版本号(从 0 递增) |
| CreateUserId | INT | - | - | 是 | 创建人 |
| CreateTime | DATETIME(6) | - | - | 是 | 创建时间 |
| UpdateUserId | INT | - | - | 是 | 更新人 |
| UpdateTime | DATETIME(6) | - | - | 是 | 更新时间 |
| IsDeleted | TINYINT(1) | - | - | 是 | 是否删除:0-否,1-是,默认0 |
| OrganizationId | BIGINT | - | - | 否 | 所属组织ID |
手机、邮箱、微信头像/昵称见
t_auth_account(PrincipalType=user,PrincipalId=本表.Id)。员工人事类字段(证件、入离职、仓库等)仅存本表;岗位/学历存t_dict.DictCode(user_position、user_education),联查与校验见 1.14。档案字段的读写:管理端POST /rbac/user/create、PUT /rbac/user/update、GET /rbac/user/get、列表见 4.6(与下表字段一一对应;注册场景不必带档案,见导读与 4.6 说明)。
1.4 客户表 t_customer
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|---|---|---|---|---|---|
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
| CustomerCode | VARCHAR | 32 | - | 是 | 客户编号,规则:前缀w+年份+序号(如W2501) |
| CustomerName | VARCHAR | 128 | - | 是 | 客户名称(平台主展示;可与 CompanyName 并存) |
| CompanyName | VARCHAR | 256 | - | 否 | 公司名称(工商全称) |
| BusinessLicenseNo | VARCHAR | 32 | - | 否 | 营业执照号码;敏感须脱敏 |
| BusinessLicenseImageUrl | VARCHAR | 512 | - | 否 | 执照扫描件/照片 URL |
| LegalRepresentative | VARCHAR | 64 | - | 否 | 法定代表人 |
| Website | VARCHAR | 255 | - | 否 | 官网 URL |
| SettlementMethodCode | VARCHAR | 64 | - | 否 | 结算方式:存 t_dict.DictCode,DictType=customer_settlement_method;联查 DictName(IsDeleted=0);下拉可按 DictSort |
| CustomerTypeCode | VARCHAR | 64 | - | 否 | 客户类型:存 t_dict.DictCode,DictType=customer_type;联查 DictName(IsDeleted=0);下拉可按 DictSort |
| AppId | VARCHAR | 64 | - | 否 | 应用 Id(开放平台/子应用/对接侧约定的字符串标识) |
| ReceivableFinanceUserId | BIGINT | - | 外键(user.Id) |
否 | 所属应收财务(内部用户) |
| Balance | DECIMAL | 18,2 | - | 是 | 账户余额,默认0 |
| TotalQuota | DECIMAL | 18,2 | - | 是 | 总额度,默认0 |
| UsedQuota | DECIMAL | 18,2 | - | 是 | 已用额度,默认0 |
| Status | TINYINT | - | - | 是 | 状态:0-禁用,1-正常,2-冻结 |
| Remark | VARCHAR | 512 | - | 否 | 备注信息 |
| CreateUserId | INT | - | - | 是 | 创建人(系统/管理员账号) |
| CreateTime | DATETIME | - | - | 是 | 创建时间,默认当前时间 |
| UpdateUserId | INT | - | - | 否 | 更新人 |
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
| Salesman | VARCHAR | 32 | - | 否 | 所属销售员账号 |
| CustomerService | VARCHAR | 32 | - | 否 | 所属客服账号 |
| CustomerGroup | TINYINT | - | - | 否 | 客户分组:0-普通客户,1-VIP1,2-VIP2,3-VIP3,4-VIP4,5-VIP5 |
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
CustomerGroup 枚举:0-普通客户、1-VIP1、2-VIP2、3-VIP3、4-VIP4、5-VIP5
权限
t_principal_role;手机/邮箱与微信/最后登录见t_auth_account(微信非注册必须)。客户类型、结算方式存t_dict.DictCode(customer_type、customer_settlement_method),联查DictName且IsDeleted=0;应收财务指向user。管理端客户资料与重置密码:OpenAPI OMS-Customer 见 5.1(请求/响应示例);主/子账号 RBAC 目标接口见 5.2。
1.5 子账号表 t_customer_sub_account
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|---|---|---|---|---|---|
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
| MainCustomerId | BIGINT | - | 外键(t_customer.Id) | 是 | 主客户ID |
| SubAccountCode | VARCHAR | 32 | - | 是 | 子账号编号,规则:主客户编号+"_"+序号 |
| SubStatus | TINYINT | - | - | 是 | 子账号状态:0-禁用,1-正常 |
| CreateUserId | INT | - | - | 是 | 创建人(主客户/管理员) |
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
| UpdateUserId | INT | - | - | 否 | 更新人 |
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是 |
独立登录:
t_auth_account(customer_sub+ 本表Id);Phone/t_principal_role。
1.6 组织表 organization
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|---|---|---|---|---|---|
| Id | BIGINT | - | 主键 | 是 | 自增主键 |
| ParentId | BIGINT | - | - | 否 | 父级组织ID |
| Name | VARCHAR | 128 | - | 是 | 组织名称 |
| Code | VARCHAR | 64 | - | 是 | 组织编码(唯一) |
| Type | VARCHAR | 32 | - | 是 | 组织类型 |
| LeaderId | BIGINT | - | - | 否 | 负责人用户ID |
| Order | INT | - | - | 是 | 排序 |
| Description | VARCHAR | 512 | - | 否 | 描述 |
| IsEnabled | TINYINT(1) | - | - | 是 | 是否启用:0-否,1-是 |
| ConcurrencyStamp | INT | - | - | 是 | 乐观并发版本号(从 0 递增) |
| CreateUserId | INT | - | - | 是 | 创建人 |
| CreateTime | DATETIME(6) | - | - | 是 | 创建时间 |
| UpdateUserId | INT | - | - | 是 | 更新人 |
| UpdateTime | DATETIME(6) | - | - | 是 | 更新时间 |
| IsDeleted | TINYINT(1) | - | - | 是 | 是否删除:0-否,1-是,默认0 |
行级数据范围由业务查询叠加
OrganizationId/org_id,非 RBAC 权限码。
1.7 角色表 role
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|---|---|---|---|---|---|
| Id | BIGINT | - | 主键 | 是 | 自增主键 |
| Name | VARCHAR | 32 | - | 是 | 角色名称 |
| Code | VARCHAR | 64 | - | 是 | 角色编码(唯一);与客户 /customer/* 入参 role 字符串同一套编码,长度须一致 |
| AllowedPrincipalTypes | VARCHAR | 128 | - | 是 | 逗号分隔 user/customer/customer_sub;写 t_principal_role 前校验 |
| ConcurrencyStamp | INT | - | - | 是 | 乐观并发版本号(从 0 递增) |
| CreateUserId | INT | - | - | 是 | 创建人 |
| CreateTime | DATETIME(6) | - | - | 是 | 创建时间 |
| UpdateUserId | INT | - | - | 是 | 更新人 |
| UpdateTime | DATETIME(6) | - | - | 是 | 更新时间 |
| IsDeleted | TINYINT(1) | - | - | 是 | 是否删除:0-否,1-是,默认0 |
1.8 角色权限关联表 role_permission
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|---|---|---|---|---|---|
| Id | BIGINT | - | 主键 | 是 | 自增主键 |
| RoleId | BIGINT | - | 外键(关联 role.Id) |
是 | 角色ID |
| PermissionCode | VARCHAR | 255 | - | 是 | 须 ∈ t_permission_definition.Code(未删) |
| CreateUserId | INT | - | - | 是 | 创建人 |
| CreateTime | DATETIME(6) | - | - | 是 | 创建时间 |
变更
role_permission不逐行刷各主体PermissionRevision时,须 短 access TTL(建议 ≤15 分钟) 或缓存失效(见 2)。
1.9 权限定义表 t_permission_definition
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|---|---|---|---|---|---|
| Id | BIGINT | - | 主键 | 是 | 自增主键 |
| Code | VARCHAR | 255 | - | 是 | 权限编码(全局唯一) |
| DisplayName | VARCHAR | 128 | - | 是 | 展示名 |
| Module | VARCHAR | 64 | - | 否 | 分组,供权限树 |
| SortOrder | INT | - | - | 是 | 排序,默认 0 |
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
| CreateUserId | INT | - | - | 是 | 创建人 |
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
| UpdateUserId | INT | - | - | 否 | 更新人 |
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
权威库;
get-all-permissions/get-permission-tree基于本表。
1.9.1 权限树与表字段(实现约定)
t_permission_definition无ParentId列 时,GET /rbac/permission/get-permission-tree(4.3.2)的children由实现生成:常见做法为按Module+SortOrder聚类,或维护 独立树配置/物化路径,须在 OpenAPI 与实现注释中写清来源;不得以未在t_permission_definition注册的节点 作为鉴权依据。
1.10 主体角色关联表 t_principal_role
与
t_auth_account主体维度一致;主体与角色的绑定只维护本表。扩展新主体类型时仅增加PrincipalType取值,并在role.AllowedPrincipalTypes、相关接口与鉴权中一并约定。
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|---|---|---|---|---|---|
| Id | BIGINT | - | 主键 | 是 | 自增主键 |
| PrincipalType | VARCHAR | 32 | - | 是 | 主体类型,与 t_auth_account 一致:user(内部用户)/ customer(客户主账号)/ customer_sub(客户子账号);预留扩展 |
| PrincipalId | BIGINT | - | - | 是 | 主体业务主键:对应 user.Id、t_customer.Id、t_customer_sub_account.Id 等(由 PrincipalType 解释,数据库层一般不建跨表 FK) |
| RoleId | BIGINT | - | 外键(关联 role.Id) |
是 | 角色 ID |
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
| CreateUserId | INT | - | - | 是 | 创建人 |
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
| UpdateUserId | INT | - | - | 否 | 更新人 |
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
IsDeleted=0时PrincipalType+PrincipalId+RoleId唯一;/rbac/user/assign-roles等即维护本表对应行。
1.11 菜单表 menu
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|---|---|---|---|---|---|
| Id | BIGINT | - | 主键 | 是 | 自增主键 |
| PId | BIGINT | - | - | 是 | 父级菜单ID(根节点为 0) |
| Name | VARCHAR | 64 | - | 是 | 菜单名称 |
| PermissionCode | VARCHAR | 255 | - | 是 | 须与 t_permission_definition.Code 等长语义一致(建议 255);须 ∈ 权限定义且未删;IsDeleted=0 时全局唯一 |
| Type | INT | - | - | 是 | 菜单类型 |
| Icon | VARCHAR | 64 | - | 否 | 图标 |
| Route | VARCHAR | 64 | - | 否 | 路由地址 |
| Component | VARCHAR | 64 | - | 否 | 组件路径 |
| Order | INT | - | - | 是 | 排序 |
| IsHidden | TINYINT(1) | - | - | 是 | 是否隐藏:0-否,1-是 |
| IsCached | TINYINT(1) | - | - | 是 | 是否缓存:0-否,1-是 |
| Description | VARCHAR | 256 | - | 否 | 描述 |
| ConcurrencyStamp | INT | - | - | 是 | 乐观并发版本号(从 0 递增) |
| CreateUserId | INT | - | - | 是 | 创建人 |
| CreateTime | DATETIME(6) | - | - | 是 | 创建时间 |
| UpdateUserId | INT | - | - | 是 | 更新人 |
| UpdateTime | DATETIME(6) | - | - | 是 | 更新时间 |
| IsDeleted | TINYINT(1) | - | - | 是 | 是否删除:0-否,1-是,默认0 |
menu.PermissionCode须在权限定义表中注册;长度与t_permission_definition.Code对齐,避免出现「权限码已注册但菜单列截断」。
1.12 子账号权限白名单表 t_customer_sub_permission
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|---|---|---|---|---|---|
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
| SubAccountId | BIGINT | - | 外键(t_customer_sub_account.Id) |
是 | 子账号 ID |
| PermissionCode | VARCHAR | 255 | - | 是 | 须 ∈ t_permission_definition.Code(与 role_permission 同源) |
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
| CreateUserId | INT | - | - | 是 | 创建人 |
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
有未删行:最终权限 = 子账号
t_principal_role角色并集 ∩ 白名单;无白名单行:= 角色并集。恒满足 ⊆ 主账号 (P_{\text{主}})(见 2.2)。变更后子账号认证行PermissionRevision+1。
1.13 统一审计与操作日志表 t_operation_log(RBAC 动作 + 业务:字段级 / 快照)
定位:RBAC 动作与业务数据变更统一写入本表;不另建与
t_operation_log职责重叠的 RBAC 专用审计表。按行类型使用不同列(勿在同一业务动作里混用字段级与快照以免重复):
- RBAC 动作行:一次权限/会话类操作 1 行,
Action为 动作码(非ADD/UPDATE/DELETE),DetailJson/TargetPrincipal*/RoleId等承载语义;FieldName、逐字段列与快照列通常为NULL。- 业务·字段级:
FieldName+OldValue/NewValue,便于 SQL 按字段过滤、合规导出「一行一字段」。- 业务·快照:
OldSnapshotJson/NewSnapshotJson存整条(或约定范围内的)对象前后态,后端不写 diff;前端深度对比展示差异。
若既要「前端好展示」又要「库内可按字段检索」,可采用 快照为主 + 异步任务根据快照生成字段级行(物化),或仅保留快照、检索走搜索引擎/数仓。
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|---|---|---|---|---|---|
| Id | BIGINT | - | 主键 | 是 | 自增主键 |
| OpPrincipalType | VARCHAR | 32 | - | 是 | 操作者主体类型:user / customer / customer_sub / system(定时任务等非登录主体) |
| OpPrincipalId | BIGINT | - | - | 否 | 操作者业务主键;system 时 NULL |
| OpDisplayName | VARCHAR | 64 | - | 是 | 操作者展示名快照(防删除后无法展示);system 时固定 系统自动 |
| OpTime | DATETIME(6) | - | - | 是 | 记录时间(服务端写入时刻) |
| ModuleCode | VARCHAR | 64 | - | 否 | 模块编码(如 rbac、order、customer),便于筛选 |
| TargetType | VARCHAR | 64 | - | 否 | 业务行:被操作对象类型(全库统一编码)。RBAC 动作行可 NULL 或填 Principal / Role 等(实现约定) |
| TargetId | VARCHAR | 64 | - | 否 | 业务行:对象主键字符串化。RBAC 动作行可 NULL(若已用 TargetPrincipal* 表达) |
| TargetIdSecondary | VARCHAR | 64 | - | 否 | 可选第二键(子行、明细序号等) |
| TargetPrincipalType | VARCHAR | 32 | - | 否 | RBAC:受影响主体类型(如 user、customer_sub) |
| TargetPrincipalId | BIGINT | - | - | 否 | RBAC:受影响主体 Id |
| RoleId | BIGINT | - | - | 否 | RBAC:相关角色 Id |
| Action | VARCHAR | 64 | - | 是 | 业务:ADD / UPDATE / DELETE(大写;批量仍不合并行)。RBAC:动作码,如 principal_role_assign、role_permission_assign、sub_whitelist_assign、auth_session_revoke、auth_password_change |
| DetailJson | JSON | - | - | 否 | RBAC:请求/变更摘要。业务行可 NULL 或补充元数据 |
| FieldName | VARCHAR | 128 | - | 否 | 业务·字段级必填(列名或路径);快照 / RBAC 行 NULL |
| OldValue | TEXT | - | - | 否 | 业务·字段级按 Action 规则;其余可 NULL |
| NewValue | TEXT | - | - | 否 | 业务·字段级按 Action 规则;其余可 NULL |
| OldSnapshotJson | JSON | - | - | 否 | 业务·快照按 Action 规则;RBAC 通常 NULL |
| NewSnapshotJson | JSON | - | - | 否 | 业务·快照按 Action 规则;RBAC 通常 NULL |
| BatchId | VARCHAR | 64 | - | 否 | 同一批次关联号;仅关联,不合并日志行 |
| RequestId | VARCHAR | 64 | - | 否 | 请求链路 ID |
| ClientIp | VARCHAR | 64 | - | 否 | 客户端 IP |
| Remark | VARCHAR | 512 | - | 否 | 补充说明;system 操作时建议必填触发原因(如「定时任务更新价格」) |
1.13.1 记录形态约定
| 形态 | 每行含义 | 必填字段 | 典型场景 |
|---|---|---|---|
| RBAC 动作 | 一次管理动作 1 行 | Action(动作码)、OpPrincipalType/OpDisplayName/OpTime;ModuleCode 建议 rbac;TargetPrincipal* / RoleId / DetailJson 按动作填充 |
分配角色、改角色权限、子账号白名单、吊销会话、改密等 |
| 业务·字段级 | 同一 Target + Action + OpTime(及 BatchId)下,每个变化字段 1 行 |
TargetType、TargetId、Action∈{ADD,UPDATE,DELETE}、FieldName、OldValue/NewValue(按规则);快照与 RBAC 专用列 NULL |
报表/审计 SQL 直接查「谁改了金额」 |
| 业务·快照 | 同一 Target + Action + OpTime(及 BatchId)下,每次对象变更 1 行 |
TargetType、TargetId、Action、OldSnapshotJson/NewSnapshotJson(按规则) |
前端 diff、嵌套结构 |
快照模式注意:UPDATE 建议在事务内基于库中完整旧行与提交后完整新行各序列化一份 JSON,避免「只提交部分字段」导致快照缺键、前端 diff 误判为大量删除。JSON 键顺序不影响语义,但 diff 库可能对顺序敏感——前端对比前可做 key 排序归一化(实现约定)。
1.13.2 写入规则(必须遵守)
A. 业务·字段级模式
-
新增(
ADD)- 对每个需要留痕的字段写 1 条记录(若配置为「仅关键字段」,则只对配置集内字段写)。
OldValue=NULL,NewValue= 录入值(字符串化存储;JSON 可整段序列化)。
-
修改(
UPDATE)- 仅当字段值相对库中旧值发生变化才写日志;无变化不写。
- 每个变化字段 1 条记录:
OldValue= 修改前,NewValue= 修改后。
-
删除(
DELETE)OldValue= 删除前的值,NewValue=NULL。- 可按策略记录全部字段或关键字段(与 ADD 一致,由模块配置)。
-
批量操作
- 逐条、逐字段写入,禁止把多条数据合并成一条摘要;保证 每条数据、每个字段变更 均有 独立日志行(可用相同
BatchId关联同一事务/任务)。
- 逐条、逐字段写入,禁止把多条数据合并成一条摘要;保证 每条数据、每个字段变更 均有 独立日志行(可用相同
B. 业务·快照模式
-
新增(
ADD)OldSnapshotJson=NULL,NewSnapshotJson= 录入后对象(建议含主键与业务键,便于前端展示)。- 每个新增对象 1 行(批量则多行,同一
BatchId)。
-
修改(
UPDATE)- 若整体与库中相比无任何字段变化,可不写日志。
- 每次更新 1 行:
OldSnapshotJson= 变更前完整对象,NewSnapshotJson= 变更后完整对象。
-
删除(
DELETE)- 每个删除对象 1 行:
OldSnapshotJson= 删除前完整对象,NewSnapshotJson=NULL。
- 每个删除对象 1 行:
-
批量操作
- 逐对象写入,不合并;每条变更独立一行,可用相同
BatchId。
- 逐对象写入,不合并;每条变更独立一行,可用相同
C. RBAC 动作行
- 一次 API 或后台动作 1 行(勿与业务字段级/快照混在同一语义行内重复描述同一事件)。
Action使用已注册动作码;ModuleCode建议rbac。TargetPrincipalType/TargetPrincipalId、RoleId、DetailJson按具体接口填充;无对应实体时列可NULL。
D. 共用规则
- 系统自动操作
OpPrincipalType=system,OpPrincipalId=NULL,OpDisplayName=系统自动。Remark必填:写明触发原因(如「定时任务更新价格」「规则引擎自动调账」「MQ 消费重试」)。
操作者字段:操作者为内部用户时,使用 OpPrincipalType=user,OpPrincipalId 填其 user.Id。本规范所列 RBAC 动作与业务留痕均写入 t_operation_log,不拆分为其它 RBAC 专用审计表。
1.14 字典表 t_dict
| 字段名 | 字段类型 | 长度 | 主键/外键 | 非空 | 说明 |
|---|---|---|---|---|---|
| Id | BIGINT | - | 主键 | 是 | 自增唯一标识 |
| DictType | VARCHAR | 64 | - | 是 | 字典类型(如 notice_type、warehouse_type、user_position(岗位)、user_education(学历)、customer_type(客户类型)、customer_settlement_method(客户结算方式)) |
| DictSort | INT | - | - | 是 | 字典排序,值越小越靠前 |
| DictCode | VARCHAR | 64 | - | 是 | 字典编码(同类型下唯一) |
| DictName | VARCHAR | 128 | - | 是 | 字典名称 |
| IsDeleted | TINYINT | - | - | 是 | 是否删除:0-否,1-是,默认0 |
| CreateUserId | INT | - | - | 是 | 创建人 |
| CreateTime | DATETIME | - | - | 是 | 创建时间 |
| UpdateUserId | INT | - | - | 否 | 更新人 |
| UpdateTime | DATETIME | - | - | 否 | 更新时间 |
约束:业务存储、联查与接口校验均针对
DictType+DictCode;须IsDeleted=0方为有效字典项;同一DictType下有效行DictCode唯一。下拉/列表可按DictSort升序(值越小越靠前)。
本规范已使用的 DictType:user_position(内部用户岗位)、user_education(内部用户学历)、customer_type(客户类型)、customer_settlement_method(客户结算方式)。
2 令牌与鉴权约定
本章导读:抓住三条线——access / refresh 与
PermissionRevision/SessionVersion;改角色、改白名单、改密、吊销;客户子账号 ⊆ 主账号。2.1 全局约定;2.2 客户域风控表;2.3 三类主体注册时 微信绑定 的两种产品路径(与创建/更新接口对应)。
2.1 统一认证与鉴权约定
- 模型分层与链路见 1.1;客户主账号/子账号细则见 2.2。
- 命名:
/rbac/user/*仅PrincipalType=user;客户主/子账号角色与白名单走/customer/role/*、/customer/sub-account/*(目标接口见 5.2);客户资料 CRUD 见 5.1。 - 登录:校验
t_auth_account→PrincipalType+PrincipalId。 - 注册:
Phone、Email必填且全局唯一;微信字段可选,仅便捷登录。 - 登录:请求参数
account= 手机号或邮箱(与Phone或Email匹配)+ 口令和/或已绑微信(实现可约定 Query/Body)。登录 / 刷新 响应体与访问令牌中标识主体:principalType+principalId(与t_auth_account一致)。 - 口令、手机/邮箱、微信与展示字段 仅以
t_auth_account为准。user.Avatar仅为非微信头像。 - 权限码:
t_permission_definition为权威;role_permission、menu、API 声明须 ∈ 该表。 - 令牌 claims:含
permission_rev、session_ver(同库字段);与库不一致则 401;或 access ≤15 分钟 且变更主体绑定后PermissionRevision+1。- 与示例的关系:
expiresIn/exp(如登录响应里7200秒)以实现与网关配置为准;上文「≤15 分钟」指role_permission批量变更 且不刷各主体PermissionRevision时,靠 短 TTL 收敛权限漂移的策略建议(1.8),不表示响应里的秒数必须等于 900。
- 与示例的关系:
- refresh:内嵌
session_ver与库一致;改密、revoke-all-sessions、冻结则SessionVersion+1。 - **
PermissionRevision+1时机**:覆盖写该主体t_principal_role、或该customer_sub` 的白名单成功后。 role_permission批量变更:不逐行刷 revision 时依赖短 TTL 或全局缓存失效。- 数据隔离:账单用
main_customer_id/principal_id;行级范围用org_id/OrganizationId叠加查询。 - 接口域:
user走管理端;customer/customer_sub走客户中心。 - 微信字段落库:一律
t_auth_account(1.2);与注册/绑定的接口分工见 2.3。
2.2 客户域 RBAC 与子账号权限(模型与风控)
| 项目 | 说明 |
|---|---|
| 角色与扩展 | 可绑 RoleId 受 role.AllowedPrincipalTypes 约束。 |
主账号 P_{\text{主}} |
PrincipalType=customer 的角色 → role_permission 并集。 |
子账号 P_{\text{子}} |
角色并集 ∩ 白名单(若有);必须 (P_{\text{子}}\subseteq P_{\text{主}})。 |
| 保存强校验 | assign-roles / assign-permissions 事务内计算,⊄ 则 400;主账号 收紧 致子超限:默认 拒绝 直至先调整子账号。 |
| 登录与操作 | 写 t_operation_log;删子账号吊销 token。 |
| 改密 | SessionVersion+1。 |
2.3 主体注册与微信绑定(两种路径)
适用于 PrincipalType = user / customer / customer_sub:微信仅作便捷登录与展示,不能替代 Phone + Email 开户(1.2)。前端一般先走微信 OAuth / 扫码拿到 openid(及可选 unionid)、头像、昵称,再与业务创建/更新配合。
| 路径 | 流程 | 接口 |
|---|---|---|
| A. 先微信,再开户 | 用户完成微信授权 → 再填写手机、邮箱、密码(及主体业务字段)→ 一次性创建主体与认证行 | POST /rbac/user/create、POST /customer/create、POST /customer/sub-account/create(4.6.3、5.1.5、5.2.5):Body 携带 wechatOpenid / wechatUnionid(可选)/ wechatAvatar / wechatNickname,写入 t_auth_account |
| B. 先开户,后绑微信 | 创建时 微信字段为空 → 后续用户在微信侧完成授权 → 调用更新接口补全绑定 | PUT /rbac/user/update、POST /customer/update、POST /customer/sub-account/update(4.6.4、5.1.6、5.2.6):Body 携带上述微信字段;服务端更新 t_auth_account,WechatOpenid/WechatUnionid 全局唯一(IsDeleted=0 下不得与其它主体重复) |
实现注意:路径 B 若提供独立 bind-wechat 接口,仍须写 t_auth_account 并做 OpenID 占用校验;与「更新接口带微信字段」二选一或并存时,在 OpenAPI 标明即可。
3 接口清单
本章导读:只关心「有哪些 URL、什么方法」时看本节。下面 3.1 为说明与链接;3.1.1~3.1.6 按 认证 → 权限 → 角色 → 组织 → 用户 → 菜单 排,与 OpenAPI 标签顺序可能不一致。3.1.7 为 OMS-Customer 已发布路径;3.1.8 为客户域 RBAC 目标路径(详见 5.2)。请求体、错误码、流程见 4(RBAC)、5(客户域)。
下表按业务链路重排;3.1.1~3.1.6 仅作分组标题,与实现侧 OpenAPI 中的出现顺序可能不一致。
3.1 RBAC 接口路径一览
来源:
https://api.nanafa.com/admin/index.html?urls.primaryName=RBAC实际文档地址:
/admin/swagger/RBAC/swagger.json(接口数以运行时 OpenAPI 为准,含revoke-all-sessions)
3.1.1 认证(Auth)
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|---|---|---|---|
| 登录 | POST | /rbac/auth/login | 用户登录 |
| 刷新令牌 | POST | /rbac/auth/refresh-token | 刷新 token |
| 修改密码 | POST | /rbac/auth/change-password | 当前用户改密(SessionVersion+1) |
| 当前用户信息 | GET | /rbac/auth/get-current-user | 获取当前登录用户 |
| 吊销全部会话 | POST | /rbac/auth/revoke-all-sessions | 当前主体所有 refresh 失效(SessionVersion+1) |
3.1.2 权限(Permission)
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|---|---|---|---|
| 全量权限 | GET | /rbac/permission/get-all-permissions | 查询全部权限编码 |
| 权限树 | GET | /rbac/permission/get-permission-tree | 查询权限树 |
| 单权限校验 | POST | /rbac/permission/check-permission | 校验单个权限(唯一单码判权入口;不再提供 /rbac/user/has-permission) |
| 多权限校验 | POST | /rbac/permission/check-permissions | 批量校验权限 |
3.1.3 角色(Role)
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|---|---|---|---|
| 角色列表 | GET | /rbac/role/get-list | 分页查询角色 |
| 全部角色 | GET | /rbac/role/get-all-roles | 查询全部角色 |
| 角色详情 | GET | /rbac/role/get | 查询角色详情 |
| 创建角色 | POST | /rbac/role/create | 新增角色 |
| 更新角色 | PUT | /rbac/role/update | 编辑角色 |
| 删除角色 | DELETE | /rbac/role/delete | 删除角色 |
| 分配权限 | POST | /rbac/role/assign-permissions | 给角色分配权限 |
| 角色权限 | GET | /rbac/role/get-role-permissions | 查询角色权限 |
| 角色用户数 | GET | /rbac/role/get-role-user-count | 查询角色下用户数 |
| 角色用户列表 | GET | /rbac/role/get-role-users | 查询角色下用户列表 |
3.1.4 组织(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 | 调整组织节点位置 |
3.1.5 用户(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-avatar | 更新用户头像 |
| 更新用户密码 | PUT | /rbac/user/update-password | 重置/修改用户密码 |
3.1.6 菜单(Menu)
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|---|---|---|---|
| 菜单详情 | GET | /rbac/menu/get | 查询菜单详情 |
| 菜单树 | GET | /rbac/menu/get-menu-tree | 查询菜单树 |
| 用户菜单 | GET | /rbac/menu/get-user-menus | 查询当前用户可见菜单 |
| 创建菜单 | POST | /rbac/menu/create | 新增菜单 |
| 更新菜单 | PUT | /rbac/menu/update | 编辑菜单 |
| 删除菜单 | DELETE | /rbac/menu/delete | 删除菜单 |
| 移动菜单 | POST | /rbac/menu/move-menu | 移动菜单节点 |
| 更新菜单排序 | PUT | /rbac/menu/update-order | 更新菜单排序 |
3.1.7 客户资料(OMS-Customer)
来源:Swagger UI — OMS-Customer ·
/admin/swagger/OMS-Customer/swagger.json— 详细设计见 5.1。
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|---|---|---|---|
| 客户列表 | GET | /customer/list | 分页与筛选 |
| 客户详情 | GET | /customer/get | 按 id |
| 新增客户 | POST | /customer/create | CreateCustomerInput |
| 更新客户 | POST | /customer/update | UpdateCustomerInput |
| 删除客户 | POST | /customer/delete | IdInput |
| 重置客户密码 | POST | /customer/reset-password | SessionVersion+1,见 5.1.8 |
3.1.8 客户域 RBAC(目标)
截至 OMS-Customer v1 可能未出现在同一 Swagger;路径与 5.2 一致。
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|---|---|---|---|
| 主账号分配角色 | POST | /customer/role/assign | 5.2.2 |
| 主账号角色列表 | GET | /customer/role/list | 5.2.3 |
| 子账号列表 | GET | /customer/sub-account/list | 5.2.4 |
| 创建子账号 | POST | /customer/sub-account/create | 5.2.5 |
| 更新子账号 | POST | /customer/sub-account/update | 5.2.6 |
| 删除子账号 | POST | /customer/sub-account/delete | 5.2.7 |
| 子账号分配角色 | POST | /customer/sub-account/assign-roles | 5.2.8 |
| 子账号配置白名单 | POST | /customer/sub-account/assign-permissions | 5.2.9 |
| 子账号有效权限预览 | GET | /customer/sub-account/effective-permissions | 5.2.10 |
4 接口详细设计
本章导读:写代码 / 写用例以本节为主。结构为 4.2~4.7 六大块(与 3.1.1~3.1.6 接口分组同序);块内
4.x.y.z为接口与子步骤编号(便于搜索),每个接口含 示例 → 业务逻辑 → 校验与异常。篇幅长,请 按路径搜索(如/rbac/role/create)。
4.1 接口详细设计总述
下文顺序:认证 → 权限 → 角色 → 组织 → 用户 → 菜单,与 4.2~4.7 分块一致;接口条目为 4.2.1、4.3.1 形式;路径以运行时 API 为准。令牌
permission_rev/session_ver、PermissionRevision/SessionVersion及审计要求见 2。客户域(客户资料 + 客户侧 RBAC 目标接口)详细设计见 5。
4.1.1 标识符与请求参数约定(principalId / userId / id)
为避免 t_auth_account.Id(认证行主键)、PrincipalId(业务主体主键)、user.Id 与各类 Query 同名混用,统一约定如下(OpenAPI 须与之一致):
| 符号 / 参数 | 含义 | 典型出现位置 |
|---|---|---|
principalType + principalId(camelCase) |
与 t_auth_account 一致:principalId 永远是业务主体主键(user.Id / t_customer.Id / t_customer_sub_account.Id),不是 t_auth_account.Id。 |
登录/刷新响应(4.2.1~4.2.2)、令牌 claims;POST /rbac/permission/check-permission 等 |
userId |
仅表示 user.Id(内部员工)。在 **PrincipalType=user 时 userId = 该主体的 PrincipalId。 |
/rbac/user/assign-roles、get-user-roles、get-user-permissions、get-user-menus 等 4.6 / 4.7 示例 |
id(裸 Query) |
随资源而变,须读接口路径判断目标表:例如 /rbac/user/* 下多为 user.Id;/rbac/role/* 下为 role.Id;/rbac/menu/* 下为 menu.Id;/customer/get 下为 t_customer.Id(即客户主账号的 PrincipalId,但参数名沿用 OpenAPI id)。 |
各节请求示例 |
customerId / subAccountId |
分别对应 t_customer.Id、t_customer_sub_account.Id;写入 t_principal_role 时即同类型的 PrincipalId。 |
5.2 客户域 RBAC |
须写进 OpenAPI 的实现决策(当前规范留歧义处):
GET /rbac/menu/get-user-menus?userId=:userId是否仅允许查询本人(忽略 Query,以 token 为准)、还是允许管理员查任意用户——二者选一并实现,禁止默认可查他人而不鉴权。
库表字段名提示:各业务表 CreateUserId/UpdateUserId 为 INT 与 user.Id BIGINT 并存时,以实现与迁移为准;新库建议审计列与 user.Id 类型对齐,避免大 Id 溢出。
4.2 认证(Auth)
4.2.1 登录(POST /rbac/auth/login)
4.2.1.1 接口请求示例
请求 /rbac/auth/login(POST,Query 参数)
?account=13800138000&password=123456&type=0
响应
{
"accessToken": "eyJhbGciOi...",
"refreshToken": "rt_refresh_token_01",
"tokenType": "Bearer",
"expiresIn": 7200,
"principalType": "user",
"principalId": 1
}
4.2.1.2 接口业务逻辑
4.2.1.2.1 核心流程
account为 手机号或邮箱(与t_auth_account.Phone或Email匹配);password与/或已绑微信鉴权;成功后签发accessToken与refreshToken。- 返回
LoginResultDto:principalType、principalId与t_auth_account一致;令牌 claims 与客户端缓存均以二者为唯一主体标识。
4.2.1.2.2 校验与异常
- 校验
account必填;password与「已绑微信」对应的鉴权材料至少满足其一(与 4.2.1.2.1 一致;纯微信登录时 Body/Query 形态由 OpenAPI 约定),不得再写「密码必填」以免与微信鉴权矛盾。type默认0(内部管理端);其它取值由部署约定(如客户门户type=1),语义见 2.2。 - 鉴权失败或主体状态异常时返回
ApplicationExceptionResult(400)。 - 客户主账号 / 子账号登录成功:按主体从
t_principal_role(及子账号t_customer_sub_permission白名单)聚合roles/permissions/menus(2.2)。零角色绑定 时permissions可为空或仅登录/个人中心最小集(须在t_permission_definition注册)。
4.2.2 刷新令牌(POST /rbac/auth/refresh-token)
4.2.2.1 接口请求示例
请求 /rbac/auth/refresh-token(POST,Query 参数)
?refreshToken=rt_refresh_token_01
响应
{
"accessToken": "eyJhbGciOi...",
"refreshToken": "rt_refresh_token_02",
"tokenType": "Bearer",
"expiresIn": 7200,
"principalType": "user",
"principalId": 1
}
4.2.2.2 接口业务逻辑
4.2.2.2.1 核心流程
- 校验
refreshToken有效且内嵌session_ver与t_auth_account.SessionVersion一致,生成并返回新令牌对(可旋转 refresh);principalType/principalId同 4.2.1。
4.2.2.2.2 校验与异常
refreshToken无效、过期、session_ver不匹配或被吊销时返回ApplicationExceptionResult(400)。
4.2.3 修改密码(POST /rbac/auth/change-password)
4.2.3.1 接口请求示例
请求 /rbac/auth/change-password(POST,Query 参数)
?oldPassword=old123&newPassword=new123456
响应
{
"code": 0,
"msg": "success",
"data": null
}
4.2.3.2 接口业务逻辑
4.2.3.2.1 核心流程
- 使用当前登录主体校验旧密码,成功后仅更新当前主体对应
t_auth_account的PasswordHash/PasswordSalt,不写入user表;SessionVersion+1;写t_operation_log(auth_password_change)。
4.2.3.2.2 校验与异常
- 旧密码错误、密码策略不满足或主体不存在时返回
ApplicationExceptionResult(400)。
4.2.4 当前用户信息(GET /rbac/auth/get-current-user)
4.2.4.1 接口请求示例
请求 /rbac/auth/get-current-user(GET)
无请求参数,基于当前登录令牌解析用户身份。
响应
{
"id": 1,
"name": "管理员A",
"phone": "13800138000",
"email": "admin@example.com",
"avatar": "https://cdn.example.com/avatar/admin.png",
"organizationId": 10,
"organizationName": "总部",
"roles": [
"Admin"
],
"permissions": [
"rbac:user:get-list",
"rbac:role:get-list"
],
"menus": []
}
4.2.4.2 接口业务逻辑
4.2.4.2.1 核心流程
- 根据 token 解析主体,聚合返回基础信息、角色、权限与菜单。
4.2.4.2.2 校验与异常
- token 无效、主体不存在或已禁用时返回
ApplicationExceptionResult(400)。 principal_type为customer/customer_sub:按 2.2 聚合权限与菜单;子账号响应可带mainCustomerId(与令牌 claimmain_customer_id一致)。
4.2.5 吊销全部会话(POST /rbac/auth/revoke-all-sessions)
4.2.5.1 接口请求示例
请求 /rbac/auth/revoke-all-sessions(POST)
无 Body;基于当前 access token 识别主体。
响应
{
"code": 0,
"msg": "success",
"data": null
}
4.2.5.2 接口业务逻辑
4.2.5.2.1 核心流程
- 当前主体
SessionVersion+1,吊销全部 refresh;写t_operation_log(auth_session_revoke)。
4.2.5.2.2 校验与异常
- token 无效时返回
ApplicationExceptionResult(400)。
4.3 权限(Permission)
4.3.1 全量权限(GET /rbac/permission/get-all-permissions)
4.3.1.1 接口请求示例
请求 /rbac/permission/get-all-permissions(GET)
无请求参数。 响应
[
"rbac:user:get-list",
"rbac:role:get-list"
]
4.3.1.2 接口业务逻辑
4.3.1.2.1 核心流程
- 返回系统全部权限编码列表。
4.3.1.2.2 校验与异常
- 查询异常返回
ApplicationExceptionResult(400)。
4.3.2 权限树(GET /rbac/permission/get-permission-tree)
4.3.2.1 接口请求示例
请求 /rbac/permission/get-permission-tree(GET)
无请求参数。 响应
[
{
"permissionCode": "rbac:user",
"children": [
{
"permissionCode": "rbac:user:get-list"
}
]
}
]
4.3.2.2 接口业务逻辑
4.3.2.2.1 核心流程
- 返回权限树结构,供角色授权界面使用;树形与
t_permission_definition库表字段的对应关系见 1.9.1。
4.3.2.2.2 校验与异常
- 数据异常时返回
ApplicationExceptionResult(400)。
4.3.3 单权限校验(POST /rbac/permission/check-permission)
唯一单码判权接口:不提供
POST /rbac/user/has-permission。对内部用户判权使用principalType=user且principalId=user.Id(见 4.1.1)。
4.3.3.1 接口请求示例
请求 /rbac/permission/check-permission(POST,Query 参数)
?principalType=user&principalId=1&permissionCode=rbac:user:get-list
响应
true
4.3.3.2 接口业务逻辑
4.3.3.2.1 核心流程
- 按
principalType+principalId解析t_auth_account与t_principal_role(及子账号白名单),判断是否具备目标permissionCode。
4.3.3.2.2 校验与异常
- 主体不存在、
permissionCode未注册或权限码为空时返回ApplicationExceptionResult(400)。
4.3.4 多权限校验(POST /rbac/permission/check-permissions)
4.3.4.1 接口请求示例
请求 /rbac/permission/check-permissions(POST,Query + Body)
?principalType=user&principalId=1
[
"rbac:user:get-list",
"rbac:role:get-list"
]
响应
{
"rbac:user:get-list": true,
"rbac:role:get-list": false
}
4.3.4.2 接口业务逻辑
4.3.4.2.1 核心流程
- 批量校验权限并返回映射结果;主体解析同 4.3.3。
4.3.4.2.2 校验与异常
- 请求体为空或主体不存在时返回
ApplicationExceptionResult(400)。
4.4 角色(Role)
4.4.1 角色列表(GET /rbac/role/get-list)
4.4.1.1 接口请求示例
请求 /rbac/role/get-list(GET,Query 参数)
?Keyword=admin&Page=1&PageSize=20&OrderBy=CreateTime&Descending=true
响应
{
"total": 1,
"items": [
{
"id": 20,
"name": "管理员",
"code": "Admin",
"permissions": [
"rbac:user:get-list"
]
}
]
}
4.4.1.2 接口业务逻辑
4.4.1.2.1 核心流程
- 分页查询角色并返回
RoleDtoPagedResultDto。
4.4.1.2.2 校验与异常
- 分页参数非法时返回
ApplicationExceptionResult(400)。
4.4.2 全部角色(GET /rbac/role/get-all-roles)
4.4.2.1 接口请求示例
请求 /rbac/role/get-all-roles(GET)
无请求参数。 响应
[
{
"id": 20,
"name": "管理员",
"code": "Admin"
}
]
4.4.2.2 接口业务逻辑
4.4.2.2.1 核心流程
- 返回全部可用角色列表。
4.4.2.2.2 校验与异常
- 查询异常返回
ApplicationExceptionResult(400)。
4.4.3 角色详情(GET /rbac/role/get)
4.4.3.1 接口请求示例
请求 /rbac/role/get(GET,Query 参数)
?id=20
响应
{
"id": 20,
"name": "管理员",
"code": "Admin",
"permissions": [
"rbac:user:get-list"
]
}
4.4.3.2 接口业务逻辑
4.4.3.2.1 核心流程
- 根据角色 ID 返回
RoleDto。
4.4.3.2.2 校验与异常
- 角色不存在时返回
ApplicationExceptionResult(400)。
4.4.4 创建角色(POST /rbac/role/create)
4.4.4.1 接口请求示例
请求 /rbac/role/create(POST,Body)
{
"name": "运营",
"code": "Operator",
"allowedPrincipalTypes": "user",
"permissions": [
"rbac:user:get-list",
"rbac:role:get-list"
]
}
响应
{
"id": 21,
"name": "运营",
"code": "Operator",
"allowedPrincipalTypes": "user",
"permissions": [
"rbac:user:get-list",
"rbac:role:get-list"
]
}
4.4.4.2 接口业务逻辑
4.4.4.2.1 核心流程
- 写入
role(含AllowedPrincipalTypes) 与role_permission;权限码须注册;写t_operation_log(RBAC 动作行)。
4.4.4.2.2 校验与异常
code重复、allowedPrincipalTypes非法或权限编码未注册时返回ApplicationExceptionResult(400)。
4.4.5 更新角色(PUT /rbac/role/update)
4.4.5.1 接口请求示例
请求 /rbac/role/update(PUT,Query + Body)
?id=21
{
"name": "运营A"
}
响应
{
"id": 21,
"name": "运营A",
"code": "Operator"
}
4.4.5.2 接口业务逻辑
4.4.5.2.1 核心流程
- 更新角色名称并返回
RoleDto。
4.4.5.2.2 校验与异常
- 角色不存在时返回
ApplicationExceptionResult(400)。
4.4.6 删除角色(DELETE /rbac/role/delete)
4.4.6.1 接口请求示例
请求 /rbac/role/delete(DELETE,Query 参数)
?id=21
响应
{
"code": 0,
"msg": "success",
"data": null
}
4.4.6.2 接口业务逻辑
4.4.6.2.1 核心流程
- 删除角色及其关联权限关系。
4.4.6.2.2 校验与异常
- 角色被使用且不允许删除时返回
ApplicationExceptionResult(400)。
4.4.7 分配权限(POST /rbac/role/assign-permissions)
4.4.7.1 接口请求示例
请求 /rbac/role/assign-permissions(POST,Query + Body)
?roleId=21
[
"rbac:user:get-list",
"rbac:role:get-list"
]
响应
{
"code": 0,
"msg": "success",
"data": null
}
4.4.7.2 接口业务逻辑
4.4.7.2.1 核心流程
- 校验
PermissionCode∈t_permission_definition;覆盖更新role_permission;写t_operation_log(role_permission_assign)。(不逐行刷各主体PermissionRevision时依赖 2所述短 TTL 或缓存失效。)
4.4.7.2.2 校验与异常
- 角色不存在或权限码未注册时返回
ApplicationExceptionResult(400)。
4.4.8 角色权限(GET /rbac/role/get-role-permissions)
4.4.8.1 接口请求示例
请求 /rbac/role/get-role-permissions(GET,Query 参数)
?roleId=21
响应
[
"rbac:user:get-list",
"rbac:role:get-list"
]
4.4.8.2 接口业务逻辑
4.4.8.2.1 核心流程
- 查询角色已授权权限编码列表。
4.4.8.2.2 校验与异常
- 角色不存在时返回
ApplicationExceptionResult(400)。
4.4.9 角色用户数(GET /rbac/role/get-role-user-count)
4.4.9.1 接口请求示例
请求 /rbac/role/get-role-user-count(GET,Query 参数)
?roleId=21
响应
12
4.4.9.2 接口业务逻辑
4.4.9.2.1 核心流程
- 统计角色下用户数。
4.4.9.2.2 校验与异常
- 角色不存在时返回
ApplicationExceptionResult(400)。
4.4.10 角色用户列表(GET /rbac/role/get-role-users)
4.4.10.1 接口请求示例
请求 /rbac/role/get-role-users(GET,Query 参数)
?roleId=21
响应
[
{
"id": 1,
"name": "管理员A",
"phone": "13800138000"
}
]
4.4.10.2 接口业务逻辑
4.4.10.2.1 核心流程
- 返回角色下用户集合。
4.4.10.2.2 校验与异常
- 角色不存在时返回
ApplicationExceptionResult(400)。
4.5 组织(Organization)
4.5.1 组织列表(GET /rbac/organization/get-list)
4.5.1.1 接口请求示例
请求 /rbac/organization/get-list(GET)
无请求参数。 响应
[
{
"id": 10,
"parentId": null,
"name": "总部",
"code": "HQ",
"type": "Company",
"order": 1,
"isEnabled": true
}
]
4.5.1.2 接口业务逻辑
4.5.1.2.1 核心流程
- 查询组织平铺列表。
4.5.1.2.2 校验与异常
- 查询异常返回
ApplicationExceptionResult(400)。
4.5.2 组织树(GET /rbac/organization/get-tree)
4.5.2.1 接口请求示例
请求 /rbac/organization/get-tree(GET)
无请求参数。 响应
[
{
"id": 10,
"name": "总部",
"children": []
}
]
4.5.2.2 接口业务逻辑
4.5.2.2.1 核心流程
- 按父子关系构建并返回组织树。
4.5.2.2.2 校验与异常
- 数据异常(循环父子关系)时返回
ApplicationExceptionResult(400)。
4.5.3 组织详情(GET /rbac/organization/get)
4.5.3.1 接口请求示例
请求 /rbac/organization/get(GET,Query 参数)
?id=10
响应
{
"id": 10,
"parentId": null,
"name": "总部",
"code": "HQ",
"type": "Company",
"leaderId": 1,
"leaderName": "管理员A",
"order": 1,
"description": "总部组织",
"isEnabled": true
}
4.5.3.2 接口业务逻辑
4.5.3.2.1 核心流程
- 根据组织 ID 查询
OrganizationDto。
4.5.3.2.2 校验与异常
- 组织不存在时返回
ApplicationExceptionResult(400)。
4.5.4 创建组织(POST /rbac/organization/create)
4.5.4.1 接口请求示例
请求 /rbac/organization/create(POST,Body)
{
"parentId": 10,
"name": "运营部",
"code": "OP",
"type": "Department",
"leaderId": 1,
"order": 10,
"description": "运营部门",
"isEnabled": true
}
响应
{
"id": 11,
"name": "运营部",
"code": "OP",
"type": "Department"
}
4.5.4.2 接口业务逻辑
4.5.4.2.1 核心流程
- 新增组织节点并返回
OrganizationDto。
4.5.4.2.2 校验与异常
- 编码重复、父节点不存在时返回
ApplicationExceptionResult(400)。
4.5.5 更新组织(PUT /rbac/organization/update)
4.5.5.1 接口请求示例
请求 /rbac/organization/update(PUT,Query + Body)
?id=11
{
"name": "运营中心",
"type": "Department",
"leaderId": 1,
"order": 20,
"description": "运营中心",
"isEnabled": true
}
响应
{
"id": 11,
"name": "运营中心",
"code": "OP"
}
4.5.5.2 接口业务逻辑
4.5.5.2.1 核心流程
- 按 ID 更新组织信息。
4.5.5.2.2 校验与异常
- 组织不存在或状态非法时返回
ApplicationExceptionResult(400)。
4.5.6 删除组织(DELETE /rbac/organization/delete)
4.5.6.1 接口请求示例
请求 /rbac/organization/delete(DELETE,Query 参数)
?id=11
响应
{
"code": 0,
"msg": "success",
"data": null
}
4.5.6.2 接口业务逻辑
4.5.6.2.1 核心流程
- 删除组织节点。
4.5.6.2.2 校验与异常
- 有子节点或被用户引用时返回
ApplicationExceptionResult(400)。
4.5.7 移动组织(POST /rbac/organization/move)
4.5.7.1 接口请求示例
请求 /rbac/organization/move(POST,Query + Body)
?id=11
{
"newParentId": 10
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
4.5.7.2 接口业务逻辑
4.5.7.2.1 核心流程
- 调整组织父节点,重建树关系。
4.5.7.2.2 校验与异常
- 新父节点非法、移动后形成循环层级时返回
ApplicationExceptionResult(400)。
4.6 用户(User)
命名:本节 Query 中的
userId=user.Id(即PrincipalType=user时的PrincipalId),见 4.1.1。
员工档案与接口:user表人事字段见 1.3。POST /rbac/user/create、PUT /rbac/user/update在姓名、手机、邮箱、组织、角色等基础字段之外,均可选携带与 1.3 一致的档案属性(positionCode、age、idCardNo、birthday、educationCode、address、graduateSchool、emergencyContact、emergencyContactPhone、hireDate、leaveDate、warehouseId;命名建议 camelCase 与 OpenAPI 一致)。微信绑定与t_auth_account的两种路径见 2.3:创建时可带wechatOpenid等;仅后续绑定时用 update 写入。GET /rbac/user/get返回完整UserDto;列表可为UserSummaryDto。positionCode/educationCode响应可附带positionName/educationName(t_dict,IsDeleted=0)。idCardNo按权限脱敏;warehouseId须校验仓库存在。
4.6.1 用户列表(GET /rbac/user/get-list)
4.6.1.1 接口请求示例
请求 /rbac/user/get-list(GET,Query 参数)
?Keyword=admin&RoleId=&OrganizationId=&Page=1&PageSize=20&OrderBy=CreateTime&Descending=true
响应
{
"total": 1,
"items": [
{
"id": 1,
"name": "管理员A",
"phone": "13800138000",
"email": "admin@example.com",
"organizationId": 10,
"organizationName": "总部",
"positionCode": "pos_dev",
"positionName": "开发",
"warehouseId": 100,
"hireDate": "2023-01-15",
"roles": [
"Admin"
]
}
]
}
4.6.1.2 接口业务逻辑
4.6.1.2.1 核心流程
- 按关键字、角色、组织与分页参数查询用户,返回
UserDtoPagedResultDto;列表项可含 1.3 中常用档案列(如岗位、仓库、入职日),敏感列默认不出或按权限裁剪。
4.6.1.2.2 校验与异常
- 分页参数非法或排序字段不支持时返回
ApplicationExceptionResult(400)。
4.6.2 用户详情(GET /rbac/user/get)
4.6.2.1 接口请求示例
请求 /rbac/user/get(GET,Query 参数)
?id=1
响应
{
"id": 1,
"name": "管理员A",
"phone": "13800138000",
"email": "admin@example.com",
"avatar": "https://cdn.example.com/avatar/admin.png",
"organizationId": 10,
"organizationName": "总部",
"positionCode": "pos_dev",
"positionName": "开发",
"age": 32,
"idCardNo": "110101********1234",
"birthday": "1993-06-01",
"educationCode": "edu_bachelor",
"educationName": "本科",
"address": "北京市朝阳区…",
"graduateSchool": "某某大学",
"emergencyContact": "李四",
"emergencyContactPhone": "13900000000",
"hireDate": "2020-03-01",
"leaveDate": null,
"warehouseId": 100,
"concurrencyStamp": 3,
"roles": [
"Admin"
]
}
4.6.2.2 接口业务逻辑
4.6.2.2.1 核心流程
- 根据用户 ID 查询并返回
UserDto(含 1.3 全部员工档案字段;idCardNo等敏感字段按数据权限脱敏或掩码;concurrencyStamp供更新时乐观并发可选使用)。
4.6.2.2.2 校验与异常
id不存在或已删除返回ApplicationExceptionResult(400)。
4.6.3 创建用户(POST /rbac/user/create)
4.6.3.1 接口请求示例
请求 /rbac/user/create(POST,Body)
{
"name": "测试用户",
"phone": "13800138001",
"password": "123456",
"email": "test001@example.com",
"avatar": "",
"organizationId": 10,
"roleIds": [20],
"positionCode": "pos_operator",
"age": 28,
"idCardNo": "110101199001011234",
"birthday": "1990-01-01",
"educationCode": "edu_bachelor",
"address": "上海市浦东新区…",
"graduateSchool": "某某学院",
"emergencyContact": "王五",
"emergencyContactPhone": "13800000000",
"hireDate": "2025-05-01",
"leaveDate": null,
"warehouseId": 100,
"wechatOpenid": "oXXXX_先微信授权再开户时可带",
"wechatUnionid": null,
"wechatAvatar": "https://thirdwx.qlogo.cn/...",
"wechatNickname": "微信昵称"
}
上列
positionCode~warehouseId均为可选;wechatOpenid~wechatNickname可选(路径 A:与手机邮箱密码同单创建,见 2.3)。不传微信字段则 路径 B 留待 update 绑定。
响应
{
"id": 2,
"name": "测试用户",
"phone": "13800138001",
"email": "test001@example.com",
"organizationId": 10,
"organizationName": "总部",
"positionCode": "pos_operator",
"positionName": "运营",
"hireDate": "2025-05-01",
"warehouseId": 100,
"roles": [
"Operator"
]
}
4.6.3.2 接口业务逻辑
4.6.3.2.1 核心流程
- 建
user+t_auth_account(Phone、Email必填;口令仅存认证表;请求体avatar落user.Avatar);wechatOpenid/wechatUnionid/wechatAvatar/wechatNickname若有则写入t_auth_account(路径 A,2.3)。员工档案字段(1.3)与请求体 camelCase 一一写入user;PositionCode/EducationCode非空须t_dict有效;WarehouseId/IdCardNo校验同前;roleIds→t_principal_role(user + user.Id);返回UserDto。
4.6.3.2.2 校验与异常
phone/email重复、微信 OpenID/UnionID 已被占用、组织不存在、角色无效、字典码无效、仓库不存在、身份证号冲突时返回ApplicationExceptionResult(400)。
4.6.4 更新用户(PUT /rbac/user/update)
4.6.4.1 接口请求示例
请求 /rbac/user/update(PUT,Query + Body)
?id=2
{
"name": "测试用户A",
"phone": "13800138001",
"email": "test001a@example.com",
"avatar": "https://cdn.example.com/avatar/test001.png",
"organizationId": 10,
"positionCode": "pos_operator",
"educationCode": "edu_master",
"emergencyContactPhone": "13800000001",
"leaveDate": null,
"warehouseId": 101,
"concurrencyStamp": 3,
"wechatOpenid": "oXXXX_后绑微信时由更新写入",
"wechatUnionid": null,
"wechatAvatar": "https://thirdwx.qlogo.cn/...",
"wechatNickname": "微信昵称"
}
部分更新:仅传需要变更的字段即可;未出现的档案字段保持原值。
concurrencyStamp若传入须与库中一致,否则 409。路径 B(2.3):首次绑定或换绑微信时提交wechatOpenid等;仅改展示可不重复传 OpenID。
响应
{
"id": 2,
"name": "测试用户A",
"phone": "13800138001",
"email": "test001a@example.com",
"avatar": "https://cdn.example.com/avatar/test001.png",
"organizationId": 10,
"positionCode": "pos_operator",
"positionName": "运营",
"educationCode": "edu_master",
"educationName": "硕士",
"warehouseId": 101,
"concurrencyStamp": 4
}
4.6.4.2 接口业务逻辑
4.6.4.2.1 核心流程
- 按
id更新用户基础信息与员工档案字段(1.3 全部,可部分更新),字典字段校验同创建;phone/email变更同步t_auth_account;微信字段变更同步t_auth_account(路径 B,2.3),OpenID/UnionID 不得与其它主体冲突。可选concurrencyStamp与user.ConcurrencyStamp对齐后 +1;返回完整UserDto。
4.6.4.2.2 校验与异常
- 用户不存在、邮箱/手机冲突、微信 OpenID 已被占用、组织非法、字典码无效、仓库不存在、并发戳冲突时返回
ApplicationExceptionResult(400/409)。
4.6.5 删除用户(DELETE /rbac/user/delete)
4.6.5.1 接口请求示例
请求 /rbac/user/delete(DELETE,Query 参数)
?id=2
响应
{
"code": 0,
"msg": "success",
"data": null
}
4.6.5.2 接口业务逻辑
4.6.5.2.1 核心流程
- 逻辑删除指定用户及其关联关系。
4.6.5.2.2 校验与异常
- 用户不存在、不可删除主体(如内置管理员)返回
ApplicationExceptionResult(400)。
4.6.6 批量删除用户(DELETE /rbac/user/delete-batch)
4.6.6.1 接口请求示例
请求 /rbac/user/delete-batch(DELETE,Query 参数)
?ids=2,3
响应
{
"code": 0,
"msg": "success",
"data": null
}
4.6.6.2 接口业务逻辑
4.6.6.2.1 核心流程
- 批量逻辑删除用户。
4.6.6.2.2 校验与异常
- 任一 ID 无效或存在不可删除用户时返回
ApplicationExceptionResult(400)。
4.6.7 分配角色(POST /rbac/user/assign-roles)
4.6.7.1 接口请求示例
请求 /rbac/user/assign-roles(POST,Query + Body)
?userId=2
[
20,
21
]
响应
{
"code": 0,
"msg": "success",
"data": null
}
4.6.7.2 接口业务逻辑
4.6.7.2.1 核心流程
- 校验角色
AllowedPrincipalTypes含user;覆盖写t_principal_role;该用户认证行PermissionRevision+1;写t_operation_log(principal_role_assign)。
4.6.7.2.2 校验与异常
- 用户不存在、角色不适用
user或 Id 非法时返回ApplicationExceptionResult(400)。
4.6.8 用户角色(GET /rbac/user/get-user-roles)
4.6.8.1 接口请求示例
请求 /rbac/user/get-user-roles(GET,Query 参数)
?userId=2
响应
[
{
"id": 20,
"name": "管理员",
"code": "Admin"
}
]
4.6.8.2 接口业务逻辑
4.6.8.2.1 核心流程
- 查询指定用户角色列表。
4.6.8.2.2 校验与异常
- 用户不存在时返回
ApplicationExceptionResult(400)。
4.6.9 用户权限(GET /rbac/user/get-user-permissions)
4.6.9.1 接口请求示例
请求 /rbac/user/get-user-permissions(GET,Query 参数)
?userId=2
响应
[
"rbac:user:get-list",
"rbac:role:get-list"
]
4.6.9.2 接口业务逻辑
4.6.9.2.1 核心流程
- 聚合用户全部权限编码并返回。
4.6.9.2.2 校验与异常
- 用户不存在或状态异常时返回
ApplicationExceptionResult(400)。
4.6.10 角色下用户(GET /rbac/user/get-users-by-role)
4.6.10.1 接口请求示例
请求 /rbac/user/get-users-by-role(GET,Query 参数)
?roleId=20
响应
[
{
"id": 1,
"name": "管理员A",
"phone": "13800138000"
}
]
4.6.10.2 接口业务逻辑
4.6.10.2.1 核心流程
- 查询某角色下的用户列表。
4.6.10.2.2 校验与异常
- 角色不存在时返回
ApplicationExceptionResult(400)。
4.6.11 更新用户头像(PUT /rbac/user/update-avatar)
4.6.11.1 接口请求示例
请求 /rbac/user/update-avatar(PUT,Query 参数)
?id=2&avatarUrl=https://cdn.example.com/avatar/test001.png
响应
{
"code": 0,
"msg": "success",
"data": null
}
4.6.11.2 接口业务逻辑
4.6.11.2.1 核心流程
- 更新指定用户头像地址。
4.6.11.2.2 校验与异常
- 用户不存在或头像地址非法时返回
ApplicationExceptionResult(400)。
4.6.12 更新用户密码(PUT /rbac/user/update-password)
4.6.12.1 接口请求示例
请求 /rbac/user/update-password(PUT,Query + Body)
?id=2
{
"oldPassword": "123456",
"newPassword": "new123456"
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
4.6.12.2 接口业务逻辑
4.6.12.2.1 核心流程
- 按用户 ID 定位
t_auth_account(PrincipalType=user),校验旧密码后更新PasswordHash/PasswordSalt,不写入user表;SessionVersion+1;写t_operation_log(auth_password_change)。
4.6.12.2.2 校验与异常
- 原密码错误、密码策略不满足时返回
ApplicationExceptionResult(400)。
4.7 菜单(Menu)
4.7.1 菜单详情(GET /rbac/menu/get)
4.7.1.1 接口请求示例
请求 /rbac/menu/get(GET,Query 参数)
?id=30
响应
{
"id": 30,
"pId": 0,
"name": "系统管理",
"permissionCode": "rbac:system",
"type": 1,
"route": "/system",
"component": "system/index",
"order": 1,
"isHidden": false,
"isCached": true,
"description": null
}
4.7.1.2 接口业务逻辑
4.7.1.2.1 核心流程
- 根据菜单 ID 查询
MenuDto。
4.7.1.2.2 校验与异常
- 菜单不存在时返回
ApplicationExceptionResult(400)。
4.7.2 菜单树(GET /rbac/menu/get-menu-tree)
4.7.2.1 接口请求示例
请求 /rbac/menu/get-menu-tree(GET)
无请求参数。 响应
[
{
"id": 30,
"name": "系统管理",
"children": []
}
]
4.7.2.2 接口业务逻辑
4.7.2.2.1 核心流程
- 返回完整菜单树。
4.7.2.2.2 校验与异常
- 查询异常返回
ApplicationExceptionResult(400)。
4.7.3 用户菜单(GET /rbac/menu/get-user-menus)
4.7.3.1 接口请求示例
请求 /rbac/menu/get-user-menus(GET,Query 参数)
?userId=1
响应
[
{
"id": 30,
"name": "系统管理",
"route": "/system"
}
]
4.7.3.2 接口业务逻辑
4.7.3.2.1 核心流程
- 根据
userId(即user.Id,见 4.1.1) 解析PrincipalType=user+PrincipalId,再按有效权限码过滤可见菜单。
4.7.3.2.2 校验与异常
- 用户不存在时返回
ApplicationExceptionResult(400)。 - 授权:调用者能否指定任意
userId须按 4.1.1 在 OpenAPI 中写死(仅本人 / 或具备用户管理权限),避免越权枚举他人菜单。
4.7.4 创建菜单(POST /rbac/menu/create)
4.7.4.1 接口请求示例
请求 /rbac/menu/create(POST,Body)
{
"pId": 0,
"name": "用户管理",
"permissionCode": "rbac:user:get-list",
"type": 2,
"icon": "User",
"route": "/system/user",
"component": "system/user/index",
"order": 10,
"isHidden": false,
"isCached": true,
"description": "用户管理菜单"
}
响应
{
"id": 31,
"name": "用户管理",
"permissionCode": "rbac:user:get-list"
}
4.7.4.2 接口业务逻辑
4.7.4.2.1 核心流程
- 新增菜单并返回
MenuDto;permissionCode须已注册于t_permission_definition且未删(1.9、1.11)。
4.7.4.2.2 校验与异常
permissionCode未注册、与同表IsDeleted=0下行重复、父节点不存在时返回ApplicationExceptionResult(400)。
4.7.5 更新菜单(PUT /rbac/menu/update)
4.7.5.1 接口请求示例
请求 /rbac/menu/update(PUT,Query + Body)
?id=31
{
"name": "用户中心",
"permissionCode": "rbac:user:get-list",
"icon": "User",
"route": "/system/user",
"component": "system/user/index",
"isHidden": false,
"isCached": true,
"description": "用户中心菜单"
}
响应
{
"id": 31,
"name": "用户中心",
"permissionCode": "rbac:user:get-list"
}
4.7.5.2 接口业务逻辑
4.7.5.2.1 核心流程
- 更新菜单信息并返回
MenuDto。
4.7.5.2.2 校验与异常
- 菜单不存在、编码冲突时返回
ApplicationExceptionResult(400)。
4.7.6 删除菜单(DELETE /rbac/menu/delete)
4.7.6.1 接口请求示例
请求 /rbac/menu/delete(DELETE,Query 参数)
?id=31
响应
{
"code": 0,
"msg": "success",
"data": null
}
4.7.6.2 接口业务逻辑
4.7.6.2.1 核心流程
- 删除菜单节点。
4.7.6.2.2 校验与异常
- 菜单存在子节点或被引用时返回
ApplicationExceptionResult(400)。
4.7.7 移动菜单(POST /rbac/menu/move-menu)
4.7.7.1 接口请求示例
请求 /rbac/menu/move-menu(POST,Query 参数)
?menuId=31&targetParentId=30
响应
{
"code": 0,
"msg": "success",
"data": null
}
4.7.7.2 接口业务逻辑
4.7.7.2.1 核心流程
- 调整菜单父子层级。
4.7.7.2.2 校验与异常
- 节点非法或形成循环层级时返回
ApplicationExceptionResult(400)。
4.7.8 更新菜单排序(PUT /rbac/menu/update-order)
4.7.8.1 接口请求示例
请求 /rbac/menu/update-order(PUT,Query 参数)
?id=31&order=20
响应
{
"code": 0,
"msg": "success",
"data": null
}
4.7.8.2 接口业务逻辑
4.7.8.2.1 核心流程
- 更新菜单排序值。
4.7.8.2.2 校验与异常
order非法或菜单不存在时返回ApplicationExceptionResult(400)。
5 客户域接口详细设计
本章导读:客户主账号、子账号 不走
/rbac/user/*。写法与 4 一致:每接口 请求示例 → 业务逻辑(核心流程 / 校验与异常)。5.1 为管理端 OMS-Customer OpenAPI(Swagger UI — OMS-Customer);5.2 为落实 1.1 / 2.2 / 1.10 / 1.12 的 目标接口(截至 OMS-Customer v1 可能尚未出现在同一 Swagger,落地后以 OpenAPI 为准)。须满足 2.2(子账号权限 ⊆ 主账号)。
5.1 客户资料(OMS-Customer)
在线文档:Swagger UI — OMS-Customer
机器可读:/admin/swagger/OMS-Customer/swagger.json(servers[0].url一般为https://api.nanafa.com/admin,下列路径相对该 base;以部署环境为准)。
鉴权:Authorization: Bearer {token}。
5.1.1 接口路径速查
| 接口名称 | 请求方式 | 路径(相对 admin base) | 说明 |
|---|---|---|---|
| 客户列表 | GET | /customer/list | Query:Page、PageSize、Keyword、Status、CustomerGroup、Salesman、CustomerService |
| 客户详情 | GET | /customer/get | Query:id(long) |
| 新增客户 | POST | /customer/create | Body:CreateCustomerInput |
| 更新客户 | POST | /customer/update | Body:UpdateCustomerInput |
| 删除客户 | POST | /customer/delete | Body:IdInput |
| 重置客户密码 | POST | /customer/reset-password | Body:ResetCustomerPasswordInput |
5.1.2 与 1.4 t_customer / 1.2 t_auth_account 及 DTO 约定
- 主键参数名:
/customer/get等 OMS-Customer 路径使用 Queryid表示t_customer.Id;5.2 客户 RBAC 使用customerId。二者同值(即客户主账号的PrincipalId),异名为兼容既有 OpenAPI 与 RBAC 草案,勿与user.Id混淆(见 4.1.1)。 t_customer:创建/更新 Body 与列表、详情响应以 5.1.3~5.1.6 JSON 为准;settlementMethodCode/customerTypeCode非空须t_dict有效(1.14);receivableFinanceUserId非空须指向未删user。t_auth_account:手机、邮箱、口令(仅创建)、微信。管理端代客重置口令 走/customer/reset-password(5.1.8)。若产品对客户主账号 / 子账号开放/rbac/auth/change-password(4.2.3),则 自助改密 与之相同机制(SessionVersion+1,见 2),不要把「改密」理解成仅有 5.1.8 一条路。微信:路径 A 在/customer/create一次写入;路径 B 在/customer/update补写(2.3)。CustomerStatus:0禁用、1正常、2冻结。role(字符串):写入t_principal_role的映射由实现约定,或与roleIds(5.2.2)统一。
5.1.3 客户列表(GET /customer/list)
5.1.3.1 接口请求示例
请求 /customer/list(GET,Query 参数)
?Page=1&PageSize=30&Keyword=&Status=1&CustomerGroup=0&Salesman=&CustomerService=
响应
{
"items": [
{
"id": 1001,
"customerCode": "W2601",
"customerName": "示例客户A",
"companyName": "示例科技有限公司",
"businessLicenseNo": "91110000MA01234567",
"businessLicenseImageUrl": "https://cdn.example.com/license/w2601.jpg",
"legalRepresentative": "张三",
"website": "https://example.com",
"settlementMethodCode": "settle_monthly",
"settlementMethodName": "月结",
"customerTypeCode": "type_enterprise",
"customerTypeName": "企业客户",
"appId": "app_oms_001",
"receivableFinanceUserId": 88,
"wechatAvatar": null,
"wechatNickname": null,
"email": "a@example.com",
"phone": "13800138000",
"balance": 0,
"totalQuota": 100000,
"usedQuota": 0,
"status": 1,
"remark": null,
"salesman": "zhangsan",
"customerService": "lisi",
"customerGroup": 0,
"role": "CustomerDefault",
"lastLoginIp": "10.0.0.1",
"lastLoginTime": "2026-05-08T10:00:00Z",
"createTime": "2026-01-01T08:00:00Z",
"updateTime": "2026-05-08T10:00:00Z"
}
],
"total": 1
}
5.1.3.2 接口业务逻辑
5.1.3.2.1 核心流程
- 按
Keyword(客户编号/名称/手机等,实现约定)、Status、CustomerGroup、Salesman、CustomerService过滤t_customer(IsDeleted=0)。 - 分页:
Page从 1 开始,PageSize与 3 中 TablePro 约定对齐时可默认 30。 - 列表项返回 1.4 业务列;
settlementMethodCode/customerTypeCode联t_dict填充settlementMethodName/customerTypeName。 - 联查
t_auth_account展示phone/email/微信/最后登录;role可由t_principal_role+role.Code聚合为首角色或主角色(与实现一致即可,须在 OpenAPI 说明)。
5.1.3.2.2 校验与异常
- 分页参数非法、排序字段不支持时返回
ApplicationExceptionResult(400)。 - 未授权或 token 无效时 401(与网关一致)。
5.1.4 客户详情(GET /customer/get)
5.1.4.1 接口请求示例
请求 /customer/get(GET,Query 参数)
?id=1001
响应
{
"id": 1001,
"customerCode": "W2601",
"customerName": "示例客户A",
"companyName": "示例科技有限公司",
"businessLicenseNo": "91110000MA01234567",
"businessLicenseImageUrl": "https://cdn.example.com/license/w2601.jpg",
"legalRepresentative": "张三",
"website": "https://example.com",
"settlementMethodCode": "settle_monthly",
"settlementMethodName": "月结",
"customerTypeCode": "type_enterprise",
"customerTypeName": "企业客户",
"appId": "app_oms_001",
"receivableFinanceUserId": 88,
"wechatAvatar": null,
"wechatNickname": null,
"email": "a@example.com",
"phone": "13800138000",
"balance": 0,
"totalQuota": 100000,
"usedQuota": 0,
"status": 1,
"remark": null,
"salesman": "zhangsan",
"customerService": "lisi",
"customerGroup": 0,
"role": "CustomerDefault",
"lastLoginIp": "10.0.0.1",
"lastLoginTime": "2026-05-08T10:00:00Z",
"createTime": "2026-01-01T08:00:00Z",
"createBy": "admin",
"updateTime": "2026-05-08T10:00:00Z",
"updateBy": "admin"
}
5.1.4.2 接口业务逻辑
5.1.4.2.1 核心流程
- 按
id加载t_customer;不存在或已删则异常。 - 拼接
t_auth_account(customer+PrincipalId=id)展示联系方式与登录信息。 - 返回 1.4 业务列;
settlementMethodCode/customerTypeCode联t_dict填充settlementMethodName/customerTypeName(见 5.1.4.1 响应示例)。
5.1.4.2.2 校验与异常
id缺失、客户不存在或已删除时返回ApplicationExceptionResult(400)。
5.1.5 新增客户(POST /customer/create)
5.1.5.1 接口请求示例
请求 /customer/create(POST,Body:CreateCustomerInput)
{
"customerCode": "W2602",
"customerName": "新客户B",
"companyName": "新客户B有限公司",
"businessLicenseNo": "91310115MA0ABCDEF0",
"businessLicenseImageUrl": "https://cdn.example.com/license/w2602.jpg",
"legalRepresentative": "李四",
"website": "https://newb.example.com",
"settlementMethodCode": "settle_prepaid",
"customerTypeCode": "type_enterprise",
"appId": "app_oms_002",
"receivableFinanceUserId": 88,
"phone": "13800138002",
"email": "b@example.com",
"password": "Initial#123",
"wechatOpenid": "oXXXX_先微信再开户",
"wechatUnionid": null,
"wechatAvatar": "https://thirdwx.qlogo.cn/...",
"wechatNickname": "客户微信昵称",
"balance": 0,
"totalQuota": 50000,
"usedQuota": 0,
"status": 1,
"remark": "线下签约",
"salesman": "zhangsan",
"customerService": "lisi",
"customerGroup": 0,
"role": "CustomerDefault"
}
响应
OpenAPI 200 无 schema;实现可返回新建
id或CustomerOutput,建议与/customer/get对齐便于前端刷新。
{
"id": 1002
}
5.1.5.2 接口业务逻辑
5.1.5.2.1 核心流程
- 校验
customerCode唯一(IsDeleted=0)、phone/email全局唯一(1.2);settlementMethodCode/customerTypeCode、receivableFinanceUserId校验见 5.1.2。 - 插入
t_customer(1.4 业务列与 Body 一致,含工商、字典码、AppId、ReceivableFinanceUserId等)。 - 创建
t_auth_account:PrincipalType=customer,PrincipalId=新客户Id,写入PasswordHash/PasswordSalt、Phone/Email、微信列(路径 A 见 2.3,Body 可含wechatOpenid等);Status与客户状态策略一致(禁用客户则认证行禁用)。 role:按 5.1.2 约定写入t_principal_role(仅customer主体),角色AllowedPrincipalTypes须含customer。- 写
t_operation_log(业务ADD快照或字段级,模块customer)。 PermissionRevision:若创建即绑定角色,主账号认证行PermissionRevision+1(1.1.6)。
5.1.5.2.2 校验与异常
- 必填缺失、编号/手机/邮箱冲突、微信 OpenID/UnionID 冲突、密码策略不满足、字典码无效、应收财务用户不存在、角色编码不存在或不允许
customer主体时返回ApplicationExceptionResult(400)。
5.1.6 更新客户(POST /customer/update)
5.1.6.1 接口请求示例
请求 /customer/update(POST,Body:UpdateCustomerInput)
{
"id": 1002,
"customerCode": "W2602",
"customerName": "新客户B(更名)",
"companyName": "新客户B有限公司",
"businessLicenseNo": "91310115MA0ABCDEF0",
"businessLicenseImageUrl": "https://cdn.example.com/license/w2602.jpg",
"legalRepresentative": "李四",
"website": "https://newb.example.com",
"settlementMethodCode": "settle_monthly",
"customerTypeCode": "type_enterprise",
"appId": "app_oms_002",
"receivableFinanceUserId": 90,
"phone": "13800138002",
"email": "b-new@example.com",
"wechatOpenid": "oXXXX_后绑微信",
"wechatUnionid": null,
"wechatAvatar": "https://thirdwx.qlogo.cn/...",
"wechatNickname": "客户微信昵称",
"balance": 1000,
"totalQuota": 80000,
"usedQuota": 0,
"status": 1,
"remark": "已续约",
"salesman": "zhangsan",
"customerService": "wangwu",
"customerGroup": 1,
"role": "CustomerVIP"
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
5.1.6.2 接口业务逻辑
5.1.6.2.1 核心流程
- 加载客户与认证行;部分字段未在 Body 出现时可保持原值(实现约定 PATCH 语义或全量替换,须在 OpenAPI 写清;上例为常见「可编辑字段全量提交」)。
- 更新
t_customer;phone/email/微信 同步t_auth_account(路径 B 补绑微信见 2.3),OpenID/UnionID 唯一性校验。 role变更:覆盖更新t_principal_role(customer主体),成功后PermissionRevision+1。- 若
status变为禁用/冻结:可同步认证行Status,是否立即SessionVersion+1 由产品决定(建议冻结时 +1 踢下线)。 - 写
t_operation_log(UPDATE)。
5.1.6.2.2 校验与异常
- 客户不存在、编号/手机/邮箱冲突、微信 OpenID 已被占用、额度逻辑非法(如
usedQuota>totalQuota)、字典码无效、应收财务用户不存在、角色非法时返回ApplicationExceptionResult(400)。
5.1.7 删除客户(POST /customer/delete)
5.1.7.1 接口请求示例
请求 /customer/delete(POST,Body:IdInput)
{
"id": 1002
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
5.1.7.2 接口业务逻辑
5.1.7.2.1 核心流程
- 逻辑删除
t_customer(IsDeleted=1);逻辑删除或禁用t_auth_account;清理/失效 refresh(SessionVersion+1 或等价吊销)。 - 软删
t_principal_role相关行(或保留历史由IsDeleted表达,与实现统一即可)。 - 若存在
t_customer_sub_account:须先业务校验(禁止删主客户或级联策略由产品定)。 - 写
t_operation_log(DELETE或 RBAC 相关动作码)。
5.1.7.2.2 校验与异常
- 客户不存在、存在未处理子账号或业务禁止删除时返回
ApplicationExceptionResult(400)。
5.1.8 重置客户密码(POST /customer/reset-password)
5.1.8.1 接口请求示例
请求 /customer/reset-password(POST,Body:ResetCustomerPasswordInput)
{
"id": 1001,
"newPassword": "Reset#2026"
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
5.1.8.2 接口业务逻辑
5.1.8.2.1 核心流程
- 定位
t_auth_account(customer+PrincipalId=id)。 - 更新
PasswordHash/PasswordSalt;SessionVersion+1,使已有 refresh 失效(见 2)。 - 写
t_operation_log(auth_password_change或管理端重置专用动作码,TargetPrincipalType=customer)。
5.1.8.2.2 校验与异常
- 客户不存在、认证行不存在、新密码不满足策略时返回
ApplicationExceptionResult(400)。 - 操作者须具备客户管理权限(PermissionCode 由实现注册在
t_permission_definition)。
5.2 客户域 RBAC(主账号 / 子账号 / 白名单)— 目标接口
说明:下列路径在 OMS-Customer v1 OpenAPI 中可能尚未出现;请求/响应字段以未来 OpenAPI 为准,本节为与 1.1、2.2 对齐的 规范草案。子账号
MainCustomerId、白名单t_customer_sub_permission见 1.5、1.12。
5.2.1 接口路径速查
| 接口名称 | 请求方式 | 请求路径 | 说明 |
|---|---|---|---|
| 主账号分配角色 | POST | /customer/role/assign | 覆盖写主账号 t_principal_role;PermissionRevision+1;子账号权限 ⊆ 主校验 |
| 主账号角色列表 | GET | /customer/role/list | 主账号已绑角色 |
| 子账号列表 | GET | /customer/sub-account/list | 某主客户下子账号分页 |
| 创建子账号 | POST | /customer/sub-account/create | t_customer_sub_account + t_auth_account |
| 更新子账号 | POST | /customer/sub-account/update | 状态/认证信息等 |
| 删除子账号 | POST | /customer/sub-account/delete | 软删 + 吊销登录态 |
| 子账号分配角色 | POST | /customer/sub-account/assign-roles | (P_{\text{子}}\subseteq P_{\text{主}}) |
| 子账号配置白名单 | POST | /customer/sub-account/assign-permissions | 覆盖 t_customer_sub_permission |
| 子账号有效权限预览 | GET | /customer/sub-account/effective-permissions | 聚合权限码 |
5.2.2 主账号分配角色(POST /customer/role/assign)
5.2.2.1 接口请求示例
请求 /customer/role/assign(POST,Body)
{
"customerId": 1001,
"roleIds": [200, 201]
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
5.2.2.2 接口业务逻辑
5.2.2.2.1 核心流程
- 校验客户存在且
PrincipalType=customer对应认证行有效。 - 校验每个
roleId:role.AllowedPrincipalTypes含customer,且role未删除。 - 事务内:计算主账号新权限并集 (P_{\text{主}}^{\text{new}});对所有
t_customer_sub_account(MainCustomerId=customerId,未删)计算当前 (P_{\text{子}}),须满足 (P_{\text{子}}\subseteq P_{\text{主}}^{\text{new}})(2.2);否则整单回滚 400。 - 覆盖写
t_principal_role(PrincipalType=customer,PrincipalId=customerId)。 - 主账号
t_auth_account.PermissionRevision+1。 t_operation_log:Action=principal_role_assign,TargetPrincipalType=customer,TargetPrincipalId=customerId,DetailJson含roleIds。
5.2.2.2.2 校验与异常
- 客户不存在、角色非法、子账号超限、空
roleIds是否允许(产品定)时返回ApplicationExceptionResult(400)。
5.2.3 主账号角色列表(GET /customer/role/list)
5.2.3.1 接口请求示例
请求 /customer/role/list(GET,Query)
?customerId=1001
响应
[
{
"id": 200,
"name": "客户默认角色",
"code": "CustomerDefault"
}
]
5.2.3.2 接口业务逻辑
5.2.3.2.1 核心流程
- 查
t_principal_role(customer+PrincipalId=customerId,IsDeleted=0),联role表返回 id/name/code。
5.2.3.2.2 校验与异常
customerId无效时返回ApplicationExceptionResult(400)。
5.2.4 子账号列表(GET /customer/sub-account/list)
5.2.4.1 接口请求示例
请求 /customer/sub-account/list(GET,Query)
?mainCustomerId=1001&Page=1&PageSize=30&Keyword=
响应
{
"total": 2,
"items": [
{
"id": 5001,
"mainCustomerId": 1001,
"subAccountCode": "W2601_01",
"subStatus": 1,
"phone": "13900139001",
"email": "sub1@example.com",
"createTime": "2026-03-01T08:00:00Z"
}
]
}
5.2.4.2 接口业务逻辑
5.2.4.2.1 核心流程
- 过滤
t_customer_sub_account:MainCustomerId=mainCustomerId,IsDeleted=0;Keyword可匹配编号/手机(联认证表)。 - 返回分页
total+items(与 TablePro 约定一致)。
5.2.4.2.2 校验与异常
- 主客户不存在、分页参数非法时返回
ApplicationExceptionResult(400)。
5.2.5 创建子账号(POST /customer/sub-account/create)
5.2.5.1 接口请求示例
请求 /customer/sub-account/create(POST,Body)
{
"mainCustomerId": 1001,
"subAccountCode": "W2601_02",
"phone": "13900139002",
"email": "sub2@example.com",
"password": "Sub#123456",
"wechatOpenid": null,
"wechatUnionid": null,
"wechatAvatar": null,
"wechatNickname": null,
"roleIds": [210]
}
subAccountCode可省略,由规则「主客户编号 +_+ 序号」生成(1.5)。wechatOpenid/wechatUnionid/wechatAvatar/wechatNickname:路径 A 可与本单创建一并提交(2.3);路径 B 留/customer/sub-account/update。
响应
{
"id": 5002,
"subAccountCode": "W2601_02"
}
5.2.5.2 接口业务逻辑
5.2.5.2.1 核心流程
- 校验主客户存在;
phone/email在t_auth_account全局唯一。 - 插入
t_customer_sub_account(SubStatus默认正常)。 - 创建
t_auth_account:PrincipalType=customer_sub,PrincipalId=子账号Id,口令、联系方式与可选 微信字段(1.2)写入。 roleIds:写入t_principal_role(customer_sub),角色须AllowedPrincipalTypes含customer_sub;事务内校验 (P_{\text{子}}\subseteq P_{\text{主}})(1.1.3、2.2)。- 子账号认证行
PermissionRevision初值与后续 +1 规则同 1.1.6。 t_operation_log:子账号创建(业务ADD或 RBAC 组合动作)。
5.2.5.2.2 校验与异常
- 主客户不存在、编号重复、手机邮箱冲突、微信 OpenID/UnionID 冲突、角色非法、子权限超出主账号时返回
ApplicationExceptionResult(400)。
5.2.6 更新子账号(POST /customer/sub-account/update)
5.2.6.1 接口请求示例
请求 /customer/sub-account/update(POST,Body)
{
"id": 5002,
"subStatus": 0,
"phone": "13900139022",
"email": "sub2-new@example.com",
"wechatOpenid": "oXXXX_后绑微信",
"wechatUnionid": null,
"wechatAvatar": "https://thirdwx.qlogo.cn/...",
"wechatNickname": "子账号微信昵称"
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
5.2.6.2 接口业务逻辑
5.2.6.2.1 核心流程
- 更新
t_customer_sub_account(如SubStatus)。 phone/email/微信 变更同步t_auth_account(路径 B 见 2.3),OpenID/UnionID 唯一;若禁用子账号可SessionVersion+1。- 若变更影响权限边界(如后续版本增加字段),须重新校验 (P_{\text{子}}\subseteq P_{\text{主}})。
t_operation_log(UPDATE)。
5.2.6.2.2 校验与异常
- 子账号不存在、越权(非该主客户下)、联系方式冲突、微信 OpenID 已被占用时返回
ApplicationExceptionResult(400)。
5.2.7 删除子账号(POST /customer/sub-account/delete)
5.2.7.1 接口请求示例
请求 /customer/sub-account/delete(POST,Body)
{
"id": 5002
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
5.2.7.2 接口业务逻辑
5.2.7.2.1 核心流程
- 软删
t_customer_sub_account;软删/禁用认证行;SessionVersion+1 吊销 token。 - 软删
t_principal_role、t_customer_sub_permission相关行(约定与 1.12 一致)。 t_operation_log。
5.2.7.2.2 校验与异常
- 子账号不存在或已删除时返回
ApplicationExceptionResult(400)。
5.2.8 子账号分配角色(POST /customer/sub-account/assign-roles)
5.2.8.1 接口请求示例
请求 /customer/sub-account/assign-roles(POST,Query + Body)
?subAccountId=5001
[210, 211]
响应
{
"code": 0,
"msg": "success",
"data": null
}
5.2.8.2 接口业务逻辑
5.2.8.2.1 核心流程
- 解析
subAccountId归属MainCustomerId,加载主账号有效权限 (P_{\text{主}})。 - 事务内:由
roleIds得 (P_{\text{role}});若有白名单t_customer_sub_permission未删行,则 (P = P_{\text{role}} \cap P_{\text{whitelist}}),否则 (P = P_{\text{role}})(1.1.3)。校验 (P \subseteq P_{\text{主}})。 - 覆盖写
t_principal_role(customer_sub+PrincipalId=subAccountId)。 - 子账号
PermissionRevision+1。 t_operation_log:principal_role_assign,TargetPrincipalType=customer_sub,TargetPrincipalId=subAccountId。
5.2.8.2.2 校验与异常
- 子账号不存在、角色不适用
customer_sub、权限超出主账号时返回ApplicationExceptionResult(400)。
5.2.9 子账号配置白名单(POST /customer/sub-account/assign-permissions)
5.2.9.1 接口请求示例
请求 /customer/sub-account/assign-permissions(POST,Body)
{
"subAccountId": 5001,
"permissionCodes": [
"order:list",
"order:create"
]
}
响应
{
"code": 0,
"msg": "success",
"data": null
}
5.2.9.2 接口业务逻辑
5.2.9.2.1 核心流程
- 每个
permissionCodes元素须 ∈t_permission_definition.Code且未删除。 - 事务内:令白名单集合为 (W);角色并集 (P_{\text{role}});最终 (P = P_{\text{role}} \cap W)(若
permissionCodes为空 是否表示「无白名单」= 仅用角色并集,产品须二选一并写清 OpenAPI;推荐:空数组 = 清除白名单行,回退角色并集)。 - 校验 (P \subseteq P_{\text{主}})。
- 覆盖写
t_customer_sub_permission(软删旧行 + 插入新行,或等价单表维护)。 - 子账号
PermissionRevision+1。 t_operation_log:sub_whitelist_assign,DetailJson含码表。
5.2.9.2.2 校验与异常
- 未注册权限码、超出主账号权限、子账号不存在时返回
ApplicationExceptionResult(400)。
5.2.10 子账号有效权限预览(GET /customer/sub-account/effective-permissions)
5.2.10.1 接口请求示例
请求 /customer/sub-account/effective-permissions(GET,Query)
?subAccountId=5001
响应
{
"rolePermissionCodes": ["order:list", "order:create", "order:export"],
"afterWhitelist": ["order:list", "order:create"],
"mainCustomerMax": ["order:list", "order:create", "order:export", "finance:view"]
}
afterWhitelist为最终用于 API 鉴权的集合 (P);字段命名实现可改为effectivePermissionCodes。
5.2.10.2 接口业务逻辑
5.2.10.2.1 核心流程
- 计算 (P_{\text{role}})、白名单交、(P_{\text{主}}),返回便于联调与排错的三元组(或最小返回
effectivePermissionCodes)。
5.2.10.2.2 校验与异常
- 子账号不存在、调用者非主客户管理员时返回
ApplicationExceptionResult(400)。