# RBAC 与统一认证 — 规范说明 ## 导读 ### 这份文档解决什么问题? 用直白话说,系统要搞清楚四件事: 1. **谁能登录**(内部员工、客户主账号、客户子账号)——账号与凭证放在哪张表、怎么区分类型。 2. **登录之后凭什么进业务**——访问令牌、刷新令牌,以及改权限、改密码后旧令牌如何作废。 3. **能操作哪些功能**——有效权限码来自 **`t_principal_role`** 的角色并集;**客户子账号**还可由主账号在 **`t_customer_sub_permission`** 配**可选白名单**,与角色并集**求交**再收窄(无任何白名单行时等于仅用角色)。用意是:同一角色模板下仍可把某个子账号缩到更少能力,而不必为每人微调就新建一堆细角色;**菜单由同一套有效权限码推导**,并非与白名单并列的另一套「来源」。**菜单只负责界面展示,不能代替接口鉴权**。 4. **出了问题怎么追溯**——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 与菜单。 ```mermaid 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 | - | 是 | **注册必填**;全局唯一 | | Email | 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`、`Email` **均必填、全局唯一**。微信 OpenID **可选**,仅快捷登录/展示,**不可替代**手机+邮箱开户。`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`/`Email` 注册必填**;微信/口令约定同 **2**。角色见 **`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. 业务·字段级模式** 1. **新增(`ADD`)** - 对**每个需要留痕的字段**写 **1 条**记录(若配置为「仅关键字段」,则只对配置集内字段写)。 - **`OldValue` = `NULL`**,`NewValue` = 录入值(字符串化存储;JSON 可整段序列化)。 2. **修改(`UPDATE`)** - **仅当字段值相对库中旧值发生变化**才写日志;**无变化不写**。 - **每个变化字段 1 条**记录:`OldValue` = 修改前,`NewValue` = 修改后。 3. **删除(`DELETE`)** - **`OldValue` = 删除前的值**,**`NewValue` = `NULL`**。 - 可按策略记录**全部字段**或**关键字段**(与 ADD 一致,由模块配置)。 4. **批量操作** - **逐条、逐字段**写入,**禁止**把多条数据合并成一条摘要;保证 **每条数据、每个字段变更** 均有 **独立日志行**(可用相同 `BatchId` 关联同一事务/任务)。 **B. 业务·快照模式** 1. **新增(`ADD`)** - **`OldSnapshotJson` = `NULL`**,**`NewSnapshotJson`** = 录入后对象(建议含主键与业务键,便于前端展示)。 - **每个新增对象 1 行**(批量则多行,同一 `BatchId`)。 2. **修改(`UPDATE`)** - 若整体与库中相比**无任何字段变化**,可不写日志。 - **每次更新 1 行**:`OldSnapshotJson` = 变更前完整对象,`NewSnapshotJson` = 变更后完整对象。 3. **删除(`DELETE`)** - **每个删除对象 1 行**:`OldSnapshotJson` = 删除前完整对象,**`NewSnapshotJson` = `NULL`**。 4. **批量操作** - **逐对象**写入,**不合并**;每条变更独立一行,可用相同 `BatchId`。 **C. RBAC 动作行** 1. **一次 API 或后台动作 1 行**(勿与业务字段级/快照混在同一语义行内重复描述同一事件)。 2. **`Action`** 使用已注册动作码;**`ModuleCode`** 建议 **`rbac`**。 3. **`TargetPrincipalType`/`TargetPrincipalId`、`RoleId`、`DetailJson`** 按具体接口填充;无对应实体时列可 **`NULL`**。 **D. 共用规则** 4. **系统自动操作** - **`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](http://api.nanafa.com/admin/index.html?urls.primaryName=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 的实现决策(当前规范留歧义处)**: 1. **`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 ``` **响应** ```json { "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 ``` **响应** ```json { "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 ``` **响应** ```json { "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) > 无请求参数,基于当前登录令牌解析用户身份。 **响应** ```json { "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`(与令牌 claim `main_customer_id` 一致)。 #### 4.2.5 吊销全部会话(POST /rbac/auth/revoke-all-sessions) ##### 4.2.5.1 接口请求示例 **请求 /rbac/auth/revoke-all-sessions**(POST) > 无 Body;基于当前 access token 识别主体。 **响应** ```json { "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) > 无请求参数。 **响应** ```json [ "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) > 无请求参数。 **响应** ```json [ { "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 ``` **响应** ```json 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 ``` ```json [ "rbac:user:get-list", "rbac:role:get-list" ] ``` **响应** ```json { "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 ``` **响应** ```json { "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) > 无请求参数。 **响应** ```json [ { "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 ``` **响应** ```json { "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) ```json { "name": "运营", "code": "Operator", "allowedPrincipalTypes": "user", "permissions": [ "rbac:user:get-list", "rbac:role:get-list" ] } ``` **响应** ```json { "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 ``` ```json { "name": "运营A" } ``` **响应** ```json { "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 ``` **响应** ```json { "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 ``` ```json [ "rbac:user:get-list", "rbac:role:get-list" ] ``` **响应** ```json { "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 ``` **响应** ```json [ "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 ``` **响应** ```json 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 ``` **响应** ```json [ { "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) > 无请求参数。 **响应** ```json [ { "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) > 无请求参数。 **响应** ```json [ { "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 ``` **响应** ```json { "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) ```json { "parentId": 10, "name": "运营部", "code": "OP", "type": "Department", "leaderId": 1, "order": 10, "description": "运营部门", "isEnabled": true } ``` **响应** ```json { "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 ``` ```json { "name": "运营中心", "type": "Department", "leaderId": 1, "order": 20, "description": "运营中心", "isEnabled": true } ``` **响应** ```json { "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 ``` **响应** ```json { "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 ``` ```json { "newParentId": 10 } ``` **响应** ```json { "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 ``` **响应** ```json { "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 ``` **响应** ```json { "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) ```json { "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** 绑定。 **响应** ```json { "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 ``` ```json { "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。 **响应** ```json { "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 ``` **响应** ```json { "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 ``` **响应** ```json { "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 ``` ```json [ 20, 21 ] ``` **响应** ```json { "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 ``` **响应** ```json [ { "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 ``` **响应** ```json [ "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 ``` **响应** ```json [ { "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 ``` **响应** ```json { "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 ``` ```json { "oldPassword": "123456", "newPassword": "new123456" } ``` **响应** ```json { "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 ``` **响应** ```json { "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) > 无请求参数。 **响应** ```json [ { "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 ``` **响应** ```json [ { "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) ```json { "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": "用户管理菜单" } ``` **响应** ```json { "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 ``` ```json { "name": "用户中心", "permissionCode": "rbac:user:get-list", "icon": "User", "route": "/system/user", "component": "system/user/index", "isHidden": false, "isCached": true, "description": "用户中心菜单" } ``` **响应** ```json { "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 ``` **响应** ```json { "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 ``` **响应** ```json { "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 ``` **响应** ```json { "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](http://api.nanafa.com/admin/index.html?urls.primaryName=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](http://api.nanafa.com/admin/index.html?urls.primaryName=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 路径使用 Query **`id`** 表示 **`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= ``` **响应** ```json { "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 ``` **响应** ```json { "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`) ```json { "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`** 对齐便于前端刷新。 ```json { "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`) ```json { "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" } ``` **响应** ```json { "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`) ```json { "id": 1002 } ``` **响应** ```json { "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`) ```json { "id": 1001, "newPassword": "Reset#2026" } ``` **响应** ```json { "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) ```json { "customerId": 1001, "roleIds": [200, 201] } ``` **响应** ```json { "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 ``` **响应** ```json [ { "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= ``` **响应** ```json { "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) ```json { "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`**。 **响应** ```json { "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) ```json { "id": 5002, "subStatus": 0, "phone": "13900139022", "email": "sub2-new@example.com", "wechatOpenid": "oXXXX_后绑微信", "wechatUnionid": null, "wechatAvatar": "https://thirdwx.qlogo.cn/...", "wechatNickname": "子账号微信昵称" } ``` **响应** ```json { "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) ```json { "id": 5002 } ``` **响应** ```json { "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 ``` ```json [210, 211] ``` **响应** ```json { "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) ```json { "subAccountId": 5001, "permissionCodes": [ "order:list", "order:create" ] } ``` **响应** ```json { "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 ``` **响应** ```json { "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)。