2026-04-29 12:43:05 +08:00

193 lines
7.2 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 定义 room-service 的启动配置和 YAML 加载入口。
package config
import (
"fmt"
"net/http"
"strings"
"time"
"hyapp/pkg/configx"
"hyapp/pkg/tencentim"
)
// Config 描述 room-service 进程启动所需的最小配置。
type Config struct {
// NodeID 是当前 room-service 实例标识Redis lease owner 使用它。
NodeID string `yaml:"node_id"`
// GRPCAddr 是内部 gRPC 监听地址gateway-service 通过它访问 room-service。
GRPCAddr string `yaml:"grpc_addr"`
// LeaseTTL 是 room owner 租约有效期,节点故障后必须等它过期才能接管。
LeaseTTL time.Duration `yaml:"lease_ttl"`
// RankLimit 是房间本地礼物榜 top N。
RankLimit int `yaml:"rank_limit"`
// SnapshotEveryN 控制按房间版本保存快照的频率。
SnapshotEveryN int64 `yaml:"snapshot_every_n"`
// WalletServiceAddr 是 SendGift 同步扣费 gRPC 地址。
WalletServiceAddr string `yaml:"wallet_service_addr"`
// TencentIM 是腾讯云 IM 群组和房间系统消息的外部集成配置。
TencentIM TencentIMConfig `yaml:"tencent_im"`
// MySQLDSN 是 rooms、snapshot、command log 和 outbox 的持久化连接串。
MySQLDSN string `yaml:"mysql_dsn"`
// MySQLMaxOpenConns 控制 MySQL 最大打开连接数。
MySQLMaxOpenConns int `yaml:"mysql_max_open_conns"`
// MySQLMaxIdleConns 控制 MySQL 空闲连接数。
MySQLMaxIdleConns int `yaml:"mysql_max_idle_conns"`
// MySQLAutoMigrate 控制启动时是否自动建表。
MySQLAutoMigrate bool `yaml:"mysql_auto_migrate"`
// RedisAddr 是 room owner lease 目录地址。
RedisAddr string `yaml:"redis_addr"`
// RedisPassword 是 Redis 鉴权密码,本地默认留空。
RedisPassword string `yaml:"redis_password"`
// RedisDB 是 Redis logical DB。
RedisDB int `yaml:"redis_db"`
// PresenceStaleAfter 是业务 presence 超过多久未刷新后被视为断线离房。
PresenceStaleAfter time.Duration `yaml:"presence_stale_after"`
// PresenceStaleScanInterval 是本节点扫描已装载 Room Cell 的周期。
PresenceStaleScanInterval time.Duration `yaml:"presence_stale_scan_interval"`
// OutboxWorker 控制 room_outbox 到腾讯云 IM 补偿投递 worker。
OutboxWorker OutboxWorkerConfig `yaml:"outbox_worker"`
}
// OutboxWorkerConfig 控制 room_outbox pending 事件的补偿投递循环。
type OutboxWorkerConfig struct {
// Enabled 为 false 时不启动补偿 worker测试或临时止血可用。
Enabled bool `yaml:"enabled"`
// PollInterval 是两轮 pending outbox 扫描之间的固定间隔。
PollInterval time.Duration `yaml:"poll_interval"`
// BatchSize 是单轮最多处理的 pending outbox 数,防止全表扫描。
BatchSize int `yaml:"batch_size"`
// PublishTimeout 是单条 outbox 投递腾讯云 IM 的最大耗时。
PublishTimeout time.Duration `yaml:"publish_timeout"`
// RetryStrategy 当前只接受 fixed_interval避免配置错误静默改变投递语义。
RetryStrategy string `yaml:"retry_strategy"`
}
// TencentIMConfig 描述 room-service 调用腾讯云 IM REST API 的配置。
type TencentIMConfig struct {
// Enabled 控制是否启用腾讯云 IM REST 桥接;关闭时只保留 Room Cell 内部提交。
Enabled bool `yaml:"enabled"`
// SDKAppID 是腾讯云 IM 控制台分配的应用 ID。
SDKAppID int64 `yaml:"sdk_app_id"`
// SecretKey 用于生成 App 管理员 UserSig不能写入公开配置。
SecretKey string `yaml:"secret_key"`
// AdminIdentifier 是腾讯云 IM App 管理员账号。
AdminIdentifier string `yaml:"admin_identifier"`
// AdminUserSigTTL 是 REST API 管理员票据有效期。
AdminUserSigTTL time.Duration `yaml:"admin_user_sig_ttl"`
// Endpoint 是腾讯云 IM REST API 区域域名。
Endpoint string `yaml:"endpoint"`
// GroupType 是语音房对应的腾讯云 IM 群组类型。
GroupType string `yaml:"group_type"`
// RequestTimeout 是单次 REST API 调用超时。
RequestTimeout time.Duration `yaml:"request_timeout"`
}
// RESTConfig 把 YAML 配置转换成 pkg/tencentim 的 REST 客户端配置。
func (c TencentIMConfig) RESTConfig() tencentim.RESTConfig {
timeout := c.RequestTimeout
if timeout <= 0 {
// 腾讯云 REST 后台超时约 3 秒,本地客户端默认给 5 秒预算。
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},
}
}
// Default 返回没有外部私有密钥时可启动的基础配置。
func Default() Config {
// 默认端口遵守项目 13xxx 约束MySQL/Redis 指向本地 Docker 暴露端口。
return Config{
NodeID: "room-node-local",
GRPCAddr: ":13001",
LeaseTTL: 10 * time.Second,
RankLimit: 20,
SnapshotEveryN: 1,
WalletServiceAddr: "127.0.0.1:13004",
TencentIM: TencentIMConfig{
Enabled: false,
AdminIdentifier: "administrator",
AdminUserSigTTL: 24 * time.Hour,
Endpoint: tencentim.DefaultEndpoint,
GroupType: tencentim.DefaultGroupType,
RequestTimeout: 5 * time.Second,
},
MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:13306)/hyapp_room?parseTime=true&charset=utf8mb4&loc=Local",
MySQLMaxOpenConns: 20,
MySQLMaxIdleConns: 10,
MySQLAutoMigrate: true,
RedisAddr: "127.0.0.1:13379",
RedisDB: 0,
PresenceStaleAfter: 2 * time.Minute,
PresenceStaleScanInterval: 30 * time.Second,
OutboxWorker: defaultOutboxWorkerConfig(),
}
}
func defaultOutboxWorkerConfig() OutboxWorkerConfig {
return OutboxWorkerConfig{
Enabled: true,
PollInterval: time.Second,
BatchSize: 100,
PublishTimeout: 3 * time.Second,
RetryStrategy: "fixed_interval",
}
}
// Normalize 补齐动态配置默认值,并拒绝会改变补偿语义的未知策略。
func Normalize(cfg Config) (Config, error) {
outboxWorker, err := normalizeOutboxWorkerConfig(cfg.OutboxWorker)
if err != nil {
return Config{}, err
}
cfg.OutboxWorker = outboxWorker
return cfg, nil
}
func normalizeOutboxWorkerConfig(cfg OutboxWorkerConfig) (OutboxWorkerConfig, error) {
defaults := defaultOutboxWorkerConfig()
if cfg.PollInterval <= 0 {
cfg.PollInterval = defaults.PollInterval
}
if cfg.BatchSize <= 0 {
cfg.BatchSize = defaults.BatchSize
}
if cfg.PublishTimeout <= 0 {
cfg.PublishTimeout = defaults.PublishTimeout
}
cfg.RetryStrategy = strings.TrimSpace(cfg.RetryStrategy)
if cfg.RetryStrategy == "" {
cfg.RetryStrategy = defaults.RetryStrategy
}
if cfg.RetryStrategy != defaults.RetryStrategy {
return OutboxWorkerConfig{}, fmt.Errorf("unsupported outbox_worker retry_strategy %q", cfg.RetryStrategy)
}
return cfg, nil
}
// Load 从独立 config.yaml 读取 room-service 配置。
func Load(path string) (Config, error) {
cfg := Default()
if path == "" {
// 空路径代表显式使用默认配置,便于单元测试和最小启动。
return Normalize(cfg)
}
if err := configx.LoadYAML(path, &cfg); err != nil {
// YAML 解析失败返回空 Config避免调用方误用部分默认值启动。
return Config{}, err
}
return Normalize(cfg)
}