dual check v0.1

This commit is contained in:
cancerai committed 2026-08-29 23:34:01 +08:00
1 parent 78bad1fa3d
commit c137299016
36 files changed
+3727 -81

No files matched your search

+169
View File
@@ -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()
}