120 lines
3.5 KiB
Go
120 lines
3.5 KiB
Go
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()
|
||
}
|
||
})
|
||
}
|