# 权限调整设计与验收约定 日期:2026-09-06。本文按“资料 → 产品原型 → 设计 → 开发 → 测试”记录本轮授权范围、实现依据和验收边界。文中的迁移后角色矩阵是根据旧环境快照与 036 迁移规则推导的目标值;真实 API、角色切换 E2E、截图及最终构建结果由本目录的最终测试报告补充,不能把设计目标当作已通过的实测结果。 ## 1. 资料:先确认旧系统实际允许什么 依据: - 用户本轮确认的权限方案:管理员保留固定的实际管理权限;内部维护身份独立保护;页面与操作按权限判定;教学、资源和内容动作细分;头像菜单支持切换已分配的工作身份。 - 旧环境脱敏快照:[role-permissions-audit-20260906.json](data/role-permissions-audit-20260906.json)。采集时间为 2026-09-06 03:08:01 UTC,授权模式为 `SINGLE_ACTIVE`;交付副本仅含角色与权限,不含用户个人资料或凭据。 - 动作合同:[granular-permissions-contract.json](data/granular-permissions-contract.json)。列出本轮新增的 87 个动作码、所属页面、旧码来源和迁移条件。 - 迁移文件:[20260906_036_role_permissions_maintenance.sql](../../../unreal_tran_api/database/20260906_036_role_permissions_maintenance.sql)。以显式清单固定管理员授权,迁移普通角色的已有能力,增加维护审计范围。 - 服务端授权说明:[08-角色授权模式.md](../../../unreal_tran_api/wiki/08-角色授权模式.md)。页面权限、动作权限、数据范围、会话身份及维护账号约束分别生效。 旧快照中的角色状态: | 当前角色 | 旧授权记录 | 旧数据范围 | 实际含义 | | --- | --- | --- | --- | | 管理员 `admin` | 动态取得全部 65 个启用叶子权限,旧 `superAdmin=true` | `ALL` | 权限目录看似“全部”,但内容写 API 和前端另有教员身份门槛;因此模型、场景等实际仍只读。不能把“目录全选”当作其真实业务写能力。 | | 教员 `teacher` | 显式 33 项,含 3 个共用内容写权限、教学管理与运行权限 | `SYSTEM` | 可制作内容、组织教学;资源库和维修知识主要有页面权限,未持有对应旧 `manage` 权限。 | | 学员 `student` | 显式 14 项,含教学领取、提交、运行及知识页面 | `SELF` | 参与本人或本人队组被分配的教学,不具有内容制作、教学管理和系统管理权限。 | 旧数据说明了两处需要修正的表达:一是管理员不能再显示成动态全权限后又在页面按角色名拦住;二是角色名不能替代一项可以独立授予或撤销的业务能力。迁移以旧实际能力为边界,不借权限拆分给教员补齐资源管理功能,也不给管理员补齐内容制作功能。 ## 2. 产品原型:保持已有入口,明确能看与能做 登录页仍为教员、学员、系统管理员三个等宽身份选项。此选择用于请求服务端确认会话身份,不是页面授权结果。普通界面不增加“内部维护”登录选项或维护人员条目。 列表、工作区和系统配置页遵守同一规则:有页面权限才进入;进入后,新建、编辑、删除、发布、审核分别显示或启用;权限不足时仍可查看已授权的数据。缺少编辑权限的三维工作区保留浏览、相机查看和已有的只读预览能力,阻止编辑字段、导入上传、拖动修改与自动保存。 头像菜单和个人中心共用身份切换流程,只列出当前用户已分配且有效的角色;`SINGLE_ACTIVE` 且有效角色多于一个时可切换。`UNION` 沿用权限并集,不显示会改变授权的“切换身份”入口。 OFD 默认工作台仍是已有本地原型入口。本轮将其假定教员、无条件允许操作的上下文替换为真实会话与细分权限,并阻止无权限本地写入。原型没有的审批业务不会因为新增 `content.ofd.review` 就被临时拼出来;正式 OFD 接口与保留的正式工作流组件按已有审批状态机控制批准、驳回。 ## 3. 设计:能力、身份与数据范围分开 ### 3.1 授权判定 有效操作必须同时满足:已认证会话、当前有效授权集合、对应页面、具体动作、服务端数据范围,以及原有项目状态和版本保护。前端只负责可用性提示,服务端仍校验每个实际请求。 `SINGLE_ACTIVE` 只使用当前活动角色的权限和数据范围;一个账号同时持有管理员、教员时,管理员身份不会自动合并教员的内容写权限。`UNION` 按服务端计算的有效角色并集处理。页面不得再通过 `roleCode === teacher/admin` 赋予通用编辑能力。 学员本人、分组、队伍、岗位、正式考核投影、设备事件来源等仍属于业务约束;不能因页面改看权限而放开这些条件。版本号、固定发布依赖、数据范围和考核答案隔离继续由服务端判断。 ### 3.2 迁移后角色能力矩阵 下表对应“旧快照 + 036 首次迁移”,不是对所有同名角色强行恢复一套默认权限。可配置角色后续被撤销的细分权限,不应因重复执行迁移而重新获得。 | 能力 | 管理员固定角色 | 教员迁移结果 | 学员迁移结果 | | --- | --- | --- | --- | | 工作总览 | 可进入 | 可进入 | 可进入本人视图 | | 模型、场景、训练编排、OFD 内容页面 | 保留查看,默认不含内容写 | 保留四类页面与制作能力 | 默认无内容制作页面 | | 内容新建、复制、编辑、上传、删除、发布 | 固定清单不授予 | 由原共用内容写权限展开为四类独立动作 | 不授予 | | OFD 批准、驳回 | 固定清单不授予 | 从原有内容修改能力迁移为 `content.ofd.review`,仍须对应审批状态和实际正式入口 | 不授予 | | 虚拟、实装、对抗、考试任务管理 | 保留新建、修改、发布、撤回、归档、评审、终止等管理动作 | 保留既有管理能力,按对应页面分别拆分 | 不授予管理动作 | | 教学领取、提交、正式考试执行 | 不因原动态全权限继续保留学员专属动作 | 不因教员身份新增学员专属动作 | 按四类教学页面分别取得领取、提交;正式考试执行使用独立 `teaching.exams.execute` | | 原有虚拟、实装、对抗与沉浸运行权限 | 管理固定清单不包含学员运行能力 | 保留原有 execute 权限,实际运行还须满足任务、参与人和考核约束 | 保留原有 execute 权限,限定本人/本人队组 | | 对抗配置、故障与干预 | 保留配置;故障增改删、干预独立授权 | 按原有配置/管理权限展开 | 不授予管理或注入能力 | | 指导书教学使用 | 反馈处理和统计 | 反馈处理和统计 | 学习进度、提交反馈 | | 资源管理、维修知识 | 保留管理能力并拆分增改删、发布和分类动作 | 保留原页面;因未有旧 manage,不自动新增资源/知识增改删 | 保留知识页面,不新增知识写权限 | | 数据处理 | 保留管理、测试、控制、校验、处理等已有能力 | 保留原页面与 ingest;不自动授予管道管理能力 | 默认不授予 | | AI 模型管理 | 保留对应管理、测试 | 旧快照无该页及管理授权,迁移不增加 | 不授予 | | 辅助保障 | 保留监控、干预 | 保留原监控、干预 | 不授予 | | 系统用户、部门、角色、权限、配置等 | 保留明确管理动作;普通 API 不能改管理员固定角色本身 | 旧快照未授予,不自动增加 | 不授予 | | 数据范围 | 保留 `ALL`,不代表内容写或维护身份 | 保留 `SYSTEM` | 保留 `SELF` | 管理员是固定的普通管理角色,不再依赖动态全选。其显式清单不随以后新增权限自动扩张;普通角色权限接口也不能修改该固定清单。它仍可在已有系统管理授权内管理可配置角色。一个账号被合法分配其他角色后,使用该角色需要按授权模式重新计算能力,这与修改管理员固定模板是两件事。 内部维护角色与普通管理员分开:仅后端同时确认受保护维护账号标记和有效内部角色分配,才赋予内部维护能力。用户名、前端标记或单独的角色关联都不能提权。维护角色动态取得启用的页面/动作权限;分组节点不成为可授予的实际权限。 ### 3.3 内容四类操作与真实动作对应 | 页面及服务端类型 | 新建/复制 | 编辑/保存/上传/封面/恢复草稿 | 删除工程 | 发布/撤回发布 | 批准/驳回 | | --- | --- | --- | --- | --- | --- | | 模型制作 `MODEL` | `content.model.create` | `content.model.update` | `content.model.delete` | `content.model.publish` | 无独立模型审核码 | | 场景制作 `SCENE` | `content.scene.create` | `content.scene.update` | `content.scene.delete` | `content.scene.publish` | 无独立场景审核码 | | 训练编排 `TRAINING` | `content.training.create` | `content.training.update` | `content.training.delete` | `content.training.publish` | 无独立训练审核码 | | 作业指导书 `GUIDE` | `content.ofd.create` | `content.ofd.update` | `content.ofd.delete` | `content.ofd.publish` | `content.ofd.review` | `GUIDE` 的页面后缀使用 `ofd`,不是另造 `content.guide.*`。提交审核、撤回送审、修订归入 `update`;批准、驳回归入 OFD `review`;撤回已发布版本归入 `publish`。这些动作仍受原状态机约束,权限不会凭空增加生命周期转移。 上述删除权限指删除内容工程。编辑工程内部的模型节点、场景对象、训练步骤或文档块,属于修改工程文档,仍使用该类内容的 `update`。封面截图和模型二进制上传同属编辑,不新增独立“封面权限”。 “发布已保存版本”只需要发布动作;“编辑后保存并发布”必须同时拥有编辑与发布。不能为了让发布按钮可用而暗中开放更新接口。场景、训练、模型宿主在只有发布权限时跳过保存,直接提交已加载的服务端版本号。OFD 正式发布还须存在与当前内容指纹匹配的两份交付物;只有发布权限的用户可以复用已保存的 OFD/离线包,缺失或过期时提示编辑者处理,不代为上传,也不发布旧内容的交付物。 ### 3.4 内部维护与审计隔离 维护账号和内部角色不出现在普通用户列表、人员目录、角色列表、统计及角色分配入口;普通用户 API 不能对其修改、停用、删除、重置密码或改角色。此保护来自后端身份合同,不靠前端隐藏。 系统、内容、资源智能审计增加写入时的 `visibility_scope` 快照,区分 `BUSINESS` 与 `INTERNAL`。维护账号产生的内部审计不会因后来切换活动角色而变为普通业务审计;读取内部审计仍要求当前有效内部维护角色。普通查询和导出不应通过详情 ID、筛选条件或其它目录绕过隔离。 036 先做结构与授权迁移,不在 SQL 写入账号凭据,也不提前解除旧管理员保护。维护恢复能力通过一次性私密配置初始化;验证凭据、内部角色及有效授权后,才交接普通管理员的保护状态。报告、截图、源码及 DDL 均不记录密码、令牌或个人资料。 ### 3.5 角色切换与未保存内容 切换前依次调用所有已注册工作区的离开确认,包括仍被缓存的后台编辑器。保存失败、模型/场景正在绑定或用户关闭确认框时,不发送角色切换请求。 单个编辑器选择“放弃修改”不会立即删除恢复数据或清除脏标记;后续另一个编辑器取消、或角色切换接口失败时,仍保留原工作区状态。只有所有确认完成且服务端切换成功后,才标记身份提交成功并整页进入工作总览;新启动重新取得 `/auth/me` 和菜单,释放旧身份的编辑器、缓存和在途上下文。成功切换后避免 `beforeunload` 再提示或把已放弃的模型草稿写回恢复区。 ## 4. 开发:本轮落地范围 - 内容权限映射集中在 [contentPermissions.ts](../../../unreal_tran_web/src/utils/contentPermissions.ts),`canUsePermission` 只检查服务端返回的权限集合。旧共用动作码停用,不以“新码或旧码任选其一”的方式绕过细分授权。 - 内容列表按类型分别控制新建、复制、编辑、恢复、删除和发布。保留的正式工作台按具体生命周期动作授权。 - 场景和训练旧编辑器接入宿主能力,增加只读字段/按钮和直接回调保护;训练高级编排不再在只读会话中写本地工程缓存。发布失败继续阻断后续动作,不清除未保存内容,不取消已有版本锁或固定场景依赖。 - 模型 iframe 在同源宿主增加权限桥接,控制编辑面、变换控件、保存、发布、自动保存与恢复写入;不改旧仓库或已生成的静态 bundle。只读查看旧工程可做内存归一化,不触发自动迁移 PUT,不恢复未授权的本地编辑副本。 - OFD 本地运行时使用实际会话身份;本地创建、保存、删除、恢复、生命周期与资源编辑均有能力检查。向导把初始文档随新建一起保存,避免只有新建权限的用户先创建空工程后再因无修改权限失败。 - 头像入口与个人中心共享 [useRoleSwitch.ts](../../../unreal_tran_web/src/composables/useRoleSwitch.ts),工作区通过 [workspaceLeaveGuards.ts](../../../unreal_tran_web/src/utils/workspaceLeaveGuards.ts) 注册确认。页面标签存储按用户和活动角色隔离。 - 教学任务以已保存的任务类型/渠道选择动作码;正式考试走 `teaching.exams.*`。资源智能将旧 manage 拆为对应页面的真实动作,保留已有专用 ingest、execute、configure 等操作合同。 ## 5. 测试:区分已完成检查与待实测结果 本文件形成时,内容范围已执行: | 检查 | 命令或证据 | 结果 | | --- | --- | --- | | 前端类型与 SFC 检查 | `pnpm exec vue-tsc --noEmit` | 通过;不等同于完整生产构建 | | 内容权限、OFD 本地写隔离、模型宿主与交付物复用 | `node --test tools/content-permissions.test.mjs` | 10/10 通过 | | 训练封面、异步保存、绑定并发、只读/发布分权 | `node --test tools/training-cover.test.mjs` | 19/19 通过 | | 场景封面、版本冲突、离开确认、只读/发布分权 | `node --test tools/scene-cover-api.test.mjs` | 28/28 通过,含后补的 8 项加载/上传/保存/发布并发离开回归 | | 上述三个 Node 测试文件此前联合执行 | `node --test --test-reporter=spec tools/content-permissions.test.mjs tools/training-cover.test.mjs tools/scene-cover-api.test.mjs` | 当时 49/49 通过;后补的 8 项已在场景测试文件独立复跑通过 | 真实联调报告还需要记录以下独立验收项的实际结果,未取得证据前不得填为“通过”: 1. 036 执行及维护恢复交接后,分别读取三个普通角色的实际 `/auth/me`、页面目录与数据范围;对照矩阵检查没有跨页扩大授权。 2. 管理员内容列表只读、教员原有内容制作可用、学员无内容制作入口;自定义角色只授予某一内容类型的某个动作时,其余类型/动作均被 UI 和 API 拦截。 3. 新建、复制、编辑、上传封面、恢复版本、删除、发布及 OFD 审核逐项验证;发布权限不能替代更新,更新不能替代删除/发布/审核。 4. 通过真实三维编辑器确认只读画面仍能加载模型或场景,禁止编辑、上传与自动写;不能只根据接口成功或 DOM 对象数量判断视觉正常。 5. 在有未保存修改时,从头像菜单切换角色:取消无切换请求;保存失败保留内容;多工作区前一项放弃、后一项取消不丢草稿;成功切换后重新获取身份与菜单,不显示旧角色页面缓存。 6. 验证维护账号/角色不可经普通目录、详情、统计、人员下拉与修改接口泄漏或被维护;普通审计查询/导出不包含内部审计。 7. 验证教学管理动作与学员动作分离,正式考试、岗位、队组、数据范围及固定快照约束仍有效。 8. 完整 `pnpm run build`、真实 API 自动化、E2E 截图和必要的失败 trace 由最终报告统一记录。测试产生的临时角色、权限调整、工程和审计数据应说明保留或清理情况。 ## 6. 明确边界 本轮是权限判定和工作区适配,不新增内容制作业务、教学流程或 OFD 审批产品功能。OFD 默认原型仍使用本地存储,前端能力约束不能被描述成完整服务端隔离、跨设备协作或数据范围安全;本轮没有将该本地业务整体迁移至内容 API。 普通管理员保留配置其它角色的能力,但其固定角色本身没有内容制作权限。教员原来未获授予的资源/知识管理动作,不因角色名称自动补授。学员教学执行仍须任务已分配且符合本人/队组/岗位要求。存在权限不代表可以绕过发布前置检查、已发布内容不可变约束或乐观锁。 本资料不包含维护凭据、用户个人资料、访问令牌或数据库备份内容;也不把尚在执行的 API/E2E 项目记为已完成。