2026-06-18 14:19:47 +08:00

175 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package mysql
import "context"
func (r *Repository) ensureAgencyOpeningTables(ctx context.Context) error {
statements := []string{
`CREATE TABLE IF NOT EXISTS agency_opening_cycles (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
cycle_id VARCHAR(96) NOT NULL COMMENT '代理开业周期 ID',
activity_code VARCHAR(64) NOT NULL DEFAULT 'agency_opening' COMMENT '活动编码',
title VARCHAR(128) NOT NULL DEFAULT '' COMMENT '周期标题',
status VARCHAR(32) NOT NULL DEFAULT 'draft' COMMENT 'draft/active/disabled/settling/settled',
start_ms BIGINT NOT NULL COMMENT 'UTC epoch ms 开始,包含',
end_ms BIGINT NOT NULL COMMENT 'UTC epoch ms 结束,不包含',
min_host_count INT NOT NULL DEFAULT 10 COMMENT '申请要求的有效主播数',
max_agency_age_days INT NOT NULL DEFAULT 7 COMMENT '代理创建后允许申请的最大天数0 表示不限制',
created_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '创建管理员',
updated_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '更新管理员',
settled_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '结算完成时间',
locked_by VARCHAR(96) NOT NULL DEFAULT '' COMMENT '结算 worker 锁',
lock_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT '结算锁过期时间',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, cycle_id),
KEY idx_agency_opening_cycle_current (app_code, activity_code, status, start_ms, end_ms),
KEY idx_agency_opening_cycle_due (app_code, activity_code, status, end_ms, lock_until_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='代理开业活动周期'`,
`CREATE TABLE IF NOT EXISTS agency_opening_cycle_rewards (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
cycle_id VARCHAR(96) NOT NULL COMMENT '代理开业周期 ID',
rank_no INT NOT NULL COMMENT '档位序号',
threshold_coin_spent BIGINT NOT NULL DEFAULT 0 COMMENT '命中该档需要的代理房间贡献值 HeatValue',
reward_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '奖励金币',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, cycle_id, rank_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='代理开业流水奖励档位'`,
`CREATE TABLE IF NOT EXISTS agency_opening_applications (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
application_id VARCHAR(96) NOT NULL COMMENT '申请 ID',
cycle_id VARCHAR(96) NOT NULL COMMENT '代理开业周期 ID',
agency_id BIGINT NOT NULL COMMENT '代理 ID',
agency_owner_user_id BIGINT NOT NULL COMMENT '代理 owner 用户 ID',
agency_name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '代理名称快照',
host_count INT NOT NULL DEFAULT 0 COMMENT '申请时有效主播数快照',
agency_created_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '代理创建时间快照',
status VARCHAR(32) NOT NULL DEFAULT 'applied' COMMENT 'applied/approved/pending/granted/failed/expired',
score_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '历史字段名:周期内开业房间贡献值 HeatValue',
reward_rank_no INT NOT NULL DEFAULT 0 COMMENT '结算命中的档位序号',
reward_threshold_coin_spent BIGINT NOT NULL DEFAULT 0 COMMENT '结算命中的流水阈值',
reward_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '结算奖励金币',
wallet_command_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '钱包幂等命令',
wallet_transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '钱包交易 ID',
failure_reason VARCHAR(512) NOT NULL DEFAULT '' COMMENT '失败原因',
applied_at_ms BIGINT NOT NULL COMMENT '申请时间',
approved_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '后台同意时间',
score_start_ms BIGINT NOT NULL DEFAULT 0 COMMENT '计分开始时间',
score_end_ms BIGINT NOT NULL DEFAULT 0 COMMENT '计分截止时间',
reviewed_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '审核管理员',
granted_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '发奖时间',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, application_id),
UNIQUE KEY uk_agency_opening_owner_once (app_code, agency_owner_user_id),
KEY idx_agency_opening_wallet_command (app_code, wallet_command_id),
KEY idx_agency_opening_application_owner (app_code, agency_owner_user_id, applied_at_ms),
KEY idx_agency_opening_agency_cycle (app_code, cycle_id, agency_id),
KEY idx_agency_opening_application_rank (app_code, cycle_id, score_coin_amount, applied_at_ms, agency_id),
KEY idx_agency_opening_application_status (app_code, cycle_id, status, updated_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='代理开业申请和结算事实'`,
`CREATE TABLE IF NOT EXISTS agency_opening_score_events (
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
source_event_id VARCHAR(128) NOT NULL COMMENT 'room-service outbox event_id',
cycle_id VARCHAR(96) NOT NULL COMMENT '代理开业周期 ID',
application_id VARCHAR(96) NOT NULL COMMENT '命中的申请 ID',
agency_id BIGINT NOT NULL COMMENT '代理 ID',
target_user_id BIGINT NOT NULL COMMENT '收礼主播用户 ID',
score_delta BIGINT NOT NULL COMMENT '本次房间贡献值 HeatValue',
occurred_at_ms BIGINT NOT NULL COMMENT '事件发生时间UTC epoch ms',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
PRIMARY KEY (app_code, source_event_id),
KEY idx_agency_opening_score_event_application (app_code, application_id, occurred_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='代理开业流水事件幂等表'`,
}
for _, statement := range statements {
if _, err := r.db.ExecContext(ctx, statement); err != nil {
return err
}
}
if err := r.ensureAgencyOpeningRewardTierColumns(ctx); err != nil {
return err
}
return r.ensureAgencyOpeningApplicationOwnerOnceIndex(ctx)
}
func (r *Repository) ensureAgencyOpeningRewardTierColumns(ctx context.Context) error {
if exists, err := r.columnExists(ctx, "agency_opening_cycle_rewards", "threshold_coin_spent"); err != nil {
return err
} else if !exists {
// 旧本地表按 Top 名次发奖新增阈值列后rank_no 只作为档位序号保留,实际结算按 threshold_coin_spent 命中。
if _, err := r.db.ExecContext(ctx, `ALTER TABLE agency_opening_cycle_rewards ADD COLUMN threshold_coin_spent BIGINT NOT NULL DEFAULT 0 COMMENT '命中该档需要的代理房间流水金币' AFTER rank_no`); err != nil {
return err
}
}
if exists, err := r.columnExists(ctx, "agency_opening_applications", "reward_threshold_coin_spent"); err != nil {
return err
} else if !exists {
if _, err := r.db.ExecContext(ctx, `ALTER TABLE agency_opening_applications ADD COLUMN reward_threshold_coin_spent BIGINT NOT NULL DEFAULT 0 COMMENT '结算命中的流水阈值' AFTER reward_rank_no`); err != nil {
return err
}
}
if exists, err := r.columnExists(ctx, "agency_opening_applications", "approved_at_ms"); err != nil {
return err
} else if !exists {
if _, err := r.db.ExecContext(ctx, `ALTER TABLE agency_opening_applications ADD COLUMN approved_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '后台同意时间' AFTER applied_at_ms`); err != nil {
return err
}
}
if exists, err := r.columnExists(ctx, "agency_opening_applications", "score_start_ms"); err != nil {
return err
} else if !exists {
if _, err := r.db.ExecContext(ctx, `ALTER TABLE agency_opening_applications ADD COLUMN score_start_ms BIGINT NOT NULL DEFAULT 0 COMMENT '计分开始时间' AFTER approved_at_ms`); err != nil {
return err
}
}
if exists, err := r.columnExists(ctx, "agency_opening_applications", "score_end_ms"); err != nil {
return err
} else if !exists {
if _, err := r.db.ExecContext(ctx, `ALTER TABLE agency_opening_applications ADD COLUMN score_end_ms BIGINT NOT NULL DEFAULT 0 COMMENT '计分截止时间' AFTER score_start_ms`); err != nil {
return err
}
}
if exists, err := r.columnExists(ctx, "agency_opening_applications", "reviewed_by_admin_id"); err != nil {
return err
} else if !exists {
if _, err := r.db.ExecContext(ctx, `ALTER TABLE agency_opening_applications ADD COLUMN reviewed_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '审核管理员' AFTER score_end_ms`); err != nil {
return err
}
}
return nil
}
func (r *Repository) ensureAgencyOpeningApplicationOwnerOnceIndex(ctx context.Context) error {
ownerOnceExists, err := r.indexExists(ctx, "agency_opening_applications", "uk_agency_opening_owner_once")
if err != nil {
return err
}
if !ownerOnceExists {
// 申请资格是用户维度的一次性活动,唯一键先落在 owner_user_id 上,避免新周期或更换 agency 后重复参与。
if _, err := r.db.ExecContext(ctx, `ALTER TABLE agency_opening_applications ADD UNIQUE KEY uk_agency_opening_owner_once (app_code, agency_owner_user_id)`); err != nil {
return err
}
}
oldAgencyCycleUniqueExists, err := r.indexExists(ctx, "agency_opening_applications", "uk_agency_opening_agency_cycle")
if err != nil {
return err
}
if oldAgencyCycleUniqueExists {
// 旧唯一键只限制同一周期同一 agency不能表达“每个用户只能参与一次”降级成普通查询索引即可。
if _, err := r.db.ExecContext(ctx, `ALTER TABLE agency_opening_applications DROP INDEX uk_agency_opening_agency_cycle`); err != nil {
return err
}
}
agencyCycleIndexExists, err := r.indexExists(ctx, "agency_opening_applications", "idx_agency_opening_agency_cycle")
if err != nil {
return err
}
if !agencyCycleIndexExists {
if _, err := r.db.ExecContext(ctx, `ALTER TABLE agency_opening_applications ADD KEY idx_agency_opening_agency_cycle (app_code, cycle_id, agency_id)`); err != nil {
return err
}
}
return nil
}