where('id', $actor)->whereNull('delete_time')->where('disable', 0)->lock(true)->find(); if (!$row) { throw new DomainException('账号已停用或无权访问'); } return [ 'admin_id' => $actor, 'id' => $actor, 'root' => (int) $row['root'], 'name' => (string) $row['name'], 'role_id' => Db::name('admin_role')->where('admin_id', $actor)->lock(true)->column('role_id'), 'dept_id' => Db::name('admin_dept')->where('admin_id', $actor)->lock(true)->column('dept_id'), ]; } public static function allowed(int $actor, array $info, string $permission): bool { if ($actor <= 0) { return false; } if ((int) ($info['root'] ?? 0) === 1) { return true; } // Explicit current reads also avoid permission-cache and MVCC snapshot staleness. $roles = Db::name('admin_role')->where('admin_id', $actor)->lock(true)->column('role_id'); $menus = $roles ? Db::name('system_role_menu')->whereIn('role_id', $roles)->lock(true)->column('menu_id') : []; $permissions = $menus ? Db::name('system_menu')->whereIn('id', array_unique($menus)) ->where('is_disable', 0)->lock(true)->column('perms') : []; return in_array(strtolower($permission), array_map('strtolower', $permissions), true); } public static function diagnosis(int $diagnosisId, int $actor, array $info, bool $daily = false, bool $enforcePreviewScope = true): array { if ($enforcePreviewScope) { FollowupAudioGate::assertScope($diagnosisId, $actor); } if ($diagnosisId <= 0) { throw new DomainException('诊单不存在或无权操作'); } // Acquire the target row before rebuilding current actor/scope after any lock wait. $row = Db::name('tcm_diagnosis')->where('id', $diagnosisId)->whereNull('delete_time')->lock(true)->find(); $info = self::actor($actor); if (!$row || !self::allowed($actor, $info, 'tcm.diagnosis/edit') || ($daily && !self::allowed($actor, $info, 'tcm.diagnosis/dailyRecord'))) { throw new DomainException('诊单不存在或无权操作'); } $query = Db::name('tcm_diagnosis')->alias('d')->where('d.id', $diagnosisId) ->whereNull('d.delete_time')->where('d.status', 1); MyPatientLogic::applyScope($query, $actor, $info); if (!$query->lock(true)->find()) { throw new DomainException('诊单不存在或无权操作'); } return $row; } public static function task(int $taskId, int $actor, array $info, bool $daily = false): array { $task = FollowupAudioStore::task($taskId); $diagnosis = self::diagnosis((int) $task['diagnosis_id'], $actor, $info, $daily); if (array_key_exists('patient_id', $task) && (int) $task['patient_id'] !== (int) ($diagnosis['patient_id'] ?? 0)) { throw new DomainException('诊单患者归属已变化,不能访问原回访录音'); } return $task; } public static function canDaily(int $actor, array $info): bool { $info = self::actor($actor); return self::allowed($actor, $info, 'tcm.diagnosis/dailyRecord'); } }