SFT/docs/superpowers/specs/2026-07-16-org-data-isolati...

7.9 KiB

按机构数据隔离改造 —— 设计文档

日期:2026-07-16

背景与目标

当前系统需要按照用户所属机构做数据隔离:例如用户 1 属于机构 A,则用户 1 只能看到机构 A 及机构 A 下属机构的数据。具体要求:

  1. 用户登录时获取其所属机构 ID(已实现,authStore.userInfo.organizationId)。
  2. 所有已有"所属机构"字段的查询页面,该字段默认选中当前登录用户的机构。
  3. 用户点击"所属机构"选择框改选其他机构时,可选范围只能是"当前用户所在机构及其下属机构",不能显示无权限的机构。
  4. 角色管理页面目前没有"所属机构"筛选字段,需要新增。

现状调研结论

  • 技术栈:Vue 3 + ant-design-vue 4 + Pinia + axios,非 TypeScript,路由/请求封装均已就位,登录态用 access_token(localStorage)+ Pinia authStore.userInfo(内存,不落盘)。
  • 机构选择组件 src/components/OrgTreeSelect.vue 已存在并被 15 处表单/筛选框复用,内部调用 fetchOrgTreeApi()(GET /api/organization/query-tree)/fetchOrgListAllApi()(GET /api/organization/list-all)获取全量机构数据,当前没有按登录用户做任何范围限制。
  • 已有"所属机构"查询筛选字段的页面:UserList.vue(organizationId)、PersonalList.vue(organizationIdEq)、PersonalCustomerPicker.vue(organizationIdEq,弹窗选择器)。字段命名不统一,以各自后端接口实际入参名为准。
  • 角色管理页面(RoleList.vue)当前只有"角色名称"筛选,没有"所属机构"筛选;后端真实 swagger 确认 POST /api/role/page 目前只接受 roleName/page/pageSize,不接受机构过滤参数(该缺口已在《缺失的后端接口.md》Part 2 第 7 条登记过)。
  • 机构模型存在两个不同含义的 id:数字主键 id(被 role.organizationIduser.organizationId 等外键字段引用,登录/用户信息接口返回的 organizationId 就是这个数字值)与字符串业务码 organizationId(仅用于机构自身管理页 update/delete 定位,以及 query-tree 的筛选参数)。两者类型不同、不能互相替代,因此前端无法把"当前用户机构数字 id"直接作为现有 query-treeorganizationId 参数来筛选子树
  • 结论:要做到"下拉框可选范围只能是本机构+下属机构",技术上无法用一次前端 hack 解决,必须由后端基于登录用户的 JWT 身份,在机构树/机构列表接口内部自动限定返回范围。本次改造采用"前端默认值改造 + 记录后端待办"的组合方案,不做前端临时裁剪兼容代码。

方案总览

1. 后端契约变更(登记待办,本次不改后端代码)

在《缺失的后端接口.md》Part 2 追加以下条目(编号从 52 开始):

  • 52. GET /api/organization/query-tree 需基于 Authorization 头中的登录用户身份,自动将返回的机构树限定为"当前用户机构 + 其下属机构"(顶级机构用户仍返回全树),不新增请求参数,现有 organizationName/organizationId 筛选在此范围内叠加生效。
  • 53. GET /api/organization/list-all 同样按登录用户机构自动限定返回的扁平列表范围,规则同上。
  • 54. POST /api/role/page 需新增 organizationId(数字,机构主键)请求参数,传入时按"该机构及其下属机构"过滤角色列表(呼应 Part 2 第 7 条,补充明确参数名与语义)。
  • 55. 安全边界说明:上述改造只解决 UI 展示层"默认值 + 可选范围"的问题;真正的数据安全隔离必须由后端在各业务查询接口内部,基于当前登录用户可见机构范围校验/过滤查询条件与结果,不能仅信任前端传入的 organizationId 参数,否则可被直接调用接口绕过,建议在通用鉴权层增加"按机构数据权限"的统一拦截能力。

