feat: 建立QQBot消息推送持久化模型
This commit is contained in:
parent
ddc0e9580b
commit
5b34a4d8b1
@ -1106,6 +1106,76 @@ SELECT role.`id`, menu.`id`
|
||||
FROM `admin_role` role
|
||||
JOIN `admin_menu` menu ON menu.`name` LIKE 'QqBot%'
|
||||
WHERE role.`role_code` IN ('super', 'admin')
|
||||
AND role.`status` = 1
|
||||
AND role.`is_deleted` = 0
|
||||
AND menu.`is_deleted` = 0;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `qqbot_message_subscription` (
|
||||
`id` bigint NOT NULL, `name` varchar(100) NOT NULL, `source_key` varchar(128) NOT NULL,
|
||||
`source_config` json NOT NULL, `source_config_digest` char(64) NOT NULL, `active_key` varchar(255) DEFAULT NULL,
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT 1, `remark` varchar(500) DEFAULT NULL, `is_deleted` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`create_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), `update_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
PRIMARY KEY (`id`), UNIQUE KEY `uk_qqbot_message_subscription_active_key` (`active_key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `qqbot_message_template` (
|
||||
`id` bigint NOT NULL, `name` varchar(100) NOT NULL, `source_key` varchar(128) NOT NULL, `content` text NOT NULL,
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT 1, `remark` varchar(500) DEFAULT NULL, `is_deleted` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`create_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), `update_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `qqbot_message_publish_binding` (
|
||||
`id` bigint NOT NULL, `subscription_id` bigint NOT NULL, `account_id` bigint NOT NULL, `self_id` varchar(64) NOT NULL, `template_id` bigint NOT NULL,
|
||||
`active_key` varchar(255) DEFAULT NULL, `enabled` tinyint(1) NOT NULL DEFAULT 1, `is_deleted` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`create_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), `update_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
PRIMARY KEY (`id`), UNIQUE KEY `uk_qqbot_message_publish_binding_active_key` (`active_key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `qqbot_message_publish_target` (
|
||||
`id` bigint NOT NULL, `binding_id` bigint NOT NULL, `target_type` varchar(16) NOT NULL, `target_id` varchar(64) NOT NULL, `target_name` varchar(120) DEFAULT NULL,
|
||||
`active_key` varchar(300) DEFAULT NULL, `enabled` tinyint(1) NOT NULL DEFAULT 1, `is_deleted` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`create_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), `update_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
PRIMARY KEY (`id`), UNIQUE KEY `uk_qqbot_message_publish_target_active_key` (`active_key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `qqbot_message_event` (
|
||||
`id` bigint NOT NULL, `event_id` varchar(128) NOT NULL, `source_key` varchar(128) NOT NULL, `resource_key` varchar(128) NOT NULL, `occurred_at` datetime(6) NOT NULL,
|
||||
`payload` json NOT NULL, `fanout_status` varchar(32) NOT NULL DEFAULT 'accepted', `fanout_attempt_count` int unsigned NOT NULL DEFAULT 0,
|
||||
`next_fanout_at` datetime(6) DEFAULT NULL, `fanout_lease_until` datetime(6) DEFAULT NULL, `last_error_code` varchar(64) DEFAULT NULL, `last_error_message` varchar(500) DEFAULT NULL,
|
||||
`create_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), `update_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
PRIMARY KEY (`id`), UNIQUE KEY `uk_qqbot_message_event_event_id` (`event_id`), KEY `idx_qqbot_message_event_dispatch` (`fanout_status`, `next_fanout_at`), KEY `idx_qqbot_message_event_lease` (`fanout_lease_until`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `qqbot_message_delivery` (
|
||||
`id` bigint NOT NULL, `message_event_id` bigint NOT NULL, `publish_target_id` bigint NOT NULL, `binding_id` bigint NOT NULL, `subscription_id` bigint NOT NULL,
|
||||
`self_id` varchar(64) NOT NULL, `target_type` varchar(16) NOT NULL, `target_id` varchar(64) NOT NULL, `template_id` bigint NOT NULL,
|
||||
`template_content` text NOT NULL, `variable_snapshot` json NOT NULL, `rendered_message` text NOT NULL, `status` varchar(32) NOT NULL, `attempt_count` int unsigned NOT NULL DEFAULT 0,
|
||||
`next_attempt_at` datetime(6) DEFAULT NULL, `processing_lease_until` datetime(6) DEFAULT NULL, `send_log_id` bigint DEFAULT NULL, `last_error_code` varchar(64) DEFAULT NULL, `last_error_message` varchar(500) DEFAULT NULL, `expires_at` datetime(6) NOT NULL,
|
||||
`create_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), `update_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
PRIMARY KEY (`id`), UNIQUE KEY `uk_qqbot_message_delivery_event_target` (`message_event_id`, `publish_target_id`), KEY `idx_qqbot_message_delivery_dispatch` (`status`, `next_attempt_at`), KEY `idx_qqbot_message_delivery_lease` (`processing_lease_until`), KEY `idx_qqbot_message_delivery_history` (`subscription_id`, `message_event_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
INSERT INTO `qqbot_message_template` (`id`, `name`, `source_key`, `content`, `enabled`, `remark`, `is_deleted`)
|
||||
SELECT 2041700000000200601, 'STUN 映射端口变更默认模板', 'network.stun.mapping-port-changed', '当前STUN的端口已变更为${{endpoint}}', 1, '系统默认模板', 0
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `qqbot_message_template`
|
||||
WHERE `source_key` = 'network.stun.mapping-port-changed' AND `name` = 'STUN 映射端口变更默认模板' AND `is_deleted` = 0
|
||||
);
|
||||
|
||||
INSERT INTO `admin_menu` (`id`, `pid`, `name`, `path`, `component`, `redirect`, `auth_code`, `type`, `meta`, `status`, `sort`) VALUES
|
||||
(2041700000000100413,2041700000000100400,'QqBotMessageSubscription','/qqbot/message-subscription','/qqbot/message-subscription/list',NULL,'QqBot:MessageSubscription:List','menu','{"icon":"lucide:bell-ring","title":"系统消息订阅"}',1,10),
|
||||
(2041700000000100414,2041700000000100400,'QqBotMessageTemplate','/qqbot/message-template','/qqbot/message-template/list',NULL,'QqBot:MessageTemplate:List','menu','{"icon":"lucide:message-square-plus","title":"系统消息模板"}',1,11),
|
||||
(2041700000000120461,2041700000000100413,'QqBotMessageSubscriptionList',NULL,NULL,NULL,'QqBot:MessageSubscription:List','button','{"title":"common.list"}',1,0),(2041700000000120462,2041700000000100413,'QqBotMessageSubscriptionCreate',NULL,NULL,NULL,'QqBot:MessageSubscription:Create','button','{"title":"common.create"}',1,0),(2041700000000120463,2041700000000100413,'QqBotMessageSubscriptionUpdate',NULL,NULL,NULL,'QqBot:MessageSubscription:Update','button','{"title":"common.edit"}',1,0),(2041700000000120464,2041700000000100413,'QqBotMessageSubscriptionDelete',NULL,NULL,NULL,'QqBot:MessageSubscription:Delete','button','{"title":"common.delete"}',1,0),(2041700000000120465,2041700000000100413,'QqBotMessageSubscriptionToggle',NULL,NULL,NULL,'QqBot:MessageSubscription:Toggle','button','{"title":"启停"}',1,0),
|
||||
(2041700000000120471,2041700000000100414,'QqBotMessageTemplateList',NULL,NULL,NULL,'QqBot:MessageTemplate:List','button','{"title":"common.list"}',1,0),(2041700000000120472,2041700000000100414,'QqBotMessageTemplateCreate',NULL,NULL,NULL,'QqBot:MessageTemplate:Create','button','{"title":"common.create"}',1,0),(2041700000000120473,2041700000000100414,'QqBotMessageTemplateUpdate',NULL,NULL,NULL,'QqBot:MessageTemplate:Update','button','{"title":"common.edit"}',1,0),(2041700000000120474,2041700000000100414,'QqBotMessageTemplateDelete',NULL,NULL,NULL,'QqBot:MessageTemplate:Delete','button','{"title":"common.delete"}',1,0),(2041700000000120475,2041700000000100414,'QqBotMessageTemplateToggle',NULL,NULL,NULL,'QqBot:MessageTemplate:Toggle','button','{"title":"启停"}',1,0),(2041700000000120476,2041700000000100414,'QqBotMessageTemplatePreview',NULL,NULL,NULL,'QqBot:MessageTemplate:Preview','button','{"title":"预览"}',1,0),
|
||||
(2041700000000120481,2041700000000100410,'QqBotAccountMessagePushList',NULL,NULL,NULL,'QqBot:Account:MessagePush:List','button','{"title":"common.list"}',1,0),(2041700000000120482,2041700000000100410,'QqBotAccountMessagePushCreate',NULL,NULL,NULL,'QqBot:Account:MessagePush:Create','button','{"title":"common.create"}',1,0),(2041700000000120483,2041700000000100410,'QqBotAccountMessagePushUpdate',NULL,NULL,NULL,'QqBot:Account:MessagePush:Update','button','{"title":"common.edit"}',1,0),(2041700000000120484,2041700000000100410,'QqBotAccountMessagePushDelete',NULL,NULL,NULL,'QqBot:Account:MessagePush:Delete','button','{"title":"common.delete"}',1,0),(2041700000000120485,2041700000000100410,'QqBotAccountMessagePushToggle',NULL,NULL,NULL,'QqBot:Account:MessagePush:Toggle','button','{"title":"启停"}',1,0)
|
||||
ON DUPLICATE KEY UPDATE `pid`=VALUES(`pid`),`path`=VALUES(`path`),`component`=VALUES(`component`),`redirect`=VALUES(`redirect`),`auth_code`=VALUES(`auth_code`),`type`=VALUES(`type`),`meta`=VALUES(`meta`),`status`=VALUES(`status`),`sort`=VALUES(`sort`),`is_deleted`=0;
|
||||
|
||||
INSERT IGNORE INTO `admin_role_menu` (`role_id`, `menu_id`)
|
||||
SELECT role.`id`, menu.`id`
|
||||
FROM `admin_role` role
|
||||
JOIN `admin_menu` menu ON menu.`name` LIKE 'QqBot%'
|
||||
WHERE role.`role_code` IN ('super', 'admin')
|
||||
AND role.`status` = 1
|
||||
AND role.`is_deleted` = 0
|
||||
AND menu.`is_deleted` = 0;
|
||||
|
||||
|
||||
@ -1242,3 +1242,107 @@ CREATE TABLE IF NOT EXISTS qqbot_napcat_webui_gateway_audit (
|
||||
KEY idx_napcat_webui_gateway_audit_account_event (account_id, event_type),
|
||||
KEY idx_napcat_webui_gateway_audit_admin_time (admin_user_id, create_time)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_message_subscription (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
source_key VARCHAR(128) NOT NULL,
|
||||
source_config JSON NOT NULL,
|
||||
source_config_digest CHAR(64) NOT NULL,
|
||||
active_key VARCHAR(255) NULL,
|
||||
enabled TINYINT(1) NOT NULL DEFAULT 1,
|
||||
remark VARCHAR(500) NULL,
|
||||
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
update_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
UNIQUE KEY uk_qqbot_message_subscription_active_key (active_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_message_template (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
source_key VARCHAR(128) NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
enabled TINYINT(1) NOT NULL DEFAULT 1,
|
||||
remark VARCHAR(500) NULL,
|
||||
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
update_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_message_publish_binding (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
subscription_id BIGINT NOT NULL,
|
||||
account_id BIGINT NOT NULL,
|
||||
self_id VARCHAR(64) NOT NULL,
|
||||
template_id BIGINT NOT NULL,
|
||||
active_key VARCHAR(255) NULL,
|
||||
enabled TINYINT(1) NOT NULL DEFAULT 1,
|
||||
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
update_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
UNIQUE KEY uk_qqbot_message_publish_binding_active_key (active_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_message_publish_target (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
binding_id BIGINT NOT NULL,
|
||||
target_type VARCHAR(16) NOT NULL,
|
||||
target_id VARCHAR(64) NOT NULL,
|
||||
target_name VARCHAR(120) NULL,
|
||||
active_key VARCHAR(300) NULL,
|
||||
enabled TINYINT(1) NOT NULL DEFAULT 1,
|
||||
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
|
||||
create_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
update_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
UNIQUE KEY uk_qqbot_message_publish_target_active_key (active_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_message_event (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
event_id VARCHAR(128) NOT NULL,
|
||||
source_key VARCHAR(128) NOT NULL,
|
||||
resource_key VARCHAR(128) NOT NULL,
|
||||
occurred_at DATETIME(6) NOT NULL,
|
||||
payload JSON NOT NULL,
|
||||
fanout_status VARCHAR(32) NOT NULL DEFAULT 'accepted',
|
||||
fanout_attempt_count INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
next_fanout_at DATETIME(6) NULL,
|
||||
fanout_lease_until DATETIME(6) NULL,
|
||||
last_error_code VARCHAR(64) NULL,
|
||||
last_error_message VARCHAR(500) NULL,
|
||||
create_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
update_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
UNIQUE KEY uk_qqbot_message_event_event_id (event_id),
|
||||
KEY idx_qqbot_message_event_dispatch (fanout_status, next_fanout_at),
|
||||
KEY idx_qqbot_message_event_lease (fanout_lease_until)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qqbot_message_delivery (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
message_event_id BIGINT NOT NULL,
|
||||
publish_target_id BIGINT NOT NULL,
|
||||
binding_id BIGINT NOT NULL,
|
||||
subscription_id BIGINT NOT NULL,
|
||||
self_id VARCHAR(64) NOT NULL,
|
||||
target_type VARCHAR(16) NOT NULL,
|
||||
target_id VARCHAR(64) NOT NULL,
|
||||
template_id BIGINT NOT NULL,
|
||||
template_content TEXT NOT NULL,
|
||||
variable_snapshot JSON NOT NULL,
|
||||
rendered_message TEXT NOT NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
attempt_count INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
next_attempt_at DATETIME(6) NULL,
|
||||
processing_lease_until DATETIME(6) NULL,
|
||||
send_log_id BIGINT NULL,
|
||||
last_error_code VARCHAR(64) NULL,
|
||||
last_error_message VARCHAR(500) NULL,
|
||||
expires_at DATETIME(6) NOT NULL,
|
||||
create_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
update_time DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
UNIQUE KEY uk_qqbot_message_delivery_event_target (message_event_id, publish_target_id),
|
||||
KEY idx_qqbot_message_delivery_dispatch (status, next_attempt_at),
|
||||
KEY idx_qqbot_message_delivery_lease (processing_lease_until),
|
||||
KEY idx_qqbot_message_delivery_history (subscription_id, message_event_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
@ -630,3 +630,20 @@ ON DUPLICATE KEY UPDATE
|
||||
enabled = VALUES(enabled),
|
||||
cooldown_seconds = VALUES(cooldown_seconds),
|
||||
is_deleted = 0;
|
||||
|
||||
INSERT INTO qqbot_message_template (id, name, source_key, content, enabled, remark, is_deleted)
|
||||
SELECT 2041700000000200601, 'STUN 映射端口变更默认模板', 'network.stun.mapping-port-changed', '当前STUN的端口已变更为${{endpoint}}', 1, '系统默认模板', 0
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM qqbot_message_template
|
||||
WHERE source_key = 'network.stun.mapping-port-changed'
|
||||
AND name = 'STUN 映射端口变更默认模板'
|
||||
AND is_deleted = 0
|
||||
);
|
||||
|
||||
INSERT INTO admin_menu (id, pid, name, path, component, redirect, auth_code, type, meta, status, sort) VALUES
|
||||
(2041700000000100413,2041700000000100400,'QqBotMessageSubscription','/qqbot/message-subscription','/qqbot/message-subscription/list',NULL,'QqBot:MessageSubscription:List','menu','{"icon":"lucide:bell-ring","title":"系统消息订阅"}',1,10),
|
||||
(2041700000000100414,2041700000000100400,'QqBotMessageTemplate','/qqbot/message-template','/qqbot/message-template/list',NULL,'QqBot:MessageTemplate:List','menu','{"icon":"lucide:message-square-plus","title":"系统消息模板"}',1,11),
|
||||
(2041700000000120461,2041700000000100413,'QqBotMessageSubscriptionList',NULL,NULL,NULL,'QqBot:MessageSubscription:List','button','{"title":"common.list"}',1,0),(2041700000000120462,2041700000000100413,'QqBotMessageSubscriptionCreate',NULL,NULL,NULL,'QqBot:MessageSubscription:Create','button','{"title":"common.create"}',1,0),(2041700000000120463,2041700000000100413,'QqBotMessageSubscriptionUpdate',NULL,NULL,NULL,'QqBot:MessageSubscription:Update','button','{"title":"common.edit"}',1,0),(2041700000000120464,2041700000000100413,'QqBotMessageSubscriptionDelete',NULL,NULL,NULL,'QqBot:MessageSubscription:Delete','button','{"title":"common.delete"}',1,0),(2041700000000120465,2041700000000100413,'QqBotMessageSubscriptionToggle',NULL,NULL,NULL,'QqBot:MessageSubscription:Toggle','button','{"title":"启停"}',1,0),
|
||||
(2041700000000120471,2041700000000100414,'QqBotMessageTemplateList',NULL,NULL,NULL,'QqBot:MessageTemplate:List','button','{"title":"common.list"}',1,0),(2041700000000120472,2041700000000100414,'QqBotMessageTemplateCreate',NULL,NULL,NULL,'QqBot:MessageTemplate:Create','button','{"title":"common.create"}',1,0),(2041700000000120473,2041700000000100414,'QqBotMessageTemplateUpdate',NULL,NULL,NULL,'QqBot:MessageTemplate:Update','button','{"title":"common.edit"}',1,0),(2041700000000120474,2041700000000100414,'QqBotMessageTemplateDelete',NULL,NULL,NULL,'QqBot:MessageTemplate:Delete','button','{"title":"common.delete"}',1,0),(2041700000000120475,2041700000000100414,'QqBotMessageTemplateToggle',NULL,NULL,NULL,'QqBot:MessageTemplate:Toggle','button','{"title":"启停"}',1,0),(2041700000000120476,2041700000000100414,'QqBotMessageTemplatePreview',NULL,NULL,NULL,'QqBot:MessageTemplate:Preview','button','{"title":"预览"}',1,0),
|
||||
(2041700000000120481,2041700000000100410,'QqBotAccountMessagePushList',NULL,NULL,NULL,'QqBot:Account:MessagePush:List','button','{"title":"common.list"}',1,0),(2041700000000120482,2041700000000100410,'QqBotAccountMessagePushCreate',NULL,NULL,NULL,'QqBot:Account:MessagePush:Create','button','{"title":"common.create"}',1,0),(2041700000000120483,2041700000000100410,'QqBotAccountMessagePushUpdate',NULL,NULL,NULL,'QqBot:Account:MessagePush:Update','button','{"title":"common.edit"}',1,0),(2041700000000120484,2041700000000100410,'QqBotAccountMessagePushDelete',NULL,NULL,NULL,'QqBot:Account:MessagePush:Delete','button','{"title":"common.delete"}',1,0),(2041700000000120485,2041700000000100410,'QqBotAccountMessagePushToggle',NULL,NULL,NULL,'QqBot:Account:MessagePush:Toggle','button','{"title":"启停"}',1,0)
|
||||
ON DUPLICATE KEY UPDATE pid = VALUES(pid), path = VALUES(path), component = VALUES(component), redirect = VALUES(redirect), auth_code = VALUES(auth_code), type = VALUES(type), meta = VALUES(meta), status = VALUES(status), sort = VALUES(sort), is_deleted = 0;
|
||||
|
||||
@ -22,6 +22,29 @@ SELECT 'napcat_login_event' AS table_name, COUNT(*) AS row_count FROM napcat_log
|
||||
SELECT 'napcat_risk_mode' AS table_name, COUNT(*) AS row_count FROM napcat_risk_mode;
|
||||
SELECT 'qqbot_plugin_task' AS table_name, COUNT(*) AS row_count FROM qqbot_plugin_task;
|
||||
SELECT 'qqbot_plugin_task_run' AS table_name, COUNT(*) AS row_count FROM qqbot_plugin_task_run;
|
||||
SELECT 'qqbot_message_subscription' AS table_name, COUNT(*) AS row_count FROM qqbot_message_subscription;
|
||||
SELECT 'qqbot_message_template' AS table_name, COUNT(*) AS row_count FROM qqbot_message_template;
|
||||
SELECT 'qqbot_message_publish_binding' AS table_name, COUNT(*) AS row_count FROM qqbot_message_publish_binding;
|
||||
SELECT 'qqbot_message_publish_target' AS table_name, COUNT(*) AS row_count FROM qqbot_message_publish_target;
|
||||
SELECT 'qqbot_message_event' AS table_name, COUNT(*) AS row_count FROM qqbot_message_event;
|
||||
SELECT 'qqbot_message_delivery' AS table_name, COUNT(*) AS row_count FROM qqbot_message_delivery;
|
||||
|
||||
SELECT 'seed_qqbot_message_template' AS check_name, COUNT(*) AS matched_rows
|
||||
FROM qqbot_message_template
|
||||
WHERE id = 2041700000000200601
|
||||
AND name = 'STUN 映射端口变更默认模板'
|
||||
AND source_key = 'network.stun.mapping-port-changed'
|
||||
AND content = '当前STUN的端口已变更为${{endpoint}}'
|
||||
AND enabled = 1
|
||||
AND is_deleted = 0;
|
||||
|
||||
SELECT 'seed_qqbot_message_push_menu' AS check_name, COUNT(*) AS matched_rows
|
||||
FROM admin_menu
|
||||
WHERE auth_code IN (
|
||||
'QqBot:MessageSubscription:List', 'QqBot:MessageSubscription:Create', 'QqBot:MessageSubscription:Update', 'QqBot:MessageSubscription:Delete', 'QqBot:MessageSubscription:Toggle',
|
||||
'QqBot:MessageTemplate:List', 'QqBot:MessageTemplate:Create', 'QqBot:MessageTemplate:Update', 'QqBot:MessageTemplate:Delete', 'QqBot:MessageTemplate:Toggle', 'QqBot:MessageTemplate:Preview',
|
||||
'QqBot:Account:MessagePush:List', 'QqBot:Account:MessagePush:Create', 'QqBot:Account:MessagePush:Update', 'QqBot:Account:MessagePush:Delete', 'QqBot:Account:MessagePush:Toggle'
|
||||
) AND status = 1 AND is_deleted = 0;
|
||||
|
||||
SELECT 'qqbot_napcat_webui_gateway_audit table exists' AS check_name, COUNT(*) AS matched_rows
|
||||
FROM information_schema.tables
|
||||
|
||||
@ -399,4 +399,36 @@ VALUES
|
||||
(2041700000000010003, 2041700000000100010),
|
||||
(2041700000000010003, 2041700000000100011);
|
||||
|
||||
INSERT INTO `admin_menu` (`id`, `pid`, `name`, `path`, `component`, `redirect`, `auth_code`, `type`, `meta`, `status`, `sort`)
|
||||
VALUES
|
||||
(2041700000000100413, 2041700000000100400, 'QqBotMessageSubscription', '/qqbot/message-subscription', '/qqbot/message-subscription/list', NULL, 'QqBot:MessageSubscription:List', 'menu', '{"icon":"lucide:bell-ring","title":"系统消息订阅"}', 1, 10),
|
||||
(2041700000000100414, 2041700000000100400, 'QqBotMessageTemplate', '/qqbot/message-template', '/qqbot/message-template/list', NULL, 'QqBot:MessageTemplate:List', 'menu', '{"icon":"lucide:message-square-plus","title":"系统消息模板"}', 1, 11),
|
||||
(2041700000000120461, 2041700000000100413, 'QqBotMessageSubscriptionList', NULL, NULL, NULL, 'QqBot:MessageSubscription:List', 'button', '{"title":"common.list"}', 1, 0),
|
||||
(2041700000000120462, 2041700000000100413, 'QqBotMessageSubscriptionCreate', NULL, NULL, NULL, 'QqBot:MessageSubscription:Create', 'button', '{"title":"common.create"}', 1, 0),
|
||||
(2041700000000120463, 2041700000000100413, 'QqBotMessageSubscriptionUpdate', NULL, NULL, NULL, 'QqBot:MessageSubscription:Update', 'button', '{"title":"common.edit"}', 1, 0),
|
||||
(2041700000000120464, 2041700000000100413, 'QqBotMessageSubscriptionDelete', NULL, NULL, NULL, 'QqBot:MessageSubscription:Delete', 'button', '{"title":"common.delete"}', 1, 0),
|
||||
(2041700000000120465, 2041700000000100413, 'QqBotMessageSubscriptionToggle', NULL, NULL, NULL, 'QqBot:MessageSubscription:Toggle', 'button', '{"title":"启停"}', 1, 0),
|
||||
(2041700000000120471, 2041700000000100414, 'QqBotMessageTemplateList', NULL, NULL, NULL, 'QqBot:MessageTemplate:List', 'button', '{"title":"common.list"}', 1, 0),
|
||||
(2041700000000120472, 2041700000000100414, 'QqBotMessageTemplateCreate', NULL, NULL, NULL, 'QqBot:MessageTemplate:Create', 'button', '{"title":"common.create"}', 1, 0),
|
||||
(2041700000000120473, 2041700000000100414, 'QqBotMessageTemplateUpdate', NULL, NULL, NULL, 'QqBot:MessageTemplate:Update', 'button', '{"title":"common.edit"}', 1, 0),
|
||||
(2041700000000120474, 2041700000000100414, 'QqBotMessageTemplateDelete', NULL, NULL, NULL, 'QqBot:MessageTemplate:Delete', 'button', '{"title":"common.delete"}', 1, 0),
|
||||
(2041700000000120475, 2041700000000100414, 'QqBotMessageTemplateToggle', NULL, NULL, NULL, 'QqBot:MessageTemplate:Toggle', 'button', '{"title":"启停"}', 1, 0),
|
||||
(2041700000000120476, 2041700000000100414, 'QqBotMessageTemplatePreview', NULL, NULL, NULL, 'QqBot:MessageTemplate:Preview', 'button', '{"title":"预览"}', 1, 0),
|
||||
(2041700000000120481, 2041700000000100410, 'QqBotAccountMessagePushList', NULL, NULL, NULL, 'QqBot:Account:MessagePush:List', 'button', '{"title":"common.list"}', 1, 0),
|
||||
(2041700000000120482, 2041700000000100410, 'QqBotAccountMessagePushCreate', NULL, NULL, NULL, 'QqBot:Account:MessagePush:Create', 'button', '{"title":"common.create"}', 1, 0),
|
||||
(2041700000000120483, 2041700000000100410, 'QqBotAccountMessagePushUpdate', NULL, NULL, NULL, 'QqBot:Account:MessagePush:Update', 'button', '{"title":"common.edit"}', 1, 0),
|
||||
(2041700000000120484, 2041700000000100410, 'QqBotAccountMessagePushDelete', NULL, NULL, NULL, 'QqBot:Account:MessagePush:Delete', 'button', '{"title":"common.delete"}', 1, 0),
|
||||
(2041700000000120485, 2041700000000100410, 'QqBotAccountMessagePushToggle', NULL, NULL, NULL, 'QqBot:Account:MessagePush:Toggle', 'button', '{"title":"启停"}', 1, 0)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
`pid` = VALUES(`pid`), `path` = VALUES(`path`), `component` = VALUES(`component`), `redirect` = VALUES(`redirect`), `auth_code` = VALUES(`auth_code`), `type` = VALUES(`type`), `meta` = VALUES(`meta`), `status` = VALUES(`status`), `sort` = VALUES(`sort`), `is_deleted` = 0;
|
||||
|
||||
INSERT IGNORE INTO `admin_role_menu` (`role_id`, `menu_id`)
|
||||
SELECT role.`id`, menu.`id`
|
||||
FROM `admin_role` role
|
||||
JOIN `admin_menu` menu ON menu.`name` LIKE 'QqBot%'
|
||||
WHERE role.`role_code` IN ('super', 'admin')
|
||||
AND role.`status` = 1
|
||||
AND role.`is_deleted` = 0
|
||||
AND menu.`is_deleted` = 0;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
import type { EntityManager } from 'typeorm';
|
||||
|
||||
export type SystemMessageScalar = boolean | null | number | string;
|
||||
export type SystemMessageFanoutStatus = 'accepted' | 'completed' | 'failed' | 'processing' | 'retry';
|
||||
export type SystemMessageDeliveryStatus = 'cancelled' | 'failed' | 'pending' | 'processing' | 'retry' | 'success' | 'superseded' | 'waiting_ddns';
|
||||
export type QqbotMessagePushTargetType = 'group' | 'private';
|
||||
export type SystemMessageTemplateToken = { kind: 'text'; value: string } | { key: string; kind: 'variable' };
|
||||
|
||||
export interface SystemMessageSourceVariableDefinition { description: string; example: string; key: string; label: string; type: 'boolean' | 'number' | 'string'; }
|
||||
export interface SystemMessageSourceFieldDefinition { dependsOn?: string; key: string; label: string; optionCollection: 'ddnsRecords' | 'portForwards'; required: true; type: 'select'; }
|
||||
export interface SystemMessageSourceDefinition { description: string; displayName: string; sourceKey: string; subscriptionFields: SystemMessageSourceFieldDefinition[]; variables: SystemMessageSourceVariableDefinition[]; version: 1; }
|
||||
export interface StunMappingPortChangedSubscriptionConfig { ddnsRecordId: string; portForwardId: string; }
|
||||
export interface StunMappingPortChangedOptionsResponse { ddnsRecords: Array<{ disabledReasonCode: null | string; eligible: boolean; fqdn: string; id: string; name: string; portForwardId: string; }>; portForwards: Array<{ disabledReasonCode: null | string; eligible: boolean; externalPort: number; id: string; internalPort: number; name: string; protocol: 'tcp' | 'udp'; }>; }
|
||||
|
||||
export interface MessageSubscriptionView { createTime: string; enabled: boolean; id: string; invalidReasonCode: null | string; name: string; remark: null | string; sourceConfig: StunMappingPortChangedSubscriptionConfig; sourceKey: string; sourceName: string; sourceSummary: string; updateTime: string; valid: boolean; }
|
||||
export interface MessageTemplateView { content: string; createTime: string; enabled: boolean; id: string; name: string; referenceCount: number; remark: null | string; sourceKey: string; sourceName: string; updateTime: string; }
|
||||
export interface MessageTemplatePreview { renderedMessage: string; variables: Record<string, boolean | number | string>; }
|
||||
export interface QqbotMessagePublishTargetView { enabled: boolean; id: string; targetId: string; targetName: null | string; targetType: QqbotMessagePushTargetType; }
|
||||
export interface QqbotMessagePublishBindingView { available: boolean; createTime: string; enabled: boolean; id: string; invalidReasonCode: null | string; sourceKey: string; sourceName: string; subscriptionId: string; subscriptionName: string; targets: QqbotMessagePublishTargetView[]; templateId: string; templateName: string; updateTime: string; }
|
||||
|
||||
export type SystemMessageDeliveryReadiness =
|
||||
| { reasonCode: null; status: 'ready'; variables: Record<string, boolean | number | string>; }
|
||||
| { reasonCode: 'ddns_not_synced'; status: 'waiting_ddns'; variables: Record<string, boolean | number | string>; }
|
||||
| { reasonCode: string; status: 'cancelled' | 'superseded'; variables?: never; };
|
||||
|
||||
export interface SystemMessageSourceAdapter {
|
||||
readonly definition: SystemMessageSourceDefinition;
|
||||
inspectSubscription(config: Record<string, unknown>): Promise<{ invalidReasonCode: null | string; sourceSummary: string; valid: boolean; }>;
|
||||
listSubscriptionOptions(): Promise<Record<string, unknown>>;
|
||||
normalizeSubscriptionConfig(input: unknown): Promise<{ canonicalConfig: Record<string, string>; resourceKey: string; sourceSummary: string; }>;
|
||||
resolveDelivery(input: { eventPayload: Record<string, SystemMessageScalar>; subscriptionConfig: Record<string, unknown>; }): Promise<SystemMessageDeliveryReadiness>;
|
||||
validateEventPayload(payload: Record<string, unknown>): Record<string, SystemMessageScalar>;
|
||||
}
|
||||
|
||||
export interface SystemMessageEventInput { eventId: string; occurredAt: string; payload: Record<string, SystemMessageScalar>; resourceKey: string; sourceKey: string; }
|
||||
export const SYSTEM_MESSAGE_EVENT_STAGER = Symbol('SYSTEM_MESSAGE_EVENT_STAGER');
|
||||
export interface SystemMessageEventStager { stage(manager: EntityManager, input: SystemMessageEventInput): Promise<'accepted' | 'duplicate'>; }
|
||||
export const SYSTEM_MESSAGE_DELIVERY_COORDINATOR = Symbol('SYSTEM_MESSAGE_DELIVERY_COORDINATOR');
|
||||
export interface SystemMessageDeliveryCoordinator { notifyDdnsSynced(input: { appliedAddress: string; ddnsRecordId: string; }): Promise<void>; requestDrain(): void; }
|
||||
|
||||
export interface MessageSubscriptionListQuery { enabled?: boolean; name?: string; pageNo?: number; pageSize?: number; sourceKey?: string; }
|
||||
export interface MessageSubscriptionInput { enabled: boolean; name: string; remark?: string; sourceConfig: Record<string, unknown>; sourceKey: string; }
|
||||
export interface MessageTemplateListQuery { enabled?: boolean; name?: string; pageNo?: number; pageSize?: number; sourceKey?: string; }
|
||||
export interface MessageTemplateInput { content: string; enabled: boolean; name: string; remark?: string; sourceKey: string; }
|
||||
export interface QqbotMessagePublishTargetInput { targetId: string; targetName?: string; targetType: QqbotMessagePushTargetType; }
|
||||
export interface QqbotMessagePublishBindingInput { enabled: boolean; subscriptionId: string; targets: QqbotMessagePublishTargetInput[]; templateId: string; }
|
||||
export interface QqbotMessagePushTargetOption { label: string; targetId: string; targetType: QqbotMessagePushTargetType; }
|
||||
export interface QqbotMessagePushTargetOptionsResponse { available: boolean; options: QqbotMessagePushTargetOption[]; reasonCode: null | string; }
|
||||
export interface StrictPlainTextSendInput { attemptNumber: number; deliveryId: string; message: string; selfId: string; targetId: string; targetType: QqbotMessagePushTargetType; }
|
||||
export interface QqbotSendAttemptErrorOptions { code: string; message: string; retryable: boolean; sendLogId: null | string; }
|
||||
|
||||
export class SystemMessageContractError extends Error {
|
||||
/** Creates a stable, non-sensitive domain contract error. */
|
||||
constructor(public readonly code: string) { super(code); this.name = 'SystemMessageContractError'; }
|
||||
}
|
||||
|
||||
export const QQBOT_MESSAGE_PUSH_TABLE_CONTRACT = {
|
||||
binding: ['id', 'subscription_id', 'account_id', 'self_id', 'template_id', 'active_key', 'enabled', 'is_deleted', 'create_time', 'update_time'],
|
||||
delivery: ['id', 'message_event_id', 'publish_target_id', 'binding_id', 'subscription_id', 'self_id', 'target_type', 'target_id', 'template_id', 'template_content', 'variable_snapshot', 'rendered_message', 'status', 'attempt_count', 'next_attempt_at', 'processing_lease_until', 'send_log_id', 'last_error_code', 'last_error_message', 'expires_at', 'create_time', 'update_time'],
|
||||
event: ['id', 'event_id', 'source_key', 'resource_key', 'occurred_at', 'payload', 'fanout_status', 'fanout_attempt_count', 'next_fanout_at', 'fanout_lease_until', 'last_error_code', 'last_error_message', 'create_time', 'update_time'],
|
||||
subscription: ['id', 'name', 'source_key', 'source_config', 'source_config_digest', 'active_key', 'enabled', 'remark', 'is_deleted', 'create_time', 'update_time'],
|
||||
target: ['id', 'binding_id', 'target_type', 'target_id', 'target_name', 'active_key', 'enabled', 'is_deleted', 'create_time', 'update_time'],
|
||||
template: ['id', 'name', 'source_key', 'content', 'enabled', 'remark', 'is_deleted', 'create_time', 'update_time'],
|
||||
} as const;
|
||||
@ -12,6 +12,12 @@ export const QQBOT_CORE_DOMAIN_CONTRACT = {
|
||||
'qqbot_send_task',
|
||||
'qqbot_send_log',
|
||||
'qqbot_dedupe_event',
|
||||
'qqbot_message_subscription',
|
||||
'qqbot_message_template',
|
||||
'qqbot_message_publish_binding',
|
||||
'qqbot_message_publish_target',
|
||||
'qqbot_message_event',
|
||||
'qqbot_message_delivery',
|
||||
],
|
||||
status: {
|
||||
accountTable: 'qqbot_account',
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
import { BeforeInsert, Column, Entity, Index, PrimaryColumn } from 'typeorm';
|
||||
import { ensureSnowflakeId, KtCreateDateColumn, KtDateTime, KtDateTimeColumn, KtUpdateDateColumn } from '@/common';
|
||||
import type { QqbotMessagePushTargetType, SystemMessageDeliveryStatus, SystemMessageScalar } from '../../../contract/message-push/qqbot-message-push.types';
|
||||
|
||||
@Entity('qqbot_message_delivery')
|
||||
@Index('uk_qqbot_message_delivery_event_target', ['messageEventId', 'publishTargetId'], { unique: true })
|
||||
@Index('idx_qqbot_message_delivery_dispatch', ['status', 'nextAttemptAt'])
|
||||
@Index('idx_qqbot_message_delivery_lease', ['processingLeaseUntil'])
|
||||
@Index('idx_qqbot_message_delivery_history', ['subscriptionId', 'messageEventId'])
|
||||
export class QqbotMessageDelivery {
|
||||
@PrimaryColumn({ type: 'bigint' }) id: string;
|
||||
@Column({ name: 'message_event_id', type: 'bigint' }) messageEventId: string;
|
||||
@Column({ name: 'publish_target_id', type: 'bigint' }) publishTargetId: string;
|
||||
@Column({ name: 'binding_id', type: 'bigint' }) bindingId: string;
|
||||
@Column({ name: 'subscription_id', type: 'bigint' }) subscriptionId: string;
|
||||
@Column({ length: 64, name: 'self_id' }) selfId: string;
|
||||
@Column({ length: 16, name: 'target_type' }) targetType: QqbotMessagePushTargetType;
|
||||
@Column({ length: 64, name: 'target_id' }) targetId: string;
|
||||
@Column({ name: 'template_id', type: 'bigint' }) templateId: string;
|
||||
@Column({ name: 'template_content', type: 'text' }) templateContent: string;
|
||||
@Column({ name: 'variable_snapshot', type: 'json' }) variableSnapshot: Record<string, SystemMessageScalar>;
|
||||
@Column({ name: 'rendered_message', type: 'text' }) renderedMessage: string;
|
||||
@Column({ length: 32 }) status: SystemMessageDeliveryStatus;
|
||||
@Column({ default: 0, name: 'attempt_count', type: 'int', unsigned: true }) attemptCount: number;
|
||||
@KtDateTimeColumn({ name: 'next_attempt_at', nullable: true, precision: 6 }) nextAttemptAt: KtDateTime | null;
|
||||
@KtDateTimeColumn({ name: 'processing_lease_until', nullable: true, precision: 6 }) processingLeaseUntil: KtDateTime | null;
|
||||
@Column({ name: 'send_log_id', nullable: true, type: 'bigint' }) sendLogId: null | string;
|
||||
@Column({ length: 64, name: 'last_error_code', nullable: true }) lastErrorCode: null | string;
|
||||
@Column({ length: 500, name: 'last_error_message', nullable: true }) lastErrorMessage: null | string;
|
||||
@KtDateTimeColumn({ name: 'expires_at', precision: 6 }) expiresAt: KtDateTime;
|
||||
@KtCreateDateColumn({ name: 'create_time', precision: 6 }) createTime: KtDateTime;
|
||||
@KtUpdateDateColumn({ name: 'update_time', precision: 6 }) updateTime: KtDateTime;
|
||||
|
||||
/** Assigns the Snowflake primary key before this delivery task is persisted. */
|
||||
@BeforeInsert()
|
||||
createId() { ensureSnowflakeId(this); }
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
import { BeforeInsert, Column, Entity, Index, PrimaryColumn } from 'typeorm';
|
||||
import { ensureSnowflakeId, KtCreateDateColumn, KtDateTime, KtDateTimeColumn, KtUpdateDateColumn } from '@/common';
|
||||
import type { SystemMessageFanoutStatus, SystemMessageScalar } from '../../../contract/message-push/qqbot-message-push.types';
|
||||
|
||||
@Entity('qqbot_message_event')
|
||||
@Index('uk_qqbot_message_event_event_id', ['eventId'], { unique: true })
|
||||
@Index('idx_qqbot_message_event_dispatch', ['fanoutStatus', 'nextFanoutAt'])
|
||||
@Index('idx_qqbot_message_event_lease', ['fanoutLeaseUntil'])
|
||||
export class QqbotMessageEvent {
|
||||
@PrimaryColumn({ type: 'bigint' }) id: string;
|
||||
@Column({ length: 128, name: 'event_id' }) eventId: string;
|
||||
@Column({ length: 128, name: 'source_key' }) sourceKey: string;
|
||||
@Column({ length: 128, name: 'resource_key' }) resourceKey: string;
|
||||
@KtDateTimeColumn({ name: 'occurred_at', precision: 6 }) occurredAt: KtDateTime;
|
||||
@Column({ type: 'json' }) payload: Record<string, SystemMessageScalar>;
|
||||
@Column({ default: 'accepted', length: 32, name: 'fanout_status' }) fanoutStatus: SystemMessageFanoutStatus;
|
||||
@Column({ default: 0, name: 'fanout_attempt_count', type: 'int', unsigned: true }) fanoutAttemptCount: number;
|
||||
@KtDateTimeColumn({ name: 'next_fanout_at', nullable: true, precision: 6 }) nextFanoutAt: KtDateTime | null;
|
||||
@KtDateTimeColumn({ name: 'fanout_lease_until', nullable: true, precision: 6 }) fanoutLeaseUntil: KtDateTime | null;
|
||||
@Column({ length: 64, name: 'last_error_code', nullable: true }) lastErrorCode: null | string;
|
||||
@Column({ length: 500, name: 'last_error_message', nullable: true }) lastErrorMessage: null | string;
|
||||
@KtCreateDateColumn({ name: 'create_time', precision: 6 }) createTime: KtDateTime;
|
||||
@KtUpdateDateColumn({ name: 'update_time', precision: 6 }) updateTime: KtDateTime;
|
||||
|
||||
/** Assigns the Snowflake primary key before this outbox event is persisted. */
|
||||
@BeforeInsert()
|
||||
createId() { ensureSnowflakeId(this); }
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
import { BeforeInsert, Column, Entity, Index, PrimaryColumn } from 'typeorm';
|
||||
import { ensureSnowflakeId, KtCreateDateColumn, KtDateTime, KtUpdateDateColumn } from '@/common';
|
||||
|
||||
@Entity('qqbot_message_publish_binding')
|
||||
@Index('uk_qqbot_message_publish_binding_active_key', ['activeKey'], { unique: true })
|
||||
export class QqbotMessagePublishBinding {
|
||||
@PrimaryColumn({ type: 'bigint' }) id: string;
|
||||
@Column({ name: 'subscription_id', type: 'bigint' }) subscriptionId: string;
|
||||
@Column({ name: 'account_id', type: 'bigint' }) accountId: string;
|
||||
@Column({ length: 64, name: 'self_id' }) selfId: string;
|
||||
@Column({ name: 'template_id', type: 'bigint' }) templateId: string;
|
||||
@Column({ length: 255, name: 'active_key', nullable: true }) activeKey: null | string;
|
||||
@Column({ default: true }) enabled: boolean;
|
||||
@Column({ default: false, name: 'is_deleted' }) isDeleted: boolean;
|
||||
@KtCreateDateColumn({ name: 'create_time', precision: 6 }) createTime: KtDateTime;
|
||||
@KtUpdateDateColumn({ name: 'update_time', precision: 6 }) updateTime: KtDateTime;
|
||||
|
||||
/** Assigns the Snowflake primary key before this account binding is persisted. */
|
||||
@BeforeInsert()
|
||||
createId() { ensureSnowflakeId(this); }
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
import { BeforeInsert, Column, Entity, Index, PrimaryColumn } from 'typeorm';
|
||||
import { ensureSnowflakeId, KtCreateDateColumn, KtDateTime, KtUpdateDateColumn } from '@/common';
|
||||
import type { QqbotMessagePushTargetType } from '../../../contract/message-push/qqbot-message-push.types';
|
||||
|
||||
@Entity('qqbot_message_publish_target')
|
||||
@Index('uk_qqbot_message_publish_target_active_key', ['activeKey'], { unique: true })
|
||||
export class QqbotMessagePublishTarget {
|
||||
@PrimaryColumn({ type: 'bigint' }) id: string;
|
||||
@Column({ name: 'binding_id', type: 'bigint' }) bindingId: string;
|
||||
@Column({ length: 16, name: 'target_type' }) targetType: QqbotMessagePushTargetType;
|
||||
@Column({ length: 64, name: 'target_id' }) targetId: string;
|
||||
@Column({ length: 120, name: 'target_name', nullable: true }) targetName: null | string;
|
||||
@Column({ length: 300, name: 'active_key', nullable: true }) activeKey: null | string;
|
||||
@Column({ default: true }) enabled: boolean;
|
||||
@Column({ default: false, name: 'is_deleted' }) isDeleted: boolean;
|
||||
@KtCreateDateColumn({ name: 'create_time', precision: 6 }) createTime: KtDateTime;
|
||||
@KtUpdateDateColumn({ name: 'update_time', precision: 6 }) updateTime: KtDateTime;
|
||||
|
||||
/** Assigns the Snowflake primary key before this publish target is persisted. */
|
||||
@BeforeInsert()
|
||||
createId() { ensureSnowflakeId(this); }
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
import { BeforeInsert, Column, Entity, Index, PrimaryColumn } from 'typeorm';
|
||||
import { ensureSnowflakeId, KtCreateDateColumn, KtDateTime, KtUpdateDateColumn } from '@/common';
|
||||
|
||||
@Entity('qqbot_message_subscription')
|
||||
@Index('uk_qqbot_message_subscription_active_key', ['activeKey'], { unique: true })
|
||||
export class QqbotMessageSubscription {
|
||||
@PrimaryColumn({ type: 'bigint' }) id: string;
|
||||
@Column({ length: 100 }) name: string;
|
||||
@Column({ length: 128, name: 'source_key' }) sourceKey: string;
|
||||
@Column({ name: 'source_config', type: 'json' }) sourceConfig: Record<string, unknown>;
|
||||
@Column({ length: 64, name: 'source_config_digest', type: 'char' }) sourceConfigDigest: string;
|
||||
@Column({ length: 255, name: 'active_key', nullable: true }) activeKey: null | string;
|
||||
@Column({ default: true }) enabled: boolean;
|
||||
@Column({ length: 500, nullable: true }) remark: null | string;
|
||||
@Column({ default: false, name: 'is_deleted' }) isDeleted: boolean;
|
||||
@KtCreateDateColumn({ name: 'create_time', precision: 6 }) createTime: KtDateTime;
|
||||
@KtUpdateDateColumn({ name: 'update_time', precision: 6 }) updateTime: KtDateTime;
|
||||
|
||||
/** Assigns the Snowflake primary key before this subscription is persisted. */
|
||||
@BeforeInsert()
|
||||
createId() { ensureSnowflakeId(this); }
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
import { BeforeInsert, Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
import { ensureSnowflakeId, KtCreateDateColumn, KtDateTime, KtUpdateDateColumn } from '@/common';
|
||||
|
||||
@Entity('qqbot_message_template')
|
||||
export class QqbotMessageTemplate {
|
||||
@PrimaryColumn({ type: 'bigint' }) id: string;
|
||||
@Column({ length: 100 }) name: string;
|
||||
@Column({ length: 128, name: 'source_key' }) sourceKey: string;
|
||||
@Column({ type: 'text' }) content: string;
|
||||
@Column({ default: true }) enabled: boolean;
|
||||
@Column({ length: 500, nullable: true }) remark: null | string;
|
||||
@Column({ default: false, name: 'is_deleted' }) isDeleted: boolean;
|
||||
@KtCreateDateColumn({ name: 'create_time', precision: 6 }) createTime: KtDateTime;
|
||||
@KtUpdateDateColumn({ name: 'update_time', precision: 6 }) updateTime: KtDateTime;
|
||||
|
||||
/** Assigns the Snowflake primary key before this template is persisted. */
|
||||
@BeforeInsert()
|
||||
createId() { ensureSnowflakeId(this); }
|
||||
}
|
||||
@ -41,6 +41,12 @@ import { QqbotRateLimitService } from '@/modules/qqbot/core/application/send/qqb
|
||||
import { QqbotSendController } from '@/modules/qqbot/core/contract/send/qqbot-send.controller';
|
||||
import { QqbotSendLog } from '@/modules/qqbot/core/infrastructure/persistence/send/qqbot-send-log.entity';
|
||||
import { QqbotSendService } from '@/modules/qqbot/core/application/send/qqbot-send.service';
|
||||
import { QqbotMessageDelivery } from '@/modules/qqbot/core/infrastructure/persistence/message-push/qqbot-message-delivery.entity';
|
||||
import { QqbotMessageEvent } from '@/modules/qqbot/core/infrastructure/persistence/message-push/qqbot-message-event.entity';
|
||||
import { QqbotMessagePublishBinding } from '@/modules/qqbot/core/infrastructure/persistence/message-push/qqbot-message-publish-binding.entity';
|
||||
import { QqbotMessagePublishTarget } from '@/modules/qqbot/core/infrastructure/persistence/message-push/qqbot-message-publish-target.entity';
|
||||
import { QqbotMessageSubscription } from '@/modules/qqbot/core/infrastructure/persistence/message-push/qqbot-message-subscription.entity';
|
||||
import { QqbotMessageTemplate } from '@/modules/qqbot/core/infrastructure/persistence/message-push/qqbot-message-template.entity';
|
||||
|
||||
export { QQBOT_CORE_DOMAIN_CONTRACT } from './contract/qqbot-core.contract';
|
||||
|
||||
@ -55,6 +61,12 @@ export const QQBOT_CORE_ENTITIES = [
|
||||
QqbotConversation,
|
||||
QqbotDedupe,
|
||||
QqbotMessage,
|
||||
QqbotMessageDelivery,
|
||||
QqbotMessageEvent,
|
||||
QqbotMessagePublishBinding,
|
||||
QqbotMessagePublishTarget,
|
||||
QqbotMessageSubscription,
|
||||
QqbotMessageTemplate,
|
||||
QqbotRule,
|
||||
QqbotSendLog,
|
||||
];
|
||||
|
||||
@ -222,6 +222,12 @@ describe('QQBot core module contract', () => {
|
||||
'QqbotConversation',
|
||||
'QqbotDedupe',
|
||||
'QqbotMessage',
|
||||
'QqbotMessageSubscription',
|
||||
'QqbotMessageTemplate',
|
||||
'QqbotMessagePublishBinding',
|
||||
'QqbotMessagePublishTarget',
|
||||
'QqbotMessageEvent',
|
||||
'QqbotMessageDelivery',
|
||||
'QqbotRule',
|
||||
'QqbotSendLog',
|
||||
]),
|
||||
|
||||
@ -0,0 +1,83 @@
|
||||
import { getMetadataArgsStorage } from 'typeorm';
|
||||
import {
|
||||
QQBOT_CORE_ENTITIES,
|
||||
} from '../../../../src/modules/qqbot/core/qqbot-core.module';
|
||||
import { QqbotMessageDelivery } from '../../../../src/modules/qqbot/core/infrastructure/persistence/message-push/qqbot-message-delivery.entity';
|
||||
import { QqbotMessageEvent } from '../../../../src/modules/qqbot/core/infrastructure/persistence/message-push/qqbot-message-event.entity';
|
||||
import { QqbotMessagePublishBinding } from '../../../../src/modules/qqbot/core/infrastructure/persistence/message-push/qqbot-message-publish-binding.entity';
|
||||
import { QqbotMessagePublishTarget } from '../../../../src/modules/qqbot/core/infrastructure/persistence/message-push/qqbot-message-publish-target.entity';
|
||||
import { QqbotMessageSubscription } from '../../../../src/modules/qqbot/core/infrastructure/persistence/message-push/qqbot-message-subscription.entity';
|
||||
import { QqbotMessageTemplate } from '../../../../src/modules/qqbot/core/infrastructure/persistence/message-push/qqbot-message-template.entity';
|
||||
|
||||
const messagePushEntities = [
|
||||
QqbotMessageSubscription,
|
||||
QqbotMessageTemplate,
|
||||
QqbotMessagePublishBinding,
|
||||
QqbotMessagePublishTarget,
|
||||
QqbotMessageEvent,
|
||||
QqbotMessageDelivery,
|
||||
];
|
||||
|
||||
const expectedColumns = {
|
||||
qqbot_message_delivery: [
|
||||
'id', 'message_event_id', 'publish_target_id', 'binding_id',
|
||||
'subscription_id', 'self_id', 'target_type', 'target_id', 'template_id',
|
||||
'template_content', 'variable_snapshot', 'rendered_message', 'status',
|
||||
'attempt_count', 'next_attempt_at', 'processing_lease_until',
|
||||
'send_log_id', 'last_error_code', 'last_error_message', 'expires_at',
|
||||
'create_time', 'update_time',
|
||||
],
|
||||
qqbot_message_event: [
|
||||
'id', 'event_id', 'source_key', 'resource_key', 'occurred_at', 'payload',
|
||||
'fanout_status', 'fanout_attempt_count', 'next_fanout_at',
|
||||
'fanout_lease_until', 'last_error_code', 'last_error_message',
|
||||
'create_time', 'update_time',
|
||||
],
|
||||
qqbot_message_publish_binding: [
|
||||
'id', 'subscription_id', 'account_id', 'self_id', 'template_id',
|
||||
'active_key', 'enabled', 'is_deleted', 'create_time', 'update_time',
|
||||
],
|
||||
qqbot_message_publish_target: [
|
||||
'id', 'binding_id', 'target_type', 'target_id', 'target_name',
|
||||
'active_key', 'enabled', 'is_deleted', 'create_time', 'update_time',
|
||||
],
|
||||
qqbot_message_subscription: [
|
||||
'id', 'name', 'source_key', 'source_config', 'source_config_digest',
|
||||
'active_key', 'enabled', 'remark', 'is_deleted', 'create_time',
|
||||
'update_time',
|
||||
],
|
||||
qqbot_message_template: [
|
||||
'id', 'name', 'source_key', 'content', 'enabled', 'remark',
|
||||
'is_deleted', 'create_time', 'update_time',
|
||||
],
|
||||
} as const;
|
||||
|
||||
/** Reads the table name declared by a TypeORM entity. */
|
||||
const tableNameOf = (entity: object) =>
|
||||
getMetadataArgsStorage().tables.find((table) => table.target === entity)
|
||||
?.name;
|
||||
|
||||
/** Reads the database column names declared by a TypeORM entity. */
|
||||
const columnNamesOf = (entity: object) =>
|
||||
getMetadataArgsStorage()
|
||||
.columns.filter((column) => column.target === entity)
|
||||
.map((column) => `${column.options.name || column.propertyName}`);
|
||||
|
||||
describe('QQBot message-push persistence contract', () => {
|
||||
it('registers the six message-push entities in QQBot Core', () => {
|
||||
expect(QQBOT_CORE_ENTITIES).toEqual(
|
||||
expect.arrayContaining(messagePushEntities),
|
||||
);
|
||||
});
|
||||
|
||||
it('maps every required message-push column with string Snowflake IDs', () => {
|
||||
for (const entity of messagePushEntities) {
|
||||
const tableName = tableNameOf(entity);
|
||||
|
||||
expect(tableName).toBeTruthy();
|
||||
expect(columnNamesOf(entity)).toEqual(
|
||||
expect.arrayContaining([...expectedColumns[tableName as keyof typeof expectedColumns]]),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,97 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
const tables = [
|
||||
'qqbot_message_subscription',
|
||||
'qqbot_message_template',
|
||||
'qqbot_message_publish_binding',
|
||||
'qqbot_message_publish_target',
|
||||
'qqbot_message_event',
|
||||
'qqbot_message_delivery',
|
||||
] as const;
|
||||
|
||||
const menuEntries = [
|
||||
['2041700000000100413', 'QqBotMessageSubscription', 'QqBot:MessageSubscription:List'],
|
||||
['2041700000000100414', 'QqBotMessageTemplate', 'QqBot:MessageTemplate:List'],
|
||||
['2041700000000120461', 'QqBotMessageSubscriptionList', 'QqBot:MessageSubscription:List'],
|
||||
['2041700000000120462', 'QqBotMessageSubscriptionCreate', 'QqBot:MessageSubscription:Create'],
|
||||
['2041700000000120463', 'QqBotMessageSubscriptionUpdate', 'QqBot:MessageSubscription:Update'],
|
||||
['2041700000000120464', 'QqBotMessageSubscriptionDelete', 'QqBot:MessageSubscription:Delete'],
|
||||
['2041700000000120465', 'QqBotMessageSubscriptionToggle', 'QqBot:MessageSubscription:Toggle'],
|
||||
['2041700000000120471', 'QqBotMessageTemplateList', 'QqBot:MessageTemplate:List'],
|
||||
['2041700000000120472', 'QqBotMessageTemplateCreate', 'QqBot:MessageTemplate:Create'],
|
||||
['2041700000000120473', 'QqBotMessageTemplateUpdate', 'QqBot:MessageTemplate:Update'],
|
||||
['2041700000000120474', 'QqBotMessageTemplateDelete', 'QqBot:MessageTemplate:Delete'],
|
||||
['2041700000000120475', 'QqBotMessageTemplateToggle', 'QqBot:MessageTemplate:Toggle'],
|
||||
['2041700000000120476', 'QqBotMessageTemplatePreview', 'QqBot:MessageTemplate:Preview'],
|
||||
['2041700000000120481', 'QqBotAccountMessagePushList', 'QqBot:Account:MessagePush:List'],
|
||||
['2041700000000120482', 'QqBotAccountMessagePushCreate', 'QqBot:Account:MessagePush:Create'],
|
||||
['2041700000000120483', 'QqBotAccountMessagePushUpdate', 'QqBot:Account:MessagePush:Update'],
|
||||
['2041700000000120484', 'QqBotAccountMessagePushDelete', 'QqBot:Account:MessagePush:Delete'],
|
||||
['2041700000000120485', 'QqBotAccountMessagePushToggle', 'QqBot:Account:MessagePush:Toggle'],
|
||||
] as const;
|
||||
|
||||
/** Reads SQL with case and quote differences removed for contract assertions. */
|
||||
const readNormalizedSql = (relativePath: string) =>
|
||||
readFileSync(resolve(process.cwd(), relativePath), 'utf8')
|
||||
.toLowerCase()
|
||||
.replace(/`/g, '')
|
||||
.replace(/\s+/g, ' ');
|
||||
|
||||
describe('QQBot message-push SQL contract', () => {
|
||||
const bootstrapSql = readNormalizedSql('sql/qqbot-init.sql');
|
||||
const schemaSql = readNormalizedSql('sql/refactor-v3/00-full-schema.sql');
|
||||
const seedSql = readNormalizedSql('sql/refactor-v3/01-seed-core.sql');
|
||||
const verifySql = readNormalizedSql('sql/refactor-v3/99-verify.sql');
|
||||
const vbenSql = readNormalizedSql('sql/vben-admin-init.sql');
|
||||
|
||||
it.each(tables)('declares %s in current and bootstrap schema SQL', (table) => {
|
||||
expect(bootstrapSql).toContain(`create table if not exists ${table}`);
|
||||
expect(schemaSql).toContain(`create table if not exists ${table}`);
|
||||
});
|
||||
|
||||
it('keeps six-table indexes, datetime precision, and JSON fields aligned', () => {
|
||||
for (const sql of [bootstrapSql, schemaSql]) {
|
||||
expect(sql).toContain('uk_qqbot_message_subscription_active_key');
|
||||
expect(sql).toContain('uk_qqbot_message_publish_binding_active_key');
|
||||
expect(sql).toContain('uk_qqbot_message_publish_target_active_key');
|
||||
expect(sql).toContain('uk_qqbot_message_event_event_id');
|
||||
expect(sql).toContain('uk_qqbot_message_delivery_event_target');
|
||||
expect(sql).toContain('idx_qqbot_message_event_dispatch');
|
||||
expect(sql).toContain('idx_qqbot_message_event_lease');
|
||||
expect(sql).toContain('idx_qqbot_message_delivery_dispatch');
|
||||
expect(sql).toContain('idx_qqbot_message_delivery_lease');
|
||||
expect(sql).toContain('idx_qqbot_message_delivery_history');
|
||||
expect(sql).toContain('source_config json');
|
||||
expect(sql).toContain('payload json');
|
||||
expect(sql).toContain('variable_snapshot json');
|
||||
expect(sql).toContain('datetime(6)');
|
||||
}
|
||||
});
|
||||
|
||||
it('seeds the default template and every stable menu node idempotently', () => {
|
||||
for (const sql of [bootstrapSql, seedSql]) {
|
||||
expect(sql).toContain('2041700000000200601');
|
||||
expect(sql).toContain('network.stun.mapping-port-changed');
|
||||
expect(sql).toContain('当前stun的端口已变更为${{endpoint}}');
|
||||
expect(sql).toContain('where not exists');
|
||||
}
|
||||
|
||||
for (const [id, name, authCode] of menuEntries) {
|
||||
for (const sql of [bootstrapSql, seedSql, vbenSql]) {
|
||||
expect(sql).toContain(id);
|
||||
expect(sql).toContain(name.toLowerCase());
|
||||
expect(sql).toContain(authCode.toLowerCase());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('verifies every persisted message-push table and seeded menu contract', () => {
|
||||
for (const table of tables) {
|
||||
expect(verifySql).toContain(table);
|
||||
}
|
||||
expect(verifySql).toContain('2041700000000200601');
|
||||
expect(verifySql).toContain('qqbot:messagesubscription:list');
|
||||
expect(verifySql).toContain('qqbot:account:messagepush:toggle');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user