311 lines
14 KiB
SQL
311 lines
14 KiB
SQL
-- Aslan Game King: dedicated consume-to-draw campaign, ranking and settlement.
|
|
--
|
|
-- Performance review (DDL only; this migration must not run ad-hoc against production):
|
|
-- 1. Wallet hot path inserts one ledger row by uq_activity_asset, then updates one overall PK row
|
|
-- and one daily PK row. All are O(log N); ranking never GROUP BYs the append-only ledger.
|
|
-- 2. Overall/daily Tycoon and Victorious rankings read at most 30/100 rows through dimension
|
|
-- indexes whose prefixes bind activity_id (and stat_date for daily rankings).
|
|
-- 3. Current campaign lookup uses idx_aslan_gk_activity_window. start_time is the range column;
|
|
-- end_time remains an index-covered residual filter, which is acceptable because concurrent
|
|
-- enabled campaigns per sys_origin are expected to be very small.
|
|
-- 4. Draw request and settlement retry paths use unique keys. The recovery scan uses
|
|
-- idx_aslan_gk_draw_recover (delivery_status, update_time, id), so the five-minute task does
|
|
-- not scan or filesort the full draw table as records accumulate.
|
|
-- 5. Settlement due scans bind settlement_status first and range on settlement_time through
|
|
-- idx_aslan_gk_activity_settle; end_time remains available for the bounded due-order scan.
|
|
-- Per-reward delivery writes only the small configured group and reads it by owner/status.
|
|
-- 6. No UPDATE/DELETE scans an unindexed predicate. The migration only creates empty tables and
|
|
-- idempotent menu rows; it does not backfill wallet history or lock existing business tables.
|
|
-- 7. Unlimited prize stock (-1) is never updated at draw time, avoiding a global hot-row lock;
|
|
-- only finite stock uses the activity/prize primary-key update.
|
|
|
|
SET NAMES utf8mb4;
|
|
|
|
CREATE TABLE IF NOT EXISTS aslan_game_king_activity (
|
|
id BIGINT NOT NULL,
|
|
activity_code VARCHAR(64) NOT NULL,
|
|
activity_name VARCHAR(128) NOT NULL,
|
|
activity_desc VARCHAR(1000) NULL,
|
|
sys_origin VARCHAR(32) NOT NULL,
|
|
time_zone VARCHAR(64) NOT NULL DEFAULT 'Asia/Riyadh',
|
|
start_time DATETIME(3) NOT NULL,
|
|
end_time DATETIME(3) NOT NULL,
|
|
settlement_delay_minutes INT NOT NULL DEFAULT 30,
|
|
settlement_time DATETIME(3) NOT NULL,
|
|
coin_per_draw BIGINT NOT NULL,
|
|
event_types VARCHAR(2000) NOT NULL,
|
|
ranking_type VARCHAR(32) NOT NULL DEFAULT 'TYCOON',
|
|
ranking_period VARCHAR(32) NOT NULL DEFAULT 'OVERALL',
|
|
enabled TINYINT(1) NOT NULL DEFAULT 0,
|
|
settlement_status VARCHAR(32) NOT NULL DEFAULT 'NOT_STARTED',
|
|
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uq_aslan_gk_activity_code (activity_code),
|
|
KEY idx_aslan_gk_activity_window (sys_origin, enabled, start_time, end_time),
|
|
KEY idx_aslan_gk_activity_settle (settlement_status, settlement_time, end_time)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
COMMENT='Aslan Game King campaign';
|
|
|
|
CREATE TABLE IF NOT EXISTS aslan_game_king_prize (
|
|
id BIGINT NOT NULL,
|
|
activity_id BIGINT NOT NULL,
|
|
prize_name VARCHAR(128) NOT NULL,
|
|
prize_image VARCHAR(500) NULL,
|
|
weight BIGINT NOT NULL,
|
|
stock BIGINT NOT NULL DEFAULT -1 COMMENT '-1 unlimited, 0 depleted, positive remaining',
|
|
resource_group_id BIGINT NOT NULL,
|
|
enabled TINYINT(1) NOT NULL DEFAULT 1,
|
|
sort_order TINYINT NOT NULL,
|
|
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uq_aslan_gk_prize_position (activity_id, sort_order),
|
|
KEY idx_aslan_gk_prize_enabled (activity_id, enabled)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
COMMENT='Seven configured prizes for Aslan Game King';
|
|
|
|
CREATE TABLE IF NOT EXISTS aslan_game_king_rank_reward (
|
|
id BIGINT NOT NULL,
|
|
activity_id BIGINT NOT NULL,
|
|
start_rank SMALLINT NOT NULL,
|
|
end_rank SMALLINT NOT NULL,
|
|
resource_group_id BIGINT NOT NULL,
|
|
reward_name VARCHAR(128) NULL,
|
|
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uq_aslan_gk_rank_range (activity_id, start_rank, end_rank),
|
|
KEY idx_aslan_gk_rank_start (activity_id, start_rank)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
COMMENT='Six exact end-settlement rank reward ranges';
|
|
|
|
CREATE TABLE IF NOT EXISTS aslan_game_king_wallet_ledger (
|
|
id BIGINT NOT NULL,
|
|
activity_id BIGINT NOT NULL,
|
|
asset_record_id BIGINT NOT NULL,
|
|
user_id BIGINT NOT NULL,
|
|
sys_origin VARCHAR(32) NOT NULL,
|
|
event_type VARCHAR(64) NOT NULL,
|
|
event_id VARCHAR(128) NULL,
|
|
receipt_type TINYINT NOT NULL COMMENT '0 income/winning, 1 expenditure/consumption',
|
|
amount DECIMAL(24,2) NOT NULL,
|
|
event_time DATETIME(3) NOT NULL,
|
|
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uq_aslan_gk_activity_asset (activity_id, asset_record_id),
|
|
KEY idx_aslan_gk_ledger_user (activity_id, user_id, event_time)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
COMMENT='Idempotent eligible game expenditure and winning ledger';
|
|
|
|
CREATE TABLE IF NOT EXISTS aslan_game_king_user (
|
|
activity_id BIGINT NOT NULL,
|
|
user_id BIGINT NOT NULL,
|
|
total_consumed DECIMAL(24,2) NOT NULL DEFAULT 0,
|
|
total_won DECIMAL(24,2) NOT NULL DEFAULT 0,
|
|
used_chances BIGINT NOT NULL DEFAULT 0,
|
|
consume_reached_time DATETIME(3) NULL,
|
|
win_reached_time DATETIME(3) NULL,
|
|
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (activity_id, user_id),
|
|
KEY idx_aslan_gk_user_tycoon (
|
|
activity_id,
|
|
total_consumed DESC,
|
|
consume_reached_time ASC,
|
|
user_id ASC
|
|
),
|
|
KEY idx_aslan_gk_user_victorious (
|
|
activity_id,
|
|
total_won DESC,
|
|
win_reached_time ASC,
|
|
user_id ASC
|
|
)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
COMMENT='Overall Tycoon/Victorious aggregates and used draw chances';
|
|
|
|
CREATE TABLE IF NOT EXISTS aslan_game_king_user_daily (
|
|
activity_id BIGINT NOT NULL,
|
|
stat_date DATE NOT NULL,
|
|
user_id BIGINT NOT NULL,
|
|
total_consumed DECIMAL(24,2) NOT NULL DEFAULT 0,
|
|
total_won DECIMAL(24,2) NOT NULL DEFAULT 0,
|
|
consume_reached_time DATETIME(3) NULL,
|
|
win_reached_time DATETIME(3) NULL,
|
|
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (activity_id, stat_date, user_id),
|
|
KEY idx_aslan_gk_daily_tycoon (
|
|
activity_id, stat_date, total_consumed DESC, consume_reached_time ASC, user_id ASC
|
|
),
|
|
KEY idx_aslan_gk_daily_victorious (
|
|
activity_id, stat_date, total_won DESC, win_reached_time ASC, user_id ASC
|
|
)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
COMMENT='Timezone-materialized daily Tycoon/Victorious aggregates';
|
|
|
|
CREATE TABLE IF NOT EXISTS aslan_game_king_draw_record (
|
|
id BIGINT NOT NULL,
|
|
activity_id BIGINT NOT NULL,
|
|
user_id BIGINT NOT NULL,
|
|
request_id VARCHAR(64) NOT NULL,
|
|
prize_id BIGINT NOT NULL,
|
|
prize_name VARCHAR(128) NOT NULL,
|
|
prize_image VARCHAR(500) NULL,
|
|
resource_group_id BIGINT NULL,
|
|
delivery_status VARCHAR(32) NOT NULL,
|
|
failure_reason VARCHAR(500) NULL,
|
|
draw_time DATETIME(3) NOT NULL,
|
|
deliver_time DATETIME(3) NULL,
|
|
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uq_aslan_gk_draw_request (activity_id, user_id, request_id),
|
|
KEY idx_aslan_gk_draw_user (activity_id, user_id, id),
|
|
KEY idx_aslan_gk_draw_manage (activity_id, delivery_status, id),
|
|
KEY idx_aslan_gk_draw_recover (delivery_status, update_time, id)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
COMMENT='Idempotent Aslan Game King draws and delivery state';
|
|
|
|
CREATE TABLE IF NOT EXISTS aslan_game_king_settlement_record (
|
|
id BIGINT NOT NULL,
|
|
activity_id BIGINT NOT NULL,
|
|
user_id BIGINT NOT NULL,
|
|
rank_no SMALLINT NOT NULL,
|
|
total_consumed DECIMAL(24,2) NOT NULL,
|
|
rank_reward_id BIGINT NOT NULL,
|
|
resource_group_id BIGINT NOT NULL,
|
|
business_no VARCHAR(128) NOT NULL,
|
|
delivery_status VARCHAR(32) NOT NULL,
|
|
retry_count INT NOT NULL DEFAULT 0,
|
|
failure_reason VARCHAR(500) NULL,
|
|
deliver_time DATETIME(3) NULL,
|
|
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uq_aslan_gk_settlement_user (activity_id, user_id),
|
|
UNIQUE KEY uq_aslan_gk_settlement_rank (activity_id, rank_no),
|
|
UNIQUE KEY uq_aslan_gk_settlement_business (business_no),
|
|
KEY idx_aslan_gk_settlement_status (activity_id, delivery_status)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
COMMENT='Frozen top-30 settlement snapshot and retry state';
|
|
|
|
CREATE TABLE IF NOT EXISTS aslan_game_king_delivery_item (
|
|
id BIGINT NOT NULL,
|
|
owner_type VARCHAR(20) NOT NULL COMMENT 'DRAW or SETTLEMENT',
|
|
owner_id BIGINT NOT NULL,
|
|
activity_id BIGINT NOT NULL,
|
|
user_id BIGINT NOT NULL,
|
|
reward_config_id BIGINT NOT NULL,
|
|
resource_group_id BIGINT NOT NULL,
|
|
reward_type VARCHAR(64) NOT NULL,
|
|
detail_type VARCHAR(128) NULL,
|
|
content VARCHAR(2000) NULL,
|
|
quantity INT NULL,
|
|
sort_order INT NULL,
|
|
remark VARCHAR(1000) NULL,
|
|
delivery_status VARCHAR(32) NOT NULL,
|
|
retry_count INT NOT NULL DEFAULT 0,
|
|
failure_reason VARCHAR(500) NULL,
|
|
deliver_time DATETIME(3) NULL,
|
|
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uq_aslan_gk_delivery_config (owner_type, owner_id, reward_config_id),
|
|
KEY idx_aslan_gk_delivery_owner (owner_type, owner_id, delivery_status),
|
|
KEY idx_aslan_gk_delivery_activity (activity_id, delivery_status, id)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
COMMENT='Per reward-config transactional delivery outbox';
|
|
|
|
-- The following menu/resource statements are idempotent and inherit only roles that already own
|
|
-- the Operations directory. They do not grant the campaign page to unrelated roles.
|
|
INSERT INTO sys_resource (id, resource_name, mapping, method, auth_type, perm, update_time)
|
|
VALUES
|
|
('activity:game-king:list', '游戏王活动列表', '/activity/game-king/list', 'GET', 3, 'activity:game-king:list', NOW()),
|
|
('activity:game-king:detail', '游戏王活动详情', '/activity/game-king/detail/**', 'GET', 3, 'activity:game-king:detail', NOW()),
|
|
('activity:game-king:save', '保存游戏王活动', '/activity/game-king/save', 'POST', 3, 'activity:game-king:save', NOW()),
|
|
('activity:game-king:enable', '启停游戏王活动', '/activity/game-king/enable/**', 'PUT', 3, 'activity:game-king:enable', NOW()),
|
|
('activity:game-king:prizes:get', '游戏王奖品列表', '/activity/game-king/prizes/**', 'GET', 3, 'activity:game-king:prizes:get', NOW()),
|
|
('activity:game-king:prizes:save', '保存游戏王奖品', '/activity/game-king/prizes/**', 'PUT', 3, 'activity:game-king:prizes:save', NOW()),
|
|
('activity:game-king:rank:get', '游戏王排名奖励', '/activity/game-king/rank-rewards/**', 'GET', 3, 'activity:game-king:rank:get', NOW()),
|
|
('activity:game-king:rank:save', '保存游戏王排名奖励', '/activity/game-king/rank-rewards/**', 'PUT', 3, 'activity:game-king:rank:save', NOW()),
|
|
('activity:game-king:draw:get', '游戏王抽奖记录', '/activity/game-king/draw-records/**', 'GET', 3, 'activity:game-king:draw:get', NOW()),
|
|
('activity:game-king:draw:retry', '重试游戏王抽奖奖励', '/activity/game-king/draw/retry/**', 'POST', 3, 'activity:game-king:draw:retry', NOW()),
|
|
('activity:game-king:delivery:resolve', '核账处理游戏王未知奖励', '/activity/game-king/delivery-item/resolve/**', 'POST', 3, 'activity:game-king:delivery:resolve', NOW()),
|
|
('activity:game-king:settlement:get', '游戏王结算记录', '/activity/game-king/settlement-records/**', 'GET', 3, 'activity:game-king:settlement:get', NOW()),
|
|
('activity:game-king:settlement:run', '执行游戏王结算', '/activity/game-king/settlement/**', 'POST', 3, 'activity:game-king:settlement:run', NOW()),
|
|
('activity:game-king:backfill', '补算游戏王消费', '/activity/game-king/backfill/**', 'POST', 3, 'activity:game-king:backfill', NOW())
|
|
ON DUPLICATE KEY UPDATE
|
|
resource_name = VALUES(resource_name),
|
|
mapping = VALUES(mapping),
|
|
method = VALUES(method),
|
|
auth_type = VALUES(auth_type),
|
|
perm = VALUES(perm),
|
|
update_time = NOW();
|
|
|
|
INSERT INTO sys_menu (
|
|
parent_id, menu_name, path, router, menu_type, icon, alias, sort, status,
|
|
create_user, update_user, create_time, update_time
|
|
)
|
|
SELECT
|
|
parent.id,
|
|
'游戏王活动',
|
|
'activity/game-king/index',
|
|
'activity/game-king',
|
|
2,
|
|
'trophy',
|
|
'ActivityGameKing',
|
|
88,
|
|
0,
|
|
'0',
|
|
'0',
|
|
NOW(),
|
|
NOW()
|
|
FROM sys_menu parent
|
|
LEFT JOIN sys_menu existing ON existing.alias = 'ActivityGameKing'
|
|
WHERE (parent.alias = 'OperationManager' OR parent.menu_name = '运营管理')
|
|
AND parent.menu_type = 1
|
|
AND existing.id IS NULL
|
|
LIMIT 1;
|
|
|
|
UPDATE sys_menu campaign_menu
|
|
JOIN (
|
|
SELECT id
|
|
FROM sys_menu
|
|
WHERE (alias = 'OperationManager' OR menu_name = '运营管理')
|
|
AND menu_type = 1
|
|
LIMIT 1
|
|
) parent ON 1 = 1
|
|
SET
|
|
campaign_menu.parent_id = parent.id,
|
|
campaign_menu.menu_name = '游戏王活动',
|
|
campaign_menu.path = 'activity/game-king/index',
|
|
campaign_menu.router = 'activity/game-king',
|
|
campaign_menu.menu_type = 2,
|
|
campaign_menu.icon = 'trophy',
|
|
campaign_menu.sort = 88,
|
|
campaign_menu.status = 0,
|
|
campaign_menu.update_user = '0',
|
|
campaign_menu.update_time = NOW()
|
|
WHERE campaign_menu.alias = 'ActivityGameKing';
|
|
|
|
INSERT INTO sys_menu_resource (menu_id, resource_id)
|
|
SELECT menu.id, resource.id
|
|
FROM sys_menu menu
|
|
JOIN sys_resource resource ON resource.id LIKE 'activity:game-king:%'
|
|
LEFT JOIN sys_menu_resource existing
|
|
ON existing.menu_id = menu.id AND existing.resource_id = resource.id
|
|
WHERE menu.alias = 'ActivityGameKing'
|
|
AND existing.id IS NULL;
|
|
|
|
INSERT INTO sys_role_menu (role_id, menu_id)
|
|
SELECT DISTINCT source_role.role_id, campaign_menu.id
|
|
FROM sys_role_menu source_role
|
|
JOIN sys_menu source_menu ON source_menu.id = source_role.menu_id
|
|
JOIN sys_menu campaign_menu ON campaign_menu.alias = 'ActivityGameKing'
|
|
LEFT JOIN sys_role_menu existing
|
|
ON existing.role_id = source_role.role_id AND existing.menu_id = campaign_menu.id
|
|
WHERE (source_menu.alias = 'OperationManager' OR source_menu.menu_name = '运营管理')
|
|
AND source_menu.menu_type = 1
|
|
AND existing.id IS NULL;
|