Pārlūkot izejas kodu

feat: 静态代码审计,修复逻辑bug和安全漏洞

BaiLuoYan 4 nedēļas atpakaļ
vecāks
revīzija
a38aad86fb
1 mainītis faili ar 430 papildinājumiem un 54 dzēšanām
  1. 430 54
      README.md

+ 430 - 54
README.md

@@ -757,7 +757,20 @@ Content-Type: application/json
 
 #### POST /api/auth/login — 产品端登录
 
-**供产品后端调用**,超级管理员无法通过此接口登录。
+产品终端用户的统一登录入口。用户通过用户名密码 + 产品编码登录指定产品,登录成功后获得 JWT 令牌对和该用户在此产品下的权限列表。
+
+**调用场景:**
+
+- **产品前端登录页**:用户在 CRM、OA 等业务系统的登录页面输入账号密码,前端调用此接口获取 token
+- **产品后端代理登录**:产品后端收到用户凭据后通过 HTTP 或 gRPC 调用此接口,将返回的 token 下发给客户端
+- **移动端 / 小程序登录**:移动客户端直接调用此接口完成产品侧身份认证
+
+**安全约束:**
+
+- 超级管理员账号无法通过此接口登录(必须走 `/auth/adminLogin`),防止超管凭据在产品端暴露
+- 产品必须处于启用状态,否则拒绝登录
+- 用户必须是该产品的有效成员(`status=1`),且账号未冻结
+- 受 IP 维度限流保护,防止暴力破解;仅对已存在的用户名消耗限流配额
 
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
@@ -769,14 +782,25 @@ Content-Type: application/json
 
 | 字段 | 类型 | 说明 |
 | ------ | ------ | ------ |
-| accessToken | string | 访问令牌 |
-| refreshToken | string | 刷新令牌 |
+| accessToken | string | 访问令牌(用于后续 API 调用,默认 2 小时有效) |
+| refreshToken | string | 刷新令牌(用于续期,默认 7 天有效) |
 | expires | int64 | accessToken 过期时间(Unix 时间戳,秒) |
-| userInfo | object | 用户信息(含 perms 权限码数组) |
+| userInfo | object | 用户信息(含 `perms` 权限码数组,前端据此控制菜单/按钮显隐) |
 
 #### POST /api/auth/adminLogin — 管理后台登录
 
-**仅供权限系统管理后台使用**,需要传入配置的 `managementKey` 进行身份验证。
+权限管理系统自身管理后台的登录入口。仅限超级管理员使用,必须额外提供 `managementKey` 验证身份。
+
+**调用场景:**
+
+- **管理后台前端登录**:超级管理员在权限系统管理 UI 的登录页面输入账号密码 + managementKey
+- **运维脚本自动化**:运维人员通过脚本调用此接口获取超管 token,执行批量管理操作(如创建产品、批量建用户)
+
+**安全约束:**
+
+- 必须传入与服务端配置一致的 `managementKey`,该密钥仅管理后台前端持有,不可泄露给产品端
+- `managementKey` 校验在限流之前执行,无效密钥不会消耗限流配额,防止 DoS 攻击
+- 受 IP 维度限流保护
 
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
@@ -784,30 +808,57 @@ Content-Type: application/json
 | password | string | 是 | 密码 |
 | managementKey | string | 是 | 管理端密钥(配置文件中的 `Auth.ManagementKey`) |
 
-**响应 data:** 与产品端登录接口相同。登录后不携带产品上下文,token 中 `productCode` 和 `perms` 为空。
+**响应 data:** 与产品端登录接口相同。登录后不携带产品上下文,token 中 `productCode` 和 `perms` 为空。超管拥有所有产品的全部权限,不需要在 token 中枚举。
 
 #### POST /api/auth/refreshToken — 刷新令牌
 
-通过 `Authorization: Bearer {refreshToken}` 请求头传入 refresh token。
+使用有效的 refreshToken 换取全新的令牌对。采用**令牌轮转**策略,每次刷新后旧令牌即时失效,确保单会话安全。
+
+**调用场景:**
+
+- **accessToken 即将过期时自动续期**:前端在 accessToken 到期前(如提前 5 分钟)调用此接口,无感刷新用户会话
+- **accessToken 已过期后恢复会话**:前端收到 401 响应后,用 refreshToken 尝试换取新令牌,成功则重试原请求,失败则跳转登录
+- **切换产品上下文**:管理后台超管登录后,通过传入不同的 `productCode` 切换到特定产品的权限视角,获取该产品下的权限列表
+
+**安全约束:**
+
+- 通过 `Authorization: Bearer {refreshToken}` 请求头传入,必须是 `tokenType=refresh` 的令牌
+- 每次刷新递增 `tokenVersion`,旧的 access/refresh 令牌即时失效——即使令牌被窃取,攻击者也只能用一次
+- 产品禁用时拒绝刷新,立即切断该产品下所有用户的会话
+- refreshToken 有固定有效期(默认 7 天),过期后必须重新登录
 
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
 | Authorization | header | 是 | `Bearer {refreshToken}` |
 | productCode | string | 否 | 切换产品上下文时传入(Body) |
 
