feat: main assembly — wire all components, gin router, graceful shutdown

This commit is contained in:
2026-06-27 13:28:27 +08:00
parent 2271a54139
commit fa7f7490f5
6 changed files with 215 additions and 14 deletions
+140 -1
View File
@@ -1,21 +1,160 @@
package main
import (
"context"
"fmt"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"aiaa-notification-service/internal/adapter"
"aiaa-notification-service/internal/cache"
"aiaa-notification-service/internal/config"
"aiaa-notification-service/internal/engine"
"aiaa-notification-service/internal/handler"
"aiaa-notification-service/internal/store"
"github.com/gin-gonic/gin"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
slog.SetDefault(logger)
// Load config
cfg, err := config.Load("config/config.yaml")
if err != nil {
slog.Error("failed to load config", "error", err)
os.Exit(1)
}
slog.Info("config loaded", "port", cfg.Server.Port)
// Connect MySQL
st, err := store.NewStore(cfg.Database)
if err != nil {
slog.Error("failed to connect to MySQL", "error", err)
os.Exit(1)
}
defer st.Close()
// Connect Redis
var redisCache *cache.Cache
if cfg.Redis.Host != "" {
redisCache, err = cache.NewCache(cfg.Redis)
if err != nil {
slog.Warn("redis connection failed, running without cache", "error", err)
redisCache = nil
}
} else {
slog.Warn("redis not configured, running without cache")
}
if redisCache != nil {
defer redisCache.Close()
}
// Build engine
matcher := engine.NewMatcher(st, redisCache)
renderer := engine.NewRenderer()
// Build sender factory
senderFactory := func(channelType string) (adapter.ChannelSender, error) {
return adapter.NewSender(channelType, &cfg.SMTP)
}
router := engine.NewRouter(st, redisCache, senderFactory)
// Build handlers
notifyH := handler.NewNotifyHandler(st, redisCache, matcher, renderer, router)
sourceH := handler.NewSourceHandler(st, redisCache)
templateH := handler.NewTemplateHandler(st, redisCache)
channelH := handler.NewChannelHandler(st, redisCache)
ruleH := handler.NewRuleHandler(st, redisCache)
msgLogH := handler.NewMessageLogHandler(st)
// Setup Gin
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Recovery())
// Health check
r.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
api := r.Group("/api/v1")
// Notify endpoint (source auth + rate limit)
notifyGroup := api.Group("/notify")
notifyGroup.Use(handler.SourceAuth(st))
if redisCache != nil {
notifyGroup.Use(handler.RateLimit(redisCache, cfg.RateLimit.Default))
}
notifyGroup.POST("", notifyH.Handle)
// Management endpoints (admin auth)
admin := api.Group("")
admin.Use(handler.AdminAuth(cfg.Server.AdminKey))
// Sources
admin.POST("/sources", sourceH.Create)
admin.GET("/sources", sourceH.List)
admin.GET("/sources/:id", sourceH.Get)
admin.PUT("/sources/:id", sourceH.Update)
admin.DELETE("/sources/:id", sourceH.Delete)
// Templates
admin.POST("/templates", templateH.Create)
admin.GET("/templates", templateH.List)
admin.GET("/templates/:id", templateH.Get)
admin.PUT("/templates/:id", templateH.Update)
admin.DELETE("/templates/:id", templateH.Delete)
// Channels
admin.POST("/channels", channelH.Create)
admin.GET("/channels", channelH.List)
admin.GET("/channels/:id", channelH.Get)
admin.PUT("/channels/:id", channelH.Update)
admin.DELETE("/channels/:id", channelH.Delete)
// Rules
admin.POST("/rules", ruleH.Create)
admin.GET("/rules", ruleH.List)
admin.GET("/rules/:id", ruleH.Get)
admin.PUT("/rules/:id", ruleH.Update)
admin.DELETE("/rules/:id", ruleH.Delete)
admin.PATCH("/rules/:id/enable", ruleH.Enable)
admin.PATCH("/rules/:id/disable", ruleH.Disable)
admin.PATCH("/rules/:id/channels/:channel_id/enable", ruleH.EnableChannel)
admin.PATCH("/rules/:id/channels/:channel_id/disable", ruleH.DisableChannel)
// Message Logs
admin.GET("/message-logs", msgLogH.List)
// Start server
srv := &http.Server{
Addr: fmt.Sprintf(":%d", cfg.Server.Port),
Handler: r,
}
go func() {
slog.Info("server starting", "port", cfg.Server.Port)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
slog.Error("server error", "error", err)
os.Exit(1)
}
}()
// Graceful shutdown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
slog.Info("shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
slog.Error("forced shutdown", "error", err)
}
slog.Info("server stopped")
}