Files
backend/internal/grpc/runner/server.go
lan c561a0bb0c feat(grpc): add gRPC server infrastructure and schedule sync service
Add gRPC server support with configurable TLS, environment-based settings, and Wire injection. Implement schedule synchronization service with task runner integration for external course data fetching.

- Add gRPC configuration with env var overrides (APP_GRPC_ENABLED, APP_GRPC_PORT, etc.)
- Create gRPC server infrastructure with runner task management
- Implement ScheduleSyncService for course data synchronization
- Add sync endpoint to schedule handler for external system integration
- Update repository with context-aware batch delete operation
- Wire all dependencies including zap logger for structured logging
2026-03-13 20:40:20 +08:00

146 lines
3.2 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 runner
import (
"context"
"fmt"
"net"
"time"
"carrot_bbs/internal/config"
"carrot_bbs/proto/runner"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)
// Server gRPC 服务器
type Server struct {
cfg *config.GRPCConfig
logger *zap.Logger
hub *RunnerHub
grpcSrv *grpc.Server
}
// NewServer 创建新的 gRPC 服务器
func NewServer(cfg *config.GRPCConfig, logger *zap.Logger) *Server {
// 创建 TaskManager 和 RunnerHub解决循环依赖
taskManager := NewTaskManager(nil, 60*time.Second, logger)
hub := NewRunnerHub(taskManager, logger)
taskManager.SetHub(hub)
return &Server{
cfg: cfg,
logger: logger,
hub: hub,
}
}
// Start 启动 gRPC 服务器
func (s *Server) Start() error {
if !s.cfg.Enabled {
s.logger.Info("gRPC server is disabled")
return nil
}
lis, err := net.Listen("tcp", s.cfg.Address())
if err != nil {
return fmt.Errorf("failed to listen on %s: %w", s.cfg.Address(), err)
}
// 配置 gRPC 服务器选项
var opts []grpc.ServerOption
// 配置 TLS
if s.cfg.TLSEnabled {
if s.cfg.TLSCertFile == "" || s.cfg.TLSKeyFile == "" {
return fmt.Errorf("TLS enabled but cert/key files not specified")
}
creds, err := credentials.NewServerTLSFromFile(s.cfg.TLSCertFile, s.cfg.TLSKeyFile)
if err != nil {
return fmt.Errorf("failed to load TLS credentials: %w", err)
}
opts = append(opts, grpc.Creds(creds))
s.logger.Info("gRPC server TLS enabled")
} else {
s.logger.Warn("gRPC server running without TLS")
}
s.grpcSrv = grpc.NewServer(opts...)
runner.RegisterRunnerHubServer(s.grpcSrv, s.hub)
s.logger.Info("gRPC server starting", zap.String("address", s.cfg.Address()))
go func() {
if err := s.grpcSrv.Serve(lis); err != nil {
s.logger.Error("gRPC server error", zap.Error(err))
}
}()
// 启动后台任务清理协程
go s.backgroundCleanup()
return nil
}
// backgroundCleanup 后台清理任务
func (s *Server) backgroundCleanup() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for range ticker.C {
// 清理超时的 Runner 连接
s.hub.CheckStaleRunners(90 * time.Second)
}
}
// Stop 停止 gRPC 服务器
func (s *Server) Stop() {
if s.grpcSrv != nil {
s.logger.Info("Stopping gRPC server")
s.grpcSrv.GracefulStop()
}
}
// GetHub 获取 RunnerHub
func (s *Server) GetHub() *RunnerHub {
return s.hub
}
// GetTaskManager 获取 TaskManager
func (s *Server) GetTaskManager() *TaskManager {
return s.hub.taskManager
}
// Shutdown 优雅关闭
func (s *Server) Shutdown(ctx context.Context) error {
if s.grpcSrv == nil {
return nil
}
done := make(chan struct{})
go func() {
s.grpcSrv.GracefulStop()
close(done)
}()
select {
case <-done:
s.logger.Info("gRPC server stopped gracefully")
return nil
case <-ctx.Done():
s.logger.Warn("gRPC server shutdown timeout, forcing stop")
s.grpcSrv.Stop()
return ctx.Err()
}
}
// GetClientCredentials 获取客户端凭证配置
func GetClientCredentials(cfg *config.GRPCConfig) (credentials.TransportCredentials, error) {
if cfg.TLSEnabled {
return credentials.NewClientTLSFromFile(cfg.TLSCertFile, "")
}
return insecure.NewCredentials(), nil
}