-**响应 data:** 与登录接口相同。采用令牌轮转策略:每次刷新都会递增 `tokenVersion`,返回全新的 accessToken 和 refreshToken,旧令牌即时失效。refresh token 有固定有效期,过期后需重新登录。
+**响应 data:** 与登录接口相同,包含全新的 accessToken + refreshToken + userInfo
 
 #### POST /api/perm/sync — 同步产品权限
 
-通过 appKey/appSecret 认证,产品后端上报全量权限列表。
+产品后端全量上报权限列表,系统自动对比差异执行新增、更新和禁用操作。这是产品权限声明的唯一入口,管理员无需手动创建权限。
+
+**调用场景:**
+
+- **产品服务启动时自动同步**:产品后端在 main 函数中调用此接口,确保代码中新增或重命名的权限点自动注册到权限系统。推荐放在服务启动阶段(非请求链路),失败时 `log.Fatal` 终止启动
+- **CI/CD 部署流程中调用**:在产品部署脚本中调用此接口,确保新版本的权限定义在服务上线前就已同步
+- **权限定义热更新**:产品无需重启即可通过调用此接口更新权限列表(如通过管理接口触发)
+
+**处理逻辑:**
+
+- 列表中已存在的权限:按 `code` 匹配,更新 `name` 和 `remark`
+- 列表中新增的权限:自动创建
+- 数据库中有但列表中没有的权限:自动禁用(`status=2`),不删除——已绑定到角色的记录保留但权限计算时过滤
+
+**安全约束:** 通过 `appKey`/`appSecret` 认证(非 JWT),无需登录,受 IP 维度限流保护
 
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
 | appKey | string | 是 | 产品接入密钥 |
 | appSecret | string | 是 | 产品签名密钥 |
-| perms | array | 是 | 权限列表 |
-| perms[].code | string | 是 | 权限码(如 `user:create`) |
-| perms[].name | string | 是 | 权限名 |
+| perms | array | 是 | 权限列表(全量,不在列表中的已有权限将被禁用) |
+| perms[].code | string | 是 | 权限码(如 `user:create`,产品内唯一) |
+| perms[].name | string | 是 | 权限名(用于管理后台展示) |
 | perms[].remark | string | 否 | 备注 |
 
 **响应 data:** `{"added": 3, "updated": 1, "disabled": 0}`
@@ -816,16 +867,43 @@ Content-Type: application/json
 
 #### POST /api/auth/logout — 用户注销
 
-无请求参数。递增当前用户的 `tokenVersion`,使所有已签发的 access/refresh 令牌立即失效,并清除用户缓存。
+主动吊销当前用户的所有已签发令牌,使其在所有设备/浏览器上立即失效。
+
+**调用场景:**
+
+- **用户主动退出登录**:前端「退出登录」按钮点击后调用此接口,确保服务端即时吊销令牌(而非仅清理客户端本地存储)
+- **安全事件应急处置**:用户发现账号异常(如收到异地登录提醒),通过注销接口使所有设备上的令牌立即失效
+- **管理后台切换账号前**:超管在管理后台切换到其他账号前,先注销当前会话
 
-**响应 data:** `null`
+**处理逻辑:** 递增当前用户的 `tokenVersion`,所有持有旧版本的 access/refresh 令牌在下次使用时都会被拒绝;同时清除 UserDetailsLoader 缓存。
+
+无请求参数。**响应 data:** `null`
 
 #### POST /api/auth/userInfo — 获取当前用户信息
 
-无请求参数。**响应 data:** `UserInfo` 对象。
+返回当前登录用户的完整信息,包括基本资料、部门归属、产品成员身份和权限列表。数据来自 UserDetailsLoader 缓存。
+
+**调用场景:**
+
+- **前端页面初始化**:用户刷新页面或首次加载时,前端调用此接口获取用户信息,渲染头像、昵称、菜单权限等
+- **前端权限刷新**:当管理员调整了用户的角色/权限后,前端可主动调用此接口重新获取最新权限列表,无需重新登录
+- **移动端个人中心**:展示用户头像、昵称、部门等个人资料
+- **前端权限守卫**:路由切换前调用此接口确认用户仍有有效会话及对应权限
+
+无请求参数。**响应 data:** `UserInfo` 对象(含 userId、username、nickname、avatar、email、phone、deptName、memberType、perms 等)。
 
 #### POST /api/auth/changePassword — 修改密码
 
+用户修改自己的登录密码。修改成功后递增 `tokenVersion`,所有已签发令牌即时失效,用户需重新登录。
+
+**调用场景:**
+
+- **用户主动修改密码**:用户在个人设置页面修改密码
+- **首次登录强制改密**:管理员创建用户时设置了初始密码,用户首次登录后系统提示修改密码(前端检查 `mustChangePassword` 标志)
+- **密码泄露后重置**:用户怀疑密码泄露,通过修改密码使所有已签发令牌失效
+
+**安全约束:** 必须验证原密码正确后才能修改;新密码需 6-72 字符且不能与旧密码相同。
+
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
 | oldPassword | string | 是 | 原密码 |
@@ -835,9 +913,18 @@ Content-Type: application/json
 
 #### POST /api/product/create — 创建产品
 
