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() } }) }