fix game room rps im config
This commit is contained in:
parent
cc84e14860
commit
8628de3dbd
@ -8,6 +8,16 @@ wallet_service_addr: "wallet-service:13004"
|
||||
user_service_addr: "user-service:13005"
|
||||
activity_service_addr: "activity-service:13006"
|
||||
launch_session_ttl: "15m"
|
||||
tencent_im:
|
||||
# Docker/testbox 联调房内猜拳 PK 房间群消息;必须和 gateway/room 使用同一个 IM 应用。
|
||||
enabled: true
|
||||
sdk_app_id: 20036101
|
||||
secret_key: "dcdf53135f27e7cf8541c4ae9798fb0cb2f0e4d6c5c20022dbaea053f35cbb8c"
|
||||
admin_identifier: "900100100"
|
||||
admin_user_sig_ttl: "24h"
|
||||
endpoint: "adminapisgp.im.qcloud.com"
|
||||
group_type: "ChatRoom"
|
||||
request_timeout: "5s"
|
||||
rocketmq:
|
||||
enabled: true
|
||||
name_servers: ["rocketmq-namesrv:9876"]
|
||||
|
||||
@ -8,6 +8,16 @@ wallet_service_addr: "wallet-service:13004"
|
||||
user_service_addr: "user-service:13005"
|
||||
activity_service_addr: "10.2.1.16:13006"
|
||||
launch_session_ttl: "15m"
|
||||
tencent_im:
|
||||
# 房内猜拳发起、应战、揭晓和结算事件都走腾讯 IM 房间群自定义消息。
|
||||
enabled: true
|
||||
sdk_app_id: 1400000000
|
||||
secret_key: "TENCENT_IM_SECRET_KEY"
|
||||
admin_identifier: "TENCENT_IM_ADMIN_IDENTIFIER"
|
||||
admin_user_sig_ttl: "24h"
|
||||
endpoint: "adminapisgp.im.qcloud.com"
|
||||
group_type: "ChatRoom"
|
||||
request_timeout: "5s"
|
||||
rocketmq:
|
||||
enabled: true
|
||||
name_servers: []
|
||||
|
||||
@ -8,6 +8,16 @@ wallet_service_addr: "127.0.0.1:13004"
|
||||
user_service_addr: "127.0.0.1:13005"
|
||||
activity_service_addr: "127.0.0.1:13006"
|
||||
launch_session_ttl: "15m"
|
||||
tencent_im:
|
||||
# 本地联调房内猜拳 PK 房间群消息;必须和 gateway/room 使用同一个 IM 应用。
|
||||
enabled: true
|
||||
sdk_app_id: 20036101
|
||||
secret_key: "dcdf53135f27e7cf8541c4ae9798fb0cb2f0e4d6c5c20022dbaea053f35cbb8c"
|
||||
admin_identifier: "900100100"
|
||||
admin_user_sig_ttl: "24h"
|
||||
endpoint: "adminapisgp.im.qcloud.com"
|
||||
group_type: "ChatRoom"
|
||||
request_timeout: "5s"
|
||||
rocketmq:
|
||||
enabled: true
|
||||
name_servers: ["127.0.0.1:19876"]
|
||||
|
||||
@ -98,14 +98,21 @@ func New(cfg config.Config) (*App, error) {
|
||||
svc := gameservice.New(gameservice.Config{LaunchSessionTTL: cfg.LaunchSessionTTL}, repo, walletClient, userClient, client.NewActivityGrowthClient(activityConn))
|
||||
// 骰子服务复用同一个 MySQL repository 和 wallet client;局事实走 dice 表,金币事实仍走 game_orders + wallet-service。
|
||||
diceSvc := diceservice.New(diceservice.Config{}, repo, walletClient)
|
||||
roomRPSPublisher, err := roomrpsservice.NewTencentIMPublisherFromEnv()
|
||||
if err != nil {
|
||||
_ = listener.Close()
|
||||
_ = activityConn.Close()
|
||||
_ = userConn.Close()
|
||||
_ = walletConn.Close()
|
||||
_ = repo.Close()
|
||||
return nil, err
|
||||
var roomRPSPublisher roomrpsservice.Publisher
|
||||
if cfg.TencentIM.Enabled {
|
||||
// 房内 RPS 的“发起 PK”需要同步投递腾讯 IM 房间群消息;启动阶段先创建 REST client,避免运行中才发现配置缺失。
|
||||
roomRPSPublisher, err = roomrpsservice.NewTencentIMPublisher(cfg.TencentIM.RESTConfig())
|
||||
if err != nil {
|
||||
_ = listener.Close()
|
||||
_ = activityConn.Close()
|
||||
_ = userConn.Close()
|
||||
_ = walletConn.Close()
|
||||
_ = repo.Close()
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// 显式关闭时保留 HTTP/RPC 功能,便于纯本地测试;测试服和线上必须在 YAML 中打开,否则用户看不到房内 PK。
|
||||
logx.Warn(context.Background(), "room_rps_im_disabled", slog.String("reason", "tencent_im.enabled=false"))
|
||||
}
|
||||
// 房内猜拳独立于骰子和独立猜拳:配置、挑战状态机和 IM 事件全部由 room-rps service 承接,transport 只做 RPC 适配。
|
||||
roomRPSSvc := roomrpsservice.New(roomrpsservice.Config{}, userClient, roomRPSPublisher)
|
||||
|
||||
@ -3,11 +3,13 @@ package config
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"hyapp/pkg/configx"
|
||||
"hyapp/pkg/logx"
|
||||
"hyapp/pkg/tencentim"
|
||||
)
|
||||
|
||||
// Config 描述 game-service 启动所需的最小依赖。
|
||||
@ -17,15 +19,58 @@ type Config struct {
|
||||
Environment string `yaml:"environment"`
|
||||
GRPCAddr string `yaml:"grpc_addr"`
|
||||
// HealthHTTPAddr 是给 CLB/发布脚本探活使用的 HTTP 监听地址,不承载业务流量。
|
||||
HealthHTTPAddr string `yaml:"health_http_addr"`
|
||||
MySQLDSN string `yaml:"mysql_dsn"`
|
||||
WalletServiceAddr string `yaml:"wallet_service_addr"`
|
||||
UserServiceAddr string `yaml:"user_service_addr"`
|
||||
ActivityServiceAddr string `yaml:"activity_service_addr"`
|
||||
LaunchSessionTTL time.Duration `yaml:"launch_session_ttl"`
|
||||
RocketMQ RocketMQConfig `yaml:"rocketmq"`
|
||||
OutboxWorker OutboxWorkerConfig `yaml:"outbox_worker"`
|
||||
Log logx.Config `yaml:"log"`
|
||||
HealthHTTPAddr string `yaml:"health_http_addr"`
|
||||
MySQLDSN string `yaml:"mysql_dsn"`
|
||||
WalletServiceAddr string `yaml:"wallet_service_addr"`
|
||||
UserServiceAddr string `yaml:"user_service_addr"`
|
||||
ActivityServiceAddr string `yaml:"activity_service_addr"`
|
||||
LaunchSessionTTL time.Duration `yaml:"launch_session_ttl"`
|
||||
// TencentIM 是房内猜拳向房间群投递 PK 事件的腾讯 IM REST 配置;缺配置时必须显式关闭,不能靠环境变量隐式漂移。
|
||||
TencentIM TencentIMConfig `yaml:"tencent_im"`
|
||||
RocketMQ RocketMQConfig `yaml:"rocketmq"`
|
||||
OutboxWorker OutboxWorkerConfig `yaml:"outbox_worker"`
|
||||
Log logx.Config `yaml:"log"`
|
||||
}
|
||||
|
||||
// TencentIMConfig 描述 game-service 发房内 RPS 群自定义消息需要的服务端 IM 配置。
|
||||
type TencentIMConfig struct {
|
||||
// Enabled 控制房内 RPS 是否向腾讯 IM 房间群投递事件;开启后缺少密钥或管理员账号应启动失败。
|
||||
Enabled bool `yaml:"enabled"`
|
||||
// SDKAppID 是腾讯云 IM 应用 ID,必须和 gateway 签发 UserSig 使用同一个应用。
|
||||
SDKAppID int64 `yaml:"sdk_app_id"`
|
||||
// SecretKey 只用于服务端签 admin UserSig,不能出现在响应或业务日志里。
|
||||
SecretKey string `yaml:"secret_key"`
|
||||
// AdminIdentifier 是服务端 REST 发房间 PK 消息的管理员账号。
|
||||
AdminIdentifier string `yaml:"admin_identifier"`
|
||||
// AdminUserSigTTL 控制 REST 调用签名有效期,默认 24h。
|
||||
AdminUserSigTTL time.Duration `yaml:"admin_user_sig_ttl"`
|
||||
// Endpoint 是腾讯 IM REST 地域入口,例如 adminapisgp.im.qcloud.com。
|
||||
Endpoint string `yaml:"endpoint"`
|
||||
// GroupType 是房间群类型;房内 RPS 复用房间群,默认 ChatRoom。
|
||||
GroupType string `yaml:"group_type"`
|
||||
// RequestTimeout 是单次腾讯 IM REST 调用预算,避免创建 PK 时无限等待外部服务。
|
||||
RequestTimeout time.Duration `yaml:"request_timeout"`
|
||||
}
|
||||
|
||||
// RESTConfig 收敛为共享腾讯 IM client 配置,调用方不用重复处理默认超时和群类型。
|
||||
func (c TencentIMConfig) RESTConfig() tencentim.RESTConfig {
|
||||
timeout := c.RequestTimeout
|
||||
if timeout <= 0 {
|
||||
timeout = 5 * time.Second
|
||||
}
|
||||
ttl := c.AdminUserSigTTL
|
||||
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: c.GroupType,
|
||||
HTTPClient: &http.Client{Timeout: timeout},
|
||||
}
|
||||
}
|
||||
|
||||
type RocketMQConfig struct {
|
||||
@ -68,7 +113,14 @@ func Default() Config {
|
||||
UserServiceAddr: "127.0.0.1:13005",
|
||||
ActivityServiceAddr: "127.0.0.1:13006",
|
||||
LaunchSessionTTL: 15 * time.Minute,
|
||||
RocketMQ: defaultRocketMQConfig(),
|
||||
TencentIM: TencentIMConfig{
|
||||
AdminIdentifier: "administrator",
|
||||
AdminUserSigTTL: 24 * time.Hour,
|
||||
Endpoint: tencentim.DefaultEndpoint,
|
||||
GroupType: tencentim.DefaultGroupType,
|
||||
RequestTimeout: 5 * time.Second,
|
||||
},
|
||||
RocketMQ: defaultRocketMQConfig(),
|
||||
OutboxWorker: OutboxWorkerConfig{
|
||||
Enabled: false,
|
||||
PollInterval: time.Second,
|
||||
@ -138,6 +190,10 @@ func (cfg *Config) Normalize() error {
|
||||
if cfg.LaunchSessionTTL <= 0 {
|
||||
cfg.LaunchSessionTTL = 15 * time.Minute
|
||||
}
|
||||
cfg.normalizeTencentIM()
|
||||
if cfg.TencentIM.Enabled && (cfg.TencentIM.SDKAppID <= 0 || cfg.TencentIM.SecretKey == "" || cfg.TencentIM.AdminIdentifier == "") {
|
||||
return errors.New("tencent_im enabled requires sdk_app_id, secret_key and admin_identifier")
|
||||
}
|
||||
cfg.RocketMQ = normalizeRocketMQConfig(cfg.RocketMQ)
|
||||
if cfg.OutboxWorker.PollInterval <= 0 {
|
||||
cfg.OutboxWorker.PollInterval = time.Second
|
||||
@ -162,6 +218,28 @@ func (cfg *Config) Normalize() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *Config) normalizeTencentIM() {
|
||||
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
|
||||
}
|
||||
cfg.TencentIM.GroupType = strings.TrimSpace(cfg.TencentIM.GroupType)
|
||||
if cfg.TencentIM.GroupType == "" {
|
||||
cfg.TencentIM.GroupType = tencentim.DefaultGroupType
|
||||
}
|
||||
if cfg.TencentIM.AdminUserSigTTL <= 0 {
|
||||
cfg.TencentIM.AdminUserSigTTL = 24 * time.Hour
|
||||
}
|
||||
if cfg.TencentIM.RequestTimeout <= 0 {
|
||||
cfg.TencentIM.RequestTimeout = 5 * time.Second
|
||||
}
|
||||
}
|
||||
|
||||
func defaultRocketMQConfig() RocketMQConfig {
|
||||
return RocketMQConfig{
|
||||
Enabled: false,
|
||||
|
||||
@ -25,3 +25,44 @@ func TestLoadConfigsKeepActivityServiceAddr(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigsEnableRoomRPSIM(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
}{
|
||||
{name: "local", path: "../../configs/config.yaml"},
|
||||
{name: "docker", path: "../../configs/config.docker.yaml"},
|
||||
{name: "tencent", path: "../../configs/config.tencent.example.yaml"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg, err := Load(tt.path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load %s config failed: %v", tt.name, err)
|
||||
}
|
||||
if !cfg.TencentIM.Enabled {
|
||||
t.Fatalf("room-rps tencent_im must be enabled in %s config", tt.name)
|
||||
}
|
||||
if cfg.TencentIM.SDKAppID <= 0 || cfg.TencentIM.SecretKey == "" || cfg.TencentIM.AdminIdentifier == "" {
|
||||
t.Fatalf("room-rps tencent_im config is incomplete: sdk_app_id_set=%t secret_key_set=%t admin_identifier_set=%t", cfg.TencentIM.SDKAppID > 0, cfg.TencentIM.SecretKey != "", cfg.TencentIM.AdminIdentifier != "")
|
||||
}
|
||||
if cfg.TencentIM.Endpoint != "adminapisgp.im.qcloud.com" {
|
||||
t.Fatalf("room-rps tencent_im endpoint mismatch: %q", cfg.TencentIM.Endpoint)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeRejectsEnabledIncompleteTencentIM(t *testing.T) {
|
||||
cfg := Default()
|
||||
cfg.TencentIM.Enabled = true
|
||||
cfg.TencentIM.SDKAppID = 0
|
||||
cfg.TencentIM.SecretKey = ""
|
||||
cfg.TencentIM.AdminIdentifier = ""
|
||||
|
||||
if err := cfg.Normalize(); err == nil {
|
||||
t.Fatal("Normalize should reject enabled incomplete tencent_im config")
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,8 +6,6 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -101,33 +99,10 @@ func New(config Config, user UserClient, publisher Publisher) *Service {
|
||||
}
|
||||
}
|
||||
|
||||
// NewTencentIMPublisherFromEnv 按环境变量创建真实腾讯 IM publisher。
|
||||
// 密钥、账号和地域入口都属于敏感或部署差异配置,因此这里只读取环境,不在 game-service 默认配置里伪造。
|
||||
func NewTencentIMPublisherFromEnv() (Publisher, error) {
|
||||
sdkAppID, _ := strconv.ParseInt(strings.TrimSpace(os.Getenv("TENCENT_IM_SDK_APP_ID")), 10, 64)
|
||||
secretKey := strings.TrimSpace(os.Getenv("TENCENT_IM_SECRET_KEY"))
|
||||
adminIdentifier := strings.TrimSpace(os.Getenv("TENCENT_IM_ADMIN_IDENTIFIER"))
|
||||
if sdkAppID <= 0 || secretKey == "" || adminIdentifier == "" {
|
||||
// 未配置 IM 时不阻断本地服务启动;真实 IM smoke 会显式带环境变量,并要求事件投递成功。
|
||||
return nil, nil
|
||||
}
|
||||
ttl := time.Hour
|
||||
if raw := strings.TrimSpace(os.Getenv("TENCENT_IM_ADMIN_USERSIG_TTL")); raw != "" {
|
||||
parsed, err := time.ParseDuration(raw)
|
||||
if err != nil || parsed <= 0 {
|
||||
return nil, fmt.Errorf("TENCENT_IM_ADMIN_USERSIG_TTL is invalid")
|
||||
}
|
||||
ttl = parsed
|
||||
}
|
||||
return tencentim.NewRESTClient(tencentim.RESTConfig{
|
||||
SDKAppID: sdkAppID,
|
||||
SecretKey: secretKey,
|
||||
AdminIdentifier: adminIdentifier,
|
||||
AdminUserSigTTL: ttl,
|
||||
Endpoint: envOr("TENCENT_IM_ENDPOINT", tencentim.DefaultEndpoint),
|
||||
GroupType: tencentim.DefaultGroupType,
|
||||
HTTPClient: &http.Client{Timeout: 10 * time.Second},
|
||||
})
|
||||
// NewTencentIMPublisher 创建真实腾讯 IM publisher。
|
||||
// game-service 的房内 PK 发起接口是同步返回给客户端的入口,因此 IM 配置必须来自已校验的 YAML,而不是进程环境变量的隐式副作用。
|
||||
func NewTencentIMPublisher(config tencentim.RESTConfig) (Publisher, error) {
|
||||
return tencentim.NewRESTClient(config)
|
||||
}
|
||||
|
||||
func (s *Service) GetConfig(ctx context.Context, app string) (*gamev1.RoomRPSConfig, int64, error) {
|
||||
@ -768,11 +743,3 @@ func newChallengeID(app string) string {
|
||||
}
|
||||
return fmt.Sprintf("rps_%s_%d_%s", appcode.Normalize(app), time.Now().UnixMilli(), hex.EncodeToString(entropy[:]))
|
||||
}
|
||||
|
||||
func envOr(key string, fallback string) string {
|
||||
value := strings.TrimSpace(os.Getenv(key))
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user