+为一个新的业务系统注册接入权限系统。创建后自动生成 `appKey`/`appSecret`(用于权限同步认证)和初始 ADMIN 管理员账号。
+
+**调用场景:**
+
+- **新业务系统接入**:公司新开发了一个 CRM/OA/电商后台等系统,超管在管理后台创建产品,获取接入凭证交给产品开发团队
+- **多租户场景拓展**:SaaS 平台新增一个租户,通过创建产品实现权限隔离
+
+**注意事项:** 响应中的 `appKey`、`appSecret`、`adminUser`、`adminPassword` 仅在创建时返回一次,请立即保存。
+
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
-| code | string | 是 | 产品编码(全局唯一) |
+| code | string | 是 | 产品编码(全局唯一,仅允许字母/数字/下划线/中划线,不能以数字开头,上限 64 字符) |
 | name | string | 是 | 产品名称 |
 | remark | string | 否 | 备注 |
 
@@ -845,6 +932,16 @@ Content-Type: application/json
 
 #### POST /api/product/update — 更新产品
 
+修改产品的名称、备注或启用/禁用状态。
+
+**调用场景:**
+
+- **修改产品信息**:产品改名(如从「客户系统」改为「CRM 系统」)、补充备注说明
+- **禁用产品**:项目下线或暂停运营时,将产品状态设为禁用(`status=2`)。禁用后该产品下所有在线用户的令牌即时失效(JWT 中间件和 gRPC 接口均拦截),任何登录/刷新请求都将被拒绝
+- **重新启用产品**:暂停后恢复运营,将产品状态改回启用(`status=1`)
+
+**副作用:** 更新操作会清除该产品下所有用户的 UserDetailsLoader 缓存(`CleanByProduct`),确保状态变更即时生效。
+
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
 | id | int64 | 是 | 产品 ID |
@@ -854,6 +951,14 @@ Content-Type: application/json
 
 #### POST /api/product/list — 产品列表
 
+分页查询所有已注册的产品。
+
+**调用场景:**
+
+- **管理后台产品管理页面**:超管查看所有接入的产品列表,进行管理操作
+- **创建角色/成员时选择产品**:管理后台在创建角色、添加成员等操作时,需要先选择目标产品,此接口提供产品下拉列表数据
+- **运维监控面板**:展示当前系统管理的产品总数和各产品状态
+
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
 | page | int64 | 否 | 页码,默认 1 |
@@ -863,6 +968,13 @@ Content-Type: application/json
 
 #### POST /api/product/detail — 产品详情
 
+查询单个产品的完整信息(含 `appKey`,不含 `appSecret`)。
+
+**调用场景:**
+
+- **产品详情/编辑页面**:管理后台进入产品编辑页时,先调用此接口获取当前数据回填表单
+- **查看产品接入凭证**:超管需要查看某产品的 `appKey`(`appSecret` 仅创建时返回,此接口不包含)
+
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
 | id | int64 | 是 | 产品 ID |
@@ -871,18 +983,37 @@ Content-Type: application/json
 
 #### POST /api/dept/create — 创建部门
 
+在组织架构中新增一个部门节点。部门是全局概念,不隶属于任何产品。
+
+**调用场景:**
+
+- **搭建组织架构**:公司初始化时创建一级部门(研发部、市场部、运营部),再创建子部门(前端组、后端组、测试组)
+- **组织调整新增部门**:公司扩张新增业务线或团队时,在对应的上级部门下创建新部门
+- **创建研发部门**:设置 `deptType=DEV` 的部门中的成员加入任何产品后自动拥有全部权限,适用于需要跨产品调试的研发团队
+
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
 | parentId | int64 | 是 | 父部门 ID,0 表示顶级部门 |
 | name | string | 是 | 部门名称 |
-| sort | int64 | 否 | 排序值 |
-| deptType | string | 否 | `NORMAL`(默认)或 `DEV`(研发部门) |
+| sort | int64 | 否 | 排序值(同级部门间的排列顺序,数值越小越靠前) |
+| deptType | string | 否 | `NORMAL`(默认)或 `DEV`(研发部门,成员在产品中自动拥有全部权限) |
 | remark | string | 否 | 备注 |
 
 **响应 data:** `{"id": 1}`
 
 #### POST /api/dept/update — 更新部门
 
+修改部门名称、排序、类型或启禁用状态。
+
+**调用场景:**
+
+- **部门更名**:组织调整时修改部门名称(如「技术部」改为「研发中心」)
+- **调整排序**:变更部门在同级中的显示顺序
+- **变更部门类型**:将普通部门升级为研发部门(`NORMAL` → `DEV`),或将研发部门降级为普通部门。类型变更立即影响该部门下所有用户在各产品中的权限
+- **禁用部门**:部门撤销时禁用,保留历史数据
+
+**副作用:** 更新操作会清除该部门下所有用户的 UserDetailsLoader 缓存,确保部门类型或状态变更即时反映到权限计算中。
+
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
 | id | int64 | 是 | 部门 ID |
@@ -894,7 +1025,14 @@ Content-Type: application/json
 
 #### POST /api/dept/delete — 删除部门
 
