Files
kra-new/internal/data/business/alert_v2_migration.go
T

136 lines
5.7 KiB
Go

package business
import (
"fmt"
"gorm.io/gorm"
"kra/pkg/database/migration"
"strings"
)
func migrateAlertV2(db *gorm.DB) error {
if err := migration.CreateMissingTables(db, &alertGapPO{}, &alertExemptionPO{}, &alertChannelLockPO{}, &alertRulePO{}, &alertRecordPO{}); err != nil {
return err
}
for _, item := range []struct {
model any
fields []string
}{
{&alertRulePO{}, []string{"ConditionMode", "AmountDirection", "AmountThreshold"}},
{&alertRecordPO{}, []string{"TotalPlayers", "TotalPay", "WarningColor", "IndependentRule"}},
} {
if db.Dialector.Name() == "mysql" {
statement := &gorm.Statement{DB: db}
if err := statement.Parse(item.model); err != nil {
return err
}
columns := []string{}
args := []any{}
for _, name := range item.fields {
if !db.Migrator().HasColumn(item.model, name) {
field := statement.Schema.LookUpField(name)
definition := db.Migrator().FullDataTypeOf(field)
columns = append(columns, "ADD COLUMN `"+field.DBName+"` "+definition.SQL)
args = append(args, definition.Vars...)
}
}
// MySQL 5.7 添加普通列会重建表;合并执行,避免每个字段重建一次。
if len(columns) > 0 {
if err := db.Exec("ALTER TABLE `"+statement.Schema.Table+"` "+strings.Join(columns, ", ")+", ALGORITHM=INPLACE, LOCK=NONE", args...).Error; err != nil {
return err
}
}
continue
}
for _, field := range item.fields {
if !db.Migrator().HasColumn(item.model, field) {
if err := db.Migrator().AddColumn(item.model, field); err != nil {
return err
}
}
}
}
if err := migrateRegistrationIndex(db); err != nil {
return err
}
if err := migrateAlertV2UniqueKey(db); err != nil {
return err
}
return migrateQueryIndexes(db, alertV2QueryIndexes)
}
func migrateAlertV2UniqueKey(db *gorm.DB) error {
if db.Dialector.Name() != "mysql" {
return nil
}
// 旧唯一键将已忽略视为未闭环,也无法容纳同类的两条绝对值规则。
// 保留旧生成列,原子替换唯一键;不改写、合并或删除历史记录。
clauses := []string{}
if !db.Migrator().HasColumn("sys_alert_record", "v2_open_key") {
// MySQL 5.7 的在线虚拟列变更不能与索引变更放在同一 ALTER 中。
if err := db.Exec("ALTER TABLE sys_alert_record ADD COLUMN v2_open_key BIGINT UNSIGNED GENERATED ALWAYS AS (CASE WHEN status IN (1,2,3,4) THEN CASE WHEN independent_rule=1 THEN rule_id ELSE 0 END ELSE NULL END) VIRTUAL, ALGORITHM=INPLACE, LOCK=NONE").Error; err != nil {
return err
}
} else {
var column struct {
GenerationExpression string
Extra string
}
if err := db.Raw("SELECT generation_expression,extra FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='sys_alert_record' AND column_name='v2_open_key'").Scan(&column).Error; err != nil {
return err
}
normalize := strings.NewReplacer("`", "", " ", "", "\n", "", "\t", "", "(", "", ")", "")
if normalize.Replace(strings.ToLower(column.GenerationExpression)) != "casewhenstatusin1,2,3,4thencasewhenindependent_rule=1thenrule_idelse0endelsenullend" || !strings.Contains(strings.ToUpper(column.Extra), "VIRTUAL GENERATED") {
return fmt.Errorf("sys_alert_record.v2_open_key 的现有定义不符,未修改原唯一索引")
}
}
var existing []struct {
ColumnName string
NonUnique int
SubPart *int
}
if err := db.Raw("SELECT column_name,non_unique,sub_part FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='sys_alert_record' AND index_name='uk_alert_record_v2_open' ORDER BY seq_in_index").Scan(&existing).Error; err != nil {
return err
}
replaceV2 := false
if len(existing) > 0 {
columns := []string{}
for _, column := range existing {
if column.NonUnique != 0 || column.SubPart != nil {
return fmt.Errorf("sys_alert_record.uk_alert_record_v2_open 必须是完整字段的唯一索引")
}
columns = append(columns, column.ColumnName)
}
layout := strings.Join(columns, ",")
replaceV2 = layout == "platform_code,target_promote_id,rule_type,v2_open_key"
if layout != "business_id,platform_code,target_promote_id,rule_type,v2_open_key" && !replaceV2 {
return fmt.Errorf("sys_alert_record.uk_alert_record_v2_open 的现有索引列不符,未修改原唯一索引")
}
}
for _, name := range []string{"uk_alert_record_unclosed", "uk_alert_record_daily"} {
if db.Migrator().HasIndex("sys_alert_record", name) {
clauses = append(clauses, "DROP INDEX `"+name+"`")
}
}
if replaceV2 {
clauses = append(clauses, "DROP INDEX uk_alert_record_v2_open")
}
if len(existing) == 0 || replaceV2 {
clauses = append(clauses, "ADD UNIQUE INDEX uk_alert_record_v2_open(business_id,platform_code,target_promote_id,rule_type,v2_open_key)")
}
if len(clauses) == 0 {
return nil
}
return db.Exec("ALTER TABLE sys_alert_record " + strings.Join(clauses, ", ") + ", ALGORITHM=INPLACE, LOCK=NONE").Error
}
var alertV2QueryIndexes = []businessQueryIndex{
{"sys_alert_record", "idx_alert_owner_status_time", []string{"business_id", "status", "created_at", "id"}},
{"sys_alert_record", "idx_alert_channel_type_time", []string{"platform_code", "target_promote_id", "rule_type", "created_at", "id"}},
{"sys_alert_record", "idx_alert_owner_time", []string{"business_id", "created_at", "id"}},
{"sys_alert_record", "idx_alert_created", []string{"created_at", "id"}},
{"sys_alert_operation_log", "idx_alert_log_time", []string{"alert_id", "created_at", "id"}},
{"tab_spend", "idx_alert_spend_window", []string{"source_db", "promote_id", "pay_status", "pay_time", "pay_amount"}},
{"tab_user", "idx_alert_user_window", []string{"source_db", "promote_id", "sw_register_time_unix", "puid"}},
{"tab_promote", "idx_alert_promote_tree", []string{"source_db", "top_promote_id", "id"}},
}