-- IM registration SMS switch and registration invite admin migration. -- This file is idempotent for MySQL deployments using HotGo's default hg_ prefix. -- im_tenant is the only tenant registry. Admin identities reference it directly. SET @tenant_column_exists := ( SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'hg_admin_member' AND COLUMN_NAME = 'tenant_id' ); SET @ddl := IF( @tenant_column_exists = 0, 'ALTER TABLE hg_admin_member ADD COLUMN tenant_id BIGINT NULL COMMENT ''IM tenant ID'' AFTER id', 'SELECT 1' ); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- Preserve an existing deployment's old mapping before removing the redundant table. SET @binding_table_exists := ( SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_admin_tenant_binding' ); SET @dml := IF( @binding_table_exists > 0, 'UPDATE hg_admin_member AS m JOIN im_admin_tenant_binding AS b ON b.hotgo_tenant_id = m.id SET m.tenant_id = b.im_tenant_id WHERE b.status = 1 AND m.tenant_id IS NULL', 'SELECT 1' ); PREPARE stmt FROM @dml; EXECUTE stmt; DEALLOCATE PREPARE stmt; DROP TABLE IF EXISTS im_admin_tenant_binding; SET @tenant_index_exists := ( SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'hg_admin_member' AND INDEX_NAME = 'idx_hg_admin_member_tenant' ); SET @ddl := IF( @tenant_index_exists = 0, 'ALTER TABLE hg_admin_member ADD INDEX idx_hg_admin_member_tenant (tenant_id)', 'SELECT 1' ); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @tenant_fk_exists := ( SELECT COUNT(*) FROM information_schema.REFERENTIAL_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = DATABASE() AND TABLE_NAME = 'hg_admin_member' AND CONSTRAINT_NAME = 'fk_hg_admin_member_im_tenant' ); SET @ddl := IF( @tenant_fk_exists = 0, 'ALTER TABLE hg_admin_member ADD CONSTRAINT fk_hg_admin_member_im_tenant FOREIGN KEY (tenant_id) REFERENCES im_tenant(id)', 'SELECT 1' ); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; CREATE TABLE IF NOT EXISTS im_admin_operation_audit ( id BIGINT NOT NULL AUTO_INCREMENT, request_id VARCHAR(64) NOT NULL, tenant_id BIGINT NULL, operator_id BIGINT NOT NULL, action VARCHAR(64) NOT NULL, target_type VARCHAR(32) NOT NULL, target_id BIGINT NULL, result VARCHAR(16) NOT NULL, ip VARCHAR(64) NOT NULL DEFAULT '', detail_json JSON NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY (id), KEY idx_im_admin_audit_tenant_time (tenant_id, created_at), KEY idx_im_admin_audit_operator_time (operator_id, created_at), UNIQUE KEY uk_im_admin_audit_request_action (request_id, action) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Invitation definitions belong to the HotGo control database. Tenant -- databases retain only im_registration_invite_uses. CREATE TABLE IF NOT EXISTS im_registration_invite_batches ( id BIGINT NOT NULL, tenant_id BIGINT NOT NULL, quantity INT UNSIGNED NOT NULL, max_uses_per_code INT UNSIGNED NOT NULL DEFAULT 0, idempotency_key VARCHAR(128) NOT NULL, expires_at DATETIME(3) NOT NULL, created_by BIGINT NOT NULL, remark VARCHAR(255) NOT NULL DEFAULT '', created_at DATETIME(3) NOT NULL, updated_at DATETIME(3) NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_im_registration_invite_batch_tenant_idempotency (tenant_id, idempotency_key), KEY idx_im_registration_invite_batch_tenant_created (tenant_id, created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS im_registration_invite_codes ( id BIGINT NOT NULL, tenant_id BIGINT NOT NULL, batch_id BIGINT NULL, code CHAR(5) CHARACTER SET ascii COLLATE ascii_bin NOT NULL, status VARCHAR(16) NOT NULL, expires_at DATETIME(3) NOT NULL, max_uses INT UNSIGNED NOT NULL DEFAULT 0, bound_user_id BIGINT NULL, bound_user_label VARCHAR(128) NOT NULL DEFAULT '', usage_count INT UNSIGNED NOT NULL DEFAULT 0, last_used_at DATETIME(3) NULL, created_by BIGINT NOT NULL, remark VARCHAR(255) NOT NULL DEFAULT '', created_at DATETIME(3) NOT NULL, updated_at DATETIME(3) NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_im_registration_invite_code_tenant (tenant_id, code), KEY idx_im_registration_invite_tenant_bound_user (tenant_id, bound_user_id), KEY idx_im_registration_invite_tenant_status_expires (tenant_id, status, expires_at), KEY idx_im_registration_invite_tenant_batch (tenant_id, batch_id), KEY idx_im_registration_invite_tenant_created (tenant_id, created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT INTO hg_sys_config (`group`, `name`, `type`, `key`, `value`, `default_value`, `sort`, `tip`, `is_default`, `status`, `created_at`, `updated_at`) SELECT 'im', '注册短信发送', 'bool', 'register_sms_enabled', '1', '1', 10, '控制 IM 注册验证码是否真实发送', 1, 1, NOW(), NOW() WHERE NOT EXISTS ( SELECT 1 FROM hg_sys_config WHERE `group` = 'im' AND `key` = 'register_sms_enabled' ); INSERT INTO hg_admin_menu (pid, level, tree, title, name, path, icon, type, redirect, permissions, component, always_show, is_root, is_frame, keep_alive, hidden, affix, sort, status, created_at, updated_at) SELECT 0, 1, '', 'IM管理', 'imManager', '/im', 'AppstoreOutlined', 1, '', '', 'LAYOUT', 0, 0, 1, 0, 0, 0, 125, 1, NOW(), NOW() WHERE NOT EXISTS (SELECT 1 FROM hg_admin_menu WHERE name = 'imManager'); SET @imRoot := (SELECT id FROM hg_admin_menu WHERE name = 'imManager' LIMIT 1); UPDATE hg_admin_menu SET tree = CONCAT('tr_', id, ' '), component = 'LAYOUT' WHERE id = @imRoot; INSERT INTO hg_admin_menu (pid, level, tree, title, name, path, icon, type, redirect, permissions, component, always_show, is_root, is_frame, keep_alive, hidden, affix, sort, status, created_at, updated_at) SELECT @imRoot, 2, '', '注册邀请码', 'imRegistrationInviteIndex', 'registrationInvite', '', 2, '', '/imInvite/list', '/im/registrationInvite/index', 0, 0, 1, 0, 0, 0, 70, 1, NOW(), NOW() WHERE NOT EXISTS (SELECT 1 FROM hg_admin_menu WHERE name = 'imRegistrationInviteIndex'); SET @invitePage := (SELECT id FROM hg_admin_menu WHERE name = 'imRegistrationInviteIndex' LIMIT 1); UPDATE hg_admin_menu SET tree = CONCAT('tr_', id, ' tr_', @imRoot, ' ') WHERE id = @invitePage; SET @imTenantPage := (SELECT id FROM hg_admin_menu WHERE name = 'imTenantIndex' LIMIT 1); INSERT INTO hg_admin_menu (pid, level, tree, title, name, path, icon, type, permissions, component, hidden, sort, status, created_at, updated_at) SELECT @imTenantPage, 4, CONCAT('tr_', @imTenantPage, ' '), '租户用量与健康', 'imTenantUsage', '', '', 3, '/imTenant/usage', '', 1, 40, 1, NOW(), NOW() WHERE @imTenantPage IS NOT NULL AND NOT EXISTS (SELECT 1 FROM hg_admin_menu WHERE name = 'imTenantUsage'); INSERT INTO hg_admin_menu (pid, level, tree, title, name, path, icon, type, permissions, component, hidden, sort, status, created_at, updated_at) SELECT @imTenantPage, 4, CONCAT('tr_', @imTenantPage, ' '), title, name, '', '', 3, permissions, '', 1, sort, 1, NOW(), NOW() FROM ( SELECT '租户删除前检查' title, 'imTenantDeletionCheck' name, '/imTenant/deletionCheck' permissions, 50 sort UNION ALL SELECT '租户进入删除流程', 'imTenantEnterDeleting', '/imTenant/enterDeleting', 60 UNION ALL SELECT '恢复租户', 'imTenantRestore', '/imTenant/restore', 70 ) p WHERE @imTenantPage IS NOT NULL AND NOT EXISTS (SELECT 1 FROM hg_admin_menu m WHERE m.name = p.name); INSERT INTO hg_admin_menu (pid, level, tree, title, name, path, icon, type, permissions, component, sort, status, created_at, updated_at) SELECT @invitePage, 3, CONCAT('tr_', @invitePage, ' tr_', @imRoot, ' '), title, name, '', '', 3, permissions, '', sort, 1, NOW(), NOW() FROM ( SELECT '生成邀请码' title, 'imInviteCreate' name, '/imInvite/create' permissions, 10 sort UNION ALL SELECT '批量生成邀请码', 'imInviteBatchCreate', '/imInvite/batchCreate', 20 UNION ALL SELECT '批次详情', 'imInviteBatchView', '/imInvite/batchView', 30 UNION ALL SELECT '撤销邀请码', 'imInviteRevoke', '/imInvite/revoke', 40 UNION ALL SELECT '绑定用户', 'imInviteBindUser', '/imInvite/bindUser', 50 UNION ALL SELECT '批量撤销邀请码', 'imInviteBatchRevoke', '/imInvite/batchRevoke', 60 UNION ALL SELECT '导出邀请码', 'imInviteExport', '/imInvite/export', 70 ) p WHERE NOT EXISTS (SELECT 1 FROM hg_admin_menu m WHERE m.name = p.name); SET @systemConfigPage := (SELECT id FROM hg_admin_menu WHERE name = 'system_config' LIMIT 1); INSERT INTO hg_admin_menu (pid, level, tree, title, name, path, icon, type, permissions, component, hidden, sort, status, created_at, updated_at) SELECT @systemConfigPage, 3, CONCAT('tr_', @systemConfigPage, ' '), title, name, '', '', 3, permissions, '', 1, sort, 1, NOW(), NOW() FROM ( SELECT '查看IM注册配置' title, 'imRegistrationConfigGet' name, '/imRegistration/config/get' permissions, 80 sort UNION ALL SELECT '更新IM注册配置', 'imRegistrationConfigUpdate', '/imRegistration/config/update', 90 ) p WHERE @systemConfigPage IS NOT NULL AND NOT EXISTS (SELECT 1 FROM hg_admin_menu m WHERE m.name = p.name); SET @imUserPage := (SELECT id FROM hg_admin_menu WHERE name = 'imUserIndex' LIMIT 1); INSERT INTO hg_admin_menu (pid, level, tree, title, name, path, icon, type, permissions, component, hidden, sort, status, created_at, updated_at) SELECT @imUserPage, 4, CONCAT('tr_', @imUserPage, ' '), '重置用户密码', 'imUserResetPassword', '', '', 3, '/imUser/resetPassword', '', 1, 20, 1, NOW(), NOW() WHERE @imUserPage IS NOT NULL AND NOT EXISTS (SELECT 1 FROM hg_admin_menu WHERE name = 'imUserResetPassword'); INSERT INTO hg_admin_menu (pid, level, tree, title, name, path, icon, type, permissions, component, hidden, sort, status, created_at, updated_at) SELECT @imUserPage, 4, CONCAT('tr_', @imUserPage, ' '), '用户设备列表', 'imUserDevices', '', '', 3, '/imUser/devices', '', 1, 30, 1, NOW(), NOW() WHERE @imUserPage IS NOT NULL AND NOT EXISTS (SELECT 1 FROM hg_admin_menu WHERE name = 'imUserDevices'); INSERT INTO hg_admin_menu (pid, level, tree, title, name, path, icon, type, permissions, component, hidden, sort, status, created_at, updated_at) SELECT @imUserPage, 4, CONCAT('tr_', @imUserPage, ' '), '撤销用户会话', 'imUserRevokeSessions', '', '', 3, '/imUser/revokeSessions', '', 1, 40, 1, NOW(), NOW() WHERE @imUserPage IS NOT NULL AND NOT EXISTS (SELECT 1 FROM hg_admin_menu WHERE name = 'imUserRevokeSessions'); INSERT INTO hg_admin_menu (pid, level, tree, title, name, path, icon, type, permissions, component, hidden, sort, status, created_at, updated_at) SELECT @imUserPage, 4, CONCAT('tr_', @imUserPage, ' '), '更新用户状态', 'imUserStatus', '', '', 3, '/imUser/status', '', 1, 50, 1, NOW(), NOW() WHERE @imUserPage IS NOT NULL AND NOT EXISTS (SELECT 1 FROM hg_admin_menu WHERE name = 'imUserStatus');