diff --git a/server/admin/internal/repository/money_scope_repository.go b/server/admin/internal/repository/money_scope_repository.go index af7366a5..72c09d3e 100644 --- a/server/admin/internal/repository/money_scope_repository.go +++ b/server/admin/internal/repository/money_scope_repository.go @@ -36,7 +36,9 @@ func (access MoneyAccess) Allows(appCode string, regionID int64) bool { } // RegionIDLooseEqual 对齐 legacy 大区 ID 的浮点圆整误差:雪花/合成 ID 超出 JS 安全整数, -// 前端写回的 scope/筛选值可能是 float64 圆整后的数(相邻 ID 间距远大于圆整粒度,不会误碰)。 +// 浏览器写回的值是 JS Number 的"最短可往返十进制"(如 …690882 → …691000),与 +// int64(float64(x)) 的回转值(…690880)都不相等,唯一可靠的等价层是 float64 本身: +// 只要两个十进制落在同一个 float64 桶里就视为同一区域(相邻 ID 间距远大于桶宽,不会误碰)。 func RegionIDLooseEqual(left int64, right int64) bool { if left == right { return true @@ -44,7 +46,7 @@ func RegionIDLooseEqual(left int64, right int64) bool { if left == 0 || right == 0 { return false } - return int64(float64(left)) == right || left == int64(float64(right)) + return float64(left) == float64(right) } func (access MoneyAccess) AppCodes() []string { diff --git a/server/admin/internal/repository/money_scope_repository_test.go b/server/admin/internal/repository/money_scope_repository_test.go index f4702817..6cf4ffce 100644 --- a/server/admin/internal/repository/money_scope_repository_test.go +++ b/server/admin/internal/repository/money_scope_repository_test.go @@ -82,6 +82,15 @@ func TestRegionIDLooseEqual(t *testing.T) { if !RegionIDLooseEqual(exact, rounded) || !RegionIDLooseEqual(rounded, exact) { t.Fatalf("expected rounded/exact pair to match") } + // 浏览器实际写回的是 JS Number 的最短十进制表示(…691000),与 float64 回转值(…690880)不同, + // 必须在 float64 层等价(线上「区域 2049039732077691000」事故的回归测试)。 + jsShortest := int64(2049039732077691000) + if jsShortest == rounded { + t.Fatalf("fixture should differ from cast value") + } + if !RegionIDLooseEqual(exact, jsShortest) || !RegionIDLooseEqual(jsShortest, exact) { + t.Fatalf("expected js shortest-repr id to match exact id") + } if !RegionIDLooseEqual(7, 7) { t.Fatalf("expected exact small ids to match") } diff --git a/services/activity-service/internal/storage/mysql/repository.go b/services/activity-service/internal/storage/mysql/repository.go index 694ccc71..73e3098d 100644 --- a/services/activity-service/internal/storage/mysql/repository.go +++ b/services/activity-service/internal/storage/mysql/repository.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "strings" + "time" _ "github.com/go-sql-driver/mysql" "hyapp/pkg/appcode" @@ -29,6 +30,11 @@ func Open(ctx context.Context, dsn string) (*Repository, error) { return nil, err } + // 标准库默认不限制连接数,高并发下会打爆 MySQL max_connections,必须显式设上限。 + db.SetMaxOpenConns(20) + db.SetMaxIdleConns(10) + db.SetConnMaxLifetime(30 * time.Minute) + if err := db.PingContext(ctx); err != nil { _ = db.Close() return nil, err diff --git a/services/cron-service/internal/storage/mysql/repository.go b/services/cron-service/internal/storage/mysql/repository.go index 7a7939ab..29c026ea 100644 --- a/services/cron-service/internal/storage/mysql/repository.go +++ b/services/cron-service/internal/storage/mysql/repository.go @@ -42,6 +42,11 @@ func Open(ctx context.Context, dsn string) (*Repository, error) { if err != nil { return nil, err } + // database/sql defaults to unlimited connections; cap the pool so bursts + // cannot exhaust MySQL max_connections. + db.SetMaxOpenConns(20) + db.SetMaxIdleConns(10) + db.SetConnMaxLifetime(30 * time.Minute) if err := db.PingContext(ctx); err != nil { _ = db.Close() return nil, err diff --git a/services/game-service/internal/storage/mysql/repository.go b/services/game-service/internal/storage/mysql/repository.go index ec7c391a..7fa55b19 100644 --- a/services/game-service/internal/storage/mysql/repository.go +++ b/services/game-service/internal/storage/mysql/repository.go @@ -31,6 +31,10 @@ func Open(ctx context.Context, dsn string) (*Repository, error) { if err != nil { return nil, err } + // 标准库默认不限制连接数,高并发下会打爆 MySQL max_connections,必须显式设上限。 + db.SetMaxOpenConns(20) + db.SetMaxIdleConns(10) + db.SetConnMaxLifetime(30 * time.Minute) if err := db.PingContext(ctx); err != nil { _ = db.Close() return nil, err diff --git a/services/statistics-service/internal/storage/mysql/repository.go b/services/statistics-service/internal/storage/mysql/repository.go index 576635db..ce5474a5 100644 --- a/services/statistics-service/internal/storage/mysql/repository.go +++ b/services/statistics-service/internal/storage/mysql/repository.go @@ -43,6 +43,11 @@ func Open(ctx context.Context, dsn string, activityDSNs ...string) (*Repository, if err != nil { return nil, err } + // database/sql defaults to unlimited connections; cap the pool so bursts + // cannot exhaust MySQL max_connections. + db.SetMaxOpenConns(20) + db.SetMaxIdleConns(10) + db.SetConnMaxLifetime(30 * time.Minute) if err := db.PingContext(ctx); err != nil { _ = db.Close() return nil, err @@ -54,6 +59,11 @@ func Open(ctx context.Context, dsn string, activityDSNs ...string) (*Repository, _ = db.Close() return nil, err } + // The activity connection only serves low-volume snapshot reads, so it + // gets a smaller cap than the primary pool. + activityDB.SetMaxOpenConns(8) + activityDB.SetMaxIdleConns(4) + activityDB.SetConnMaxLifetime(30 * time.Minute) if err := activityDB.PingContext(ctx); err != nil { _ = activityDB.Close() _ = db.Close() diff --git a/services/user-service/internal/storage/mysql/db.go b/services/user-service/internal/storage/mysql/db.go index 60b6508b..49c9a247 100644 --- a/services/user-service/internal/storage/mysql/db.go +++ b/services/user-service/internal/storage/mysql/db.go @@ -5,6 +5,7 @@ import ( "context" "database/sql" "strings" + "time" "hyapp/pkg/xerr" ) @@ -29,6 +30,12 @@ func Open(ctx context.Context, dsn string) (*Repository, error) { return nil, err } + // 标准库默认不限制连接数,高并发下会打爆 MySQL max_connections,必须显式设上限。 + db.SetMaxOpenConns(20) + db.SetMaxIdleConns(10) + // 长连接定期轮换,避免云数据库或网络层回收导致长期坏连接驻留。 + db.SetConnMaxLifetime(30 * time.Minute) + if err := db.PingContext(ctx); err != nil { // 启动探测失败时关闭连接池,避免返回半初始化 repository。 _ = db.Close() diff --git a/services/wallet-service/internal/storage/mysql/repository.go b/services/wallet-service/internal/storage/mysql/repository.go index 3ed0a045..6e6c7c4f 100644 --- a/services/wallet-service/internal/storage/mysql/repository.go +++ b/services/wallet-service/internal/storage/mysql/repository.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "strings" + "time" "hyapp/pkg/xerr" ) @@ -25,6 +26,11 @@ func Open(ctx context.Context, dsn string) (*Repository, error) { return nil, err } + // 标准库默认不限制连接数,高并发下会打爆 MySQL max_connections,必须显式设上限。 + db.SetMaxOpenConns(20) + db.SetMaxIdleConns(10) + db.SetConnMaxLifetime(30 * time.Minute) + if err := db.PingContext(ctx); err != nil { _ = db.Close() return nil, err