12 KiB
桌面问诊列表:医生角色视频入口契约核对
核对日期:2026-10-17。范围仅为本地源码只读审查;未访问生产系统,未修改业务代码或测试代码。
结论
- 医生角色是
role_id=1,医助是role_id=2。3不是医生角色;“我的患者”管理角色集合为[3,7,8]。 - 管理后台 PC/H5 的旁观入口均要求:有已预约的视频挂号、当前用户等于诊单
assistant_id、拥有tcm.diagnosis/watchCall权限。无入口资格时只能看通话状态。 - 服务端
watchCall业务层再次强制assistant_id == 当前登录 admin_id,没有 root 绕过。root 仅在通用鉴权中绕过菜单权限。 - 需要区分源码现状与本次新要求:现有后台没有显式的“角色数组包含医生 1 即拒绝”分支。混合医生/医助角色如果恰好被指派为助理且有权限,按现代码仍可能进入旁观;不能把这一规则误报为现有后端保证。
- 桌面列表当前不检查角色或助理归属,仅检查 repository 视频能力、预约和通话状态,因此医生也会得到“进入视频问诊”。本次用户要求应在桌面显示和点击处都补上医生角色限制。
- 桌面该入口实际发出
mode="im",获取的是getCallSignature通话/聊天凭证,不是后台的watchCall纯拉流接口;不能仅根据文案把两者视为同一实现。
本次实施与验证
- 桌面列表对含医生角色
1的账号(含混合角色及通配菜单权限)只显示通话中 / 接通中 / —等公开状态,不显示进入视频问诊;同类工具栏入口同步隐藏。 - 表格显示、选中行按钮状态和实际点击处理使用同一资格判断;直接调用旧入口或强行启用按钮也不能发出视频请求。
- 非医生账号仍须同时满足本人诊单医助归属和
tcm.diagnosis/watchCall权限;超级管理员也不绕过医助归属。有效预约、live 状态与标识完整性仍由原有流程检查。 - 医生的图文预约继续显示“图文沟通”,不受视频入口限制;接诊台的医生通话流程未改动。未将既有 IM 接收流程改造成纯拉流旁观,也未修改后端接口。
- 新增角色/指派/权限矩阵回归在修改前 27 项失败,修改后全部通过;覆盖角色整数、字符串、数组、逗号字符串、对象列表及 UserProfile,以及混合角色、root、无身份、错误指派、仅二维码权限。
- 最终组合检查:165 通过,1 项既有分页组合用例失败(
test_frozen_rows_track_main_pixel_scroll_and_host_height_is_append_stable的请求页码为[1,2],预期[1,2,3])。恢复 Git HEAD 的两个业务模块并排除新增用例后,同一组合仍复现该失败(136 通过、1 失败)。该分页用例及整个视觉模块单独运行均通过,未修改分页实现。 - Ruff、修改范围的
git diff --check通过;离屏检查已生成 医生视角截图,使用本地示例数据,未连接生产服务。源码未打包发布,运行中的客户端需更新重启。
角色与身份数据
| 项目 | 源码契约 | 位置 |
|---|---|---|
| 医生 | ID 1;数据库角色关联按成员包含判断,而非要求只有单一角色 |
server/app/api/logic/DoctorLogic.php:96、server/app/adminapi/logic/firstvisit/MyPatientLogic.php:22 |
| 医助 | ID 2 |
server/app/adminapi/logic/firstvisit/MyPatientLogic.php:23 |
| 管理角色 | [3,7,8],用于“我的患者”的团队范围判断 |
server/app/adminapi/logic/firstvisit/MyPatientLogic.php:24 |
| 服务端旧角色字段 | Admin 模型追加 role_id,值是当前用户所有 admin_role.role_id 数组 |
server/app/common/model/auth/Admin.php:29、:44 |
| 身份接口 | auth.admin/mySelf 的 user 包含 id、root、role_ids(整数数组),兼容模型追加的 role_id 数组;外层为 user/menu/permissions |
server/app/adminapi/logic/auth/AdminLogic.php:285、:319、:327 |
| 桌面模型 | UserProfile.id、role_ids: tuple[int,...]、root: bool、permissions、raw |
app/src/doctor_workstation/core/models.py:125 |
| 桌面转换 | 优先 role_ids,再 role_id,再 roles;支持数字、逗号字符串、集合/列表和 {id: ...} 元素,归一成整数元组;root 兼容 is_root |
app/src/doctor_workstation/core/models.py:94、:141 |
| 当前用户流向 | repository 的 get_session() 读取 mySelf 并建立 UserProfile;shell 取得 current_user 后传入页面,ConsultationsPage 已保留 self.current_user |
app/src/doctor_workstation/services/repository.py:902、app/src/doctor_workstation/ui/shell.py:899、:2014、app/src/doctor_workstation/ui/pages/consultations.py:944 |
混合角色应检查集合包含 1,不能只比较 role_id == 1,也不能只检查角色数组首项。现有 shell 的角色文案也以 1 in roles 优先显示“医生”:app/src/doctor_workstation/ui/shell.py:1521。
权限 "*" 与 root 不能互相等同。后台 root 会获得 ['*'],拥有全部菜单权限的非 root 也可能得到 ['*'];桌面 PermissionSet.is_superuser 仅表示存在全局通配符,不代表 UserProfile.root 为真。
- 后台授予逻辑:
server/app/adminapi/logic/auth/AuthLogic.php:56。 - 桌面通配权限逻辑:
app/src/doctor_workstation/core/permissions.py:60。
管理后台显示与点击逻辑
PC:admin/src/views/tcm/diagnosis/index.vue。
:362:列标题为“视频旁观”。:1545:isAssignedAssistant使用row.assistant_id ?? row.assistant与userStore.userInfo.id数字比较。:1563:canWatchCallEntry = isAppointmentActiveForVideo && isAssignedAssistant && hasPermission(['tcm.diagnosis/watchCall'])。:1971:从appointments中找status==1的有效挂号,视频条件另要求canAppointmentVideoCall(type)。旧空类型按视频处理,text/phone/unknown不允许旁观视频,见admin/src/utils/appointment-type.ts:12。:1579:有入口资格且状态为live或pending_room时显示“进入旁观”;pending 状态点击后仅提示等待。:1591:点击重新校验入口资格,并要求状态必须为live,才打开旁观弹窗。:1609:无入口资格时显示公开状态:live=通话中、pending_room=接通中、none=—,其他使用服务端 label。
H5:admin/src/views/tcm/diagnosis/index_h5.vue。
:210、:1035、:1040、:1044、:1075分别对应显示、指派关系、资格、状态和点击校验;规则与 PC 一致。:1068:仅有入口资格,或状态处于 live/pending 时才显示旁观条;无资格时为只读状态文案。与 PC 的小差别是 none 状态文本为空,且整条通常不显示。
hasPermission 对 "*" 放行,但不会跳过助理归属:admin/src/utils/perm.ts:3。
服务端旁观保护与超管
server/app/adminapi/controller/tcm/DiagnosisController.php:609 的 watchCall() 从 GET 读取 diagnosis_id,但把调用者身份绑定为控制器的当前登录 $this->adminId,不采用客户端传来的 admin_id。
server/app/adminapi/logic/tcm/DiagnosisLogic.php:2678 的 getAssistantWatchRoomParams():
- diagnosis_id/admin_id 必须为正数。
- 诊单必须存在且未删除。
diag.assistant_id必须等于 admin_id,否则“仅本诊单指派的医助可旁观该通话”。- 获取该诊单
status=1的最新 CallRecord,room_id 必须存在,否则要求等医生接通。 - 返回当前登录账号对应的
doctor_{adminId}UserSig 和房间参数。
此函数没有读取角色或 root,也没有主动重复前端的有效视频挂号校验。 角色/menu 权限由通用鉴权负责;归属和进行中房间由业务逻辑负责。AuthMiddleware.php:70 的 root 放行发生在通用菜单鉴权层,不能使 watchCall 业务层的 assistant_id 检查失效。非 root 的菜单权限检查还受菜单路由登记情况影响,不宜把权限名称硬编码成额外、已存在的业务断言。
对照通话/聊天接口:桌面 get_call_ticket() 调用 tcm.diagnosis/getCallSignature(app/src/doctor_workstation/services/repository.py:2696)。该接口经 DiagnosisLogic.php:898 的 canManageDiagnosis 校验患者/诊单访问关系,并解析挂号通话策略;它不是旁观资格接口。canManageDiagnosis 使用 MyPatientLogic:root 可看全部、管理角色用团队数据范围、一线医生/医助按本人接诊/归属关系;这不意味着医生能从通用问诊列表旁观其他医生。
桌面入口缺口
| 环节 | 当前实现 | 位置 |
|---|---|---|
| 列能力 | action_policy['video_call'] = _native_video_capable,只证明 repository 有 get_call_ticket/start_call/bind_call_room/end_call 方法 |
app/src/doctor_workstation/ui/pages/consultations.py:177、:1504 |
| 表格按钮 | 有能力 + 已预约 +(图文类型或 live),显示“图文沟通”或“进入视频问诊”;只用 ID 完整性控制 enabled | app/src/doctor_workstation/ui/diagnosis_index_widgets.py:1784 |
| 顶部通话 | 可见性只检查原生能力,enabled 来自兼容 video_button | app/src/doctor_workstation/ui/pages/consultations.py:1385、:3056 |
| 兼容按钮 | “进入视频问诊”仍存在于隐藏兼容操作容器,可通过程序调用 | app/src/doctor_workstation/ui/pages/consultations.py:1467 |
| 行点击 | 选择该行后进入统一 _request_video() |
app/src/doctor_workstation/ui/pages/consultations.py:2366 |
| 最终点击处理 | 检查能力、已预约、live(text 例外)、ID 完整,然后发出 video_requested;没有医生角色/助理归属/旁观权限校验 | app/src/doctor_workstation/ui/pages/consultations.py:3499 |
| 发出内容 | source='consultations'、mode='im',注释明确此页为助理/管理员接收现有医生邀请 |
app/src/doctor_workstation/ui/pages/consultations.py:288 |
导航目前“我的患者”为 PatientsPage,“问诊列表”为 ConsultationsPage,两者不同;本次所述“视频问诊”列实际在后者 DiagnosisTableHost,见 app/src/doctor_workstation/ui/shell.py:159。本次无需扩大为其他医生主动发起通话入口的限制。
本次新要求对应的修复边界
- 医生角色集合包含
1时,问诊列表的视频列不显示可点击“进入视频问诊”,并在最终点击处理再次拦截,避免工具栏、隐藏兼容按钮、信号或直接调用绕过。 - 混合
[1,2]、[1,3]等账号也属于含医生角色。用户要求是医生角色禁用该视频入口,不能依赖它是否还有其他授权角色或"*"权限来恢复入口。 - 已有后台没有“root 优先于医生拒绝”的列表旁观特例;从本次“医生角色不允许”的文字看,含医生角色的账号即使还拥有
"*"也不应因通配权限恢复入口。若实现保留 root 特例,应明确这是另行产品规则,而非从现有 watchCall 业务代码推导出的例外。 - 医生主动接诊/接诊台的视频功能不在本次旁观入口限制范围内。
- 表格“图文沟通”共用同一 action_policy 和
_request_video(),用户反馈针对视频入口;若不另有要求,应保留 text 预约正常图文入口,避免把video_call=False当作全部沟通能力开关。 - 对不受限制的非医生账号,本次改动可保留既有流程;若还要完整对齐后台的指派助理 + watchCall 权限,则属于另外一项权限收敛,需要单独决定与验证,不能把现有 IM 接收入口直接替换成旁观拉流实现。
本文件为静态核对结果,无运行或生产授权验证结论。