前端后续调用方式(OrgTreeSelect.vue 内部调用、RoleList.vue 新增筛选字段)保持现有参数不变,一旦后端完成上述改造即可自动获得正确的裁剪效果,前端无需再次改动。在后端完成改造之前,机构下拉框仍会展示全量机构(与现状一致),只是默认选中值会变为当前用户机构。

2. 前端改造范围

新增一个小工具集中获取"当前用户机构 id":

  • src/utils/orgScope.js:导出 getCurrentOrganizationId(),内部读取 useAuthStore().userInfo?.organizationId。各页面统一从这里取值,避免重复 import 逻辑分散、便于后续如需调整取值来源时只改一处。

查询/弹窗筛选框默认值改为当前用户机构(可选范围由后端裁剪后自动生效,本次只改默认值):

文件 字段名 改动
src/views/system/user/UserList.vue organizationId initial-search 默认值改为 getCurrentOrganizationId()
src/views/customer/personal/PersonalList.vue organizationIdEq 同上
src/components/PersonalCustomerPicker.vue organizationIdEq 同上
src/views/system/role/RoleList.vue organizationId(新增字段) 新增筛选表单项(复用 OrgTreeSelect),initial-search 增加该字段并默认选中当前用户机构;ProTable 会原样透传 searchFormfetchRolePageApi,无需额外接线

新增表单"所属机构"字段默认值改为当前用户机构(仅影响 create 模式的初始值;edit/view 模式仍回填记录真实值;字段本身仍可编辑,可选范围由后端裁剪):

文件 表单字段
src/views/system/role/RoleList.vue 新增角色表单 organizationId
src/views/system/user/UserList.vue 新增用户表单 organizationId
src/views/base-config/manager/ManagerList.vue 新增客户经理表单 organizationId
src/views/base-config/channel/ChannelList.vue 新增渠道表单 organizationId
src/views/customer/personal/PersonalForm.vue organizationId
src/views/customer/enterprise/EnterpriseForm.vue organizationId
src/views/merchant/management/MerchantForm.vue organizationId
src/views/credit/loan-application/PersonalLoanForm.vue organizationId
src/views/credit/loan-application/EnterpriseLoanForm.vue organizationId
src/views/system/org/OrgList.vue 新增机构表单"上级机构"字段 parentId

清理:src/components/WalletCustomerPicker.vue 中个人客户查询 initial-search 里未绑定任何 UI 控件的 organizationId: undefined 字段(死代码),顺手删除。

不改动:src/components/OrgTreeSelect.vue 本身的数据加载逻辑(继续调用现有的 fetchOrgTreeApi()/fetchOrgListAllApi(),不传新参数,不做前端二次裁剪,完全信任后端未来返回的已裁剪数据)。

3. 验收标准

  • 所有列表 A(见上表)首次进入页面时,"所属机构"筛选框默认值 = 当前登录用户的 organizationId,查询结果与手动选中该机构一致。
  • 角色管理页面新增"所属机构"筛选框,默认选中当前用户机构,选择其他机构后请求参数中带上对应 organizationId(受限于后端未支持,实际返回列表暂不会被过滤,这是已知且已登记的后端待办)。
  • 所有新增表单 B(见上表)打开"新增"弹窗/页面时,"所属机构"字段默认值 = 当前登录用户的 organizationId,可手动改选(编辑模式不受影响,回显记录原值)。
  • 《缺失的后端接口.md》新增 52-55 条待办记录。
  • WalletCustomerPicker.vue 死代码字段已移除,功能不受影响。
  • 项目可正常 builddev 启动,无报错。

4. 已知限制

  • 在后端完成 52/53 条改造之前,机构下拉框仍会展示全量机构列表,只是默认值正确;这是前后端分工下的过渡态,已提前告知。
  • 角色列表按机构筛选在后端完成第 54 条改造前不会真正生效。
  • 本次改造属于 UI 展示层的默认值与(后端裁剪后自动生效的)可选范围控制,不等同于数据安全隔离;真正的越权防护依赖后端在业务接口内部做机构范围校验(第 55 条)。