-存在子部门时无法删除。
+永久删除一个部门。存在子部门时拒绝删除,必须先删除或迁移子部门。
+
+**调用场景:**
+
+- **清理空部门**:组织架构调整后,将人员迁移到新部门后删除空的旧部门
+- **撤销误创建的部门**:部门创建有误时及时删除
+
+**约束:** 存在子部门时返回 400 错误,防止意外删除整个子树。删除前应先通过部门树接口确认该部门无子节点。
 
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
@@ -902,36 +1040,73 @@ Content-Type: application/json
 
 #### POST /api/dept/tree — 部门树
 
-无请求参数。返回完整的部门树形结构(含 `children` 嵌套和 `deptType`)。
+返回完整的部门树形结构,包含所有层级的嵌套 `children` 和 `deptType` 标识。
+
+**调用场景:**
+
+- **管理后台组织架构页面**:以树形控件展示公司完整的部门层级结构,支持展开/折叠
+- **用户创建/编辑时选择部门**:表单中的部门选择器(树形下拉)需要此接口提供数据源
+- **权限检查的辅助参考**:管理员查看部门层级关系,了解谁能管理谁(部门层级影响管理权限范围)
+
+无请求参数。
 
 ### 权限管理
 
 #### POST /api/perm/list — 权限列表
 
+分页查询指定产品下的所有权限定义。权限由产品后端通过 `/perm/sync` 自动上报,管理员无法手动创建或删除权限。
+
+**调用场景:**
+
+- **角色绑定权限时选择权限**:管理后台在为角色分配权限时,需要先展示该产品下所有可用的权限列表(通常以穿梭框或复选列表形式)
+- **用户权限覆盖时选择权限**:管理后台在为用户设置 ALLOW/DENY 覆盖时,需要展示权限列表供选择
+- **权限审计**:管理员查看某产品当前注册了哪些权限,确认是否与代码中的定义一致
+- **排查权限同步结果**:产品部署后确认新增的权限是否已正确同步到系统中
+
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
 | productCode | string | 是 | 产品编码 |
 | page | int64 | 否 | 页码 |
 | pageSize | int64 | 否 | 每页条数 |
 
-**响应 data:** `{"total": N, "list": [PermItem...]}`
+**响应 data:** `{"total": N, "list": [PermItem...]}`(含 id、code、name、remark、status)
 
 ### 角色管理(超级管理员 或 产品管理员)
 
 #### POST /api/role/create — 创建角色
 
+在指定产品下创建一个新角色。角色是产品级概念,同名角色在不同产品中互不干扰。
+
+**调用场景:**
+
+- **按岗位建立角色体系**:产品管理员根据业务需求创建角色,如「销售经理」「普通销售」「客服」「财务审批」
+- **按操作范围划分角色**:创建「只读角色」「编辑角色」「管理角色」等不同操作级别的角色
+- **新功能上线配套角色**:产品新增功能模块时,创建对应的角色(如新增报表模块时创建「报表管理员」角色)
+
+**安全约束:** 产品必须处于启用状态;`permsLevel` 数值越小权限越高,用于同级成员间的管理权限比较。
+
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
 | productCode | string | 是 | 所属产品编码 |
 | name | string | 是 | 角色名(产品内唯一) |
 | remark | string | 否 | 备注 |
-| permsLevel | int64 | 是 | 权限等级 |
+| permsLevel | int64 | 是 | 权限等级(数值越小权限越高,用于管理权限判定) |
 
 **响应 data:** `{"id": 1}`
 
 #### POST /api/role/update — 更新角色
 
-非超级管理员不可降低角色的 `permsLevel`(即不可将数值改大)。
+修改角色名称、备注、权限等级或启禁用状态。
+
+**调用场景:**
+
+- **调整角色名称和描述**:角色职责变化后更新名称和备注
+- **调整权限等级**:将角色的 `permsLevel` 调高或调低,影响该角色用户在管理权限检查中的级别
+- **禁用角色**:角色不再使用时禁用(`status=2`),已绑定该角色的用户在权限计算时将跳过该角色的权限
+
+**安全约束:** 非超级管理员不可降低角色的 `permsLevel`(即不可将数值改大),防止产品管理员通过降低角色级别来提升自己的相对权限。
+
+**副作用:** 更新后自动清除所有绑定该角色的用户在该产品下的 UserDetailsLoader 缓存。
 
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
@@ -943,7 +1118,14 @@ Content-Type: application/json
 
 #### POST /api/role/delete — 删除角色
 
-级联删除角色关联的权限绑定和用户绑定。
+永久删除一个角色,级联清理该角色的所有权限绑定(`sys_role_perm`)和用户绑定(`sys_user_role`)。
+
+**调用场景:**
+
+- **清理废弃角色**:业务重构后旧角色不再需要,彻底删除以保持角色列表清洁
+- **合并角色**:将多个相似角色合并为一个时,先将用户迁移到新角色,再删除旧角色
+
+**副作用:** 删除前先查询所有绑定该角色的用户 ID 列表,删除后批量清除这些用户在该产品下的缓存,确保权限变更即时生效。
 
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
@@ -951,6 +1133,14 @@ Content-Type: application/json
 
 #### POST /api/role/list — 角色列表
 
