371 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 config defines cron-service runtime configuration.
package config
import (
"strings"
"hyapp/pkg/appcode"
"hyapp/pkg/configx"
"hyapp/pkg/logx"
)
const defaultAppCode = "lalu"
// Config describes cron-service startup and schedule settings.
type Config struct {
// ServiceName is written into logs and health metadata.
ServiceName string `yaml:"service_name"`
// NodeID identifies this scheduler instance in run history and task leases.
NodeID string `yaml:"node_id"`
// Environment is the runtime environment tag used by structured logs.
Environment string `yaml:"environment"`
// GRPCAddr exposes gRPC health and future internal management APIs.
GRPCAddr string `yaml:"grpc_addr"`
// HealthHTTPAddr exposes HTTP health for CLB and deploy scripts only.
HealthHTTPAddr string `yaml:"health_http_addr"`
// MySQLDSN stores cron run history and schedule-level leases only.
MySQLDSN string `yaml:"mysql_dsn"`
// UserServiceAddr is the owner endpoint for user/auth/host batch tasks.
UserServiceAddr string `yaml:"user_service_addr"`
// ActivityServiceAddr is the owner endpoint for activity/message batch tasks.
ActivityServiceAddr string `yaml:"activity_service_addr"`
// GameServiceAddr is the owner endpoint for game/order relay batch tasks.
GameServiceAddr string `yaml:"game_service_addr"`
// WalletServiceAddr is the owner endpoint for wallet/VIP/settlement tasks.
WalletServiceAddr string `yaml:"wallet_service_addr"`
// Tasks contains per-task schedule knobs. Disabled tasks are documented but not started.
Tasks map[string]TaskConfig `yaml:"tasks"`
// Log controls stdout structured logging.
Log logx.Config `yaml:"log"`
}
// TaskConfig keeps scheduler knobs as strings so YAML stays easy to read.
type TaskConfig struct {
// Enabled controls whether cron-service starts a loop for this task.
Enabled bool `yaml:"enabled"`
// Interval is the delay between successful schedule attempts.
Interval string `yaml:"interval"`
// Timeout bounds the owner service RPC call for one batch.
Timeout string `yaml:"timeout"`
// LockTTL is the owner service task claim lock budget.
LockTTL string `yaml:"lock_ttl"`
// BatchSize is passed to owner service batch RPCs.
BatchSize int `yaml:"batch_size"`
// AppCodes scopes the task by tenant; every app_code gets an independent run.
AppCodes []string `yaml:"app_codes"`
// PendingPublishMaxAge is used by mic_open_session_compensation for pending_publish sessions.
PendingPublishMaxAge string `yaml:"pending_publish_max_age"`
// PublishingSessionMaxAge is used by mic_open_session_compensation for publishing sessions.
PublishingSessionMaxAge string `yaml:"publishing_session_max_age"`
// OpenSessionMaxAge is used by room_open_session_compensation for open in-room sessions.
OpenSessionMaxAge string `yaml:"open_session_max_age"`
}
// Default returns local development defaults that follow the 13xxx port rule.
func Default() Config {
return Config{
ServiceName: "cron-service",
NodeID: "cron-local",
Environment: "local",
GRPCAddr: ":13007",
HealthHTTPAddr: ":13107",
MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_cron?parseTime=true&charset=utf8mb4&loc=UTC",
UserServiceAddr: "127.0.0.1:13005",
ActivityServiceAddr: "127.0.0.1:13006",
GameServiceAddr: "127.0.0.1:13008",
WalletServiceAddr: "127.0.0.1:13004",
Tasks: defaultTasks(),
Log: logx.Config{
Level: "info",
Format: "json",
MaxPayloadBytes: 2048,
},
}
}
// Load reads a YAML file into Config and normalizes empty fields.
func Load(path string) (Config, error) {
cfg := Default()
if path == "" {
return cfg, nil
}
if err := configx.LoadYAML(path, &cfg); err != nil {
return Config{}, err
}
normalize(&cfg)
return cfg, nil
}
func normalize(cfg *Config) {
cfg.ServiceName = strings.TrimSpace(cfg.ServiceName)
if cfg.ServiceName == "" {
cfg.ServiceName = "cron-service"
}
cfg.NodeID = strings.TrimSpace(cfg.NodeID)
if cfg.NodeID == "" {
cfg.NodeID = cfg.ServiceName
}
cfg.Environment = strings.ToLower(strings.TrimSpace(cfg.Environment))
if cfg.Environment == "" {
cfg.Environment = "local"
}
cfg.GRPCAddr = strings.TrimSpace(cfg.GRPCAddr)
if cfg.GRPCAddr == "" {
cfg.GRPCAddr = ":13007"
}
cfg.HealthHTTPAddr = strings.TrimSpace(cfg.HealthHTTPAddr)
if cfg.HealthHTTPAddr == "" {
cfg.HealthHTTPAddr = ":13107"
}
cfg.GameServiceAddr = strings.TrimSpace(cfg.GameServiceAddr)
if cfg.GameServiceAddr == "" {
cfg.GameServiceAddr = "127.0.0.1:13008"
}
cfg.Tasks = normalizeTasks(cfg.Tasks)
}
func defaultTasks() map[string]TaskConfig {
return map[string]TaskConfig{
"login_ip_risk": {
Enabled: true,
Interval: "5s",
Timeout: "3s",
LockTTL: "30s",
BatchSize: 100,
AppCodes: []string{defaultAppCode},
},
"user_region_rebuild": {
Enabled: true,
Interval: "5s",
Timeout: "5s",
LockTTL: "30s",
BatchSize: 500,
AppCodes: []string{defaultAppCode},
},
"message_fanout": {
Enabled: true,
Interval: "1s",
Timeout: "10s",
LockTTL: "30s",
BatchSize: 500,
AppCodes: []string{defaultAppCode},
},
"vip_daily_coin_rebate": {
// scheduler 启动即执行,因此不能用 24h 间隔假装 UTC 0 点;每分钟触发一次,
// wallet owner 以 UTC task_day 的 completed run 快速 no-op并在跨日后创建新批次。
Enabled: true,
Interval: "1m",
Timeout: "30s",
LockTTL: "1m",
BatchSize: 500,
AppCodes: []string{"lalu", "fami"},
},
"growth_level_reward": {
Enabled: true,
Interval: "5s",
Timeout: "10s",
LockTTL: "30s",
BatchSize: 100,
AppCodes: []string{defaultAppCode},
},
"temporary_growth_level": {
Enabled: true,
Interval: "5s",
Timeout: "15s",
LockTTL: "30s",
BatchSize: 100,
AppCodes: []string{defaultAppCode},
},
"achievement_reward": {
Enabled: true,
Interval: "5s",
Timeout: "10s",
LockTTL: "30s",
BatchSize: 100,
AppCodes: []string{defaultAppCode},
},
"weekly_star_settlement": {
Enabled: true,
Interval: "5s",
Timeout: "20s",
LockTTL: "1m",
BatchSize: 50,
AppCodes: []string{defaultAppCode},
},
"agency_opening_settlement": {
Enabled: true,
Interval: "5s",
Timeout: "20s",
LockTTL: "1m",
BatchSize: 50,
AppCodes: []string{defaultAppCode},
},
"cp_weekly_rank_settlement": {
Enabled: true,
Interval: "5m",
Timeout: "30s",
LockTTL: "5m",
BatchSize: 50,
AppCodes: []string{defaultAppCode},
},
"room_turnover_reward_settlement": {
Enabled: true,
Interval: "5m",
Timeout: "30s",
LockTTL: "5m",
BatchSize: 100,
AppCodes: []string{defaultAppCode},
},
"cp_intimacy_leaderboard": {
Enabled: true,
Interval: "5m",
Timeout: "20s",
LockTTL: "5m",
BatchSize: 10000,
AppCodes: []string{defaultAppCode},
},
"game_level_event_relay": {
Enabled: true,
Interval: "5s",
Timeout: "10s",
LockTTL: "30s",
BatchSize: 100,
AppCodes: []string{defaultAppCode},
},
"host_salary_daily_settlement": {
Enabled: true,
Interval: "24h",
Timeout: "30s",
LockTTL: "2m",
BatchSize: 200,
AppCodes: []string{defaultAppCode},
},
"host_salary_half_month_settlement": {
Enabled: true,
Interval: "24h",
Timeout: "30s",
LockTTL: "2m",
BatchSize: 200,
AppCodes: []string{defaultAppCode},
},
"host_salary_month_end": {
Enabled: true,
Interval: "24h",
Timeout: "60s",
LockTTL: "5m",
BatchSize: 200,
AppCodes: []string{defaultAppCode},
},
"mic_open_session_compensation": {
Enabled: true,
Interval: "60s",
Timeout: "10s",
LockTTL: "60s",
BatchSize: 100,
AppCodes: []string{defaultAppCode},
PendingPublishMaxAge: "2m",
PublishingSessionMaxAge: "12h",
},
"room_open_session_compensation": {
Enabled: true,
Interval: "60s",
Timeout: "10s",
LockTTL: "60s",
BatchSize: 200,
AppCodes: []string{defaultAppCode},
// stale worker 2 分钟兜底断线离房24h 只兜 room-service 节点崩溃或 outbox 丢失的极端场景。
OpenSessionMaxAge: "24h",
},
"manager_user_block_expiry": {
Enabled: true,
Interval: "30s",
Timeout: "10s",
LockTTL: "30s",
BatchSize: 100,
AppCodes: []string{defaultAppCode},
},
"admin_user_ban_expiry": {
Enabled: true,
Interval: "30s",
Timeout: "10s",
LockTTL: "30s",
BatchSize: 100,
AppCodes: []string{defaultAppCode},
},
}
}
func normalizeTasks(tasks map[string]TaskConfig) map[string]TaskConfig {
defaults := defaultTasks()
if tasks == nil {
tasks = map[string]TaskConfig{}
}
for name, def := range defaults {
current, ok := tasks[name]
if !ok {
tasks[name] = def
continue
}
tasks[name] = mergeTaskConfig(def, current)
}
for name, task := range tasks {
tasks[name] = mergeTaskConfig(TaskConfig{
Interval: "5s",
Timeout: "5s",
LockTTL: "30s",
BatchSize: 100,
AppCodes: []string{defaultAppCode},
}, task)
}
return tasks
}
func mergeTaskConfig(def TaskConfig, current TaskConfig) TaskConfig {
current.Interval = strings.TrimSpace(current.Interval)
if current.Interval == "" {
current.Interval = def.Interval
}
current.Timeout = strings.TrimSpace(current.Timeout)
if current.Timeout == "" {
current.Timeout = def.Timeout
}
current.LockTTL = strings.TrimSpace(current.LockTTL)
if current.LockTTL == "" {
current.LockTTL = def.LockTTL
}
current.PendingPublishMaxAge = strings.TrimSpace(current.PendingPublishMaxAge)
if current.PendingPublishMaxAge == "" {
current.PendingPublishMaxAge = def.PendingPublishMaxAge
}
current.PublishingSessionMaxAge = strings.TrimSpace(current.PublishingSessionMaxAge)
if current.PublishingSessionMaxAge == "" {
current.PublishingSessionMaxAge = def.PublishingSessionMaxAge
}
current.OpenSessionMaxAge = strings.TrimSpace(current.OpenSessionMaxAge)
if current.OpenSessionMaxAge == "" {
current.OpenSessionMaxAge = def.OpenSessionMaxAge
}
if current.BatchSize <= 0 {
current.BatchSize = def.BatchSize
}
current.AppCodes = normalizeAppCodes(current.AppCodes)
if len(current.AppCodes) == 0 {
current.AppCodes = def.AppCodes
}
return current
}
func normalizeAppCodes(values []string) []string {
seen := map[string]bool{}
result := make([]string, 0, len(values))
for _, value := range values {
code := appcode.Normalize(value)
if code == "" || seen[code] {
continue
}
seen[code] = true
result = append(result, code)
}
return result
}