280 lines
8.6 KiB
Go
280 lines
8.6 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
"hyapp/pkg/tencentcos"
|
|
"hyapp/services/gateway-service/internal/appconfig"
|
|
"hyapp/services/gateway-service/internal/auth"
|
|
"hyapp/services/gateway-service/internal/client"
|
|
"hyapp/services/gateway-service/internal/config"
|
|
"hyapp/services/gateway-service/internal/healthcheck"
|
|
httptransport "hyapp/services/gateway-service/internal/transport/http"
|
|
)
|
|
|
|
// App 装配 gateway 的 HTTP 入口。
|
|
type App struct {
|
|
server *http.Server
|
|
listener net.Listener
|
|
roomConn *grpc.ClientConn
|
|
userConn *grpc.ClientConn
|
|
walletConn *grpc.ClientConn
|
|
activityConn *grpc.ClientConn
|
|
appConfig *appconfig.MySQLReader
|
|
redisClose func() error
|
|
health *healthcheck.State
|
|
closeOnce sync.Once
|
|
}
|
|
|
|
// New 初始化 gateway 应用。
|
|
func New(cfg config.Config) (*App, error) {
|
|
if err := cfg.Normalize(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
roomConn, err := grpc.Dial(cfg.RoomServiceAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
userConn, err := grpc.Dial(cfg.UserServiceAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
if err != nil {
|
|
_ = roomConn.Close()
|
|
return nil, err
|
|
}
|
|
|
|
walletConn, err := grpc.Dial(cfg.WalletServiceAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
if err != nil {
|
|
_ = roomConn.Close()
|
|
_ = userConn.Close()
|
|
return nil, err
|
|
}
|
|
activityConn, err := grpc.Dial(cfg.ActivityServiceAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
if err != nil {
|
|
_ = roomConn.Close()
|
|
_ = userConn.Close()
|
|
_ = walletConn.Close()
|
|
return nil, err
|
|
}
|
|
|
|
var roomClient client.RoomClient = client.NewGRPCRoomClient(roomConn)
|
|
var roomGuardClient client.RoomGuardClient = client.NewGRPCRoomGuardClient(roomConn)
|
|
var roomQueryClient client.RoomQueryClient = client.NewGRPCRoomQueryClient(roomConn)
|
|
var userClient client.UserAuthClient = client.NewGRPCUserAuthClient(userConn)
|
|
var userIdentityClient client.UserIdentityClient = client.NewGRPCUserIdentityClient(userConn)
|
|
var userProfileClient client.UserProfileClient = client.NewGRPCUserProfileClient(userConn)
|
|
var userDeviceClient client.UserDeviceClient = client.NewGRPCUserDeviceClient(userConn)
|
|
var userCountryQueryClient client.UserCountryQueryClient = client.NewGRPCUserCountryQueryClient(userConn)
|
|
var userHostClient client.UserHostClient = client.NewGRPCUserHostClient(userConn)
|
|
var appRegistryClient client.AppRegistryClient = client.NewGRPCAppRegistryClient(userConn)
|
|
var walletClient client.WalletClient = client.NewGRPCWalletClient(walletConn)
|
|
var messageClient client.MessageInboxClient = client.NewGRPCMessageInboxClient(activityConn)
|
|
appConfigReader, err := openAppConfigReader(cfg.AppConfig)
|
|
if err != nil {
|
|
_ = roomConn.Close()
|
|
_ = userConn.Close()
|
|
_ = walletConn.Close()
|
|
_ = activityConn.Close()
|
|
return nil, err
|
|
}
|
|
handler := httptransport.NewHandlerWithConfig(roomClient, roomGuardClient, userClient, userIdentityClient, httptransport.TencentIMConfig{
|
|
SDKAppID: cfg.TencentIM.SDKAppID,
|
|
SecretKey: cfg.TencentIM.SecretKey,
|
|
UserSigTTL: cfg.TencentIM.UserSigTTL,
|
|
CallbackAuthToken: cfg.TencentIM.CallbackAuthToken,
|
|
}, userProfileClient)
|
|
handler.SetRoomQueryClient(roomQueryClient)
|
|
handler.SetUserDeviceClient(userDeviceClient)
|
|
handler.SetUserCountryQueryClient(userCountryQueryClient)
|
|
handler.SetUserHostClient(userHostClient)
|
|
handler.SetAppRegistryClient(appRegistryClient)
|
|
handler.SetWalletClient(walletClient)
|
|
handler.SetMessageInboxClient(messageClient)
|
|
if appConfigReader != nil {
|
|
handler.SetAppConfigReader(appConfigReader)
|
|
}
|
|
handler.SetTencentRTC(httptransport.TencentRTCConfig{
|
|
Enabled: cfg.TencentRTC.Enabled,
|
|
SDKAppID: cfg.TencentRTC.SDKAppID,
|
|
SecretKey: cfg.TencentRTC.SecretKey,
|
|
UserSigTTL: cfg.TencentRTC.UserSigTTL,
|
|
RoomIDType: cfg.TencentRTC.RoomIDType,
|
|
AppScene: cfg.TencentRTC.AppScene,
|
|
CallbackSignKey: cfg.TencentRTC.CallbackSignKey,
|
|
})
|
|
if cfg.TencentCOS.Enabled {
|
|
uploader, err := tencentcos.NewUploader(tencentcos.Config{
|
|
SecretID: cfg.TencentCOS.SecretID,
|
|
SecretKey: cfg.TencentCOS.SecretKey,
|
|
Bucket: cfg.TencentCOS.BucketName,
|
|
Region: cfg.TencentCOS.Region,
|
|
AccessURL: cfg.TencentCOS.AccessURL,
|
|
}, nil)
|
|
if err != nil {
|
|
if appConfigReader != nil {
|
|
_ = appConfigReader.Close()
|
|
}
|
|
_ = roomConn.Close()
|
|
_ = userConn.Close()
|
|
_ = walletConn.Close()
|
|
_ = activityConn.Close()
|
|
return nil, err
|
|
}
|
|
handler.SetObjectUploader(uploader)
|
|
}
|
|
rateLimitConfig := httptransport.AuthRateLimitConfig{
|
|
Enabled: cfg.AuthRateLimit.Enabled,
|
|
KeyPrefix: cfg.AuthRateLimit.KeyPrefix,
|
|
Window: time.Duration(cfg.AuthRateLimit.WindowSec) * time.Second,
|
|
IPLimit: cfg.AuthRateLimit.IPLimit,
|
|
DeviceIDLimit: cfg.AuthRateLimit.DeviceIDLimit,
|
|
ProviderIPLimit: cfg.AuthRateLimit.ProviderIPLimit,
|
|
DisplayUserIDIPLimit: cfg.AuthRateLimit.DisplayUserIDIPLimit,
|
|
DisplayUserIDLimit: cfg.AuthRateLimit.DisplayUserIDLimit,
|
|
SessionIDIPLimit: cfg.AuthRateLimit.SessionIDIPLimit,
|
|
}
|
|
redisClose, err := configureAuthRateLimit(handler, cfg.AuthRateLimit, rateLimitConfig)
|
|
if err != nil {
|
|
if appConfigReader != nil {
|
|
_ = appConfigReader.Close()
|
|
}
|
|
_ = roomConn.Close()
|
|
_ = userConn.Close()
|
|
_ = walletConn.Close()
|
|
_ = activityConn.Close()
|
|
return nil, err
|
|
}
|
|
verifier := auth.NewVerifier(cfg.JWTSecret)
|
|
healthState := healthcheck.NewState(nodeID(), cfg.JWTSecret, roomConn)
|
|
|
|
mux := http.NewServeMux()
|
|
healthHandler := healthcheck.NewHTTPHandler(healthState)
|
|
mux.HandleFunc("/healthz/live", healthHandler.Live)
|
|
mux.HandleFunc("/healthz/ready", healthHandler.Ready)
|
|
mux.Handle("/", handler.Routes(verifier))
|
|
|
|
listener, err := net.Listen("tcp", cfg.HTTPAddr)
|
|
if err != nil {
|
|
if redisClose != nil {
|
|
_ = redisClose()
|
|
}
|
|
if appConfigReader != nil {
|
|
_ = appConfigReader.Close()
|
|
}
|
|
_ = roomConn.Close()
|
|
_ = userConn.Close()
|
|
_ = walletConn.Close()
|
|
_ = activityConn.Close()
|
|
return nil, err
|
|
}
|
|
|
|
server := &http.Server{
|
|
Handler: mux,
|
|
}
|
|
|
|
return &App{
|
|
server: server,
|
|
listener: listener,
|
|
roomConn: roomConn,
|
|
userConn: userConn,
|
|
walletConn: walletConn,
|
|
activityConn: activityConn,
|
|
appConfig: appConfigReader,
|
|
redisClose: redisClose,
|
|
health: healthState,
|
|
}, nil
|
|
}
|
|
|
|
func openAppConfigReader(cfg config.AppConfigConfig) (*appconfig.MySQLReader, error) {
|
|
if strings.TrimSpace(cfg.MySQLDSN) == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
return appconfig.OpenMySQLReader(cfg.MySQLDSN)
|
|
}
|
|
|
|
func configureAuthRateLimit(handler *httptransport.Handler, cfg config.AuthRateLimitConfig, rateLimitConfig httptransport.AuthRateLimitConfig) (func() error, error) {
|
|
if !rateLimitConfig.Enabled {
|
|
// 显式关闭频控时仍记录配置,避免 handler 使用默认开启策略。
|
|
handler.SetAuthRateLimit(rateLimitConfig)
|
|
return nil, nil
|
|
}
|
|
|
|
redisAddr := strings.TrimSpace(cfg.RedisAddr)
|
|
if redisAddr == "" {
|
|
return nil, errors.New("auth rate limit redis_addr is required")
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
redisClient, err := httptransport.NewAuthRateLimitRedisClient(ctx, redisAddr, cfg.RedisPassword, cfg.RedisDB)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("connect auth rate limit redis: %w", err)
|
|
}
|
|
|
|
// 生产入口必须走 Redis 共享计数;内存 backend 只保留给单元测试和显式禁用频控。
|
|
handler.SetAuthRateLimitBackend(rateLimitConfig, httptransport.NewRedisAuthRateLimiter(redisClient, rateLimitConfig))
|
|
return redisClient.Close, nil
|
|
}
|
|
|
|
// Run 启动 HTTP 服务。
|
|
func (a *App) Run() error {
|
|
a.health.MarkHTTPServing()
|
|
err := a.server.Serve(a.listener)
|
|
a.health.MarkHTTPStopped()
|
|
if errors.Is(err, http.ErrServerClosed) {
|
|
return nil
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// Close 关闭 HTTP 服务。
|
|
func (a *App) Close() error {
|
|
var err error
|
|
a.closeOnce.Do(func() {
|
|
a.health.MarkDraining()
|
|
if a.roomConn != nil {
|
|
err = errors.Join(err, a.roomConn.Close())
|
|
}
|
|
if a.userConn != nil {
|
|
err = errors.Join(err, a.userConn.Close())
|
|
}
|
|
if a.walletConn != nil {
|
|
err = errors.Join(err, a.walletConn.Close())
|
|
}
|
|
if a.activityConn != nil {
|
|
err = errors.Join(err, a.activityConn.Close())
|
|
}
|
|
if a.appConfig != nil {
|
|
err = errors.Join(err, a.appConfig.Close())
|
|
}
|
|
if a.redisClose != nil {
|
|
err = errors.Join(err, a.redisClose())
|
|
}
|
|
|
|
err = errors.Join(err, a.server.Close())
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func nodeID() string {
|
|
hostname, err := os.Hostname()
|
|
if err != nil || hostname == "" {
|
|
return "gateway-service"
|
|
}
|
|
|
|
return hostname
|
|
}
|