+分页查询指定产品下的所有角色。
+
+**调用场景:**
+
+- **管理后台角色管理页面**:展示产品下的角色列表,供管理员查看、编辑、删除
+- **用户绑定角色时选择角色**:管理后台在为用户分配角色时,需要展示该产品下的角色列表(通常以多选框或穿梭框形式)
+- **权限审计**:查看某产品下有哪些角色及其权限等级分布
+
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
 | productCode | string | 是 | 产品编码 |
@@ -959,40 +1149,77 @@ Content-Type: application/json
 
 #### POST /api/role/detail — 角色详情
 
-返回角色信息及绑定的权限 ID 列表(`permIds`)。
+查询单个角色的完整信息及其绑定的权限 ID 列表。
+
+**调用场景:**
+
+- **角色编辑页面数据回填**:管理后台进入角色编辑页时,调用此接口获取角色信息和已绑定的权限列表回填表单
+- **查看角色权限配置**:管理员查看某角色具体拥有哪些权限,进行审计或排查
 
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
 | id | int64 | 是 | 角色 ID |
 
+**响应 data:** 角色信息 + `permIds`(该角色绑定的权限 ID 数组)。
+
 #### POST /api/role/bindPerms — 绑定角色权限
 
-全量替换该角色的权限。
+全量替换该角色绑定的权限列表。传入空数组清空所有绑定。
+
+**调用场景:**
+
+- **初始化角色权限**:创建角色后为其配置初始权限集合
+- **调整角色权限范围**:业务变化时增减角色权限(如销售角色新增「导出报表」权限)
+- **权限收缩**:产品下线某功能时,从相关角色中移除对应权限
+- **清空角色权限**:传入空 `permIds` 数组可清除该角色的所有权限绑定
+
+**副作用:** 操作后自动清除所有绑定该角色的用户在该产品下的缓存,确保权限变更即时反映到在线用户。
 
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
 | roleId | int64 | 是 | 角色 ID |
-| permIds | []int64 | 是 | 权限 ID 列表(空数组清空绑定) |
+| permIds | []int64 | 是 | 权限 ID 列表(全量替换,空数组清空绑定) |
 
 ### 用户管理
 
 #### POST /api/user/create — 创建用户(超级管理员 或 产品管理员)
 
+创建一个全局用户账号。用户是全局概念,创建后可通过「添加成员」接口加入多个产品。
+
+**调用场景:**
+
+- **新员工入职**:HR 或管理员为新员工创建账号,设置初始密码,并归属到对应部门
+- **批量建立账号**:运维脚本遍历员工花名册,批量调用此接口创建用户账号
+- **外部协作人员**:为外包或合作伙伴创建账号(可不设 `deptId`),后续通过成员管理授予特定产品访问权
+
+**注意事项:** 创建用户后还需通过 `/member/add` 将其加入产品才能登录使用该产品。
+
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
-| username | string | 是 | 登录名(唯一) |
+| username | string | 是 | 登录名(全局唯一) |
 | password | string | 是 | 密码(6-72 字符) |
 | nickname | string | 否 | 昵称 |
 | email | string | 否 | 邮箱(需合法格式) |
 | phone | string | 否 | 手机号(7-15 位数字,可含 `+` 前缀) |
 | remark | string | 否 | 备注 |
-| deptId | int64 | 否 | 部门 ID |
+| deptId | int64 | 否 | 部门 ID(归属研发部门的用户加入产品后自动获得全部权限) |
 
 **响应 data:** `{"id": 1}`
 
 #### POST /api/user/update — 更新用户(仅本人 或 超级管理员)
 
-支持字段清空:传 `""` 清空字符串字段,传 `0` 清空 deptId,不传字段则不更新。
+修改用户个人信息(昵称、邮箱、手机、部门等)。使用乐观锁防止并发更新冲突。
+
+**调用场景:**
+
+- **用户修改个人资料**:用户在个人设置页面更新昵称、邮箱、手机号
+- **超管调整用户部门**:组织架构调整时,超管将用户从一个部门转移到另一个部门(修改 `deptId`)。部门变更会影响权限——从研发部门转出的用户将失去自动全权限特权
+- **超管补充用户信息**:为缺失信息的用户补充邮箱、手机等联系方式
+- **取消部门归属**:传 `deptId=0` 将用户从部门中移出
+
+**字段更新规则:** 支持字段清空——传 `""` 清空字符串字段,传 `0` 清空 deptId,不传字段则不更新。
+
+**副作用:** 更新后清除该用户所有产品的 UserDetailsLoader 缓存。
 
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
@@ -1001,48 +1228,97 @@ Content-Type: application/json
 | email | *string | 否 | 邮箱 |
 | phone | *string | 否 | 手机号 |
 | remark | *string | 否 | 备注 |
-| deptId | *int64 | 否 | 部门 ID(传 0 取消部门) |
+| deptId | *int64 | 否 | 部门 ID(传 0 取消部门归属) |
 | status | int64 | 否 | 状态 |
 
 #### POST /api/user/list — 用户列表
 
