feat(M10-A): 完成统一通知中心与投递闭环
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
DROP TRIGGER IF EXISTS qipai_notification_attempts_no_delete;
|
||||
DROP TRIGGER IF EXISTS qipai_notification_attempts_no_update;
|
||||
DELETE rp FROM qipai_role_permissions rp INNER JOIN qipai_permissions p ON p.id = rp.permission_id
|
||||
WHERE p.code IN ('notification.read', 'notification.manage');
|
||||
DELETE FROM qipai_permissions WHERE code IN ('notification.read', 'notification.manage');
|
||||
DROP TABLE IF EXISTS qipai_notification_attempts;
|
||||
DROP TABLE IF EXISTS qipai_notification_deliveries;
|
||||
DROP TABLE IF EXISTS qipai_notification_subscriptions;
|
||||
DROP TABLE IF EXISTS qipai_notification_routes;
|
||||
DROP TABLE IF EXISTS qipai_notification_templates;
|
||||
DELETE FROM qipai_schema_migrations WHERE version = '2026081110';
|
||||
@@ -0,0 +1,154 @@
|
||||
CREATE TABLE IF NOT EXISTS qipai_notification_templates (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_id BIGINT UNSIGNED NOT NULL,
|
||||
store_id BIGINT UNSIGNED NULL,
|
||||
scope_store_id BIGINT UNSIGNED GENERATED ALWAYS AS (COALESCE(store_id, 0)) STORED,
|
||||
template_code VARCHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
||||
event_type VARCHAR(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
||||
channel VARCHAR(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
||||
title_template VARCHAR(256) NOT NULL,
|
||||
body_template VARCHAR(2000) NOT NULL,
|
||||
external_template_id VARCHAR(128) NOT NULL DEFAULT '',
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
|
||||
version INT UNSIGNED NOT NULL DEFAULT 1,
|
||||
created_by BIGINT UNSIGNED NULL,
|
||||
updated_by BIGINT UNSIGNED NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
CONSTRAINT fk_qipai_notification_template_tenant FOREIGN KEY (tenant_id) REFERENCES qipai_tenants(id),
|
||||
CONSTRAINT fk_qipai_notification_template_store FOREIGN KEY (tenant_id, store_id) REFERENCES qipai_stores(tenant_id, id),
|
||||
CONSTRAINT fk_qipai_notification_template_creator FOREIGN KEY (tenant_id, created_by) REFERENCES qipai_users(tenant_id, id),
|
||||
CONSTRAINT fk_qipai_notification_template_updater FOREIGN KEY (tenant_id, updated_by) REFERENCES qipai_users(tenant_id, id),
|
||||
UNIQUE KEY uq_qipai_notification_template_scope (tenant_id, scope_store_id, template_code),
|
||||
UNIQUE KEY uq_qipai_notification_template_scope_id (tenant_id, id),
|
||||
KEY idx_qipai_notification_template_event (tenant_id, event_type, channel, status),
|
||||
CONSTRAINT chk_qipai_notification_template_channel CHECK (channel IN ('IN_APP', 'WECHAT_SUBSCRIBE', 'WE_COM', 'WEBHOOK', 'CLOUD_SPEAKER')),
|
||||
CONSTRAINT chk_qipai_notification_template_status CHECK (status IN ('ACTIVE', 'DISABLED')),
|
||||
CONSTRAINT chk_qipai_notification_template_version CHECK (version >= 1)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qipai_notification_routes (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_id BIGINT UNSIGNED NOT NULL,
|
||||
store_id BIGINT UNSIGNED NULL,
|
||||
scope_store_id BIGINT UNSIGNED GENERATED ALWAYS AS (COALESCE(store_id, 0)) STORED,
|
||||
event_type VARCHAR(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
||||
template_id BIGINT UNSIGNED NOT NULL,
|
||||
recipient_type VARCHAR(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
||||
recipient_value VARCHAR(191) NOT NULL,
|
||||
quiet_start TIME NULL,
|
||||
quiet_end TIME NULL,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
|
||||
version INT UNSIGNED NOT NULL DEFAULT 1,
|
||||
created_by BIGINT UNSIGNED NULL,
|
||||
updated_by BIGINT UNSIGNED NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
CONSTRAINT fk_qipai_notification_route_tenant FOREIGN KEY (tenant_id) REFERENCES qipai_tenants(id),
|
||||
CONSTRAINT fk_qipai_notification_route_store FOREIGN KEY (tenant_id, store_id) REFERENCES qipai_stores(tenant_id, id),
|
||||
CONSTRAINT fk_qipai_notification_route_template FOREIGN KEY (tenant_id, template_id) REFERENCES qipai_notification_templates(tenant_id, id),
|
||||
CONSTRAINT fk_qipai_notification_route_creator FOREIGN KEY (tenant_id, created_by) REFERENCES qipai_users(tenant_id, id),
|
||||
CONSTRAINT fk_qipai_notification_route_updater FOREIGN KEY (tenant_id, updated_by) REFERENCES qipai_users(tenant_id, id),
|
||||
UNIQUE KEY uq_qipai_notification_route_scope_id (tenant_id, id),
|
||||
UNIQUE KEY uq_qipai_notification_route (tenant_id, scope_store_id, event_type, template_id, recipient_type, recipient_value),
|
||||
KEY idx_qipai_notification_route_match (tenant_id, event_type, store_id, status),
|
||||
CONSTRAINT chk_qipai_notification_route_recipient CHECK (recipient_type IN ('CUSTOMER', 'USER', 'ROLE', 'STORE_WEBHOOK')),
|
||||
CONSTRAINT chk_qipai_notification_route_status CHECK (status IN ('ACTIVE', 'DISABLED')),
|
||||
CONSTRAINT chk_qipai_notification_route_quiet CHECK ((quiet_start IS NULL AND quiet_end IS NULL) OR (quiet_start IS NOT NULL AND quiet_end IS NOT NULL)),
|
||||
CONSTRAINT chk_qipai_notification_route_version CHECK (version >= 1)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qipai_notification_subscriptions (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
channel VARCHAR(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
||||
template_code VARCHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
||||
status VARCHAR(16) NOT NULL,
|
||||
authorized_at DATETIME(3) NOT NULL,
|
||||
revoked_at DATETIME(3) NULL,
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
CONSTRAINT fk_qipai_notification_subscription_user FOREIGN KEY (tenant_id, user_id) REFERENCES qipai_users(tenant_id, id),
|
||||
UNIQUE KEY uq_qipai_notification_subscription (tenant_id, user_id, channel, template_code),
|
||||
CONSTRAINT chk_qipai_notification_subscription_channel CHECK (channel IN ('WECHAT_SUBSCRIBE')),
|
||||
CONSTRAINT chk_qipai_notification_subscription_status CHECK (status IN ('AUTHORIZED', 'REVOKED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qipai_notification_deliveries (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_id BIGINT UNSIGNED NOT NULL,
|
||||
store_id BIGINT UNSIGNED NULL,
|
||||
outbox_event_id BIGINT UNSIGNED NOT NULL,
|
||||
route_id BIGINT UNSIGNED NOT NULL,
|
||||
template_id BIGINT UNSIGNED NOT NULL,
|
||||
event_type VARCHAR(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
||||
channel VARCHAR(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
||||
recipient_type VARCHAR(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
||||
recipient_id VARCHAR(191) NOT NULL,
|
||||
recipient_masked VARCHAR(191) NOT NULL,
|
||||
title_snapshot VARCHAR(256) NOT NULL,
|
||||
body_snapshot VARCHAR(2000) NOT NULL,
|
||||
payload_snapshot JSON NOT NULL,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
||||
attempts INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
max_attempts INT UNSIGNED NOT NULL DEFAULT 8,
|
||||
available_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
sent_at DATETIME(3) NULL,
|
||||
read_at DATETIME(3) NULL,
|
||||
last_error_code VARCHAR(64) NOT NULL DEFAULT '',
|
||||
last_error_message VARCHAR(512) NOT NULL DEFAULT '',
|
||||
provider_message_id VARCHAR(191) NOT NULL DEFAULT '',
|
||||
manual_retry_count INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
CONSTRAINT fk_qipai_notification_delivery_tenant FOREIGN KEY (tenant_id) REFERENCES qipai_tenants(id),
|
||||
CONSTRAINT fk_qipai_notification_delivery_store FOREIGN KEY (tenant_id, store_id) REFERENCES qipai_stores(tenant_id, id),
|
||||
CONSTRAINT fk_qipai_notification_delivery_outbox FOREIGN KEY (outbox_event_id) REFERENCES qipai_outbox_events(id),
|
||||
CONSTRAINT fk_qipai_notification_delivery_route FOREIGN KEY (tenant_id, route_id) REFERENCES qipai_notification_routes(tenant_id, id),
|
||||
CONSTRAINT fk_qipai_notification_delivery_template FOREIGN KEY (tenant_id, template_id) REFERENCES qipai_notification_templates(tenant_id, id),
|
||||
UNIQUE KEY uq_qipai_notification_delivery_scope_id (tenant_id, id),
|
||||
UNIQUE KEY uq_qipai_notification_delivery_idempotency (tenant_id, outbox_event_id, channel, recipient_type, recipient_id, template_id),
|
||||
KEY idx_qipai_notification_delivery_dispatch (status, available_at, id),
|
||||
KEY idx_qipai_notification_delivery_management (tenant_id, store_id, status, created_at, id),
|
||||
KEY idx_qipai_notification_delivery_inbox (tenant_id, recipient_type, recipient_id, channel, read_at, created_at),
|
||||
CONSTRAINT chk_qipai_notification_delivery_status CHECK (status IN ('PENDING', 'PROCESSING', 'SENT', 'RETRY', 'FAILED', 'SUPPRESSED')),
|
||||
CONSTRAINT chk_qipai_notification_delivery_attempts CHECK (attempts <= max_attempts AND max_attempts BETWEEN 1 AND 20)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qipai_notification_attempts (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_id BIGINT UNSIGNED NOT NULL,
|
||||
delivery_id BIGINT UNSIGNED NOT NULL,
|
||||
attempt_no INT UNSIGNED NOT NULL,
|
||||
trigger_type VARCHAR(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
||||
status VARCHAR(16) NOT NULL,
|
||||
error_code VARCHAR(64) NOT NULL DEFAULT '',
|
||||
error_message VARCHAR(512) NOT NULL DEFAULT '',
|
||||
provider_message_id VARCHAR(191) NOT NULL DEFAULT '',
|
||||
operator_id BIGINT UNSIGNED NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
CONSTRAINT fk_qipai_notification_attempt_delivery FOREIGN KEY (tenant_id, delivery_id) REFERENCES qipai_notification_deliveries(tenant_id, id),
|
||||
CONSTRAINT fk_qipai_notification_attempt_operator FOREIGN KEY (tenant_id, operator_id) REFERENCES qipai_users(tenant_id, id),
|
||||
UNIQUE KEY uq_qipai_notification_attempt (tenant_id, delivery_id, attempt_no),
|
||||
CONSTRAINT chk_qipai_notification_attempt_trigger CHECK (trigger_type IN ('AUTO', 'MANUAL')),
|
||||
CONSTRAINT chk_qipai_notification_attempt_status CHECK (status IN ('SENT', 'FAILED', 'SUPPRESSED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
DROP TRIGGER IF EXISTS qipai_notification_attempts_no_update;
|
||||
CREATE TRIGGER qipai_notification_attempts_no_update BEFORE UPDATE ON qipai_notification_attempts
|
||||
FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'NOTIFICATION_ATTEMPT_IMMUTABLE';
|
||||
DROP TRIGGER IF EXISTS qipai_notification_attempts_no_delete;
|
||||
CREATE TRIGGER qipai_notification_attempts_no_delete BEFORE DELETE ON qipai_notification_attempts
|
||||
FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'NOTIFICATION_ATTEMPT_IMMUTABLE';
|
||||
|
||||
INSERT IGNORE INTO qipai_permissions (code, name, category) VALUES
|
||||
('notification.read', '查看通知中心', 'notification'),
|
||||
('notification.manage', '管理通知与补发', 'notification');
|
||||
INSERT IGNORE INTO qipai_role_permissions (tenant_id, role_id, permission_id)
|
||||
SELECT r.tenant_id, r.id, p.id FROM qipai_roles r
|
||||
INNER JOIN qipai_permissions p ON p.code IN ('notification.read', 'notification.manage')
|
||||
WHERE r.code IN ('STORE_ADMIN', 'TENANT_ADMIN', 'PLATFORM_ADMIN')
|
||||
AND r.status = 'ACTIVE' AND r.deleted_at IS NULL;
|
||||
|
||||
INSERT IGNORE INTO qipai_schema_migrations (version, name)
|
||||
VALUES ('2026081110', 'm10a_notification_center');
|
||||
@@ -0,0 +1,26 @@
|
||||
SELECT table_name FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE() AND table_name IN (
|
||||
'qipai_notification_templates', 'qipai_notification_routes',
|
||||
'qipai_notification_subscriptions', 'qipai_notification_deliveries',
|
||||
'qipai_notification_attempts'
|
||||
) ORDER BY table_name;
|
||||
SELECT table_name, index_name FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE() AND index_name IN (
|
||||
'uq_qipai_notification_template_scope', 'idx_qipai_notification_template_event',
|
||||
'uq_qipai_notification_route', 'idx_qipai_notification_route_match',
|
||||
'uq_qipai_notification_subscription', 'uq_qipai_notification_delivery_idempotency',
|
||||
'idx_qipai_notification_delivery_dispatch', 'idx_qipai_notification_delivery_management',
|
||||
'uq_qipai_notification_attempt'
|
||||
) GROUP BY table_name, index_name ORDER BY table_name, index_name;
|
||||
SELECT trigger_name FROM information_schema.triggers
|
||||
WHERE trigger_schema = DATABASE() AND trigger_name IN (
|
||||
'qipai_notification_attempts_no_update', 'qipai_notification_attempts_no_delete'
|
||||
) ORDER BY trigger_name;
|
||||
SELECT code FROM qipai_permissions
|
||||
WHERE code IN ('notification.read', 'notification.manage') ORDER BY code;
|
||||
SELECT COUNT(*) AS assigned_role_permission_count FROM qipai_role_permissions rp
|
||||
INNER JOIN qipai_roles r ON r.tenant_id = rp.tenant_id AND r.id = rp.role_id
|
||||
INNER JOIN qipai_permissions p ON p.id = rp.permission_id
|
||||
WHERE r.code IN ('STORE_ADMIN', 'TENANT_ADMIN', 'PLATFORM_ADMIN')
|
||||
AND p.code IN ('notification.read', 'notification.manage');
|
||||
SELECT version, name FROM qipai_schema_migrations WHERE version = '2026081110';
|
||||
Reference in New Issue
Block a user