fix: validate explicit migrations and preserve large business ids

This commit is contained in:
Yvan
2026-11-06 07:00:05 +08:00
parent f6f2a4745e
commit 0f0ec2e6d7
53 changed files with 2653 additions and 1445 deletions
+10 -2
View File
@@ -119,7 +119,7 @@ func TestChannelHTTPListsMetricsOptionsAndAtomicCreation(t *testing.T) {
rootRow := func(data map[string]any) map[string]any {
for _, raw := range data["rows"].([]any) {
row := raw.(map[string]any)
if row["id"] == float64(100) && row["sourceDb"] == "lx" {
if row["id"] == "100" && row["sourceDb"] == "lx" {
return row
}
}
@@ -151,7 +151,7 @@ func TestChannelHTTPListsMetricsOptionsAndAtomicCreation(t *testing.T) {
list(8, "?lockStatus=-1", 1)
list(1, "?busierAccount=leaf", 2)
options := call(8, "GET", "/sw/promote/bindOptions?sourceDb=lx", "", 0).([]any)
if len(options) != 1 || options[0].(map[string]any)["platformBusierId"] != float64(14) {
if len(options) != 1 || options[0].(map[string]any)["platformBusierId"] != "14" {
t.Fatal("上级未获取下级正常绑定")
}
body := `{"sourceDb":"lx","platformBusierId":"14","account":"中文渠道","password":"demo123","registerType":1}`
@@ -193,6 +193,14 @@ func TestChannelHTTPListsMetricsOptionsAndAtomicCreation(t *testing.T) {
if game["gameName"] != "汇总游戏" || game["registerCount"] != float64(2) || game["payAmount"] != "70.00" {
t.Fatalf("游戏分布误用了原始注册/充值或错误金额口径: %+v", game)
}
if err := db.Exec("UPDATE tab_promote SET id=9007199254740993 WHERE source_db='lx' AND id=100").Error; err != nil {
t.Fatal(err)
}
large := list(8, "?keyword=main-channel", 1)["rows"].([]any)[0].(map[string]any)
if large["id"] != "9007199254740993" {
t.Fatalf("HTTP response rounded channel ID: %v", large["id"])
}
call(8, "GET", "/sw/promote/game-distribution?sourceDb=lx&promoteId="+large["id"].(string), "", 0)
if err := db.Exec("DROP TABLE tab_promote").Error; err != nil {
t.Fatal(err)
}
+2 -2
View File
@@ -180,7 +180,7 @@ func TestChannelPortraitHTTPTotalTiersAndScope(t *testing.T) {
}
data = call(7, "sourceDb=lx&promoteId=101&period=last7", 0)
basic = data["basic"].(map[string]any)
if basic["parentPromoteId"] != float64(100) || basic["parentPromoteName"] != "root-account" || basic["lastLoginTime"] != nil {
if basic["parentPromoteId"] != "100" || basic["parentPromoteName"] != "root-account" || basic["lastLoginTime"] != nil {
t.Fatal("上级应读同平台当前账号,不能沿用旧快照名")
}
call(99, "sourceDb=lx&promoteId=100", 7)
@@ -189,7 +189,7 @@ func TestChannelPortraitHTTPTotalTiersAndScope(t *testing.T) {
t.Fatal(err)
}
data = call(7, "sourceDb=lx&promoteId=100", 0)
if data["available"] != false || data["metrics"] != nil || data["basic"].(map[string]any)["promoteId"] != float64(100) {
if data["available"] != false || data["metrics"] != nil || data["basic"].(map[string]any)["promoteId"] != "100" {
t.Fatal("缺数据时应保留已授权基本资料并标明未知")
}
}
+1 -1
View File
@@ -110,7 +110,7 @@ func TestLedgerHTTPQueriesAndExcelExports(t *testing.T) {
data := call(7, "GET", "/sw/register/list?sourceDb=lx&sortField=totalPayAmount", "", 0).(map[string]any)
rows := data["rows"].([]any)
first := rows[0].(map[string]any)
if data["total"] != float64(2) || first["userId"] != float64(1) || first["totalPayAmount"] != "1012.34" || first["fgameName"] != "历史游戏" || first["busierName"] != "owner→leaf" {
if data["total"] != float64(2) || first["userId"] != "1" || first["totalPayAmount"] != "1012.34" || first["fgameName"] != "历史游戏" || first["busierName"] != "owner→leaf" {
t.Fatalf("注册明细口径或字段错误: %+v", data)
}
games := call(7, "GET", "/sw/serverAnalysis/games?sourceDb=lx&limit=50", "", 0).([]any)
+21 -1
View File
@@ -9,7 +9,9 @@ import (
"strings"
"kra/internal/config"
"kra/internal/data"
"kra/internal/logging"
"kra/internal/modules"
"kra/internal/server/router"
"kra/internal/service/dto"
systemservice "kra/internal/service/system"
@@ -32,7 +34,9 @@ var (
// Version is the version of the compiled software.
Version string
// flagconf is the config flag.
flagconf string
flagconf string
flagMigrate bool
flagCheckSchema bool
id, _ = os.Hostname()
)
@@ -47,6 +51,8 @@ const (
func init() {
flag.StringVar(&flagconf, "conf", "./configs", "config path, eg: -conf config.yaml")
flag.BoolVar(&flagMigrate, "migrate", false, "apply and verify all migrations, then exit without starting the service")
flag.BoolVar(&flagCheckSchema, "check-schema", false, "verify migration versions and required schema without writing, then exit")
}
// taskRegistry builds the process-wide registry from the dependency-free task
@@ -159,6 +165,20 @@ func main() {
loggerControl.Reload(root, options)
})
defer unsubscribeLogger()
if flagMigrate || flagCheckSchema {
if flagMigrate && flagCheckSchema {
panic("-migrate and -check-schema are mutually exclusive")
}
var database *config.Database
if bc.Data != nil {
database = bc.Data.Database
}
if err := data.MigrateDatabase(context.Background(), database, modules.Catalog(), logger, flagCheckSchema); err != nil {
panic(err)
}
logger.Info("database migration verification completed", "mod", "system", "check_only", flagCheckSchema)
return
}
application, cleanup, err := wireApp(bc.Server, runtime, logger, loggerControl, Version)
if err != nil {