+分页查询用户列表,可选传入产品编码以附带成员身份信息。
+
+**调用场景:**
+
+- **管理后台用户管理页面**:展示所有用户列表,含基本信息和状态
+- **按产品筛选成员视角**:传入 `productCode` 时,列表中每个用户会附带其在该产品中的 `memberType`(ADMIN/DEVELOPER/MEMBER/空),方便管理员了解哪些用户已经是该产品的成员
+- **添加成员时选择用户**:管理后台在添加产品成员时,先查询用户列表让管理员选择要添加的用户
+
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
-| productCode | string | 否 | 产品编码(传入则附带成员类型) |
+| productCode | string | 否 | 产品编码(传入则每个用户附带在该产品中的成员类型) |
 | page | int64 | 否 | 页码 |
 | pageSize | int64 | 否 | 每页条数 |
 
 #### POST /api/user/detail — 用户详情
 
-返回用户信息及绑定的角色 ID 列表(`roleIds`)。
+查询单个用户的完整信息及其在当前产品下绑定的角色 ID 列表。
+
+**调用场景:**
+
+- **用户编辑页面数据回填**:管理后台进入用户编辑页时,调用此接口获取用户信息回填表单
+- **查看用户角色配置**:管理员查看某用户当前绑定了哪些角色,评估权限是否合理
+- **用户权限排查**:当用户反馈无法访问某功能时,管理员通过此接口查看用户的角色绑定情况
 
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
 | id | int64 | 是 | 用户 ID |
 
+**响应 data:** 用户信息 + `roleIds`(该用户在当前产品下绑定的角色 ID 数组)。
+
 #### POST /api/user/bindRoles — 绑定用户角色(需管理权限)
 
-需通过 `CheckManageAccess` 权限检查。全量替换该用户的角色。
+全量替换该用户在当前产品下的角色绑定。需通过 `CheckManageAccess` 权限检查。
+
+**调用场景:**
+
+- **为新成员分配角色**:用户被添加为产品成员后,管理员为其分配一个或多个角色
+- **调整用户职责**:用户岗位变动时,更换其角色(如从「普通销售」升级为「销售经理」)
+- **多角色组合**:为用户同时分配多个角色以组合权限(如同时拥有「订单管理」和「报表查看」角色)
+- **清空角色**:传入空 `roleIds` 数组清除用户的所有角色绑定
+
+**安全约束:** 操作者需要对目标用户有管理权限(通过 `CheckManageAccess` 校验超管/部门层级/权限级别);目标用户的成员状态必须启用。
+
+**副作用:** 操作后清除目标用户在该产品下的 UserDetailsLoader 缓存。
 
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
 | userId | int64 | 是 | 用户 ID |
-| roleIds | []int64 | 是 | 角色 ID 列表 |
+| roleIds | []int64 | 是 | 角色 ID 列表(全量替换) |
 
 #### POST /api/user/setPerms — 设置用户权限覆盖(需管理权限)
 
-需通过 `CheckManageAccess` 权限检查。全量替换用户级别的 ALLOW/DENY 权限覆盖。
+全量替换用户在当前产品下的个性化 ALLOW/DENY 权限覆盖。这是在角色权限基础上的微调机制。需通过 `CheckManageAccess` 权限检查。
+
+**调用场景:**
+
+- **给个别用户额外授权**:用户的角色不包含某权限,但业务上需要临时或长期授予(如给李四额外授予「导出报表」权限),使用 `ALLOW`
+- **给个别用户限制权限**:用户的角色包含某权限,但需要对该用户个别禁止(如禁止实习生「删除客户」),使用 `DENY`
+- **组合 ALLOW 和 DENY**:同时为用户增加某些权限并限制某些权限,实现精细化控制
+- **清空所有覆盖**:传入空 `perms` 数组清除所有个性化覆盖,用户权限完全由角色决定
+
+**权限计算:** 最终权限 = (角色权限 ∪ ALLOW) - DENY。DENY 优先级最高。
+
+**安全约束:** 目标用户的成员状态必须启用;产品必须处于启用状态。
 
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
 | userId | int64 | 是 | 用户 ID |
-| perms | array | 是 | 权限覆盖列表 |
+| perms | array | 是 | 权限覆盖列表(全量替换,空数组清除所有覆盖) |
 | perms[].permId | int64 | 是 | 权限 ID |
-| perms[].effect | string | 是 | `ALLOW` 或 `DENY` |
+| perms[].effect | string | 是 | `ALLOW`(额外授予)或 `DENY`(强制拒绝) |
 
 #### POST /api/user/updateStatus — 更新用户状态(需管理权限)
 
-需通过 `CheckManageAccess` 权限检查。不允许冻结自己和超级管理员。
+冻结或解冻用户账号。冻结后用户立即无法访问任何产品。需通过 `CheckManageAccess` 权限检查。
+
+**调用场景:**
+
+- **员工离职冻结**:员工离职后冻结其账号,即时切断所有产品的访问权限(无需逐个产品移除成员)
+- **安全事件处置**:发现账号被盗用或存在违规行为时,紧急冻结账号
+- **临时停权**:员工休长假或处于考察期时临时冻结
+- **解冻恢复**:冻结原因消除后恢复账号,用户无需重新配置角色和权限
+
+**安全约束:** 不允许冻结自己;不允许冻结超级管理员;需要对目标用户有管理权限。
+
+**副作用:** 状态变更后递增该用户的 `tokenVersion`(使所有已签发令牌失效)并清除缓存。
 
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
@@ -1053,19 +1329,47 @@ Content-Type: application/json
 
 #### POST /api/member/add — 添加产品成员
 
