-- 可重复执行,兼容已有设备绑定表。任务及设备明细长期保存。 CREATE TABLE IF NOT EXISTS member_push_device ( id BIGINT NOT NULL AUTO_INCREMENT, member_id BIGINT NOT NULL, registration_id VARCHAR(64) NOT NULL, alias VARCHAR(64) NULL, platform VARCHAR(16) NOT NULL, provider VARCHAR(16) NOT NULL DEFAULT 'jpush', app_key VARCHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT '', tenant_id BIGINT NOT NULL DEFAULT 0, creator VARCHAR(64) NULL DEFAULT '', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updater VARCHAR(64) NULL DEFAULT '', update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted BIT(1) NOT NULL DEFAULT b'0', PRIMARY KEY (id), UNIQUE KEY uk_registration_id (registration_id), KEY idx_member_id (member_id), KEY idx_app_provider_id (app_key, provider, id, tenant_id), KEY idx_app_member_provider (app_key, member_id, provider, tenant_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='会员推送设备'; -- 使用同一个数据库连接顺序执行。历史设备保留未知 AppKey,等待新版客户端重新绑定。 SET @member_push_schema_ddl = IF( (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'member_push_device' AND COLUMN_NAME = 'app_key') = 0, 'ALTER TABLE member_push_device ADD COLUMN app_key VARCHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT '''' AFTER provider', 'SELECT 1'); PREPARE member_push_schema_stmt FROM @member_push_schema_ddl; EXECUTE member_push_schema_stmt; DEALLOCATE PREPARE member_push_schema_stmt; SET @member_push_schema_ddl = IF( (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'member_push_device' AND INDEX_NAME = 'idx_app_provider_id') = 0, 'ALTER TABLE member_push_device ADD INDEX idx_app_provider_id (app_key, provider, id, tenant_id)', 'SELECT 1'); PREPARE member_push_schema_stmt FROM @member_push_schema_ddl; EXECUTE member_push_schema_stmt; DEALLOCATE PREPARE member_push_schema_stmt; SET @member_push_schema_ddl = IF( (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'member_push_device' AND INDEX_NAME = 'idx_app_member_provider') = 0, 'ALTER TABLE member_push_device ADD INDEX idx_app_member_provider (app_key, member_id, provider, tenant_id)', 'SELECT 1'); PREPARE member_push_schema_stmt FROM @member_push_schema_ddl; EXECUTE member_push_schema_stmt; DEALLOCATE PREPARE member_push_schema_stmt; CREATE TABLE IF NOT EXISTS member_push_task ( id BIGINT NOT NULL AUTO_INCREMENT, request_key VARCHAR(160) NOT NULL, request_hash VARCHAR(64) NOT NULL, source VARCHAR(16) NOT NULL, target_type VARCHAR(16) NOT NULL, member_ids MEDIUMTEXT NOT NULL, operator_id BIGINT NULL, message_id BIGINT NULL, sender_id BIGINT NULL, receiver_id BIGINT NULL, title VARCHAR(160) NOT NULL, content VARCHAR(500) NOT NULL, status VARCHAR(24) NOT NULL, audience_complete BIT(1) NOT NULL DEFAULT b'0', audience_cursor BIGINT NOT NULL DEFAULT 0, snapshot_max_id BIGINT NOT NULL DEFAULT 0, total_members INT NOT NULL DEFAULT 0, total_devices INT NOT NULL DEFAULT 0, submitted_count INT NOT NULL DEFAULT 0, failed_count INT NOT NULL DEFAULT 0, skipped_count INT NOT NULL DEFAULT 0, processed_count INT NOT NULL DEFAULT 0, last_error VARCHAR(1000) NULL, attempt_count INT NOT NULL DEFAULT 0, next_retry_time DATETIME(3) NOT NULL, expires_at DATETIME(3) NOT NULL, finish_time DATETIME(3) NULL, tenant_id BIGINT NOT NULL DEFAULT 0, creator VARCHAR(64) NULL DEFAULT '', create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updater VARCHAR(64) NULL DEFAULT '', update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), deleted BIT(1) NOT NULL DEFAULT b'0', PRIMARY KEY (id), UNIQUE KEY uk_tenant_request (tenant_id, request_key), KEY idx_pending (status, next_retry_time), KEY idx_tenant_time (tenant_id, create_time), KEY idx_message (message_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='App推送任务'; CREATE TABLE IF NOT EXISTS member_push_record ( id BIGINT NOT NULL AUTO_INCREMENT, task_id BIGINT NOT NULL, member_id BIGINT NOT NULL, nickname VARCHAR(255) NULL, user_code VARCHAR(64) NULL, device_id BIGINT NULL, platform VARCHAR(16) NULL, registration_id VARCHAR(64) NOT NULL DEFAULT '', status VARCHAR(16) NOT NULL, attempt_count INT NOT NULL DEFAULT 0, cid VARCHAR(128) NULL, batch_payload MEDIUMTEXT NULL COMMENT '批次首行保存固定请求体,用于不确定结果恢复', jpush_msg_id VARCHAR(64) NULL, error_code VARCHAR(64) NULL, error_message VARCHAR(1000) NULL, foreground_session VARCHAR(160) NULL, foreground_version BIGINT NULL, next_retry_time DATETIME(3) NOT NULL, send_time DATETIME(3) NULL, tenant_id BIGINT NOT NULL DEFAULT 0, creator VARCHAR(64) NULL DEFAULT '', create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updater VARCHAR(64) NULL DEFAULT '', update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), deleted BIT(1) NOT NULL DEFAULT b'0', PRIMARY KEY (id), UNIQUE KEY uk_task_device (task_id, member_id, registration_id), KEY idx_task_pending (task_id, status, next_retry_time), KEY idx_batch (task_id, cid), KEY idx_member_task (member_id, task_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='App推送设备记录'; -- 动态菜单按组件路径识别,可重复执行。优先复用现有 App 管理目录。 SET @app_parent = (SELECT id FROM system_menu WHERE deleted = b'0' AND type = 1 AND (path IN ('app','/app') OR name IN ('App管理','APP管理','应用管理')) ORDER BY id LIMIT 1); INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, updater) SELECT 'App管理', '', 1, 80, 0, '/app', 'ep:cellphone', '', '', 0, b'1', b'1', b'1', '1', '1' WHERE @app_parent IS NULL; SET @app_parent = COALESCE(@app_parent, LAST_INSERT_ID()); SET @push_menu = (SELECT id FROM system_menu WHERE deleted = b'0' AND component = 'app/push/index' ORDER BY id LIMIT 1); INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, updater) SELECT '推送管理', 'member:push:query', 2, 20, @app_parent, 'push', 'ep:bell', 'app/push/index', 'AppPush', 0, b'1', b'1', b'1', '1', '1' WHERE @push_menu IS NULL; SET @push_menu = COALESCE(@push_menu, LAST_INSERT_ID()); UPDATE system_menu SET permission = 'member:push:query', component_name = 'AppPush' WHERE id = @push_menu; INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, updater) SELECT '手动推送', 'member:push:send', 3, 1, @push_menu, '', '', '', '', 0, b'1', b'1', b'1', '1', '1' WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE deleted = b'0' AND permission = 'member:push:send'); -- 运行配置 -- API 和 IM 配置相同的 IM_PUSH_CALLBACK_SECRET。 -- IM_PUSH_CALLBACK_URL 指向业务 API 的 /admin-api/third-callback/im-private-message。 -- JPUSH_APP_KEY、JPUSH_MASTER_SECRET、JPUSH_APNS_PRODUCTION 使用聊天应用的极光配置。