2026-04-25 13:21:39 +08:00

120 lines
3.5 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 app
import (
"context"
"errors"
"net"
"sync"
"time"
userv1 "hyapp/api/proto/user/v1"
"hyapp/pkg/grpchealth"
"hyapp/pkg/idgen"
"hyapp/services/user-service/internal/config"
authservice "hyapp/services/user-service/internal/service/auth"
userservice "hyapp/services/user-service/internal/service/user"
"hyapp/services/user-service/internal/storage/memory"
mysqlstorage "hyapp/services/user-service/internal/storage/mysql"
grpcserver "hyapp/services/user-service/internal/transport/grpc"
"google.golang.org/grpc"
healthgrpc "google.golang.org/grpc/health/grpc_health_v1"
)
// App 装配 user-service gRPC 入口和底座依赖。
type App struct {
server *grpc.Server
listener net.Listener
health *grpchealth.ServingChecker
mysqlRepo *mysqlstorage.Repository
closeOnce sync.Once
}
// New 初始化 user-service 应用。
func New(cfg config.Config) (*App, error) {
var userRepository userservice.Repository
var authRepository authservice.Repository
var mysqlRepo *mysqlstorage.Repository
if cfg.MySQLDSN != "" {
startupCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
repo, err := mysqlstorage.Open(startupCtx, cfg.MySQLDSN)
if err != nil {
return nil, err
}
mysqlRepo = repo
userRepository = repo
authRepository = repo
} else {
// 未配置 MySQL 时使用 memory repository 让本地单测和进程骨架可独立启动。
// 生产配置必须提供 mysql_dsn不能依赖该分支保存用户主数据。
repository := memory.NewRepository()
userRepository = repository
authRepository = repository
}
listener, err := net.Listen("tcp", cfg.GRPCAddr)
if err != nil {
if mysqlRepo != nil {
_ = mysqlRepo.Close()
}
return nil, err
}
server := grpc.NewServer()
authSvc := authservice.New(authservice.Config{
Issuer: cfg.JWT.Issuer,
AccessTokenTTLSec: cfg.JWT.AccessTokenTTLSec,
RefreshTokenTTLSec: cfg.JWT.RefreshTokenTTLSec,
SigningAlg: cfg.JWT.SigningAlg,
SigningSecret: cfg.JWT.SigningSecret,
},
authservice.WithRepository(authRepository),
authservice.WithIDGenerator(idgen.NewInt64Generator(cfg.IDGenerator.NodeID)),
authservice.WithDisplayUserIDAllocateMaxAttempts(cfg.DisplayUserID.AllocateMaxAttempts),
authservice.WithThirdPartyVerifier(authservice.NewStaticThirdPartyVerifier(cfg.ThirdParty.AllowedProviders)),
)
userSvc := userservice.New(userRepository,
userservice.WithIDGenerator(idgen.NewInt64Generator(cfg.IDGenerator.NodeID)),
userservice.WithDisplayUserIDPolicy(cfg.DisplayUserID.AllocateMaxAttempts, time.Duration(cfg.DisplayUserID.ChangeCooldownSec)*time.Second),
)
userServer := grpcserver.NewServer(authSvc, userSvc)
userv1.RegisterAuthServiceServer(server, userServer)
userv1.RegisterUserServiceServer(server, userServer)
userv1.RegisterUserIdentityServiceServer(server, userServer)
health := grpchealth.NewServingChecker("user-service")
healthgrpc.RegisterHealthServer(server, grpchealth.NewServer(health))
return &App{
server: server,
listener: listener,
health: health,
mysqlRepo: mysqlRepo,
}, nil
}
// Run 启动 gRPC 服务。
func (a *App) Run() error {
a.health.MarkServing()
err := a.server.Serve(a.listener)
a.health.MarkStopped()
if errors.Is(err, grpc.ErrServerStopped) {
return nil
}
return err
}
// Close 关闭占位 gRPC 服务。
func (a *App) Close() {
a.closeOnce.Do(func() {
a.health.MarkDraining()
a.server.GracefulStop()
if a.mysqlRepo != nil {
_ = a.mysqlRepo.Close()
}
})
}