-- iMeeting legacy DB upgrade -- Goal: upgrade an old schema to the latest application-facing structure -- Strategy: -- 1) standardize system tables to sys_* base tables -- 2) align shared table columns/indexes -- 3) create missing configuration tables -- 4) migrate legacy configuration/hot-word data into new tables -- 5) rebuild menu tree and role permissions needed by the latest backend -- 6) recreate compatibility views -- ---------------------------------------------------------------------- -- 1. Rename legacy system tables to sys_* base tables when needed -- ---------------------------------------------------------------------- SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'users' AND table_type = 'BASE TABLE' ) AND NOT EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'sys_users' ), 'RENAME TABLE users TO sys_users', 'SELECT 1' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'roles' AND table_type = 'BASE TABLE' ) AND NOT EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'sys_roles' ), 'RENAME TABLE roles TO sys_roles', 'SELECT 1' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'menus' AND table_type = 'BASE TABLE' ) AND NOT EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'sys_menus' ), 'RENAME TABLE menus TO sys_menus', 'SELECT 1' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'role_menu_permissions' AND table_type = 'BASE TABLE' ) AND NOT EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'sys_role_menu_permissions' ), 'RENAME TABLE role_menu_permissions TO sys_role_menu_permissions', 'SELECT 1' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'dict_data' AND table_type = 'BASE TABLE' ) AND NOT EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'sys_dict_data' ), 'RENAME TABLE dict_data TO sys_dict_data', 'SELECT 1' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'system_parameters' AND table_type = 'BASE TABLE' ) AND NOT EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'sys_system_parameters' ), 'RENAME TABLE system_parameters TO sys_system_parameters', 'SELECT 1' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- ---------------------------------------------------------------------- -- 2. Align existing sys_* tables -- ---------------------------------------------------------------------- SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'sys_users' AND index_name = 'idx_role_id' ), 'SELECT 1', 'ALTER TABLE sys_users ADD KEY idx_role_id (role_id)' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'sys_roles' AND index_name = 'uk_role_name' ), 'SELECT 1', 'ALTER TABLE sys_roles ADD UNIQUE KEY uk_role_name (role_name)' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'sys_menus' AND column_name = 'menu_level' ), 'SELECT 1', 'ALTER TABLE sys_menus ADD COLUMN menu_level TINYINT(3) NOT NULL DEFAULT 1 COMMENT ''菜单层级(根节点为1)'' AFTER parent_id' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'sys_menus' AND column_name = 'tree_path' ), 'SELECT 1', 'ALTER TABLE sys_menus ADD COLUMN tree_path VARCHAR(255) DEFAULT NULL COMMENT ''树路径(如 /3/6)'' AFTER menu_level' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'sys_menus' AND column_name = 'is_visible' ), 'SELECT 1', 'ALTER TABLE sys_menus ADD COLUMN is_visible TINYINT(1) NOT NULL DEFAULT 1 COMMENT ''是否在侧边菜单显示'' AFTER is_active' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'sys_menus' AND index_name = 'idx_menu_level' ), 'SELECT 1', 'ALTER TABLE sys_menus ADD KEY idx_menu_level (menu_level)' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'sys_menus' AND index_name = 'idx_tree_path' ), 'SELECT 1', 'ALTER TABLE sys_menus ADD KEY idx_tree_path (tree_path)' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'sys_menus' AND index_name = 'idx_is_visible' ), 'SELECT 1', 'ALTER TABLE sys_menus ADD KEY idx_is_visible (is_visible)' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'sys_menus' AND index_name = 'idx_menus_visible_tree' ), 'SELECT 1', 'ALTER TABLE sys_menus ADD KEY idx_menus_visible_tree (is_active, is_visible, parent_id, sort_order, menu_id)' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'sys_role_menu_permissions' AND column_name = 'granted_by' ), 'SELECT 1', 'ALTER TABLE sys_role_menu_permissions ADD COLUMN granted_by INT(11) DEFAULT NULL COMMENT ''授权操作人ID'' AFTER menu_id' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'sys_role_menu_permissions' AND column_name = 'granted_at' ), 'SELECT 1', 'ALTER TABLE sys_role_menu_permissions ADD COLUMN granted_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP COMMENT ''授权时间'' AFTER granted_by' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'sys_role_menu_permissions' AND index_name = 'idx_granted_by' ), 'SELECT 1', 'ALTER TABLE sys_role_menu_permissions ADD KEY idx_granted_by (granted_by)' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'sys_role_menu_permissions' AND index_name = 'idx_granted_at' ), 'SELECT 1', 'ALTER TABLE sys_role_menu_permissions ADD KEY idx_granted_at (granted_at)' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'sys_role_menu_permissions' AND index_name = 'idx_rmp_role' ), 'SELECT 1', 'ALTER TABLE sys_role_menu_permissions ADD KEY idx_rmp_role (role_id)' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'sys_role_menu_permissions' AND index_name = 'idx_rmp_menu' ), 'SELECT 1', 'ALTER TABLE sys_role_menu_permissions ADD KEY idx_rmp_menu (menu_id)' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; ALTER TABLE `terminals` MODIFY COLUMN `current_user_id` INT(11) DEFAULT NULL COMMENT '终端绑定账号'; -- prompts: add is_system and composite index SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'prompts' AND column_name = 'is_system' ), 'SELECT 1', 'ALTER TABLE prompts ADD COLUMN is_system TINYINT(1) NOT NULL DEFAULT 0 AFTER creator_id' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := ( SELECT IF( EXISTS ( SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'prompts' AND index_name = 'idx_prompts_task_scope_active' ), 'SELECT 1', 'CREATE INDEX idx_prompts_task_scope_active ON prompts (task_type, is_system, creator_id, is_active, is_default)' ) ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; UPDATE prompts SET is_system = 1 WHERE creator_id = 1; -- ---------------------------------------------------------------------- -- 3. Create missing tables with latest structure -- ---------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS `sys_system_parameters` ( `param_id` bigint(20) NOT NULL AUTO_INCREMENT, `param_key` varchar(128) NOT NULL, `param_name` varchar(255) NOT NULL, `param_value` text, `value_type` varchar(32) NOT NULL DEFAULT 'string', `category` varchar(64) NOT NULL DEFAULT 'system', `description` varchar(500) DEFAULT NULL, `is_active` tinyint(1) NOT NULL DEFAULT '1', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`param_id`), UNIQUE KEY `uk_param_key` (`param_key`), KEY `idx_param_category` (`category`), KEY `idx_param_active` (`is_active`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `ai_model_configs` ( `model_id` bigint(20) NOT NULL AUTO_INCREMENT, `model_code` varchar(128) NOT NULL, `model_name` varchar(255) NOT NULL, `model_type` varchar(32) NOT NULL, `provider` varchar(64) DEFAULT NULL, `endpoint_url` varchar(512) DEFAULT NULL, `api_key` varchar(512) DEFAULT NULL, `llm_model_name` varchar(128) DEFAULT NULL, `llm_timeout` int(11) DEFAULT NULL, `llm_temperature` decimal(5,2) DEFAULT NULL, `llm_top_p` decimal(5,2) DEFAULT NULL, `llm_max_tokens` int(11) DEFAULT NULL, `llm_system_prompt` text, `asr_model_name` varchar(128) DEFAULT NULL, `asr_vocabulary_id` varchar(255) DEFAULT NULL, `asr_speaker_count` int(11) DEFAULT NULL, `asr_language_hints` varchar(255) DEFAULT NULL, `asr_disfluency_removal_enabled` tinyint(1) DEFAULT NULL, `asr_diarization_enabled` tinyint(1) DEFAULT NULL, `config_json` json DEFAULT NULL, `description` varchar(500) DEFAULT NULL, `is_active` tinyint(1) NOT NULL DEFAULT '1', `is_default` tinyint(1) NOT NULL DEFAULT '0', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`model_id`), UNIQUE KEY `uk_model_code` (`model_code`), KEY `idx_model_type` (`model_type`), KEY `idx_model_active` (`is_active`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `llm_model_config` ( `config_id` bigint(20) NOT NULL AUTO_INCREMENT, `model_code` varchar(128) NOT NULL, `model_name` varchar(255) NOT NULL, `provider` varchar(64) DEFAULT NULL, `endpoint_url` varchar(512) DEFAULT NULL, `api_key` varchar(512) DEFAULT NULL, `llm_model_name` varchar(128) NOT NULL, `llm_timeout` int(11) NOT NULL DEFAULT '120', `llm_temperature` decimal(5,2) NOT NULL DEFAULT '0.70', `llm_top_p` decimal(5,2) NOT NULL DEFAULT '0.90', `llm_max_tokens` int(11) NOT NULL DEFAULT '2048', `llm_system_prompt` text, `description` varchar(500) DEFAULT NULL, `is_active` tinyint(1) NOT NULL DEFAULT '1', `is_default` tinyint(1) NOT NULL DEFAULT '0', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`config_id`), UNIQUE KEY `uk_llm_model_code` (`model_code`), KEY `idx_llm_active` (`is_active`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `audio_model_config` ( `config_id` bigint(20) NOT NULL AUTO_INCREMENT, `model_code` varchar(128) NOT NULL, `model_name` varchar(255) NOT NULL, `audio_scene` varchar(32) NOT NULL, `provider` varchar(64) DEFAULT NULL, `endpoint_url` varchar(512) DEFAULT NULL, `api_key` varchar(512) DEFAULT NULL, `asr_model_name` varchar(128) DEFAULT NULL, `asr_vocabulary_id` varchar(255) DEFAULT NULL, `hot_word_group_id` int(11) DEFAULT NULL COMMENT '关联热词组ID', `extra_config` json DEFAULT NULL COMMENT '音频模型差异化配置(JSON)', `asr_speaker_count` int(11) DEFAULT NULL, `asr_language_hints` varchar(255) DEFAULT NULL, `asr_disfluency_removal_enabled` tinyint(1) DEFAULT NULL, `asr_diarization_enabled` tinyint(1) DEFAULT NULL, `vp_template_text` text, `vp_duration_seconds` int(11) DEFAULT NULL, `vp_sample_rate` int(11) DEFAULT NULL, `vp_channels` int(11) DEFAULT NULL, `vp_max_size_bytes` bigint(20) DEFAULT NULL, `description` varchar(500) DEFAULT NULL, `is_active` tinyint(1) NOT NULL DEFAULT '1', `is_default` tinyint(1) NOT NULL DEFAULT '0', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`config_id`), UNIQUE KEY `uk_audio_model_code` (`model_code`), KEY `idx_audio_scene` (`audio_scene`), KEY `idx_audio_active` (`is_active`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `hot_word_group` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '热词组名称', `description` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '描述', `vocabulary_id` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '阿里云 DashScope 词表ID', `last_sync_time` datetime DEFAULT NULL COMMENT '最后同步时间', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1:启用 0:停用', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='热词组主表'; CREATE TABLE IF NOT EXISTS `hot_word_item` ( `id` int(11) NOT NULL AUTO_INCREMENT, `group_id` int(11) NOT NULL COMMENT '热词组ID', `text` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '热词内容', `weight` int(11) NOT NULL DEFAULT '4' COMMENT '权重 1-10', `lang` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zh' COMMENT 'zh/en', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1:启用 0:停用', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_group_text` (`group_id`,`text`), KEY `idx_group_id` (`group_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='热词条目从表'; CREATE TABLE IF NOT EXISTS `prompt_config` ( `config_id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `task_type` enum('MEETING_TASK','KNOWLEDGE_TASK') NOT NULL, `prompt_id` int(11) NOT NULL, `is_enabled` tinyint(1) NOT NULL DEFAULT '1', `sort_order` int(11) NOT NULL DEFAULT '0', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`config_id`), UNIQUE KEY `uk_user_task_prompt` (`user_id`,`task_type`,`prompt_id`), KEY `idx_user_task_order` (`user_id`,`task_type`,`sort_order`), KEY `idx_prompt_id` (`prompt_id`), CONSTRAINT `fk_upc_prompt` FOREIGN KEY (`prompt_id`) REFERENCES `prompts` (`id`) ON DELETE CASCADE, CONSTRAINT `fk_upc_user` FOREIGN KEY (`user_id`) REFERENCES `sys_users` (`user_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `sys_user_mcp` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `bot_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL, `bot_secret` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL, `status` tinyint(1) NOT NULL DEFAULT '1', `last_used_at` datetime DEFAULT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_sys_user_mcp_user_id` (`user_id`), UNIQUE KEY `uk_sys_user_mcp_bot_id` (`bot_id`), KEY `idx_sys_user_mcp_status` (`status`), CONSTRAINT `fk_sys_user_mcp_user` FOREIGN KEY (`user_id`) REFERENCES `sys_users` (`user_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户MCP接入凭证'; -- ---------------------------------------------------------------------- -- 4. Migrate legacy system config data into new parameter/model tables -- ---------------------------------------------------------------------- INSERT INTO `sys_system_parameters` (`param_key`, `param_name`, `param_value`, `value_type`, `category`, `description`, `is_active`) SELECT d.`dict_code`, d.`label_cn`, JSON_UNQUOTE(JSON_EXTRACT(d.`extension_attr`, '$.value')), 'string', 'system', CONCAT('migrated from dict_data.system_config(', d.`dict_code`, ')'), CASE WHEN d.`status` = 1 THEN 1 ELSE 0 END FROM `sys_dict_data` d WHERE d.`dict_type` = 'system_config' AND d.`dict_code` NOT IN ('llm_model', 'voiceprint') AND JSON_EXTRACT(d.`extension_attr`, '$.value') IS NOT NULL ON DUPLICATE KEY UPDATE `param_name` = VALUES(`param_name`), `param_value` = VALUES(`param_value`), `description` = VALUES(`description`), `is_active` = VALUES(`is_active`); INSERT INTO `ai_model_configs` (`model_code`, `model_name`, `model_type`, `provider`, `endpoint_url`, `api_key`, `llm_model_name`, `llm_timeout`, `llm_temperature`, `llm_top_p`, `llm_max_tokens`, `llm_system_prompt`, `config_json`, `description`, `is_active`, `is_default`) SELECT 'llm_model', '默认文本模型', 'llm', 'dashscope', 'https://dashscope.aliyuncs.com/compatible-mode/v1', NULL, COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d.`extension_attr`, '$.model_name')), 'qwen-plus'), COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(d.`extension_attr`, '$.time_out')) AS UNSIGNED), 120), COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(d.`extension_attr`, '$.temperature')) AS DECIMAL(5,2)), 0.70), COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(d.`extension_attr`, '$.top_p')) AS DECIMAL(5,2)), 0.90), COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(d.`extension_attr`, '$.max_tokens')) AS UNSIGNED), 2048), JSON_UNQUOTE(JSON_EXTRACT(d.`extension_attr`, '$.system_prompt')), d.`extension_attr`, 'migrated from dict_data.system_config.llm_model', CASE WHEN d.`status` = 1 THEN 1 ELSE 0 END, 1 FROM `sys_dict_data` d WHERE d.`dict_type` = 'system_config' AND d.`dict_code` = 'llm_model' LIMIT 1 ON DUPLICATE KEY UPDATE `model_name` = VALUES(`model_name`), `provider` = VALUES(`provider`), `endpoint_url` = VALUES(`endpoint_url`), `llm_model_name` = VALUES(`llm_model_name`), `llm_timeout` = VALUES(`llm_timeout`), `llm_temperature` = VALUES(`llm_temperature`), `llm_top_p` = VALUES(`llm_top_p`), `llm_max_tokens` = VALUES(`llm_max_tokens`), `llm_system_prompt` = VALUES(`llm_system_prompt`), `config_json` = VALUES(`config_json`), `description` = VALUES(`description`), `is_active` = VALUES(`is_active`), `is_default` = VALUES(`is_default`); INSERT INTO `ai_model_configs` (`model_code`, `model_name`, `model_type`, `provider`, `endpoint_url`, `api_key`, `asr_model_name`, `asr_vocabulary_id`, `asr_speaker_count`, `asr_language_hints`, `asr_disfluency_removal_enabled`, `asr_diarization_enabled`, `config_json`, `description`, `is_active`, `is_default`) SELECT 'audio_model', '默认音频识别模型', 'audio', 'dashscope', 'https://dashscope.aliyuncs.com/api/v1/services/audio/asr/transcription', NULL, 'paraformer-v2', ( SELECT JSON_UNQUOTE(JSON_EXTRACT(d2.`extension_attr`, '$.value')) FROM `sys_dict_data` d2 WHERE d2.`dict_type` = 'system_config' AND d2.`dict_code` = 'asr_vocabulary_id' LIMIT 1 ), 10, 'zh, en', 1, 1, JSON_OBJECT( 'model', 'paraformer-v2', 'vocabulary_id', ( SELECT JSON_UNQUOTE(JSON_EXTRACT(d3.`extension_attr`, '$.value')) FROM `sys_dict_data` d3 WHERE d3.`dict_type` = 'system_config' AND d3.`dict_code` = 'asr_vocabulary_id' LIMIT 1 ), 'speaker_count', 10, 'language_hints', 'zh, en', 'disfluency_removal_enabled', TRUE, 'diarization_enabled', TRUE ), 'migrated default ASR config', 1, 1 FROM DUAL ON DUPLICATE KEY UPDATE `model_name` = VALUES(`model_name`), `provider` = VALUES(`provider`), `endpoint_url` = VALUES(`endpoint_url`), `asr_model_name` = VALUES(`asr_model_name`), `asr_vocabulary_id` = VALUES(`asr_vocabulary_id`), `asr_speaker_count` = VALUES(`asr_speaker_count`), `asr_language_hints` = VALUES(`asr_language_hints`), `asr_disfluency_removal_enabled` = VALUES(`asr_disfluency_removal_enabled`), `asr_diarization_enabled` = VALUES(`asr_diarization_enabled`), `config_json` = VALUES(`config_json`), `description` = VALUES(`description`), `is_active` = VALUES(`is_active`), `is_default` = VALUES(`is_default`); INSERT INTO `ai_model_configs` (`model_code`, `model_name`, `model_type`, `provider`, `endpoint_url`, `api_key`, `config_json`, `description`, `is_active`, `is_default`) SELECT 'voiceprint_model', '默认声纹模型', 'audio', 'funasr', 'http://127.0.0.1:10095', NULL, d.`extension_attr`, 'migrated from dict_data.system_config.voiceprint', CASE WHEN d.`status` = 1 THEN 1 ELSE 0 END, 0 FROM `sys_dict_data` d WHERE d.`dict_type` = 'system_config' AND d.`dict_code` = 'voiceprint' LIMIT 1 ON DUPLICATE KEY UPDATE `model_name` = VALUES(`model_name`), `provider` = VALUES(`provider`), `endpoint_url` = VALUES(`endpoint_url`), `config_json` = VALUES(`config_json`), `description` = VALUES(`description`), `is_active` = VALUES(`is_active`), `is_default` = VALUES(`is_default`); INSERT INTO `llm_model_config` (`model_code`, `model_name`, `provider`, `endpoint_url`, `api_key`, `llm_model_name`, `llm_timeout`, `llm_temperature`, `llm_top_p`, `llm_max_tokens`, `llm_system_prompt`, `description`, `is_active`, `is_default`) SELECT `model_code`, `model_name`, `provider`, `endpoint_url`, `api_key`, COALESCE(`llm_model_name`, JSON_UNQUOTE(JSON_EXTRACT(`config_json`, '$.model_name')), 'qwen-plus'), COALESCE(`llm_timeout`, CAST(JSON_UNQUOTE(JSON_EXTRACT(`config_json`, '$.time_out')) AS UNSIGNED), 120), COALESCE(`llm_temperature`, CAST(JSON_UNQUOTE(JSON_EXTRACT(`config_json`, '$.temperature')) AS DECIMAL(5,2)), 0.70), COALESCE(`llm_top_p`, CAST(JSON_UNQUOTE(JSON_EXTRACT(`config_json`, '$.top_p')) AS DECIMAL(5,2)), 0.90), COALESCE(`llm_max_tokens`, CAST(JSON_UNQUOTE(JSON_EXTRACT(`config_json`, '$.max_tokens')) AS UNSIGNED), 2048), COALESCE(`llm_system_prompt`, JSON_UNQUOTE(JSON_EXTRACT(`config_json`, '$.system_prompt'))), `description`, `is_active`, `is_default` FROM `ai_model_configs` WHERE `model_type` = 'llm' ON DUPLICATE KEY UPDATE `model_name` = VALUES(`model_name`), `provider` = VALUES(`provider`), `endpoint_url` = VALUES(`endpoint_url`), `api_key` = VALUES(`api_key`), `llm_model_name` = VALUES(`llm_model_name`), `llm_timeout` = VALUES(`llm_timeout`), `llm_temperature` = VALUES(`llm_temperature`), `llm_top_p` = VALUES(`llm_top_p`), `llm_max_tokens` = VALUES(`llm_max_tokens`), `llm_system_prompt` = VALUES(`llm_system_prompt`), `description` = VALUES(`description`), `is_active` = VALUES(`is_active`), `is_default` = VALUES(`is_default`); INSERT INTO `audio_model_config` (`model_code`, `model_name`, `audio_scene`, `provider`, `endpoint_url`, `api_key`, `asr_model_name`, `asr_vocabulary_id`, `asr_speaker_count`, `asr_language_hints`, `asr_disfluency_removal_enabled`, `asr_diarization_enabled`, `description`, `is_active`, `is_default`) SELECT `model_code`, `model_name`, 'asr', `provider`, `endpoint_url`, `api_key`, COALESCE(`asr_model_name`, JSON_UNQUOTE(JSON_EXTRACT(`config_json`, '$.model')), 'paraformer-v2'), COALESCE(`asr_vocabulary_id`, JSON_UNQUOTE(JSON_EXTRACT(`config_json`, '$.vocabulary_id'))), COALESCE(`asr_speaker_count`, CAST(JSON_UNQUOTE(JSON_EXTRACT(`config_json`, '$.speaker_count')) AS UNSIGNED), 10), COALESCE(`asr_language_hints`, 'zh, en'), COALESCE(`asr_disfluency_removal_enabled`, 1), COALESCE(`asr_diarization_enabled`, 1), `description`, `is_active`, `is_default` FROM `ai_model_configs` WHERE `model_code` = 'audio_model' ON DUPLICATE KEY UPDATE `model_name` = VALUES(`model_name`), `provider` = VALUES(`provider`), `endpoint_url` = VALUES(`endpoint_url`), `api_key` = VALUES(`api_key`), `asr_model_name` = VALUES(`asr_model_name`), `asr_vocabulary_id` = VALUES(`asr_vocabulary_id`), `asr_speaker_count` = VALUES(`asr_speaker_count`), `asr_language_hints` = VALUES(`asr_language_hints`), `asr_disfluency_removal_enabled` = VALUES(`asr_disfluency_removal_enabled`), `asr_diarization_enabled` = VALUES(`asr_diarization_enabled`), `description` = VALUES(`description`), `is_active` = VALUES(`is_active`), `is_default` = VALUES(`is_default`); INSERT INTO `audio_model_config` (`model_code`, `model_name`, `audio_scene`, `provider`, `endpoint_url`, `api_key`, `vp_template_text`, `vp_duration_seconds`, `vp_sample_rate`, `vp_channels`, `vp_max_size_bytes`, `description`, `is_active`, `is_default`) SELECT `model_code`, `model_name`, 'voiceprint', `provider`, `endpoint_url`, `api_key`, JSON_UNQUOTE(JSON_EXTRACT(`config_json`, '$.template_text')), CAST(JSON_UNQUOTE(JSON_EXTRACT(`config_json`, '$.duration_seconds')) AS UNSIGNED), CAST(JSON_UNQUOTE(JSON_EXTRACT(`config_json`, '$.sample_rate')) AS UNSIGNED), CAST(JSON_UNQUOTE(JSON_EXTRACT(`config_json`, '$.channels')) AS UNSIGNED), CAST(JSON_UNQUOTE(JSON_EXTRACT(`config_json`, '$.voiceprint_max_size')) AS UNSIGNED), `description`, `is_active`, `is_default` FROM `ai_model_configs` WHERE `model_code` = 'voiceprint_model' ON DUPLICATE KEY UPDATE `model_name` = VALUES(`model_name`), `provider` = VALUES(`provider`), `endpoint_url` = VALUES(`endpoint_url`), `api_key` = VALUES(`api_key`), `vp_template_text` = VALUES(`vp_template_text`), `vp_duration_seconds` = VALUES(`vp_duration_seconds`), `vp_sample_rate` = VALUES(`vp_sample_rate`), `vp_channels` = VALUES(`vp_channels`), `vp_max_size_bytes` = VALUES(`vp_max_size_bytes`), `description` = VALUES(`description`), `is_active` = VALUES(`is_active`), `is_default` = VALUES(`is_default`); -- Backfill extra_config JSON for audio model configs UPDATE `audio_model_config` SET `extra_config` = CASE WHEN `audio_scene` = 'asr' THEN JSON_OBJECT( 'model', `asr_model_name`, 'vocabulary_id', `asr_vocabulary_id`, 'speaker_count', `asr_speaker_count`, 'language_hints', `asr_language_hints`, 'disfluency_removal_enabled', `asr_disfluency_removal_enabled`, 'diarization_enabled', `asr_diarization_enabled` ) WHEN `audio_scene` = 'voiceprint' THEN JSON_OBJECT( 'model', `model_name`, 'template_text', `vp_template_text`, 'duration_seconds', `vp_duration_seconds`, 'sample_rate', `vp_sample_rate`, 'channels', `vp_channels`, 'max_size_bytes', `vp_max_size_bytes` ) ELSE JSON_OBJECT() END WHERE `extra_config` IS NULL; -- ---------------------------------------------------------------------- -- 5. Hot-word migration: old hot_words -> group/item -- ---------------------------------------------------------------------- INSERT INTO `hot_word_group` (`name`, `description`, `status`) SELECT '默认热词组', '从旧 hot_words 表迁移的热词', 1 FROM DUAL WHERE EXISTS (SELECT 1 FROM `hot_words` LIMIT 1) AND NOT EXISTS (SELECT 1 FROM `hot_word_group` WHERE `name` = '默认热词组'); UPDATE `hot_word_group` g JOIN ( SELECT JSON_UNQUOTE(JSON_EXTRACT(`extension_attr`, '$.value')) AS vocab_id FROM `sys_dict_data` WHERE `dict_type` = 'system_config' AND `dict_code` = 'asr_vocabulary_id' LIMIT 1 ) p ON 1 = 1 SET g.`vocabulary_id` = p.`vocab_id`, g.`last_sync_time` = COALESCE(g.`last_sync_time`, NOW()) WHERE g.`name` = '默认热词组' AND (g.`vocabulary_id` IS NULL OR g.`vocabulary_id` = ''); INSERT INTO `hot_word_item` (`group_id`, `text`, `weight`, `lang`, `status`, `create_time`, `update_time`) SELECT g.`id`, hw.`text`, hw.`weight`, hw.`lang`, hw.`status`, hw.`create_time`, hw.`update_time` FROM `hot_words` hw JOIN `hot_word_group` g ON g.`name` = '默认热词组' LEFT JOIN `hot_word_item` i ON i.`group_id` = g.`id` AND i.`text` = hw.`text` WHERE i.`id` IS NULL; UPDATE `audio_model_config` a JOIN `hot_word_group` g ON g.`name` = '默认热词组' SET a.`hot_word_group_id` = g.`id` WHERE a.`audio_scene` = 'asr' AND a.`hot_word_group_id` IS NULL AND a.`asr_vocabulary_id` IS NOT NULL AND a.`asr_vocabulary_id` <> ''; -- ---------------------------------------------------------------------- -- 6. Rebuild menu tree for the latest backend -- ---------------------------------------------------------------------- -- Reuse legacy root rows when possible SET @has_dashboard := (SELECT COUNT(*) FROM `sys_menus` WHERE `menu_code` = 'dashboard'); UPDATE `sys_menus` SET `menu_code` = 'dashboard', `menu_name` = 'Dashboard', `menu_icon` = 'DashboardOutlined', `menu_url` = '/dashboard', `menu_type` = 'link', `parent_id` = NULL, `sort_order` = 1, `is_active` = 1, `is_visible` = 1, `description` = '管理员桌面' WHERE `menu_code` = 'account_settings' AND @has_dashboard = 0; SET @has_desktop := (SELECT COUNT(*) FROM `sys_menus` WHERE `menu_code` = 'desktop'); UPDATE `sys_menus` SET `menu_code` = 'desktop', `menu_name` = 'Desktop', `menu_icon` = 'DesktopOutlined', `menu_url` = '/dashboard', `menu_type` = 'link', `parent_id` = NULL, `sort_order` = 2, `is_active` = 1, `is_visible` = 1, `description` = '普通用户桌面' WHERE `menu_code` = 'logout' AND @has_desktop = 0; INSERT INTO `sys_menus` (`menu_code`, `menu_name`, `menu_icon`, `menu_url`, `menu_type`, `parent_id`, `menu_level`, `tree_path`, `sort_order`, `is_active`, `is_visible`, `description`) VALUES ('dashboard', 'Dashboard', 'DashboardOutlined', '/dashboard', 'link', NULL, 1, NULL, 1, 1, 1, '管理员桌面') ON DUPLICATE KEY UPDATE `menu_name` = VALUES(`menu_name`), `menu_icon` = VALUES(`menu_icon`), `menu_url` = VALUES(`menu_url`), `menu_type` = VALUES(`menu_type`), `parent_id` = VALUES(`parent_id`), `sort_order` = VALUES(`sort_order`), `is_active` = VALUES(`is_active`), `is_visible` = VALUES(`is_visible`), `description` = VALUES(`description`); INSERT INTO `sys_menus` (`menu_code`, `menu_name`, `menu_icon`, `menu_url`, `menu_type`, `parent_id`, `menu_level`, `tree_path`, `sort_order`, `is_active`, `is_visible`, `description`) VALUES ('desktop', 'Desktop', 'DesktopOutlined', '/dashboard', 'link', NULL, 1, NULL, 2, 1, 1, '普通用户桌面') ON DUPLICATE KEY UPDATE `menu_name` = VALUES(`menu_name`), `menu_icon` = VALUES(`menu_icon`), `menu_url` = VALUES(`menu_url`), `menu_type` = VALUES(`menu_type`), `parent_id` = VALUES(`parent_id`), `sort_order` = VALUES(`sort_order`), `is_active` = VALUES(`is_active`), `is_visible` = VALUES(`is_visible`), `description` = VALUES(`description`); INSERT INTO `sys_menus` (`menu_code`, `menu_name`, `menu_icon`, `menu_url`, `menu_type`, `parent_id`, `menu_level`, `tree_path`, `sort_order`, `is_active`, `is_visible`, `description`) VALUES ('platform_admin', '平台管理', 'Shield', '/admin/management/hot-word-management', 'link', NULL, 1, NULL, 3, 1, 1, '平台管理员后台'), ('system_management', '系统管理', 'Setting', '/admin/management/user-management', 'link', NULL, 1, NULL, 4, 1, 1, '系统基础配置管理(用户、权限、字典、参数)'), ('meeting_manage', '会议管理', 'CalendarOutlined', '/meetings/center', 'link', NULL, 1, NULL, 2, 1, 1, '普通用户会议菜单') ON DUPLICATE KEY UPDATE `menu_name` = VALUES(`menu_name`), `menu_icon` = VALUES(`menu_icon`), `menu_url` = VALUES(`menu_url`), `menu_type` = VALUES(`menu_type`), `parent_id` = VALUES(`parent_id`), `sort_order` = VALUES(`sort_order`), `is_active` = VALUES(`is_active`), `is_visible` = VALUES(`is_visible`), `description` = VALUES(`description`); INSERT INTO `sys_menus` (`menu_code`, `menu_name`, `menu_icon`, `menu_url`, `menu_type`, `parent_id`, `menu_level`, `tree_path`, `sort_order`, `is_active`, `is_visible`, `description`) SELECT 'prompt_management', '提示词库', 'BookText', '/prompt-management', 'link', p.`menu_id`, 2, NULL, 3, 1, 1, '管理AI提示词模版' FROM `sys_menus` p WHERE p.`menu_code` = 'platform_admin' ON DUPLICATE KEY UPDATE `menu_name` = VALUES(`menu_name`), `menu_icon` = VALUES(`menu_icon`), `menu_url` = VALUES(`menu_url`), `menu_type` = VALUES(`menu_type`), `parent_id` = VALUES(`parent_id`), `sort_order` = VALUES(`sort_order`), `is_active` = VALUES(`is_active`), `is_visible` = VALUES(`is_visible`), `description` = VALUES(`description`); INSERT INTO `sys_menus` (`menu_code`, `menu_name`, `menu_icon`, `menu_url`, `menu_type`, `parent_id`, `menu_level`, `tree_path`, `sort_order`, `is_active`, `is_visible`, `description`) SELECT 'hot_word_management', '热词管理', 'Text', '/admin/management/hot-word-management', 'link', p.`menu_id`, 2, NULL, 1, 1, 1, 'ASR 热词与同步' FROM `sys_menus` p WHERE p.`menu_code` = 'platform_admin' ON DUPLICATE KEY UPDATE `menu_name` = VALUES(`menu_name`), `menu_icon` = VALUES(`menu_icon`), `menu_url` = VALUES(`menu_url`), `menu_type` = VALUES(`menu_type`), `parent_id` = VALUES(`parent_id`), `sort_order` = VALUES(`sort_order`), `is_active` = VALUES(`is_active`), `is_visible` = VALUES(`is_visible`), `description` = VALUES(`description`); INSERT INTO `sys_menus` (`menu_code`, `menu_name`, `menu_icon`, `menu_url`, `menu_type`, `parent_id`, `menu_level`, `tree_path`, `sort_order`, `is_active`, `is_visible`, `description`) SELECT 'model_management', '模型管理', 'Appstore', '/admin/management/model-management', 'link', p.`menu_id`, 2, NULL, 2, 1, 1, '音频/LLM模型配置管理' FROM `sys_menus` p WHERE p.`menu_code` = 'platform_admin' ON DUPLICATE KEY UPDATE `menu_name` = VALUES(`menu_name`), `menu_icon` = VALUES(`menu_icon`), `menu_url` = VALUES(`menu_url`), `menu_type` = VALUES(`menu_type`), `parent_id` = VALUES(`parent_id`), `sort_order` = VALUES(`sort_order`), `is_active` = VALUES(`is_active`), `is_visible` = VALUES(`is_visible`), `description` = VALUES(`description`); INSERT INTO `sys_menus` (`menu_code`, `menu_name`, `menu_icon`, `menu_url`, `menu_type`, `parent_id`, `menu_level`, `tree_path`, `sort_order`, `is_active`, `is_visible`, `description`) SELECT 'client_management', '客户端管理', 'Smartphone', '/admin/management/client-management', 'link', p.`menu_id`, 2, NULL, 5, 1, 1, '版本、下载地址、发布状态' FROM `sys_menus` p WHERE p.`menu_code` = 'platform_admin' ON DUPLICATE KEY UPDATE `menu_name` = VALUES(`menu_name`), `menu_icon` = VALUES(`menu_icon`), `menu_url` = VALUES(`menu_url`), `menu_type` = VALUES(`menu_type`), `parent_id` = VALUES(`parent_id`), `sort_order` = VALUES(`sort_order`), `is_active` = VALUES(`is_active`), `is_visible` = VALUES(`is_visible`), `description` = VALUES(`description`); INSERT INTO `sys_menus` (`menu_code`, `menu_name`, `menu_icon`, `menu_url`, `menu_type`, `parent_id`, `menu_level`, `tree_path`, `sort_order`, `is_active`, `is_visible`, `description`) SELECT 'external_app_management', '外部应用管理', 'AppWindow', '/admin/management/external-app-management', 'link', p.`menu_id`, 2, NULL, 6, 1, 1, '外部系统入口与图标配置' FROM `sys_menus` p WHERE p.`menu_code` = 'platform_admin' ON DUPLICATE KEY UPDATE `menu_name` = VALUES(`menu_name`), `menu_icon` = VALUES(`menu_icon`), `menu_url` = VALUES(`menu_url`), `menu_type` = VALUES(`menu_type`), `parent_id` = VALUES(`parent_id`), `sort_order` = VALUES(`sort_order`), `is_active` = VALUES(`is_active`), `is_visible` = VALUES(`is_visible`), `description` = VALUES(`description`); INSERT INTO `sys_menus` (`menu_code`, `menu_name`, `menu_icon`, `menu_url`, `menu_type`, `parent_id`, `menu_level`, `tree_path`, `sort_order`, `is_active`, `is_visible`, `description`) SELECT 'terminal_management', '终端管理', 'Monitor', '/admin/management/terminal-management', 'link', p.`menu_id`, 2, NULL, 7, 1, 1, '专用设备、激活和绑定状态' FROM `sys_menus` p WHERE p.`menu_code` = 'platform_admin' ON DUPLICATE KEY UPDATE `menu_name` = VALUES(`menu_name`), `menu_icon` = VALUES(`menu_icon`), `menu_url` = VALUES(`menu_url`), `menu_type` = VALUES(`menu_type`), `parent_id` = VALUES(`parent_id`), `sort_order` = VALUES(`sort_order`), `is_active` = VALUES(`is_active`), `is_visible` = VALUES(`is_visible`), `description` = VALUES(`description`); INSERT INTO `sys_menus` (`menu_code`, `menu_name`, `menu_icon`, `menu_url`, `menu_type`, `parent_id`, `menu_level`, `tree_path`, `sort_order`, `is_active`, `is_visible`, `description`) SELECT 'user_management', '用户管理', 'Users', '/admin/management/user-management', 'link', p.`menu_id`, 2, NULL, 1, 1, 1, '账号、角色、密码重置' FROM `sys_menus` p WHERE p.`menu_code` = 'system_management' ON DUPLICATE KEY UPDATE `menu_name` = VALUES(`menu_name`), `menu_icon` = VALUES(`menu_icon`), `menu_url` = VALUES(`menu_url`), `menu_type` = VALUES(`menu_type`), `parent_id` = VALUES(`parent_id`), `sort_order` = VALUES(`sort_order`), `is_active` = VALUES(`is_active`), `is_visible` = VALUES(`is_visible`), `description` = VALUES(`description`); INSERT INTO `sys_menus` (`menu_code`, `menu_name`, `menu_icon`, `menu_url`, `menu_type`, `parent_id`, `menu_level`, `tree_path`, `sort_order`, `is_active`, `is_visible`, `description`) SELECT 'permission_management', '权限管理', 'KeyRound', '/admin/management/permission-management', 'link', p.`menu_id`, 2, NULL, 2, 1, 1, '菜单与角色授权矩阵' FROM `sys_menus` p WHERE p.`menu_code` = 'system_management' ON DUPLICATE KEY UPDATE `menu_name` = VALUES(`menu_name`), `menu_icon` = VALUES(`menu_icon`), `menu_url` = VALUES(`menu_url`), `menu_type` = VALUES(`menu_type`), `parent_id` = VALUES(`parent_id`), `sort_order` = VALUES(`sort_order`), `is_active` = VALUES(`is_active`), `is_visible` = VALUES(`is_visible`), `description` = VALUES(`description`); INSERT INTO `sys_menus` (`menu_code`, `menu_name`, `menu_icon`, `menu_url`, `menu_type`, `parent_id`, `menu_level`, `tree_path`, `sort_order`, `is_active`, `is_visible`, `description`) SELECT 'dict_management', '字典管理', 'BookMarked', '/admin/management/dict-management', 'link', p.`menu_id`, 2, NULL, 3, 1, 1, '码表、平台类型、扩展属性' FROM `sys_menus` p WHERE p.`menu_code` = 'system_management' ON DUPLICATE KEY UPDATE `menu_name` = VALUES(`menu_name`), `menu_icon` = VALUES(`menu_icon`), `menu_url` = VALUES(`menu_url`), `menu_type` = VALUES(`menu_type`), `parent_id` = VALUES(`parent_id`), `sort_order` = VALUES(`sort_order`), `is_active` = VALUES(`is_active`), `is_visible` = VALUES(`is_visible`), `description` = VALUES(`description`); INSERT INTO `sys_menus` (`menu_code`, `menu_name`, `menu_icon`, `menu_url`, `menu_type`, `parent_id`, `menu_level`, `tree_path`, `sort_order`, `is_active`, `is_visible`, `description`) SELECT 'parameter_management', '参数管理', 'Setting', '/admin/management/parameter-management', 'link', p.`menu_id`, 2, NULL, 8, 1, 1, '系统参数管理' FROM `sys_menus` p WHERE p.`menu_code` = 'system_management' ON DUPLICATE KEY UPDATE `menu_name` = VALUES(`menu_name`), `menu_icon` = VALUES(`menu_icon`), `menu_url` = VALUES(`menu_url`), `menu_type` = VALUES(`menu_type`), `parent_id` = VALUES(`parent_id`), `sort_order` = VALUES(`sort_order`), `is_active` = VALUES(`is_active`), `is_visible` = VALUES(`is_visible`), `description` = VALUES(`description`); INSERT INTO `sys_menus` (`menu_code`, `menu_name`, `menu_icon`, `menu_url`, `menu_type`, `parent_id`, `menu_level`, `tree_path`, `sort_order`, `is_active`, `is_visible`, `description`) SELECT 'permission_menu_tree', '菜单树维护', 'AppstoreAdd', '/admin/management/permission-management', 'link', p.`menu_id`, 3, NULL, 20, 1, 0, '权限管理中的菜单树维护入口(隐藏于侧栏)' FROM `sys_menus` p WHERE p.`menu_code` = 'permission_management' ON DUPLICATE KEY UPDATE `menu_name` = VALUES(`menu_name`), `menu_icon` = VALUES(`menu_icon`), `menu_url` = VALUES(`menu_url`), `menu_type` = VALUES(`menu_type`), `parent_id` = VALUES(`parent_id`), `sort_order` = VALUES(`sort_order`), `is_active` = VALUES(`is_active`), `is_visible` = VALUES(`is_visible`), `description` = VALUES(`description`); INSERT INTO `sys_menus` (`menu_code`, `menu_name`, `menu_icon`, `menu_url`, `menu_type`, `parent_id`, `menu_level`, `tree_path`, `sort_order`, `is_active`, `is_visible`, `description`) SELECT 'meeting_center', '会议中心', 'CalendarOutlined', '/meetings/center', 'link', p.`menu_id`, 2, NULL, 1, 1, 1, '普通用户会议中心' FROM `sys_menus` p WHERE p.`menu_code` = 'meeting_manage' ON DUPLICATE KEY UPDATE `menu_name` = VALUES(`menu_name`), `menu_icon` = VALUES(`menu_icon`), `menu_url` = VALUES(`menu_url`), `menu_type` = VALUES(`menu_type`), `parent_id` = VALUES(`parent_id`), `sort_order` = VALUES(`sort_order`), `is_active` = VALUES(`is_active`), `is_visible` = VALUES(`is_visible`), `description` = VALUES(`description`); INSERT INTO `sys_menus` (`menu_code`, `menu_name`, `menu_icon`, `menu_url`, `menu_type`, `parent_id`, `menu_level`, `tree_path`, `sort_order`, `is_active`, `is_visible`, `description`) SELECT 'prompt_config', '提示词配置', 'Book', '/prompt-config', 'link', p.`menu_id`, 2, NULL, 2, 1, 1, '用户可配置启用提示词与排序' FROM `sys_menus` p WHERE p.`menu_code` = 'meeting_manage' ON DUPLICATE KEY UPDATE `menu_name` = VALUES(`menu_name`), `menu_icon` = VALUES(`menu_icon`), `menu_url` = VALUES(`menu_url`), `menu_type` = VALUES(`menu_type`), `parent_id` = VALUES(`parent_id`), `sort_order` = VALUES(`sort_order`), `is_active` = VALUES(`is_active`), `is_visible` = VALUES(`is_visible`), `description` = VALUES(`description`); INSERT INTO `sys_menus` (`menu_code`, `menu_name`, `menu_icon`, `menu_url`, `menu_type`, `parent_id`, `menu_level`, `tree_path`, `sort_order`, `is_active`, `is_visible`, `description`) SELECT 'personal_prompt_library', '个人提示词仓库', 'ReadOutlined', '/personal-prompts', 'link', p.`menu_id`, 2, NULL, 3, 0, 1, '普通用户个人提示词仓库' FROM `sys_menus` p WHERE p.`menu_code` = 'meeting_manage' ON DUPLICATE KEY UPDATE `menu_name` = VALUES(`menu_name`), `menu_icon` = VALUES(`menu_icon`), `menu_url` = VALUES(`menu_url`), `menu_type` = VALUES(`menu_type`), `parent_id` = VALUES(`parent_id`), `sort_order` = VALUES(`sort_order`), `is_active` = VALUES(`is_active`), `is_visible` = VALUES(`is_visible`), `description` = VALUES(`description`); -- Menu tree metadata (supports current depth 3) UPDATE `sys_menus` SET `menu_level` = 1, `tree_path` = CONCAT('/', `menu_id`) WHERE `parent_id` IS NULL; UPDATE `sys_menus` c JOIN `sys_menus` p ON c.`parent_id` = p.`menu_id` SET c.`menu_level` = p.`menu_level` + 1, c.`tree_path` = CONCAT(p.`tree_path`, '/', c.`menu_id`); UPDATE `sys_menus` c JOIN `sys_menus` p ON c.`parent_id` = p.`menu_id` SET c.`menu_level` = p.`menu_level` + 1, c.`tree_path` = CONCAT(p.`tree_path`, '/', c.`menu_id`); -- ---------------------------------------------------------------------- -- 7. Align role-menu permissions for latest menu model -- ---------------------------------------------------------------------- -- Admin gets all active menus INSERT IGNORE INTO `sys_role_menu_permissions` (`role_id`, `menu_id`, `granted_by`, `granted_at`) SELECT 1, m.`menu_id`, 1, NOW() FROM `sys_menus` m WHERE m.`is_active` = 1; -- Normal user only gets desktop + meeting menus + prompt config DELETE p FROM `sys_role_menu_permissions` p JOIN `sys_menus` m ON m.`menu_id` = p.`menu_id` WHERE p.`role_id` = 2 AND m.`menu_code` NOT IN ('desktop', 'meeting_manage', 'meeting_center', 'prompt_config'); INSERT IGNORE INTO `sys_role_menu_permissions` (`role_id`, `menu_id`, `granted_by`, `granted_at`) SELECT 2, m.`menu_id`, 1, NOW() FROM `sys_menus` m WHERE m.`menu_code` IN ('desktop', 'meeting_manage', 'meeting_center', 'prompt_config') AND m.`is_active` = 1; -- ---------------------------------------------------------------------- -- 8. Recreate compatibility views -- ---------------------------------------------------------------------- DROP VIEW IF EXISTS `users`; DROP VIEW IF EXISTS `roles`; DROP VIEW IF EXISTS `menus`; DROP VIEW IF EXISTS `role_menu_permissions`; DROP VIEW IF EXISTS `dict_data`; DROP VIEW IF EXISTS `system_parameters`; CREATE VIEW `users` AS SELECT * FROM `sys_users`; CREATE VIEW `roles` AS SELECT * FROM `sys_roles`; CREATE VIEW `menus` AS SELECT * FROM `sys_menus`; CREATE VIEW `role_menu_permissions` AS SELECT * FROM `sys_role_menu_permissions`; CREATE VIEW `dict_data` AS SELECT * FROM `sys_dict_data`; CREATE VIEW `system_parameters` AS SELECT * FROM `sys_system_parameters`;