ai-terminal-mvp.sql 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. -- ============================================================
  2. -- 医梦 AI 中台 统一入口客户端 MVP 数据库迁移
  3. -- 版本: 2026-05-31
  4. -- 原则: 不 DROP 现有表,仅 ALTER 补字段 + CREATE 新表
  5. -- ============================================================
  6. -- -----------------------------------------------------------
  7. -- 1. 新表:设备注册中心
  8. -- -----------------------------------------------------------
  9. CREATE TABLE IF NOT EXISTS ai_device_registry (
  10. id BIGINT PRIMARY KEY AUTO_INCREMENT,
  11. device_id VARCHAR(64) NOT NULL,
  12. project_id VARCHAR(64) NOT NULL,
  13. hospital_id VARCHAR(64) NOT NULL,
  14. device_type VARCHAR(64) NOT NULL,
  15. management_mode VARCHAR(32) NOT NULL DEFAULT 'emoon_managed',
  16. vendor VARCHAR(64),
  17. model VARCHAR(128),
  18. location_json JSON,
  19. capabilities_json JSON,
  20. status VARCHAR(32) NOT NULL DEFAULT 'pending',
  21. admission_level VARCHAR(8) NOT NULL DEFAULT 'B',
  22. client_version VARCHAR(64),
  23. last_heartbeat DATETIME,
  24. created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  25. updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  26. UNIQUE KEY uk_device_id (device_id),
  27. KEY idx_project_hospital (project_id, hospital_id)
  28. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  29. -- -----------------------------------------------------------
  30. -- 2. 新表:设备场景绑定
  31. -- -----------------------------------------------------------
  32. CREATE TABLE IF NOT EXISTS ai_device_scene_binding (
  33. id BIGINT PRIMARY KEY AUTO_INCREMENT,
  34. device_id VARCHAR(64) NOT NULL,
  35. scene_code VARCHAR(64) NOT NULL,
  36. ui_template VARCHAR(64) NOT NULL,
  37. agent_bindings_json JSON NOT NULL,
  38. tool_scopes_json JSON,
  39. card_scopes_json JSON,
  40. status VARCHAR(32) NOT NULL DEFAULT 'enabled',
  41. created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  42. updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  43. UNIQUE KEY uk_device_scene (device_id, scene_code)
  44. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  45. -- -----------------------------------------------------------
  46. -- 3. 新表:设备事件日志
  47. -- -----------------------------------------------------------
  48. CREATE TABLE IF NOT EXISTS ai_device_event (
  49. id BIGINT PRIMARY KEY AUTO_INCREMENT,
  50. event_id VARCHAR(96) NOT NULL,
  51. device_id VARCHAR(64) NOT NULL,
  52. project_id VARCHAR(64) NOT NULL COMMENT '项目标识,用于多租户隔离和审计',
  53. event_type VARCHAR(64) NOT NULL,
  54. event_payload JSON,
  55. trace_id VARCHAR(96),
  56. occurred_at DATETIME NOT NULL,
  57. created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  58. UNIQUE KEY uk_device_event (device_id, event_id),
  59. KEY idx_device_time (device_id, occurred_at),
  60. KEY idx_project (project_id)
  61. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  62. -- -----------------------------------------------------------
  63. -- 4. 新表:设备命令
  64. -- -----------------------------------------------------------
  65. CREATE TABLE IF NOT EXISTS ai_device_command (
  66. id BIGINT PRIMARY KEY AUTO_INCREMENT,
  67. command_id VARCHAR(96) NOT NULL,
  68. device_id VARCHAR(64) NOT NULL,
  69. command_type VARCHAR(64) NOT NULL,
  70. payload_json JSON NOT NULL,
  71. status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
  72. expire_at DATETIME NOT NULL,
  73. ack_at DATETIME,
  74. ack_result_json JSON,
  75. trace_id VARCHAR(96),
  76. created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  77. UNIQUE KEY uk_command_id (command_id),
  78. KEY idx_device_status (device_id, status)
  79. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  80. -- -----------------------------------------------------------
  81. -- 5. 新表:会话消息
  82. -- -----------------------------------------------------------
  83. CREATE TABLE IF NOT EXISTS ai_conversation_message (
  84. id BIGINT PRIMARY KEY AUTO_INCREMENT,
  85. message_id VARCHAR(96) NOT NULL,
  86. conversation_id VARCHAR(96) NOT NULL,
  87. role VARCHAR(32) NOT NULL,
  88. content TEXT,
  89. event_json JSON,
  90. trace_id VARCHAR(96),
  91. created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  92. UNIQUE KEY uk_message_id (message_id),
  93. KEY idx_conv_time (conversation_id, created_at)
  94. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  95. -- -----------------------------------------------------------
  96. -- 6. 新表:任务实例
  97. -- -----------------------------------------------------------
  98. CREATE TABLE IF NOT EXISTS ai_task_instance (
  99. id BIGINT PRIMARY KEY AUTO_INCREMENT,
  100. task_id VARCHAR(96) NOT NULL,
  101. project_id VARCHAR(64) NOT NULL,
  102. conversation_id VARCHAR(96) NOT NULL,
  103. task_type VARCHAR(64) NOT NULL,
  104. status VARCHAR(32) NOT NULL,
  105. current_step VARCHAR(64) NOT NULL,
  106. agent_code VARCHAR(64),
  107. context_json JSON,
  108. patient_id VARCHAR(96),
  109. device_id VARCHAR(64),
  110. trace_id VARCHAR(96),
  111. created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  112. updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  113. UNIQUE KEY uk_task_id (task_id),
  114. KEY idx_project (project_id),
  115. KEY idx_conv_status (conversation_id, status)
  116. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  117. -- -----------------------------------------------------------
  118. -- 7. 新表:文件对象
  119. -- -----------------------------------------------------------
  120. CREATE TABLE IF NOT EXISTS ai_file_object (
  121. id BIGINT PRIMARY KEY AUTO_INCREMENT,
  122. file_id VARCHAR(96) NOT NULL,
  123. project_id VARCHAR(64) NOT NULL,
  124. hospital_id VARCHAR(64),
  125. device_id VARCHAR(64),
  126. business_type VARCHAR(64) NOT NULL,
  127. sha256 VARCHAR(128),
  128. storage_path VARCHAR(512) NOT NULL,
  129. retention_policy VARCHAR(32) NOT NULL,
  130. sensitive_level VARCHAR(32) NOT NULL,
  131. status VARCHAR(32) NOT NULL DEFAULT 'available',
  132. created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  133. UNIQUE KEY uk_file_id (file_id),
  134. KEY idx_project_type (project_id, business_type)
  135. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  136. -- -----------------------------------------------------------
  137. -- 7.5 新表:卡片动作日志(幂等审计)
  138. -- -----------------------------------------------------------
  139. CREATE TABLE IF NOT EXISTS ai_card_action_log (
  140. id BIGINT PRIMARY KEY AUTO_INCREMENT,
  141. action_id VARCHAR(96) NOT NULL,
  142. card_instance_id VARCHAR(96) NOT NULL,
  143. action_name VARCHAR(64) NOT NULL,
  144. idempotency_key VARCHAR(128) NOT NULL,
  145. action_payload_json JSON,
  146. status VARCHAR(32) NOT NULL DEFAULT 'submitted',
  147. trace_id VARCHAR(96),
  148. created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  149. UNIQUE KEY uk_idempotency (idempotency_key),
  150. KEY idx_card_action (card_instance_id, action_name)
  151. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  152. -- -----------------------------------------------------------
  153. -- 8. 存量表 ai_conversation 补字段
  154. -- 现有实体: com.emoon.mcp.domain.AiConversation
  155. -- 已有字段: id, projectId, agentId, conversationId, status 等
  156. -- -----------------------------------------------------------
  157. DROP PROCEDURE IF EXISTS emoon_add_column_if_absent;
  158. DELIMITER //
  159. CREATE PROCEDURE emoon_add_column_if_absent(
  160. IN p_table_name VARCHAR(128),
  161. IN p_column_name VARCHAR(128),
  162. IN p_ddl TEXT
  163. )
  164. BEGIN
  165. IF NOT EXISTS (
  166. SELECT 1 FROM information_schema.columns
  167. WHERE table_schema = DATABASE()
  168. AND table_name = p_table_name
  169. AND column_name = p_column_name
  170. ) THEN
  171. SET @sql = p_ddl;
  172. PREPARE stmt FROM @sql;
  173. EXECUTE stmt;
  174. DEALLOCATE PREPARE stmt;
  175. END IF;
  176. END//
  177. DELIMITER ;
  178. DROP PROCEDURE IF EXISTS emoon_add_index_if_absent;
  179. DELIMITER //
  180. CREATE PROCEDURE emoon_add_index_if_absent(
  181. IN p_table_name VARCHAR(128),
  182. IN p_index_name VARCHAR(128),
  183. IN p_ddl TEXT
  184. )
  185. BEGIN
  186. IF NOT EXISTS (
  187. SELECT 1 FROM information_schema.statistics
  188. WHERE table_schema = DATABASE()
  189. AND table_name = p_table_name
  190. AND index_name = p_index_name
  191. ) THEN
  192. SET @sql = p_ddl;
  193. PREPARE stmt FROM @sql;
  194. EXECUTE stmt;
  195. DEALLOCATE PREPARE stmt;
  196. END IF;
  197. END//
  198. DELIMITER ;
  199. CALL emoon_add_column_if_absent('ai_conversation', 'hospital_id',
  200. 'ALTER TABLE ai_conversation ADD COLUMN hospital_id VARCHAR(64) NULL COMMENT ''医院标识''');
  201. CALL emoon_add_column_if_absent('ai_conversation', 'device_id',
  202. 'ALTER TABLE ai_conversation ADD COLUMN device_id VARCHAR(64) NULL COMMENT ''统一入口终端设备ID''');
  203. CALL emoon_add_column_if_absent('ai_conversation', 'client_message_id',
  204. 'ALTER TABLE ai_conversation ADD COLUMN client_message_id VARCHAR(96) NULL COMMENT ''客户端消息幂等ID''');
  205. CALL emoon_add_index_if_absent('ai_conversation', 'idx_ai_conversation_device',
  206. 'CREATE INDEX idx_ai_conversation_device ON ai_conversation(device_id)');
  207. -- -----------------------------------------------------------
  208. -- 9. 存量表 ai_card_instance 补字段
  209. -- 现有实体: com.emoon.mcp.domain.AiCardInstance
  210. -- 已有字段: id, conversationId, cardKey, instanceId, status 等
  211. -- -----------------------------------------------------------
  212. CALL emoon_add_column_if_absent('ai_card_instance', 'task_id',
  213. 'ALTER TABLE ai_card_instance ADD COLUMN task_id VARCHAR(96) NULL COMMENT ''统一入口终端任务ID''');
  214. CALL emoon_add_column_if_absent('ai_card_instance', 'trace_id',
  215. 'ALTER TABLE ai_card_instance ADD COLUMN trace_id VARCHAR(96) NULL COMMENT ''链路追踪ID''');
  216. CALL emoon_add_index_if_absent('ai_card_instance', 'idx_ai_card_instance_task',
  217. 'CREATE INDEX idx_ai_card_instance_task ON ai_card_instance(task_id)');
  218. -- -----------------------------------------------------------
  219. -- 10. 存量表 ai_card_definition 补索引
  220. -- 现有实体: com.emoon.system.domain.AiCardDefinition
  221. -- 已有字段: cardKey, version, name, schemaJson, actionsJson, status, isSystem, isLatest
  222. -- -----------------------------------------------------------
  223. CALL emoon_add_index_if_absent('ai_card_definition', 'idx_ai_card_def_key',
  224. 'CREATE INDEX idx_ai_card_def_key ON ai_card_definition(card_key)');
  225. DROP PROCEDURE IF EXISTS emoon_add_column_if_absent;
  226. DROP PROCEDURE IF EXISTS emoon_add_index_if_absent;
  227. -- -----------------------------------------------------------
  228. -- 11. Seed 数据:核心卡片定义
  229. -- -----------------------------------------------------------
  230. INSERT INTO ai_card_definition(card_key, version, name, schema_json, actions_json, status, is_system, is_latest)
  231. VALUES
  232. ('department-selection', '1.0', '科室选择', JSON_OBJECT('required', JSON_ARRAY('departments')), JSON_ARRAY(JSON_OBJECT('actionName', 'select_department')), '0', '1', TRUE),
  233. ('doctor-selection', '1.0', '医生选择', JSON_OBJECT('required', JSON_ARRAY('doctors')), JSON_ARRAY(JSON_OBJECT('actionName', 'select_doctor')), '0', '1', TRUE),
  234. ('time-slot-selection', '1.0', '号源选择', JSON_OBJECT('required', JSON_ARRAY('slots')), JSON_ARRAY(JSON_OBJECT('actionName', 'select_time_slot')), '0', '1', TRUE),
  235. ('confirm-appointment', '1.0', '挂号确认', JSON_OBJECT('required', JSON_ARRAY('summary')), JSON_ARRAY(JSON_OBJECT('actionName', 'confirm_appointment', 'requiredConfirm', TRUE)), '0', '1', TRUE),
  236. ('appointment-success', '1.0', '挂号成功', JSON_OBJECT('required', JSON_ARRAY('appointmentNo')), JSON_ARRAY(), '0', '1', TRUE),
  237. ('route-card', '1.0', '路线导航', JSON_OBJECT('required', JSON_ARRAY('routeText')), JSON_ARRAY(JSON_OBJECT('actionName', 'start_navigation')), '0', '1', TRUE),
  238. ('tongue-capture', '1.0', '舌象采集', JSON_OBJECT('required', JSON_ARRAY('uploadField')), JSON_ARRAY(JSON_OBJECT('actionName', 'submit_tongue_image')), '0', '1', TRUE),
  239. ('tongue-diagnosis-result', '1.0', '舌诊结果', JSON_OBJECT('required', JSON_ARRAY('summary')), JSON_ARRAY(), '0', '1', TRUE)
  240. ON DUPLICATE KEY UPDATE
  241. name = VALUES(name),
  242. schema_json = VALUES(schema_json),
  243. actions_json = VALUES(actions_json),
  244. status = VALUES(status),
  245. is_system = VALUES(is_system),
  246. is_latest = VALUES(is_latest);
  247. -- -----------------------------------------------------------
  248. -- 12. Seed 数据:自助机设备和场景
  249. -- -----------------------------------------------------------
  250. INSERT IGNORE INTO ai_device_registry(device_id, project_id, hospital_id, device_type, status, admission_level, capabilities_json)
  251. VALUES ('EMOON-KIOSK-001', 'hospital_demo', 'H001', 'self_service_kiosk', 'activated', 'A',
  252. JSON_ARRAY('touch','camera','id_card_ocr','file_upload'));
  253. INSERT IGNORE INTO ai_device_scene_binding(device_id, scene_code, ui_template, agent_bindings_json, card_scopes_json)
  254. VALUES ('EMOON-KIOSK-001', 'outpatient_kiosk', 'kiosk_home_v1',
  255. JSON_OBJECT('defaultAgent','opd-guide-agent','allowedAgents',JSON_ARRAY('opd-guide-agent','opd-triage-agent','opd-registration-agent','tongue-diagnosis-agent')),
  256. JSON_ARRAY('department-selection','doctor-selection','time-slot-selection','confirm-appointment','appointment-success','tongue-capture','tongue-diagnosis-result'));
  257. -- -----------------------------------------------------------
  258. -- 迁移补丁:为已执行旧版脚本的环境补 project_id
  259. -- 如果列不存在则添加,存在则跳过(MySQL 5.7+ 不支持 IF NOT EXISTS 语法,
  260. -- 使用 information_schema 预检)
  261. -- -----------------------------------------------------------
  262. SET @col_exists = (SELECT COUNT(*) FROM information_schema.COLUMNS
  263. WHERE TABLE_SCHEMA = DATABASE()
  264. AND TABLE_NAME = 'ai_device_event'
  265. AND COLUMN_NAME = 'project_id');
  266. SET @sql = IF(@col_exists = 0,
  267. 'ALTER TABLE ai_device_event ADD COLUMN project_id VARCHAR(64) NOT NULL DEFAULT '''' COMMENT ''项目标识'' AFTER device_id',
  268. 'SELECT ''ai_device_event.project_id already exists, skipping'' AS msg');
  269. PREPARE stmt FROM @sql;
  270. EXECUTE stmt;
  271. DEALLOCATE PREPARE stmt;
  272. -- 补索引(幂等:MySQL 8.0+ CREATE INDEX IF NOT EXISTS,低版本靠 PREPARE 预检)
  273. SET @idx_exists = (SELECT COUNT(*) FROM information_schema.STATISTICS
  274. WHERE TABLE_SCHEMA = DATABASE()
  275. AND TABLE_NAME = 'ai_device_event'
  276. AND INDEX_NAME = 'idx_project');
  277. SET @sql_idx = IF(@idx_exists = 0,
  278. 'CREATE INDEX idx_project ON ai_device_event(project_id)',
  279. 'SELECT ''ai_device_event.idx_project already exists, skipping'' AS msg');
  280. PREPARE stmt_idx FROM @sql_idx;
  281. EXECUTE stmt_idx;
  282. DEALLOCATE PREPARE stmt_idx;