-需通过 `CheckManageAccess` + `CheckMemberTypeAssignment` 权限检查。不可分配与自己同级或更高级的成员类型。
+将一个已有用户添加为指定产品的成员,并指定成员类型。用户成为产品成员后才能登录该产品。
+
+**调用场景:**
+
+- **新员工加入产品**:创建用户后,将其添加为 CRM/OA 等产品的成员,使其可以登录使用
+- **研发人员跨产品参与**:研发人员需要参与另一个产品的开发,将其添加为该产品的 MEMBER(因为归属研发部门,自动获得全部权限)
+- **提升为产品管理员**:将用户添加为 ADMIN 类型,使其拥有该产品下的角色/用户管理权限
+- **设置产品开发者**:将用户添加为 DEVELOPER 类型,使其自动拥有该产品的全部功能权限但不具备管理权限
+
+**安全约束:**
+
+- 产品必须处于启用状态
+- 操作者不可分配与自己同级或更高级的成员类型(如 ADMIN 不能将他人设为 ADMIN)
+- 需通过 `CheckManageAccess` + `CheckMemberTypeAssignment` 权限检查
 
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
 | productCode | string | 是 | 产品编码 |
 | userId | int64 | 是 | 用户 ID |
-| memberType | string | 是 | `ADMIN` / `DEVELOPER` / `MEMBER` |
+| memberType | string | 是 | `ADMIN`(产品管理员)/ `DEVELOPER`(开发者,全权限)/ `MEMBER`(普通成员,按角色授权) |
 
 **响应 data:** `{"id": 1}`
 
 #### POST /api/member/update — 更新成员
 
-需通过 `CheckManageAccess` + `CheckMemberTypeAssignment` 权限检查。降级产品最后一个 ADMIN 时会被拒绝。
+修改成员类型或启禁用成员状态。
+
+**调用场景:**
+
+- **成员升级**:将普通 MEMBER 提升为 DEVELOPER 或 ADMIN(如项目负责人变动)
+- **成员降级**:将 ADMIN 或 DEVELOPER 降级为 MEMBER(如不再负责管理工作)
+- **禁用成员**:临时禁止某成员访问产品,但保留成员关系和角色绑定(恢复时权限不变)
+- **恢复成员**:将禁用的成员重新启用
+
+**安全约束:**
+
+- 不可降级产品最后一个活跃 ADMIN——系统保证每个产品至少有一个 ADMIN,防止产品进入无管理状态
+- 操作者不可将成员提升到与自己同级或更高的类型
+- 需通过 `CheckManageAccess` + `CheckMemberTypeAssignment` 权限检查
+
+**副作用:** 更新后清除该用户在该产品下的 UserDetailsLoader 缓存。
 
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
@@ -1075,7 +1379,20 @@ Content-Type: application/json
 
 #### POST /api/member/remove — 移除成员
 
-需通过 `CheckManageAccess` 权限检查。不可移除产品最后一个 ADMIN。级联清理该成员在该产品下的角色绑定和权限覆盖。
+将成员从产品中移除。移除后用户无法再登录该产品,其在该产品下的所有角色绑定和权限覆盖被级联清理。
+
+**调用场景:**
+
+- **员工不再参与某产品**:研发人员调岗不再负责 CRM 项目,从 CRM 产品成员中移除
+- **外部协作结束**:外包人员合同到期,从产品中移除其成员资格
+- **权限收紧**:安全审计后移除不应有权限的人员
+
+**安全约束:**
+
+- 不可移除产品最后一个活跃 ADMIN——防止产品进入无管理状态
+- 需通过 `CheckManageAccess` 权限检查
+
+**级联清理:** 在事务内同时清理 `sys_user_role`(该用户在该产品下的角色绑定)和 `sys_user_perm`(该用户在该产品下的权限覆盖),然后删除成员记录并清除缓存。
 
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
@@ -1083,31 +1400,89 @@ Content-Type: application/json
 
 #### POST /api/member/list — 成员列表
 
+分页查询指定产品下的所有成员,含用户基本信息和成员类型/状态。
+
+**调用场景:**
+
+- **管理后台成员管理页面**:展示产品下的所有成员列表,供管理员查看、编辑、移除
+- **成员权限概览**:管理员查看产品有哪些成员、各自是什么类型(ADMIN/DEVELOPER/MEMBER),评估权限分配是否合理
+- **成员数量统计**:了解产品的使用人数和成员构成
+
 | 字段 | 类型 | 必填 | 说明 |
 | ------ | ------ | ------ | ------ |
 | productCode | string | 是 | 产品编码 |
 | page | int64 | 否 | 页码 |
 | pageSize | int64 | 否 | 每页条数 |
 
-**响应 data:** `{"total": N, "list": [MemberItem...]}`
+**响应 data:** `{"total": N, "list": [MemberItem...]}`(含 userId、username、nickname、memberType、status 等)
 
 ---
 
 ## gRPC 接口文档
 
