2026-07-06 11:10:19 +08:00

586 lines
23 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 定义 user-service 的启动配置和 YAML 加载入口。
package config
import (
"errors"
"net/http"
"strings"
"time"
"hyapp/pkg/appcode"
"hyapp/pkg/configx"
"hyapp/pkg/logx"
"hyapp/pkg/tencentim"
)
// Config 描述 user-service 启动配置。
type Config struct {
// ServiceName 是进程服务名,当前主要用于配置可读性。
ServiceName string `yaml:"service_name"`
// NodeID 是当前服务节点标识,预留给日志和后续健康响应使用。
NodeID string `yaml:"node_id"`
// Environment 是日志和部署策略使用的环境标签。
Environment string `yaml:"environment"`
// GRPCAddr 是 user-service 内部 gRPC 监听地址。
GRPCAddr string `yaml:"grpc_addr"`
// HealthHTTPAddr 是给 CLB/发布脚本探活使用的 HTTP 监听地址,不承载业务流量。
HealthHTTPAddr string `yaml:"health_http_addr"`
// MySQLDSN 是用户主数据、认证身份、session 和短号事务的必需存储连接串。
MySQLDSN string `yaml:"mysql_dsn"`
// MySQLAutoMigrate 保留给显式本地迁移;线上 schema 由发布流程或 initdb 管理。
MySQLAutoMigrate bool `yaml:"mysql_auto_migrate"`
// IDGenerator 控制 user_id 发号节点。
IDGenerator IDGeneratorConfig `yaml:"id_generator"`
// DisplayUserID 控制短号分配重试和默认短号修改冷却期。
DisplayUserID DisplayUserIDConfig `yaml:"display_user_id"`
// ThirdParty 控制本地三方登录 provider allowlist。
ThirdParty ThirdPartyConfig `yaml:"third_party"`
// InviteRechargeWorker 控制邀请有效人数的 wallet recharge outbox 消费。
InviteRechargeWorker InviteRechargeWorkerConfig `yaml:"invite_recharge_worker"`
// MicTimeWorker 控制 room-service 麦位事件消费和用户麦上时长聚合。
MicTimeWorker MicTimeWorkerConfig `yaml:"mic_time_worker"`
// CPRelationshipWorker 控制 room-service 送礼事件消费和 CP 关系申请/亲密值维护。
CPRelationshipWorker CPRelationshipWorkerConfig `yaml:"cp_relationship_worker"`
// CPIntimacyLeaderboard 控制 CP 亲密值排行榜 Redis 读模型。
CPIntimacyLeaderboard CPIntimacyLeaderboardConfig `yaml:"cp_intimacy_leaderboard"`
// OutboxWorker 控制 user_outbox 到 MQ 的补偿投递。
OutboxWorker OutboxWorkerConfig `yaml:"outbox_worker"`
// RocketMQ 控制 wallet/room outbox MQ 消费。
RocketMQ RocketMQConfig `yaml:"rocketmq"`
// LoginRisk 控制登录后 IP 风控策略、Redis session denylist 和 IP 查询 provider。
LoginRisk LoginRiskConfig `yaml:"login_risk"`
// RoomService 是封禁后驱逐当前房间用户的内部 gRPC 依赖。
RoomService RoomServiceConfig `yaml:"room_service"`
// ActivityService 是注册成功后发放注册奖励的内部 gRPC 依赖。
ActivityService ActivityServiceConfig `yaml:"activity_service"`
// WalletService 是 CP 榜头像框等资源读模型的内部 gRPC 依赖。
WalletService WalletServiceConfig `yaml:"wallet_service"`
// TencentIM 是封禁后失效用户 IM 登录态的外部集成配置。
TencentIM TencentIMConfig `yaml:"tencent_im"`
// JWT 控制 access token 和 refresh session 的签发参数。
JWT JWTConfig `yaml:"jwt"`
// Log 控制 stdout 结构化日志格式、等级和 access body 策略。
Log logx.Config `yaml:"log"`
}
// LoginRiskConfig 保存登录地区风控的 Redis、TTL 和 provider 顺序;后台扫描由 cron-service 调度。
type LoginRiskConfig struct {
Enabled bool `yaml:"enabled"`
RedisAddr string `yaml:"redis_addr"`
RedisPassword string `yaml:"redis_password"`
RedisDB int `yaml:"redis_db"`
UnsupportedCountries []string `yaml:"unsupported_countries"`
BlockedTTLSec int64 `yaml:"blocked_ttl_sec"`
AllowedTTLSec int64 `yaml:"allowed_ttl_sec"`
UnknownTTLSec int64 `yaml:"unknown_ttl_sec"`
ProviderTimeoutMs int64 `yaml:"provider_timeout_ms"`
Providers []string `yaml:"providers"`
}
// RoomServiceConfig 保存 user-service 到 room-service 的低频治理 RPC 连接参数。
type RoomServiceConfig struct {
Enabled bool `yaml:"enabled"`
Addr string `yaml:"addr"`
RequestTimeoutMs int64 `yaml:"request_timeout_ms"`
}
// ActivityServiceConfig 保存 user-service 到 activity-service 的低频注册奖励 RPC 连接参数。
type ActivityServiceConfig struct {
Enabled bool `yaml:"enabled"`
Addr string `yaml:"addr"`
RequestTimeoutMs int64 `yaml:"request_timeout_ms"`
}
// WalletServiceConfig 保存 user-service 到 wallet-service 的资源读模型 RPC 连接参数。
type WalletServiceConfig struct {
Enabled bool `yaml:"enabled"`
Addr string `yaml:"addr"`
RequestTimeoutMs int64 `yaml:"request_timeout_ms"`
}
// TencentIMConfig 描述 user-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"`
AdminUserSigTTLSec int64 `yaml:"admin_user_sig_ttl_sec"`
Endpoint string `yaml:"endpoint"`
RequestTimeoutMs int64 `yaml:"request_timeout_ms"`
}
// RESTConfig 转换成 pkg/tencentim REST client 配置。
func (c TencentIMConfig) RESTConfig() tencentim.RESTConfig {
timeout := time.Duration(c.RequestTimeoutMs) * time.Millisecond
if timeout <= 0 {
timeout = 5 * time.Second
}
ttl := time.Duration(c.AdminUserSigTTLSec) * time.Second
if ttl <= 0 {
ttl = 24 * time.Hour
}
return tencentim.RESTConfig{
SDKAppID: c.SDKAppID,
SecretKey: c.SecretKey,
AdminIdentifier: c.AdminIdentifier,
AdminUserSigTTL: ttl,
Endpoint: c.Endpoint,
GroupType: tencentim.DefaultGroupType,
HTTPClient: &http.Client{Timeout: timeout},
}
}
// IDGeneratorConfig 保存 user_id 发号节点配置。
type IDGeneratorConfig struct {
// NodeID 参与雪花 ID 发号,多个 user-service 节点必须不同。
NodeID int64 `yaml:"node_id"`
}
// DisplayUserIDConfig 保存短号分配和修改策略。
type DisplayUserIDConfig struct {
// AllocateMaxAttempts 是默认短号冲突时的最大重试次数。
AllocateMaxAttempts int `yaml:"allocate_max_attempts"`
// ChangeCooldownSec 是默认短号修改冷却期秒数。
ChangeCooldownSec int64 `yaml:"change_cooldown_sec"`
}
// ThirdPartyConfig 保存三方登录 provider allowlist 和真实 verifier 配置。
type ThirdPartyConfig struct {
// AllowedProviders 是 provider allowlistfirebase 会被强制路由到 Firebase verifier。
AllowedProviders []string `yaml:"allowed_providers"`
// Firebase 保存 Firebase Auth ID token verifier 配置。
Firebase FirebaseConfig `yaml:"firebase"`
}
// FirebaseConfig 保存 Firebase Auth ID token verifier 配置。
type FirebaseConfig struct {
// ProjectID 必须匹配 Firebase token 的 aud 和 issuer。
ProjectID string `yaml:"project_id"`
// AllowedSignInProviders 限制 firebase.sign_in_provider首版默认 google.com。
AllowedSignInProviders []string `yaml:"allowed_sign_in_providers"`
// CertsURL 只用于测试或私有代理;生产为空时使用 Firebase 官方公钥端点。
CertsURL string `yaml:"certs_url"`
// Projects 按 app_code 配置 Firebase 项目Huwaa/Lalu 必须各自校验自己的 aud/iss。
Projects map[string]FirebaseProjectConfig `yaml:"projects"`
}
// FirebaseProjectConfig 保存单个 App 的 Firebase 项目配置。
type FirebaseProjectConfig struct {
// ProjectID 必须匹配该 App Firebase token 的 aud 和 issuer。
ProjectID string `yaml:"project_id"`
// AllowedSignInProviders 为空时继承 firebase.allowed_sign_in_providers。
AllowedSignInProviders []string `yaml:"allowed_sign_in_providers"`
// CertsURL 为空时继承 firebase.certs_url生产通常不需要配置。
CertsURL string `yaml:"certs_url"`
}
// InviteRechargeWorkerConfig 保存钱包充值事实消费策略。
type InviteRechargeWorkerConfig struct {
// Enabled 控制是否启动钱包充值 MQ 消费。
Enabled bool `yaml:"enabled"`
}
// MicTimeWorkerConfig 保存房间麦位事件消费策略。
type MicTimeWorkerConfig struct {
// Enabled 控制是否启动 room outbox MQ 消费;关闭后查询仍可读取已聚合数据。
Enabled bool `yaml:"enabled"`
}
// CPRelationshipWorkerConfig 保存 CP 礼物事件消费策略。
type CPRelationshipWorkerConfig struct {
// Enabled 控制是否启动 room gift MQ 消费;关闭后只能处理历史已消费关系。
Enabled bool `yaml:"enabled"`
// ConsumerGroup 必须独立于麦位消费,避免两个读模型共享 MQ 位点。
ConsumerGroup string `yaml:"consumer_group"`
}
// CPIntimacyLeaderboardConfig 保存 CP 亲密榜 Redis zset 的连接和 key 前缀。
type CPIntimacyLeaderboardConfig struct {
Enabled bool `yaml:"enabled"`
RedisAddr string `yaml:"redis_addr"`
RedisPassword string `yaml:"redis_password"`
RedisDB int `yaml:"redis_db"`
KeyPrefix string `yaml:"key_prefix"`
}
// OutboxWorkerConfig 保存 user_outbox MQ 发布策略。
type OutboxWorkerConfig struct {
Enabled bool `yaml:"enabled"`
PollInterval time.Duration `yaml:"poll_interval"`
BatchSize int `yaml:"batch_size"`
PublishTimeout time.Duration `yaml:"publish_timeout"`
}
// RocketMQConfig 保存 user-service 消费 owner service outbox 的 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"`
WalletOutbox WalletOutboxMQConfig `yaml:"wallet_outbox"`
RoomOutbox RoomOutboxMQConfig `yaml:"room_outbox"`
UserOutbox UserOutboxMQConfig `yaml:"user_outbox"`
}
type WalletOutboxMQConfig struct {
Enabled bool `yaml:"enabled"`
Topic string `yaml:"topic"`
ConsumerGroup string `yaml:"consumer_group"`
ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"`
}
type RoomOutboxMQConfig struct {
Enabled bool `yaml:"enabled"`
Topic string `yaml:"topic"`
ConsumerGroup string `yaml:"consumer_group"`
ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"`
}
type UserOutboxMQConfig struct {
Enabled bool `yaml:"enabled"`
Topic string `yaml:"topic"`
ProducerGroup string `yaml:"producer_group"`
}
// JWTConfig 保存 access token 和 refresh token 的签发策略。
type JWTConfig struct {
// Issuer 写入 access token iss claim。
Issuer string `yaml:"issuer"`
// AccessTokenTTLSec 是 App 登录 access token 有效期;默认 168 小时,减少正常用户被动重登。
AccessTokenTTLSec int64 `yaml:"access_token_ttl_sec"`
// RefreshTokenTTLSec 是服务端 session/refresh token 有效期。
RefreshTokenTTLSec int64 `yaml:"refresh_token_ttl_sec"`
// SigningAlg 当前只支持 HS256。
SigningAlg string `yaml:"signing_alg"`
// SigningSecret 是 HS256 签名密钥,生产不能使用默认开发值。
SigningSecret string `yaml:"signing_secret"`
}
// Default 返回本地默认配置。
func Default() Config {
// 默认端口遵守项目 13xxx 约束;本地进程默认连接 docker-compose 暴露的 MySQL。
return Config{
ServiceName: "user-service",
NodeID: "user-local",
Environment: "local",
GRPCAddr: ":13005",
HealthHTTPAddr: ":13105",
MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_user?parseTime=true&charset=utf8mb4&loc=UTC",
MySQLAutoMigrate: false,
IDGenerator: IDGeneratorConfig{
NodeID: 1,
},
DisplayUserID: DisplayUserIDConfig{
AllocateMaxAttempts: 8,
ChangeCooldownSec: 2592000,
},
ThirdParty: ThirdPartyConfig{
AllowedProviders: []string{"firebase"},
Firebase: FirebaseConfig{
AllowedSignInProviders: []string{"google.com"},
},
},
InviteRechargeWorker: InviteRechargeWorkerConfig{
Enabled: false,
},
MicTimeWorker: MicTimeWorkerConfig{
Enabled: false,
},
CPRelationshipWorker: CPRelationshipWorkerConfig{
Enabled: false,
ConsumerGroup: "hyapp-user-cp-room-outbox",
},
CPIntimacyLeaderboard: CPIntimacyLeaderboardConfig{
Enabled: true,
RedisAddr: "127.0.0.1:13379",
KeyPrefix: "user:cp_intimacy_leaderboard",
},
OutboxWorker: OutboxWorkerConfig{
Enabled: false,
PollInterval: time.Second,
BatchSize: 100,
PublishTimeout: 3 * time.Second,
},
RocketMQ: defaultRocketMQConfig(),
LoginRisk: LoginRiskConfig{
Enabled: true,
RedisAddr: "127.0.0.1:13379",
UnsupportedCountries: []string{"CN"},
BlockedTTLSec: 2592000,
AllowedTTLSec: 604800,
UnknownTTLSec: 1800,
ProviderTimeoutMs: 800,
Providers: []string{"edge_country"},
},
RoomService: RoomServiceConfig{
Enabled: true,
Addr: "127.0.0.1:13001",
RequestTimeoutMs: 3000,
},
ActivityService: ActivityServiceConfig{
Enabled: true,
Addr: "127.0.0.1:13006",
RequestTimeoutMs: 3000,
},
WalletService: WalletServiceConfig{
Enabled: true,
Addr: "127.0.0.1:13004",
RequestTimeoutMs: 3000,
},
TencentIM: TencentIMConfig{
Enabled: false,
AdminIdentifier: "administrator",
AdminUserSigTTLSec: 86400,
Endpoint: tencentim.DefaultEndpoint,
RequestTimeoutMs: 5000,
},
JWT: JWTConfig{
Issuer: "hyapp",
AccessTokenTTLSec: 604800,
RefreshTokenTTLSec: 2592000,
SigningAlg: "HS256",
SigningSecret: "dev-shared-secret",
},
Log: logx.Config{
Level: "info",
Format: "json",
MaxPayloadBytes: 2048,
},
}
}
// Load 从独立 config.yaml 读取 user-service 配置。
func Load(path string) (Config, error) {
cfg := Default()
if path == "" {
// 空路径显式使用默认配置,便于单元测试和最小启动。
return cfg, nil
}
if err := configx.LoadYAML(path, &cfg); err != nil {
// 配置解析失败返回空 Config避免调用方误用部分默认值继续启动。
return Config{}, err
}
cfg.ServiceName = strings.TrimSpace(cfg.ServiceName)
if cfg.ServiceName == "" {
cfg.ServiceName = "user-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 = ":13105"
}
cfg.LoginRisk.RedisAddr = strings.TrimSpace(cfg.LoginRisk.RedisAddr)
cfg.CPIntimacyLeaderboard.RedisAddr = strings.TrimSpace(cfg.CPIntimacyLeaderboard.RedisAddr)
cfg.CPIntimacyLeaderboard.KeyPrefix = strings.Trim(strings.TrimSpace(cfg.CPIntimacyLeaderboard.KeyPrefix), ":")
if cfg.CPIntimacyLeaderboard.RedisAddr == "" {
cfg.CPIntimacyLeaderboard.RedisAddr = cfg.LoginRisk.RedisAddr
}
if cfg.CPIntimacyLeaderboard.KeyPrefix == "" {
cfg.CPIntimacyLeaderboard.KeyPrefix = "user:cp_intimacy_leaderboard"
}
cfg.ThirdParty.AllowedProviders = normalizeStringSlice(cfg.ThirdParty.AllowedProviders)
cfg.ThirdParty.Firebase = normalizeFirebaseConfig(cfg.ThirdParty.Firebase)
cfg.RoomService.Addr = strings.TrimSpace(cfg.RoomService.Addr)
if cfg.RoomService.Addr == "" {
cfg.RoomService.Addr = "127.0.0.1:13001"
}
if cfg.RoomService.RequestTimeoutMs <= 0 {
cfg.RoomService.RequestTimeoutMs = 3000
}
cfg.ActivityService.Addr = strings.TrimSpace(cfg.ActivityService.Addr)
if cfg.ActivityService.Addr == "" {
cfg.ActivityService.Addr = "127.0.0.1:13006"
}
if cfg.ActivityService.RequestTimeoutMs <= 0 {
cfg.ActivityService.RequestTimeoutMs = 3000
}
cfg.WalletService.Addr = strings.TrimSpace(cfg.WalletService.Addr)
if cfg.WalletService.Addr == "" {
cfg.WalletService.Addr = "127.0.0.1:13004"
}
if cfg.WalletService.RequestTimeoutMs <= 0 {
cfg.WalletService.RequestTimeoutMs = 3000
}
cfg.TencentIM.SecretKey = strings.TrimSpace(cfg.TencentIM.SecretKey)
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
}
if cfg.TencentIM.AdminUserSigTTLSec <= 0 {
cfg.TencentIM.AdminUserSigTTLSec = 86400
}
if cfg.TencentIM.RequestTimeoutMs <= 0 {
cfg.TencentIM.RequestTimeoutMs = 5000
}
if cfg.LoginRisk.BlockedTTLSec <= 0 {
cfg.LoginRisk.BlockedTTLSec = 2592000
}
if cfg.LoginRisk.AllowedTTLSec <= 0 {
cfg.LoginRisk.AllowedTTLSec = 604800
}
if cfg.LoginRisk.UnknownTTLSec <= 0 {
cfg.LoginRisk.UnknownTTLSec = 1800
}
if cfg.LoginRisk.ProviderTimeoutMs <= 0 {
cfg.LoginRisk.ProviderTimeoutMs = 800
}
for index, provider := range cfg.LoginRisk.Providers {
cfg.LoginRisk.Providers[index] = strings.TrimSpace(provider)
}
rocketMQ, err := normalizeRocketMQConfig(cfg.RocketMQ)
if err != nil {
return Config{}, err
}
cfg.RocketMQ = rocketMQ
if cfg.InviteRechargeWorker.Enabled && !cfg.RocketMQ.WalletOutbox.Enabled {
return Config{}, errors.New("invite_recharge_worker requires rocketmq.wallet_outbox.enabled")
}
if cfg.MicTimeWorker.Enabled && !cfg.RocketMQ.RoomOutbox.Enabled {
return Config{}, errors.New("mic_time_worker requires rocketmq.room_outbox.enabled")
}
cfg.CPRelationshipWorker.ConsumerGroup = strings.TrimSpace(cfg.CPRelationshipWorker.ConsumerGroup)
if cfg.CPRelationshipWorker.ConsumerGroup == "" {
cfg.CPRelationshipWorker.ConsumerGroup = "hyapp-user-cp-room-outbox"
}
if cfg.CPRelationshipWorker.Enabled && !cfg.RocketMQ.RoomOutbox.Enabled {
return Config{}, errors.New("cp_relationship_worker requires rocketmq.room_outbox.enabled")
}
if cfg.OutboxWorker.PollInterval <= 0 {
cfg.OutboxWorker.PollInterval = time.Second
}
if cfg.OutboxWorker.BatchSize <= 0 {
cfg.OutboxWorker.BatchSize = 100
}
if cfg.OutboxWorker.PublishTimeout <= 0 {
cfg.OutboxWorker.PublishTimeout = 3 * time.Second
}
if cfg.OutboxWorker.Enabled && !cfg.RocketMQ.UserOutbox.Enabled {
return Config{}, errors.New("outbox_worker requires rocketmq.user_outbox.enabled")
}
return cfg, nil
}
func defaultRocketMQConfig() RocketMQConfig {
return RocketMQConfig{
Enabled: false,
WalletOutbox: WalletOutboxMQConfig{
Enabled: false,
Topic: "hyapp_wallet_outbox",
ConsumerGroup: "hyapp-user-invite-wallet-outbox",
ConsumerMaxReconsumeTimes: 16,
},
RoomOutbox: RoomOutboxMQConfig{
Enabled: false,
Topic: "hyapp_room_outbox",
ConsumerGroup: "hyapp-user-mictime-room-outbox",
ConsumerMaxReconsumeTimes: 16,
},
UserOutbox: UserOutboxMQConfig{
Enabled: false,
Topic: "hyapp_user_outbox",
ProducerGroup: "hyapp-user-outbox-producer",
},
}
}
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.WalletOutbox.Topic = strings.TrimSpace(cfg.WalletOutbox.Topic); cfg.WalletOutbox.Topic == "" {
cfg.WalletOutbox.Topic = defaults.WalletOutbox.Topic
}
if cfg.WalletOutbox.ConsumerGroup = strings.TrimSpace(cfg.WalletOutbox.ConsumerGroup); cfg.WalletOutbox.ConsumerGroup == "" {
cfg.WalletOutbox.ConsumerGroup = defaults.WalletOutbox.ConsumerGroup
}
if cfg.WalletOutbox.ConsumerMaxReconsumeTimes <= 0 {
cfg.WalletOutbox.ConsumerMaxReconsumeTimes = defaults.WalletOutbox.ConsumerMaxReconsumeTimes
}
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.UserOutbox.Topic = strings.TrimSpace(cfg.UserOutbox.Topic); cfg.UserOutbox.Topic == "" {
cfg.UserOutbox.Topic = defaults.UserOutbox.Topic
}
if cfg.UserOutbox.ProducerGroup = strings.TrimSpace(cfg.UserOutbox.ProducerGroup); cfg.UserOutbox.ProducerGroup == "" {
cfg.UserOutbox.ProducerGroup = defaults.UserOutbox.ProducerGroup
}
if cfg.WalletOutbox.Enabled || cfg.RoomOutbox.Enabled || cfg.UserOutbox.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 normalizeFirebaseConfig(cfg FirebaseConfig) FirebaseConfig {
cfg.ProjectID = strings.TrimSpace(cfg.ProjectID)
cfg.AllowedSignInProviders = normalizeStringSlice(cfg.AllowedSignInProviders)
cfg.CertsURL = strings.TrimSpace(cfg.CertsURL)
if len(cfg.Projects) == 0 {
return cfg
}
projects := make(map[string]FirebaseProjectConfig, len(cfg.Projects))
for rawAppCode, project := range cfg.Projects {
app := appcode.Normalize(rawAppCode)
project.ProjectID = strings.TrimSpace(project.ProjectID)
project.CertsURL = strings.TrimSpace(project.CertsURL)
project.AllowedSignInProviders = normalizeStringSlice(project.AllowedSignInProviders)
if len(project.AllowedSignInProviders) == 0 {
// 单 App 没写登录方式时继承全局配置;全局为空时 auth verifier 仍会默认 google.com。
project.AllowedSignInProviders = cfg.AllowedSignInProviders
}
if project.CertsURL == "" {
// Firebase 公钥端点对所有项目相同;测试代理或私有代理可以只写一次。
project.CertsURL = cfg.CertsURL
}
projects[app] = project
}
cfg.Projects = projects
return cfg
}
func normalizeStringSlice(values []string) []string {
normalized := make([]string, 0, len(values))
for _, value := range values {
if value = strings.TrimSpace(value); value != "" {
normalized = append(normalized, value)
}
}
return normalized
}