PDO::ERRMODE_EXCEPTION, ]); $actual = str_replace('\\', '/', (string) $pdo->query('SELECT @@datadir')->fetchColumn()); if (strtolower(rtrim($actual, '/')) !== strtolower(rtrim($expectedDirectory, '/'))) { throw new RuntimeException('数据目录不匹配,拒绝创建测试数据库'); } $database = 'assistant_service_test_' . bin2hex(random_bytes(6)); $checks = 0; $expect = static function (bool $ok, string $message) use (&$checks): void { if (!$ok) { throw new RuntimeException($message); } $checks++; }; $runSql = static function (string $path) use ($pdo): void { $sql = preg_replace('/^--.*$/m', '', (string) file_get_contents($path)); foreach (explode(';', (string) $sql) as $statement) { if (trim($statement) !== '') { $cursor = $pdo->query($statement); $cursor->closeCursor(); } } }; $pdo->exec("CREATE DATABASE `{$database}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); try { $pdo->exec("USE `{$database}`"); $pdo->exec("CREATE TABLE zyt_system_menu ( id int unsigned AUTO_INCREMENT PRIMARY KEY, pid int NOT NULL, type varchar(1), name varchar(100), icon varchar(100), sort int, perms varchar(100), paths varchar(100), component varchar(150), selected varchar(100), params varchar(100), is_cache tinyint, is_show tinyint, is_disable tinyint, create_time int, update_time int) ENGINE=InnoDB"); $pdo->exec('CREATE TABLE zyt_system_role(id int PRIMARY KEY,name varchar(100),delete_time int NULL) ENGINE=InnoDB'); $pdo->exec('CREATE TABLE zyt_system_role_menu(role_id int,menu_id int,UNIQUE KEY uk_role_menu(role_id,menu_id)) ENGINE=InnoDB'); $pdo->exec("INSERT INTO zyt_system_role VALUES(1,'医生',NULL),(2,'医助',NULL),(3,'经理',NULL),(7,'诊室组长',NULL),(8,'管理员',NULL),(9,'已删除角色',1)"); $runSql(dirname(__DIR__) . '/sql/1.9.20260505/add_tcm_diagnosis_todo.sql'); $pdo->exec("INSERT INTO zyt_tcm_diagnosis_todo(diagnosis_id,content,status) VALUES(99,'历史提醒哨兵',1)"); $beforeTodo = $pdo->query('SELECT * FROM zyt_tcm_diagnosis_todo')->fetchAll(PDO::FETCH_ASSOC); $migration = dirname(__DIR__) . '/sql/1.9.20261109/'; $runSql($migration . '01_assistant_service_tables.sql'); $runSql($migration . '02_assistant_service_menus.sql'); $beforeMenus = $pdo->query('SELECT * FROM zyt_system_menu ORDER BY id')->fetchAll(PDO::FETCH_ASSOC); $beforeGrants = $pdo->query('SELECT * FROM zyt_system_role_menu ORDER BY role_id,menu_id')->fetchAll(PDO::FETCH_ASSOC); $runSql($migration . '01_assistant_service_tables.sql'); $runSql($migration . '02_assistant_service_menus.sql'); $expect($beforeMenus === $pdo->query('SELECT * FROM zyt_system_menu ORDER BY id')->fetchAll(PDO::FETCH_ASSOC), '重复迁移改变菜单'); $expect($beforeGrants === $pdo->query('SELECT * FROM zyt_system_role_menu ORDER BY role_id,menu_id')->fetchAll(PDO::FETCH_ASSOC), '重复迁移改变授权'); $expect($beforeTodo === $pdo->query('SELECT * FROM zyt_tcm_diagnosis_todo')->fetchAll(PDO::FETCH_ASSOC), '迁移改变历史提醒'); $expect(count($beforeMenus) === 9, '模块菜单/权限数量错误'); $grants = $pdo->query('SELECT m.perms FROM zyt_system_role_menu rm JOIN zyt_system_menu m ON m.id=rm.menu_id WHERE rm.role_id=2')->fetchAll(PDO::FETCH_COLUMN); $expect(in_array('tcm.assistantService/view', $grants, true), '医助缺少工作台权限'); $expect(in_array('tcm.assistantService/analyze', $grants, true), '医助缺少分析权限'); $expect(!in_array('tcm.assistantService/manageKnowledge', $grants, true), '医助被错误授予知识发布权限'); $expect(!in_array('tcm.assistantService/reviewQuality', $grants, true), '医助被错误授予主管质检权限'); $managerGrants = $pdo->query('SELECT m.perms FROM zyt_system_role_menu rm JOIN zyt_system_menu m ON m.id=rm.menu_id WHERE rm.role_id=3')->fetchAll(PDO::FETCH_COLUMN); $expect(in_array('tcm.assistantService/reviewQuality', $managerGrants, true), '经理缺少质检权限'); $expect(in_array('tcm.assistantService/manageKnowledge', $managerGrants, true), '经理缺少知识发布权限'); $expect((int) $pdo->query('SELECT COUNT(*) FROM zyt_system_role_menu WHERE role_id IN(1,9)')->fetchColumn() === 0, '非目标角色被新增授权'); foreach (['record', 'job', 'review', 'action', 'knowledge'] as $table) { $expect($pdo->query("SHOW CREATE TABLE zyt_assistant_service_{$table}")->fetch() !== false, '缺少模块表'); } // 分析任务、动作唯一约束在数据库层生效。 $pdo->exec("INSERT INTO zyt_assistant_service_job(diagnosis_id,actor_id,type,scenario,dedupe_key,source_hash,scope_hash,payload_json,created_at,updated_at) VALUES(1,2,'analysis','initial',REPEAT('a',64),REPEAT('b',64),REPEAT('c',64),'{}',1,1)"); $duplicateRejected = false; try { $pdo->exec("INSERT INTO zyt_assistant_service_job(diagnosis_id,actor_id,type,scenario,dedupe_key,source_hash,scope_hash,payload_json,created_at,updated_at) VALUES(1,2,'analysis','initial',REPEAT('a',64),REPEAT('b',64),REPEAT('c',64),'{}',1,1)"); } catch (PDOException $e) { $duplicateRejected = $e->getCode() === '23000'; } $expect($duplicateRejected, '重复任务未被唯一约束拦截'); $pdo->exec("INSERT INTO zyt_assistant_service_action(diagnosis_id,job_id,action_key,todo_id,assignee_id,content,due_at,created_at,updated_at) VALUES(1,1,'contact',99,2,'合成跟进',1,1,1)"); $duplicateRejected = false; try { $pdo->exec("INSERT INTO zyt_assistant_service_action(diagnosis_id,job_id,action_key,todo_id,assignee_id,content,due_at,created_at,updated_at) VALUES(1,1,'contact',100,2,'重复跟进',1,1,1)"); } catch (PDOException $e) { $duplicateRejected = $e->getCode() === '23000'; } $expect($duplicateRejected, '重复采纳动作未被唯一约束拦截'); echo "AssistantServiceMysqlMigrationTest PASS ({$checks} checks)\n"; } finally { // 名称由本脚本生成且已核对临时数据目录;只移除本次创建的数据库。 if (preg_match('/^assistant_service_test_[a-f0-9]{12}$/D', $database)) { $pdo->exec("DROP DATABASE `{$database}`"); } }