-gRPC 服务定义见 `pb/perm.proto`,默认监听 `:10002`。
+gRPC 服务定义见 `pb/perm.proto`,默认监听 `:10002`。所有 gRPC 错误使用标准 `status.Error(codes.Xxx, msg)` 格式。
 
-| 方法 | 说明 | 使用场景 |
-| ------ | ------ | ---------- |
-| `SyncPermissions` | 同步产品权限列表 | 产品启动时调用,通过 appKey/appSecret 认证 |
-| `Login` | 产品端登录 | 产品后端代理用户登录(productCode 必传,超管被拒绝) |
-| `RefreshToken` | 刷新令牌(轮转) | accessToken 过期续期,旧令牌即时失效;产品禁用时拒绝 |
-| `VerifyToken` | 验证令牌 | 产品后端验证用户 token(可选,推荐本地 JWT 验证);产品禁用时拒绝 |
-| `GetUserPerms` | 获取用户权限 | 实时查询用户最新权限 |
+gRPC 接口主要面向**产品后端服务间调用**,相比 HTTP 接口具有更高性能和更强的类型安全。推荐 Go 项目通过 `permclient` 包直接调用。
+
+### SyncPermissions — 同步权限声明
+
+产品后端在启动阶段全量上报权限列表,与 HTTP 的 `/perm/sync` 功能等价。
+
+**调用场景:**
+
+- **Go 微服务启动时自动同步**:在 `main` 函数中通过 gRPC 调用同步权限,相比 HTTP 调用减少序列化开销
+- **服务网格内部调用**:在 Kubernetes 等容器编排环境中,服务间直接通过 gRPC 通信,无需经过 HTTP 网关
+
+**认证方式:** 通过 `appKey`/`appSecret` 认证(与 HTTP 接口一致)。
+
+### Login — 产品端登录
+
+产品后端代理终端用户进行身份认证,返回 JWT 令牌对和权限列表。
+
+**调用场景:**
+
+- **Go 后端代理登录**:产品的 Go 后端收到用户登录请求后,通过 gRPC 调用权限系统进行身份验证,将返回的令牌下发给前端。相比 HTTP 调用延迟更低
+- **服务间认证委托**:微服务架构中,网关服务通过 gRPC 调用权限系统完成用户认证
+
+**安全约束:** `productCode` 必传;超级管理员被拒绝(必须走 adminLogin)。
+
+### RefreshToken — 刷新令牌(轮转)
+
+使用 refreshToken 换取全新令牌对,采用令牌轮转策略。
+
+**调用场景:**
+
+- **Go 后端代理续期**:产品后端代替前端执行令牌刷新(如 BFF 模式下,后端持有 refreshToken 并管理令牌生命周期)
+- **长连接会话保活**:WebSocket 或长连接服务在 accessToken 即将过期时,通过 gRPC 刷新令牌维持会话
+
+**安全约束:** 每次刷新递增 `tokenVersion`,旧令牌即时失效;产品禁用时返回 `codes.PermissionDenied`。
+
+### VerifyToken — 验证令牌
+
+服务端验证 accessToken 的有效性,返回用户信息和权限列表。
+
+**调用场景:**
+
+- **不支持本地 JWT 验证的语言/框架**:非 Go 语言的产品后端(如 Python、Node.js)可能未配置 JWT 密钥,通过 gRPC 调用权限系统进行服务端验证
+- **需要实时权限的场景**:本地 JWT 验证只能拿到签发时的权限快照;如果需要实时反映权限变更(如管理员刚修改了用户角色),可通过此接口获取最新权限
+- **关键操作的二次验证**:对敏感操作(如资金操作、数据删除),在本地 JWT 验证基础上再通过此接口做一次服务端校验,确保令牌未被吊销
+
+**推荐实践:** 常规场景推荐本地 JWT 验证(零网络开销);仅在上述特殊场景使用此接口。
+
+**安全约束:** 产品禁用时返回 `codes.PermissionDenied`。
+
+### GetUserPerms — 获取用户权限
+
+实时查询指定用户在指定产品下的最新权限码列表。
+
+**调用场景:**
 
-所有 gRPC 错误使用标准 `status.Error(codes.Xxx, msg)` 格式。
+- **实时权限网关**:API 网关在每次请求时通过 gRPC 查询用户最新权限,实现实时权限控制(适用于权限变更需要立即生效的高安全场景)
+- **后台任务权限校验**:异步任务/定时任务执行时,需要校验发起用户是否仍有执行权限
+- **权限缓存刷新**:产品后端自行维护权限缓存,定期通过此接口刷新,确保与权限系统保持一致
+- **管理后台权限预览**:管理员查看某用户在某产品下的实时权限,用于排查和审计
 
-Go 项目可直接引用 `permclient` 包:
+### Go SDK 使用示例
 
 ```go
 import "perms-system-server/permclient"
@@ -1117,6 +1492,7 @@ client, err := permclient.NewPermClient("perm-system:10002")
 resp, err := client.SyncPermissions(ctx, &pb.SyncPermissionsReq{...})
 resp, err := client.Login(ctx, &pb.LoginReq{...})
 resp, err := client.VerifyToken(ctx, &pb.VerifyTokenReq{AccessToken: token})
+resp, err := client.GetUserPerms(ctx, &pb.GetUserPermsReq{...})
 ```
 
 ---