package config import ( "errors" "net/http" "strings" "time" "hyapp/pkg/configx" "hyapp/pkg/logx" "hyapp/pkg/tencentim" ) // Config 描述 activity-service 启动配置。 type Config struct { ServiceName string `yaml:"service_name"` NodeID string `yaml:"node_id"` Environment string `yaml:"environment"` GRPCAddr string `yaml:"grpc_addr"` // HealthHTTPAddr 是给 CLB/发布脚本探活使用的 HTTP 监听地址,不承载业务流量。 HealthHTTPAddr string `yaml:"health_http_addr"` // MySQLDSN 是活动状态、事件消费幂等和 outbox 的必需存储连接串。 MySQLDSN string `yaml:"mysql_dsn"` // UserServiceAddr 是 fanout worker 读取目标用户游标的内部 gRPC 地址。 UserServiceAddr string `yaml:"user_service_addr"` // WalletServiceAddr 是任务奖励入账依赖;activity 只发命令,不直接写余额。 WalletServiceAddr string `yaml:"wallet_service_addr"` // RoomServiceAddr 是房间流水奖励结算读取房主快照的内部 gRPC 地址。 RoomServiceAddr string `yaml:"room_service_addr"` // FirstRechargeRewardWorker 控制首冲奖励对 wallet_outbox 充值事实的本地消费。 FirstRechargeRewardWorker FirstRechargeRewardWorkerConfig `yaml:"first_recharge_reward_worker"` // CumulativeRechargeRewardWorker 控制累充奖励对 wallet_outbox 充值事实的本地消费。 CumulativeRechargeRewardWorker CumulativeRechargeRewardWorkerConfig `yaml:"cumulative_recharge_reward_worker"` // InviteActivityRewardWorker 控制邀请活动对 wallet/user outbox 事实的本地消费。 InviteActivityRewardWorker InviteActivityRewardWorkerConfig `yaml:"invite_activity_reward_worker"` // RedPacketBroadcastWorker 控制红包创建事实转区域飘屏 outbox 的本地消费。 RedPacketBroadcastWorker RedPacketBroadcastWorkerConfig `yaml:"red_packet_broadcast_worker"` // TaskEventWorker 控制任务系统对 wallet/user outbox 事实的本地消费。 TaskEventWorker TaskEventWorkerConfig `yaml:"task_event_worker"` // UserLeaderboardWorker 控制用户送礼榜单对 wallet_outbox 送礼事实的 Redis 投影。 UserLeaderboardWorker UserLeaderboardWorkerConfig `yaml:"user_leaderboard_worker"` // LuckyGiftWorker 控制幸运礼物 draw outbox 的返奖和房间 IM 补偿。 LuckyGiftWorker LuckyGiftWorkerConfig `yaml:"lucky_gift_worker"` // TencentIM 是 activity-service 发送全局/区域播报群消息的 REST 配置。 TencentIM TencentIMConfig `yaml:"tencent_im"` // Broadcast 控制 IM 播报群 reconciler、outbox worker 和贵重礼物阈值。 Broadcast BroadcastConfig `yaml:"broadcast"` // RocketMQ 可直接消费 room-service 发布的 room_outbox fanout topic。 RocketMQ RocketMQConfig `yaml:"rocketmq"` // MySQLAutoMigrate 保留给显式本地迁移;线上 schema 由发布流程或 initdb 管理。 MySQLAutoMigrate bool `yaml:"mysql_auto_migrate"` Consumer ConsumerConfig `yaml:"consumer"` MessageInbox MessageInboxConfig `yaml:"message_inbox"` // MessageActionConfirmWorker 控制通用确认消息的 user_outbox 消费和 IM outbox 发布。 MessageActionConfirmWorker MessageActionConfirmWorkerConfig `yaml:"message_action_confirm_worker"` Log logx.Config `yaml:"log"` } // ConsumerConfig 保存 room outbox 消费底座配置。 type ConsumerConfig struct { RoomOutboxPollIntervalMs int64 `yaml:"room_outbox_poll_interval_ms"` BatchSize int `yaml:"batch_size"` } // MessageInboxConfig 保存 App system/activity inbox 的读取缓存参数;fanout 扫描由 cron-service 调度。 type MessageInboxConfig struct { UnreadCacheTTL string `yaml:"unread_cache_ttl"` } // MessageActionConfirmWorkerConfig 保存确认消息消费和 MQ 发布策略。 type MessageActionConfirmWorkerConfig struct { Enabled bool `yaml:"enabled"` OutboxPollInterval time.Duration `yaml:"outbox_poll_interval"` OutboxBatchSize int `yaml:"outbox_batch_size"` OutboxLockTTL time.Duration `yaml:"outbox_lock_ttl"` OutboxMaxRetry int `yaml:"outbox_max_retry"` PublishTimeout time.Duration `yaml:"publish_timeout"` } // FirstRechargeRewardWorkerConfig 保存钱包充值事实消费策略。 type FirstRechargeRewardWorkerConfig struct { // Enabled 控制是否启动 wallet_outbox MQ 消费;关闭后仍可通过 gRPC 消费入口补偿。 Enabled bool `yaml:"enabled"` } // CumulativeRechargeRewardWorkerConfig 保存累充奖励钱包充值事实消费策略。 type CumulativeRechargeRewardWorkerConfig struct { // Enabled 控制是否启动 wallet_outbox MQ 消费;关闭后仍可通过 gRPC 消费入口补偿。 Enabled bool `yaml:"enabled"` } // InviteActivityRewardWorkerConfig 保存邀请活动钱包和用户事实消费策略。 type InviteActivityRewardWorkerConfig struct { // Enabled 控制是否启动 wallet/user outbox MQ 消费。 Enabled bool `yaml:"enabled"` } // RedPacketBroadcastWorkerConfig 保存红包创建事实消费策略。 type RedPacketBroadcastWorkerConfig struct { // Enabled 控制是否启动 wallet_outbox MQ 消费。 Enabled bool `yaml:"enabled"` } // TaskEventWorkerConfig 保存任务进度事实消费策略。 type TaskEventWorkerConfig struct { // Enabled 控制是否启动 wallet/user outbox 任务消费者;room outbox 任务投影跟随 room_outbox 主消费者。 Enabled bool `yaml:"enabled"` } // UserLeaderboardWorkerConfig 保存用户送礼榜单 Redis 投影策略。 type UserLeaderboardWorkerConfig struct { // Enabled 控制是否启动 wallet_outbox 用户榜单消费者;关闭时 gateway 只能读到已有 Redis 聚合。 Enabled bool `yaml:"enabled"` // RedisAddr 是 activity 写入用户榜单聚合的 Redis 地址,必须与 gateway 读取地址一致。 RedisAddr string `yaml:"redis_addr"` // RedisPassword 是 Redis 鉴权密码,本地默认留空。 RedisPassword string `yaml:"redis_password"` // RedisDB 是 Redis logical DB,必须与 gateway 读取配置一致。 RedisDB int `yaml:"redis_db"` // KeyPrefix 隔离用户榜单 key,必须与 gateway 读取配置一致。 KeyPrefix string `yaml:"key_prefix"` } // LuckyGiftWorkerConfig 保存幸运礼物抽奖副作用补偿策略。 type LuckyGiftWorkerConfig struct { Enabled bool `yaml:"enabled"` WorkerPollInterval time.Duration `yaml:"worker_poll_interval"` WorkerBatchSize int `yaml:"worker_batch_size"` WorkerConcurrency int `yaml:"worker_concurrency"` WorkerLockTTL time.Duration `yaml:"worker_lock_ttl"` WorkerMaxRetry int `yaml:"worker_max_retry"` PublishTimeout time.Duration `yaml:"publish_timeout"` StatsRefreshInterval time.Duration `yaml:"stats_refresh_interval"` StatsBatchSize int `yaml:"stats_batch_size"` } // TencentIMConfig 描述 activity-service 调用腾讯云 IM REST API 的配置。 type TencentIMConfig struct { Enabled bool `yaml:"enabled"` SDKAppID int64 `yaml:"sdk_app_id"` SecretKey string `yaml:"secret_key"` AdminIdentifier string `yaml:"admin_identifier"` AdminUserSigTTL time.Duration `yaml:"admin_user_sig_ttl"` Endpoint string `yaml:"endpoint"` GroupType string `yaml:"group_type"` GroupIDPrefix string `yaml:"group_id_prefix"` RequestTimeout time.Duration `yaml:"request_timeout"` } // RESTConfig converts YAML into the shared Tencent IM REST client config. func (c TencentIMConfig) RESTConfig() tencentim.RESTConfig { timeout := c.RequestTimeout if timeout <= 0 { timeout = 5 * time.Second } return tencentim.RESTConfig{ SDKAppID: c.SDKAppID, SecretKey: c.SecretKey, AdminIdentifier: c.AdminIdentifier, AdminUserSigTTL: c.AdminUserSigTTL, Endpoint: c.Endpoint, GroupType: c.GroupType, HTTPClient: &http.Client{Timeout: timeout}, } } // BroadcastConfig stores activity-service broadcast worker policy. type BroadcastConfig struct { Enabled bool `yaml:"enabled"` SuperGiftMinValue int64 `yaml:"super_gift_min_value"` WorkerPollInterval time.Duration `yaml:"worker_poll_interval"` WorkerBatchSize int `yaml:"worker_batch_size"` WorkerLockTTL time.Duration `yaml:"worker_lock_ttl"` WorkerMaxRetry int `yaml:"worker_max_retry"` EnsureGroupsOnStartup bool `yaml:"ensure_groups_on_startup"` } // RocketMQConfig 描述 activity-service 消费房间事实的 MQ 连接。 type RocketMQConfig struct { Enabled bool `yaml:"enabled"` NameServers []string `yaml:"name_servers"` NameServerDomain string `yaml:"name_server_domain"` AccessKey string `yaml:"access_key"` SecretKey string `yaml:"secret_key"` SecurityToken string `yaml:"security_token"` Namespace string `yaml:"namespace"` VIPChannel bool `yaml:"vip_channel"` RoomOutbox RoomOutboxMQConfig `yaml:"room_outbox"` WalletOutbox WalletOutboxMQConfig `yaml:"wallet_outbox"` UserOutbox UserOutboxMQConfig `yaml:"user_outbox"` MessageActionOutbox MessageActionOutboxMQConfig `yaml:"message_action_outbox"` } // RoomOutboxMQConfig 控制 activity 对 room_outbox topic 的消费位点。 type RoomOutboxMQConfig struct { Enabled bool `yaml:"enabled"` Topic string `yaml:"topic"` ConsumerGroup string `yaml:"consumer_group"` ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"` } // WalletOutboxMQConfig 控制 activity 对 wallet_outbox topic 的独立消费组。 type WalletOutboxMQConfig struct { Enabled bool `yaml:"enabled"` Topic string `yaml:"topic"` RealtimeTopic string `yaml:"realtime_topic"` FirstRechargeConsumerGroup string `yaml:"first_recharge_consumer_group"` CumulativeRechargeConsumerGroup string `yaml:"cumulative_recharge_consumer_group"` InviteActivityConsumerGroup string `yaml:"invite_activity_consumer_group"` RedPacketBroadcastConsumerGroup string `yaml:"red_packet_broadcast_consumer_group"` TaskConsumerGroup string `yaml:"task_consumer_group"` UserLeaderboardConsumerGroup string `yaml:"user_leaderboard_consumer_group"` ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"` } // UserOutboxMQConfig 控制 activity 对 user_outbox topic 的独立消费组。 type UserOutboxMQConfig struct { Enabled bool `yaml:"enabled"` Topic string `yaml:"topic"` ConsumerGroup string `yaml:"consumer_group"` TaskConsumerGroup string `yaml:"task_consumer_group"` InviteActivityConsumerGroup string `yaml:"invite_activity_consumer_group"` MessageActionConsumerGroup string `yaml:"message_action_consumer_group"` ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"` } // MessageActionOutboxMQConfig 控制确认消息 outbox 投递到 notice-service 的 MQ producer。 type MessageActionOutboxMQConfig struct { Enabled bool `yaml:"enabled"` Topic string `yaml:"topic"` ProducerGroup string `yaml:"producer_group"` SendTimeout time.Duration `yaml:"send_timeout"` Retry int `yaml:"retry"` } // Default 返回本地默认配置。 func Default() Config { return Config{ ServiceName: "activity-service", NodeID: "activity-local", Environment: "local", GRPCAddr: ":13006", HealthHTTPAddr: ":13106", MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_activity?parseTime=true&charset=utf8mb4&loc=UTC", UserServiceAddr: "127.0.0.1:13005", WalletServiceAddr: "127.0.0.1:13004", RoomServiceAddr: "127.0.0.1:13001", FirstRechargeRewardWorker: FirstRechargeRewardWorkerConfig{ Enabled: false, }, CumulativeRechargeRewardWorker: CumulativeRechargeRewardWorkerConfig{ Enabled: false, }, InviteActivityRewardWorker: InviteActivityRewardWorkerConfig{ Enabled: true, }, RedPacketBroadcastWorker: RedPacketBroadcastWorkerConfig{ Enabled: false, }, TaskEventWorker: TaskEventWorkerConfig{ Enabled: true, }, UserLeaderboardWorker: UserLeaderboardWorkerConfig{ Enabled: false, }, MessageActionConfirmWorker: MessageActionConfirmWorkerConfig{ Enabled: true, OutboxPollInterval: time.Second, OutboxBatchSize: 100, OutboxLockTTL: 30 * time.Second, OutboxMaxRetry: 8, PublishTimeout: 5 * time.Second, }, LuckyGiftWorker: LuckyGiftWorkerConfig{ Enabled: false, WorkerPollInterval: time.Second, WorkerBatchSize: 100, WorkerConcurrency: 4, WorkerLockTTL: 30 * time.Second, WorkerMaxRetry: 8, PublishTimeout: 5 * time.Second, StatsRefreshInterval: 10 * time.Minute, StatsBatchSize: 5000, }, TencentIM: TencentIMConfig{ Enabled: false, AdminIdentifier: "administrator", AdminUserSigTTL: 24 * time.Hour, Endpoint: tencentim.DefaultEndpoint, GroupType: tencentim.DefaultGroupType, RequestTimeout: 5 * time.Second, }, Broadcast: BroadcastConfig{ Enabled: false, SuperGiftMinValue: 100000, WorkerPollInterval: time.Second, WorkerBatchSize: 100, WorkerLockTTL: 30 * time.Second, WorkerMaxRetry: 8, EnsureGroupsOnStartup: true, }, RocketMQ: defaultRocketMQConfig(), MySQLAutoMigrate: false, Consumer: ConsumerConfig{ RoomOutboxPollIntervalMs: 1000, BatchSize: 100, }, MessageInbox: MessageInboxConfig{ UnreadCacheTTL: "60s", }, Log: logx.Config{ Level: "info", Format: "json", MaxPayloadBytes: 2048, }, } } func defaultRocketMQConfig() RocketMQConfig { return RocketMQConfig{ Enabled: false, RoomOutbox: RoomOutboxMQConfig{ Enabled: false, Topic: "hyapp_room_outbox", ConsumerGroup: "hyapp-activity-room-outbox", ConsumerMaxReconsumeTimes: 16, }, WalletOutbox: WalletOutboxMQConfig{ Enabled: false, Topic: "hyapp_wallet_outbox", RealtimeTopic: "", FirstRechargeConsumerGroup: "hyapp-activity-first-recharge-wallet-outbox", CumulativeRechargeConsumerGroup: "hyapp-activity-cumulative-recharge-wallet-outbox", InviteActivityConsumerGroup: "hyapp-activity-invite-wallet-outbox", RedPacketBroadcastConsumerGroup: "hyapp-activity-red-packet-wallet-outbox", TaskConsumerGroup: "hyapp-activity-task-wallet-outbox", UserLeaderboardConsumerGroup: "hyapp-activity-user-leaderboard-wallet-outbox", ConsumerMaxReconsumeTimes: 16, }, UserOutbox: UserOutboxMQConfig{ Enabled: false, Topic: "hyapp_user_outbox", ConsumerGroup: "hyapp-activity-user-region-broadcast", TaskConsumerGroup: "hyapp-activity-task-user-outbox", InviteActivityConsumerGroup: "hyapp-activity-invite-user-outbox", MessageActionConsumerGroup: "hyapp-activity-message-action-user-outbox", ConsumerMaxReconsumeTimes: 16, }, MessageActionOutbox: MessageActionOutboxMQConfig{ Enabled: false, Topic: "hyapp_message_action_outbox", ProducerGroup: "hyapp-activity-message-action-outbox", SendTimeout: 5 * time.Second, Retry: 2, }, } } // Load 从独立 config.yaml 读取 activity-service 配置。 func Load(path string) (Config, error) { cfg := Default() if path == "" { return cfg, nil } if err := configx.LoadYAML(path, &cfg); err != nil { return Config{}, err } if strings.TrimSpace(cfg.UserServiceAddr) == "" { cfg.UserServiceAddr = Default().UserServiceAddr } if strings.TrimSpace(cfg.WalletServiceAddr) == "" { cfg.WalletServiceAddr = Default().WalletServiceAddr } if strings.TrimSpace(cfg.RoomServiceAddr) == "" { cfg.RoomServiceAddr = Default().RoomServiceAddr } cfg.ServiceName = strings.TrimSpace(cfg.ServiceName) if cfg.ServiceName == "" { cfg.ServiceName = "activity-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.HealthHTTPAddr = strings.TrimSpace(cfg.HealthHTTPAddr) if cfg.HealthHTTPAddr == "" { cfg.HealthHTTPAddr = ":13106" } cfg.TencentIM.AdminIdentifier = strings.TrimSpace(cfg.TencentIM.AdminIdentifier) if cfg.TencentIM.AdminIdentifier == "" { cfg.TencentIM.AdminIdentifier = "administrator" } cfg.TencentIM.Endpoint = strings.TrimSpace(cfg.TencentIM.Endpoint) if cfg.TencentIM.Endpoint == "" { cfg.TencentIM.Endpoint = tencentim.DefaultEndpoint } cfg.TencentIM.GroupType = strings.TrimSpace(cfg.TencentIM.GroupType) if cfg.TencentIM.GroupType == "" { cfg.TencentIM.GroupType = tencentim.DefaultGroupType } cfg.TencentIM.GroupIDPrefix = strings.TrimSpace(cfg.TencentIM.GroupIDPrefix) if cfg.TencentIM.AdminUserSigTTL <= 0 { cfg.TencentIM.AdminUserSigTTL = 24 * time.Hour } if cfg.TencentIM.RequestTimeout <= 0 { cfg.TencentIM.RequestTimeout = 5 * time.Second } if cfg.Broadcast.SuperGiftMinValue <= 0 { cfg.Broadcast.SuperGiftMinValue = 100000 } if cfg.Broadcast.WorkerPollInterval <= 0 { cfg.Broadcast.WorkerPollInterval = time.Second } if cfg.Broadcast.WorkerBatchSize <= 0 { cfg.Broadcast.WorkerBatchSize = 100 } if cfg.Broadcast.WorkerLockTTL <= 0 { cfg.Broadcast.WorkerLockTTL = 30 * time.Second } if cfg.Broadcast.WorkerMaxRetry <= 0 { cfg.Broadcast.WorkerMaxRetry = 8 } if cfg.LuckyGiftWorker.WorkerPollInterval <= 0 { cfg.LuckyGiftWorker.WorkerPollInterval = time.Second } if cfg.LuckyGiftWorker.WorkerBatchSize <= 0 { cfg.LuckyGiftWorker.WorkerBatchSize = 100 } if cfg.LuckyGiftWorker.WorkerConcurrency <= 0 { cfg.LuckyGiftWorker.WorkerConcurrency = 4 } if cfg.LuckyGiftWorker.WorkerConcurrency > cfg.LuckyGiftWorker.WorkerBatchSize { cfg.LuckyGiftWorker.WorkerConcurrency = cfg.LuckyGiftWorker.WorkerBatchSize } if cfg.LuckyGiftWorker.WorkerLockTTL <= 0 { cfg.LuckyGiftWorker.WorkerLockTTL = 30 * time.Second } if cfg.LuckyGiftWorker.WorkerMaxRetry <= 0 { cfg.LuckyGiftWorker.WorkerMaxRetry = 8 } if cfg.LuckyGiftWorker.PublishTimeout <= 0 { cfg.LuckyGiftWorker.PublishTimeout = 5 * time.Second } if cfg.MessageActionConfirmWorker.OutboxPollInterval <= 0 { cfg.MessageActionConfirmWorker.OutboxPollInterval = time.Second } if cfg.MessageActionConfirmWorker.OutboxBatchSize <= 0 { cfg.MessageActionConfirmWorker.OutboxBatchSize = 100 } if cfg.MessageActionConfirmWorker.OutboxLockTTL <= 0 { cfg.MessageActionConfirmWorker.OutboxLockTTL = 30 * time.Second } if cfg.MessageActionConfirmWorker.OutboxMaxRetry <= 0 { cfg.MessageActionConfirmWorker.OutboxMaxRetry = 8 } if cfg.MessageActionConfirmWorker.PublishTimeout <= 0 { cfg.MessageActionConfirmWorker.PublishTimeout = 5 * time.Second } cfg.UserLeaderboardWorker.RedisAddr = strings.TrimSpace(cfg.UserLeaderboardWorker.RedisAddr) cfg.UserLeaderboardWorker.KeyPrefix = strings.TrimSpace(cfg.UserLeaderboardWorker.KeyPrefix) rocketMQ, err := normalizeRocketMQConfig(cfg.RocketMQ) if err != nil { return Config{}, err } cfg.RocketMQ = rocketMQ if (cfg.FirstRechargeRewardWorker.Enabled || cfg.CumulativeRechargeRewardWorker.Enabled || cfg.InviteActivityRewardWorker.Enabled || cfg.RedPacketBroadcastWorker.Enabled || cfg.UserLeaderboardWorker.Enabled) && !cfg.RocketMQ.WalletOutbox.Enabled { return Config{}, errors.New("wallet outbox workers require rocketmq.wallet_outbox.enabled") } if cfg.UserLeaderboardWorker.Enabled && cfg.UserLeaderboardWorker.RedisAddr == "" { return Config{}, errors.New("user leaderboard worker requires redis_addr") } if cfg.InviteActivityRewardWorker.Enabled && !cfg.RocketMQ.UserOutbox.Enabled { return Config{}, errors.New("invite activity reward worker requires rocketmq.user_outbox.enabled") } if cfg.MessageActionConfirmWorker.Enabled && (!cfg.RocketMQ.UserOutbox.Enabled || !cfg.RocketMQ.MessageActionOutbox.Enabled) { return Config{}, errors.New("message action confirm worker requires rocketmq.user_outbox.enabled and rocketmq.message_action_outbox.enabled") } if cfg.LuckyGiftWorker.Enabled && !cfg.TencentIM.Enabled { return Config{}, errors.New("lucky gift worker requires tencent_im.enabled") } return cfg, nil } func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) { defaults := defaultRocketMQConfig() cfg.NameServers = normalizeStringSlice(cfg.NameServers) cfg.NameServerDomain = strings.TrimSpace(cfg.NameServerDomain) cfg.AccessKey = strings.TrimSpace(cfg.AccessKey) cfg.SecretKey = strings.TrimSpace(cfg.SecretKey) cfg.SecurityToken = strings.TrimSpace(cfg.SecurityToken) cfg.Namespace = strings.TrimSpace(cfg.Namespace) if cfg.RoomOutbox.Topic = strings.TrimSpace(cfg.RoomOutbox.Topic); cfg.RoomOutbox.Topic == "" { cfg.RoomOutbox.Topic = defaults.RoomOutbox.Topic } if cfg.RoomOutbox.ConsumerGroup = strings.TrimSpace(cfg.RoomOutbox.ConsumerGroup); cfg.RoomOutbox.ConsumerGroup == "" { cfg.RoomOutbox.ConsumerGroup = defaults.RoomOutbox.ConsumerGroup } if cfg.RoomOutbox.ConsumerMaxReconsumeTimes <= 0 { cfg.RoomOutbox.ConsumerMaxReconsumeTimes = defaults.RoomOutbox.ConsumerMaxReconsumeTimes } if cfg.WalletOutbox.Topic = strings.TrimSpace(cfg.WalletOutbox.Topic); cfg.WalletOutbox.Topic == "" { cfg.WalletOutbox.Topic = defaults.WalletOutbox.Topic } cfg.WalletOutbox.RealtimeTopic = strings.TrimSpace(cfg.WalletOutbox.RealtimeTopic) if cfg.WalletOutbox.FirstRechargeConsumerGroup = strings.TrimSpace(cfg.WalletOutbox.FirstRechargeConsumerGroup); cfg.WalletOutbox.FirstRechargeConsumerGroup == "" { cfg.WalletOutbox.FirstRechargeConsumerGroup = defaults.WalletOutbox.FirstRechargeConsumerGroup } if cfg.WalletOutbox.CumulativeRechargeConsumerGroup = strings.TrimSpace(cfg.WalletOutbox.CumulativeRechargeConsumerGroup); cfg.WalletOutbox.CumulativeRechargeConsumerGroup == "" { cfg.WalletOutbox.CumulativeRechargeConsumerGroup = defaults.WalletOutbox.CumulativeRechargeConsumerGroup } if cfg.WalletOutbox.InviteActivityConsumerGroup = strings.TrimSpace(cfg.WalletOutbox.InviteActivityConsumerGroup); cfg.WalletOutbox.InviteActivityConsumerGroup == "" { cfg.WalletOutbox.InviteActivityConsumerGroup = defaults.WalletOutbox.InviteActivityConsumerGroup } if cfg.WalletOutbox.RedPacketBroadcastConsumerGroup = strings.TrimSpace(cfg.WalletOutbox.RedPacketBroadcastConsumerGroup); cfg.WalletOutbox.RedPacketBroadcastConsumerGroup == "" { cfg.WalletOutbox.RedPacketBroadcastConsumerGroup = defaults.WalletOutbox.RedPacketBroadcastConsumerGroup } if cfg.WalletOutbox.TaskConsumerGroup = strings.TrimSpace(cfg.WalletOutbox.TaskConsumerGroup); cfg.WalletOutbox.TaskConsumerGroup == "" { cfg.WalletOutbox.TaskConsumerGroup = defaults.WalletOutbox.TaskConsumerGroup } if cfg.WalletOutbox.UserLeaderboardConsumerGroup = strings.TrimSpace(cfg.WalletOutbox.UserLeaderboardConsumerGroup); cfg.WalletOutbox.UserLeaderboardConsumerGroup == "" { cfg.WalletOutbox.UserLeaderboardConsumerGroup = defaults.WalletOutbox.UserLeaderboardConsumerGroup } if cfg.WalletOutbox.ConsumerMaxReconsumeTimes <= 0 { cfg.WalletOutbox.ConsumerMaxReconsumeTimes = defaults.WalletOutbox.ConsumerMaxReconsumeTimes } if cfg.UserOutbox.Topic = strings.TrimSpace(cfg.UserOutbox.Topic); cfg.UserOutbox.Topic == "" { cfg.UserOutbox.Topic = defaults.UserOutbox.Topic } if cfg.UserOutbox.ConsumerGroup = strings.TrimSpace(cfg.UserOutbox.ConsumerGroup); cfg.UserOutbox.ConsumerGroup == "" { cfg.UserOutbox.ConsumerGroup = defaults.UserOutbox.ConsumerGroup } if cfg.UserOutbox.TaskConsumerGroup = strings.TrimSpace(cfg.UserOutbox.TaskConsumerGroup); cfg.UserOutbox.TaskConsumerGroup == "" { cfg.UserOutbox.TaskConsumerGroup = defaults.UserOutbox.TaskConsumerGroup } if cfg.UserOutbox.InviteActivityConsumerGroup = strings.TrimSpace(cfg.UserOutbox.InviteActivityConsumerGroup); cfg.UserOutbox.InviteActivityConsumerGroup == "" { cfg.UserOutbox.InviteActivityConsumerGroup = defaults.UserOutbox.InviteActivityConsumerGroup } if cfg.UserOutbox.MessageActionConsumerGroup = strings.TrimSpace(cfg.UserOutbox.MessageActionConsumerGroup); cfg.UserOutbox.MessageActionConsumerGroup == "" { cfg.UserOutbox.MessageActionConsumerGroup = defaults.UserOutbox.MessageActionConsumerGroup } if cfg.UserOutbox.ConsumerMaxReconsumeTimes <= 0 { cfg.UserOutbox.ConsumerMaxReconsumeTimes = defaults.UserOutbox.ConsumerMaxReconsumeTimes } if cfg.MessageActionOutbox.Topic = strings.TrimSpace(cfg.MessageActionOutbox.Topic); cfg.MessageActionOutbox.Topic == "" { cfg.MessageActionOutbox.Topic = defaults.MessageActionOutbox.Topic } if cfg.MessageActionOutbox.ProducerGroup = strings.TrimSpace(cfg.MessageActionOutbox.ProducerGroup); cfg.MessageActionOutbox.ProducerGroup == "" { cfg.MessageActionOutbox.ProducerGroup = defaults.MessageActionOutbox.ProducerGroup } if cfg.MessageActionOutbox.SendTimeout <= 0 { cfg.MessageActionOutbox.SendTimeout = defaults.MessageActionOutbox.SendTimeout } if cfg.MessageActionOutbox.Retry < 0 { cfg.MessageActionOutbox.Retry = defaults.MessageActionOutbox.Retry } if cfg.RoomOutbox.Enabled || cfg.WalletOutbox.Enabled || cfg.UserOutbox.Enabled || cfg.MessageActionOutbox.Enabled { cfg.Enabled = true } if cfg.Enabled { if len(cfg.NameServers) == 0 && cfg.NameServerDomain == "" { return RocketMQConfig{}, errors.New("rocketmq name_servers or name_server_domain is required") } if (cfg.AccessKey == "") != (cfg.SecretKey == "") { return RocketMQConfig{}, errors.New("rocketmq access_key and secret_key must be configured together") } } return cfg, nil } func normalizeStringSlice(input []string) []string { out := make([]string, 0, len(input)) for _, value := range input { if value = strings.TrimSpace(value); value != "" { out = append(out, value) } } return out }