-- Admin Server schema changes for the GedaDa management-console adaptation. -- Target: MySQL 8.0+, control database `hotgo`. -- Scope: control-plane configuration only. Do not create tenant business data here. -- Review before execution. DDL statements auto-commit in MySQL. SET NAMES utf8mb4; USE `hotgo`; -- ============================================================ -- 1. User policy groups. -- status: 1=enabled, 2=disabled. -- policy_json action values: allow, deny, challenge, audit_only. -- Friend entry values: search, profile_card, group_member, qrcode, -- invite_auto_add, recommendation. -- ============================================================ CREATE TABLE IF NOT EXISTS `im_user_policy_groups` ( `id` bigint NOT NULL AUTO_INCREMENT, `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '0 means global default', `group_code` varchar(64) NOT NULL COMMENT 'Stable policy group code', `group_name` varchar(128) NOT NULL COMMENT 'Policy group name', `priority` int NOT NULL DEFAULT 0 COMMENT 'Higher value has higher priority', `status` tinyint NOT NULL DEFAULT 1 COMMENT '1 enabled, 2 disabled', `policy_json` json NOT NULL COMMENT 'Login, friend, message, group and file policies', `ip_whitelist` json DEFAULT NULL COMMENT 'IPv4/IPv6 or CIDR list', `ip_blacklist` json DEFAULT NULL COMMENT 'IPv4/IPv6 or CIDR list', `version` int unsigned NOT NULL DEFAULT 1 COMMENT 'Optimistic-lock/config version', `remark` varchar(500) NOT NULL DEFAULT '', `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `created_by` bigint NOT NULL DEFAULT 0, `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), `updated_by` bigint NOT NULL DEFAULT 0, `deleted_at` datetime(3) DEFAULT NULL, `deleted_by` bigint DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_im_user_policy_group_code` (`tenant_id`, `group_code`), KEY `idx_im_user_policy_group_list` (`tenant_id`, `status`, `priority`, `id`), KEY `idx_im_user_policy_group_deleted` (`deleted_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='IM user policy groups'; CREATE TABLE IF NOT EXISTS `im_user_policy_group_members` ( `id` bigint NOT NULL AUTO_INCREMENT, `tenant_id` bigint NOT NULL DEFAULT 0, `group_id` bigint NOT NULL, `user_id` bigint NOT NULL COMMENT 'User ID in the tenant database', `status` tinyint NOT NULL DEFAULT 1 COMMENT '1 enabled, 2 disabled', `valid_from` datetime(3) DEFAULT NULL, `valid_until` datetime(3) DEFAULT NULL, `remark` varchar(500) NOT NULL DEFAULT '', `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `created_by` bigint NOT NULL DEFAULT 0, `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), `updated_by` bigint NOT NULL DEFAULT 0, `deleted_at` datetime(3) DEFAULT NULL, `deleted_by` bigint DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_im_user_policy_group_member` (`group_id`, `user_id`), KEY `idx_im_user_policy_member_user` (`tenant_id`, `user_id`, `status`), KEY `idx_im_user_policy_member_validity` (`status`, `valid_from`, `valid_until`), KEY `idx_im_user_policy_member_deleted` (`deleted_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='IM user-to-policy-group assignments'; -- ============================================================ -- 2. Sensitive word dictionary. -- match_type: exact, contains, regex, pinyin. -- action: replace, block, audit_only, manual_review. -- risk_level: low, medium, high. -- scene_json values include private_message, group_message, user_profile, -- group_profile, feedback, report. -- ============================================================ CREATE TABLE IF NOT EXISTS `im_sensitive_word_groups` ( `id` bigint NOT NULL AUTO_INCREMENT, `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '0 means global dictionary', `group_code` varchar(64) NOT NULL, `group_name` varchar(128) NOT NULL, `priority` int NOT NULL DEFAULT 0, `status` tinyint NOT NULL DEFAULT 1 COMMENT '1 enabled, 2 disabled', `remark` varchar(500) NOT NULL DEFAULT '', `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `created_by` bigint NOT NULL DEFAULT 0, `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), `updated_by` bigint NOT NULL DEFAULT 0, `deleted_at` datetime(3) DEFAULT NULL, `deleted_by` bigint DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_im_sensitive_word_group_code` (`tenant_id`, `group_code`), KEY `idx_im_sensitive_word_group_list` (`tenant_id`, `status`, `priority`, `id`), KEY `idx_im_sensitive_word_group_deleted` (`deleted_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='IM sensitive word groups'; CREATE TABLE IF NOT EXISTS `im_sensitive_words` ( `id` bigint NOT NULL AUTO_INCREMENT, `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '0 means global dictionary', `group_id` bigint NOT NULL, `word` varchar(255) NOT NULL, `word_hash` char(64) NOT NULL COMMENT 'SHA-256 of normalized word', `match_type` varchar(24) NOT NULL DEFAULT 'contains', `scene_json` json NOT NULL COMMENT 'Enabled scenes', `action` varchar(24) NOT NULL DEFAULT 'block', `replacement` varchar(255) NOT NULL DEFAULT '', `risk_level` varchar(16) NOT NULL DEFAULT 'medium', `priority` int NOT NULL DEFAULT 0, `status` tinyint NOT NULL DEFAULT 1 COMMENT '1 enabled, 2 disabled', `remark` varchar(500) NOT NULL DEFAULT '', `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `created_by` bigint NOT NULL DEFAULT 0, `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), `updated_by` bigint NOT NULL DEFAULT 0, `deleted_at` datetime(3) DEFAULT NULL, `deleted_by` bigint DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_im_sensitive_word_normalized` (`tenant_id`, `group_id`, `word_hash`), KEY `idx_im_sensitive_word_list` (`tenant_id`, `status`, `risk_level`, `id`), KEY `idx_im_sensitive_word_group` (`group_id`, `status`, `priority`), KEY `idx_im_sensitive_word_deleted` (`deleted_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='IM sensitive words'; -- ============================================================ -- 3. Versioned security policy. -- publish_status: draft, published, archived, failed. -- The latest published row is the source of truth. The summary switches in -- im_tenant_config are updated in the same application transaction on publish. -- ============================================================ CREATE TABLE IF NOT EXISTS `im_security_policy` ( `id` bigint NOT NULL AUTO_INCREMENT, `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '0 means global fallback policy', `policy_version` int unsigned NOT NULL, `policy_name` varchar(128) NOT NULL DEFAULT '', `publish_status` varchar(16) NOT NULL DEFAULT 'draft', `password_policy` json DEFAULT NULL, `login_rate_limit` json DEFAULT NULL, `sensitive_word_policy` json DEFAULT NULL, `file_review_policy` json DEFAULT NULL, `realname_policy` json DEFAULT NULL, `risk_challenge_policy` json DEFAULT NULL, `global_friend_enabled` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Backend master switch for every add-friend entry', `friend_disabled_policy` json DEFAULT NULL COMMENT 'Profile masking, QR and group-member restrictions', `message_curfew_enabled` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Backend master switch for message curfew', `remark` varchar(500) NOT NULL DEFAULT '', `published_at` datetime(3) DEFAULT NULL, `published_by` bigint DEFAULT NULL, `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `created_by` bigint NOT NULL DEFAULT 0, `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), `updated_by` bigint NOT NULL DEFAULT 0, `deleted_at` datetime(3) DEFAULT NULL, `deleted_by` bigint DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_im_security_policy_version` (`tenant_id`, `policy_version`), KEY `idx_im_security_policy_publish` (`tenant_id`, `publish_status`, `published_at`), KEY `idx_im_security_policy_deleted` (`deleted_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Versioned IM security policies'; CREATE TABLE IF NOT EXISTS `im_message_curfew_rules` ( `id` bigint NOT NULL AUTO_INCREMENT, `tenant_id` bigint NOT NULL DEFAULT 0, `policy_id` bigint NOT NULL, `rule_code` varchar(64) NOT NULL, `rule_name` varchar(128) NOT NULL, `date_type` varchar(16) NOT NULL DEFAULT 'daily' COMMENT 'daily, weekday, weekend, custom', `custom_dates` json DEFAULT NULL COMMENT 'YYYY-MM-DD values for custom date type', `weekdays` json DEFAULT NULL COMMENT 'ISO weekdays 1 through 7', `timezone` varchar(64) NOT NULL DEFAULT 'Asia/Shanghai', `start_time` time NOT NULL, `end_time` time NOT NULL COMMENT 'Less than start_time means crossing midnight', `scope` varchar(24) NOT NULL DEFAULT 'private_message' COMMENT 'private_message or group_message', `whitelist_user_ids` json DEFAULT NULL, `whitelist_group_ids` json DEFAULT NULL, `message` varchar(255) NOT NULL DEFAULT '当前时段暂不能发送消息', `status` tinyint NOT NULL DEFAULT 1 COMMENT '1 enabled, 2 disabled', `sort` int NOT NULL DEFAULT 0, `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `created_by` bigint NOT NULL DEFAULT 0, `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), `updated_by` bigint NOT NULL DEFAULT 0, `deleted_at` datetime(3) DEFAULT NULL, `deleted_by` bigint DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_im_message_curfew_rule` (`policy_id`, `rule_code`), KEY `idx_im_message_curfew_policy` (`tenant_id`, `policy_id`, `status`, `sort`), KEY `idx_im_message_curfew_deleted` (`deleted_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Message curfew rules'; CREATE TABLE IF NOT EXISTS `im_security_ip_rules` ( `id` bigint NOT NULL AUTO_INCREMENT, `tenant_id` bigint NOT NULL DEFAULT 0, `rule_type` varchar(16) NOT NULL COMMENT 'whitelist or blacklist', `ip_cidr` varchar(64) NOT NULL COMMENT 'IPv4, IPv6 or CIDR', `action` varchar(24) NOT NULL DEFAULT 'block' COMMENT 'allow, challenge, block, audit_only', `scene_json` json NOT NULL COMMENT 'login, register, message, upload and other scenes', `risk_level` varchar(16) NOT NULL DEFAULT 'high', `status` tinyint NOT NULL DEFAULT 1 COMMENT '1 enabled, 2 disabled', `expires_at` datetime(3) DEFAULT NULL, `remark` varchar(500) NOT NULL DEFAULT '', `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `created_by` bigint NOT NULL DEFAULT 0, `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), `updated_by` bigint NOT NULL DEFAULT 0, `deleted_at` datetime(3) DEFAULT NULL, `deleted_by` bigint DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_im_security_ip_rule` (`tenant_id`, `rule_type`, `ip_cidr`), KEY `idx_im_security_ip_rule_list` (`tenant_id`, `status`, `rule_type`, `id`), KEY `idx_im_security_ip_rule_expiry` (`status`, `expires_at`), KEY `idx_im_security_ip_rule_deleted` (`deleted_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='IM IP whitelist and blacklist rules'; -- ============================================================ -- 4. Lightweight published-policy summary in im_tenant_config. -- These columns are read optimizations, not the policy source of truth. -- The conditional ALTER blocks are safe to rerun on MySQL 8.0. -- ============================================================ SET @im_sql = IF( (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_tenant_config' AND COLUMN_NAME = 'default_policy_group_id') = 0, 'ALTER TABLE `im_tenant_config` ADD COLUMN `default_policy_group_id` BIGINT DEFAULT NULL COMMENT ''Default user policy group'' AFTER `max_group_members`', 'SELECT 1' ); PREPARE im_stmt FROM @im_sql; EXECUTE im_stmt; DEALLOCATE PREPARE im_stmt; SET @im_sql = IF( (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_tenant_config' AND COLUMN_NAME = 'security_policy_version') = 0, 'ALTER TABLE `im_tenant_config` ADD COLUMN `security_policy_version` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT ''Published security policy version'' AFTER `default_policy_group_id`', 'SELECT 1' ); PREPARE im_stmt FROM @im_sql; EXECUTE im_stmt; DEALLOCATE PREPARE im_stmt; SET @im_sql = IF( (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_tenant_config' AND COLUMN_NAME = 'global_friend_enabled') = 0, 'ALTER TABLE `im_tenant_config` ADD COLUMN `global_friend_enabled` TINYINT(1) NOT NULL DEFAULT 1 COMMENT ''Published global friend switch'' AFTER `security_policy_version`', 'SELECT 1' ); PREPARE im_stmt FROM @im_sql; EXECUTE im_stmt; DEALLOCATE PREPARE im_stmt; SET @im_sql = IF( (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_tenant_config' AND COLUMN_NAME = 'message_curfew_enabled') = 0, 'ALTER TABLE `im_tenant_config` ADD COLUMN `message_curfew_enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''Published message curfew switch'' AFTER `global_friend_enabled`', 'SELECT 1' ); PREPARE im_stmt FROM @im_sql; EXECUTE im_stmt; DEALLOCATE PREPARE im_stmt; -- ============================================================ -- 5. Invitation-code compatibility for the 6-digit target format. -- Existing 5-letter codes remain stored and valid during the application -- rollout. New code generation must switch to exactly six ASCII digits, while -- Admin and IM Server temporarily accept both legacy 5-letter and new 6-digit -- values. max_uses=0 preserves the current unlimited-use behavior. -- ============================================================ SET @im_sql = IF( (SELECT CHARACTER_MAXIMUM_LENGTH FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_registration_invite_codes' AND COLUMN_NAME = 'code') = 5, 'ALTER TABLE `im_registration_invite_codes` MODIFY COLUMN `code` CHAR(6) CHARACTER SET ascii COLLATE ascii_bin NOT NULL', 'SELECT 1' ); PREPARE im_stmt FROM @im_sql; EXECUTE im_stmt; DEALLOCATE PREPARE im_stmt; SET @im_sql = IF( (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_registration_invite_codes' AND COLUMN_NAME = 'max_uses') = 0, 'ALTER TABLE `im_registration_invite_codes` ADD COLUMN `max_uses` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT ''0 means unlimited'' AFTER `expires_at`', 'SELECT 1' ); PREPARE im_stmt FROM @im_sql; EXECUTE im_stmt; DEALLOCATE PREPARE im_stmt; SET @im_sql = IF( (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_registration_invite_batches' AND COLUMN_NAME = 'max_uses_per_code') = 0, 'ALTER TABLE `im_registration_invite_batches` ADD COLUMN `max_uses_per_code` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT ''0 means unlimited'' AFTER `quantity`', 'SELECT 1' ); PREPARE im_stmt FROM @im_sql; EXECUTE im_stmt; DEALLOCATE PREPARE im_stmt; -- ============================================================ -- 6. Safe global fallback policy. -- Tenant-specific published policies override tenant_id=0. -- This keeps existing behavior permissive until an administrator publishes -- the first tenant policy. -- ============================================================ INSERT INTO `im_security_policy` ( `tenant_id`, `policy_version`, `policy_name`, `publish_status`, `global_friend_enabled`, `friend_disabled_policy`, `message_curfew_enabled`, `remark`, `published_at`, `published_by`, `created_by`, `updated_by` ) SELECT 0, 1, '全局默认安全策略', 'published', 1, JSON_OBJECT( 'hide_profile', TRUE, 'block_qrcode', TRUE, 'block_group_member_entry', TRUE, 'blocked_entries', JSON_ARRAY( 'search', 'profile_card', 'group_member', 'qrcode', 'invite_auto_add', 'recommendation' ), 'message', '当前时段暂不允许添加好友' ), 0, '系统初始化:保持原有好友和消息行为', CURRENT_TIMESTAMP(3), 0, 0, 0 WHERE NOT EXISTS ( SELECT 1 FROM `im_security_policy` WHERE `tenant_id` = 0 AND `publish_status` = 'published' AND `deleted_at` IS NULL ); UPDATE `im_security_policy` SET `friend_disabled_policy` = JSON_SET( `friend_disabled_policy`, '$.blocked_entries', JSON_ARRAY('search', 'profile_card', 'group_member', 'qrcode', 'invite_auto_add', 'recommendation') ) WHERE `tenant_id` = 0 AND `publish_status` = 'published' AND `deleted_at` IS NULL AND JSON_CONTAINS_PATH(`friend_disabled_policy`, 'one', '$.blocked_entries') = 0; -- Post-deployment checks (read-only): -- SELECT tenant_id, group_code, status, priority FROM im_user_policy_groups; -- SELECT tenant_id, policy_version, publish_status, -- global_friend_enabled, message_curfew_enabled -- FROM im_security_policy ORDER BY tenant_id, policy_version DESC; -- SELECT tenant_id, policy_id, rule_code, date_type, start_time, end_time, scope -- FROM im_message_curfew_rules WHERE deleted_at IS NULL;