dual check v0.1
This commit is contained in:
1 parent
78bad1fa3d
commit
c137299016
36 files changed
+3727
-81
No files matched your search
@@ -0,0 +1,169 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"cancerai.top/toolbox-server/configs"
|
||||
"cancerai.top/toolbox-server/internal/middleware"
|
||||
"cancerai.top/toolbox-server/internal/pkg/websocket"
|
||||
"cancerai.top/toolbox-server/internal/store"
|
||||
"cancerai.top/toolbox-server/internal/tools/dual_check"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// === 1. 加载配置 ===
|
||||
cfg, err := configs.Load()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[FATAL] load config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// === 2. 初始化日志 ===
|
||||
logger, err := initLogger(cfg.Log.Level, cfg.Log.Format)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[FATAL] init logger: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer logger.Sync()
|
||||
logger.Info("config loaded",
|
||||
zap.String("redis.addr", cfg.Redis.Addr),
|
||||
zap.String("server.addr", fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)),
|
||||
)
|
||||
|
||||
// === 3. 初始化 Redis ===
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: cfg.Redis.Addr,
|
||||
Password: cfg.Redis.Password,
|
||||
DB: cfg.Redis.DB,
|
||||
PoolSize: cfg.Redis.PoolSize,
|
||||
DialTimeout: 5 * time.Second,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 5 * time.Second,
|
||||
})
|
||||
// 验证 Redis 连通性
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
if err := rdb.Ping(ctx).Err(); err != nil {
|
||||
logger.Fatal("redis connection failed", zap.Error(err))
|
||||
}
|
||||
logger.Info("redis connected", zap.String("addr", cfg.Redis.Addr))
|
||||
|
||||
// === 4. 初始化共享组件 ===
|
||||
sessionStore := store.NewRedisSessionStore(rdb, time.Duration(cfg.Session.TTL)*time.Second)
|
||||
wsHub := websocket.NewHub()
|
||||
|
||||
// === 5. 初始化 Gin + 中间件 ===
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
r := gin.New()
|
||||
|
||||
r.Use(middleware.Recover(logger))
|
||||
r.Use(middleware.RequestID())
|
||||
r.Use(middleware.Logger(logger))
|
||||
r.Use(middleware.CORS(cfg.CORS.AllowedOrigins))
|
||||
r.Use(middleware.RateLimiter(middleware.RateLimitConfig{
|
||||
GlobalRPS: cfg.RateLimit.GlobalRPS,
|
||||
PerIPRPS: cfg.RateLimit.PerIPRPS,
|
||||
PerIPBurst: cfg.RateLimit.PerIPBurst,
|
||||
}))
|
||||
|
||||
// === 6. 健康检查 ===
|
||||
r.GET("/healthz", func(c *gin.Context) {
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second)
|
||||
defer cancel()
|
||||
if err := rdb.Ping(ctx).Err(); err != nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{
|
||||
"status": "unhealthy",
|
||||
"redis": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "ok",
|
||||
"time": time.Now().Format(time.RFC3339),
|
||||
})
|
||||
})
|
||||
|
||||
// === 7. 注册工具路由 ===
|
||||
v1 := r.Group("/api/v1")
|
||||
{
|
||||
dual_check.Register(
|
||||
v1.Group("/dual-check"),
|
||||
sessionStore,
|
||||
wsHub,
|
||||
logger,
|
||||
cfg.Session.TTL,
|
||||
cfg.Session.MaxTextLength,
|
||||
)
|
||||
// 未来新增工具示例:
|
||||
// to_qrcode.Register(v1.Group("/to-qrcode"), ...)
|
||||
}
|
||||
|
||||
// === 8. 启动 HTTP Server ===
|
||||
addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: r,
|
||||
ReadTimeout: cfg.Server.ReadTimeout,
|
||||
WriteTimeout: cfg.Server.WriteTimeout,
|
||||
IdleTimeout: cfg.Server.IdleTimeout,
|
||||
}
|
||||
|
||||
go func() {
|
||||
logger.Info("server listening", zap.String("addr", addr))
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
logger.Fatal("listen failed", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
// === 9. 优雅关停 ===
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
sig := <-quit
|
||||
logger.Info("received shutdown signal", zap.String("signal", sig.String()))
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
logger.Error("http server shutdown error", zap.Error(err))
|
||||
}
|
||||
|
||||
// 关闭 Redis 连接
|
||||
if err := rdb.Close(); err != nil {
|
||||
logger.Error("redis close error", zap.Error(err))
|
||||
}
|
||||
|
||||
logger.Info("server exited gracefully")
|
||||
}
|
||||
|
||||
func initLogger(level, format string) (*zap.Logger, error) {
|
||||
var cfg zap.Config
|
||||
switch format {
|
||||
case "console":
|
||||
cfg = zap.NewDevelopmentConfig()
|
||||
default:
|
||||
cfg = zap.NewProductionConfig()
|
||||
}
|
||||
|
||||
switch level {
|
||||
case "debug":
|
||||
cfg.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
|
||||
case "warn":
|
||||
cfg.Level = zap.NewAtomicLevelAt(zap.WarnLevel)
|
||||
case "error":
|
||||
cfg.Level = zap.NewAtomicLevelAt(zap.ErrorLevel)
|
||||
default:
|
||||
cfg.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
|
||||
}
|
||||
|
||||
return cfg.Build()
|
||||
}
|
||||
Reference in new issue
Block a user