package main import ( "backend/common" shipcommon "backend/sdk/ship/common" minigame "backend/sdk/ship/model/mini_game" "backend/sdk/ship/model/test" "backend/sdk/ship/model/tuyou" "backend/util" "context" "fmt" "io" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/gin-gonic/gin" rotatelogs "github.com/lestrrat-go/file-rotatelogs" ) var ( rl *rotatelogs.RotateLogs logWriter io.Writer errWriter io.Writer ) func init() { // 确保日志目录存在 if err := os.MkdirAll("./log", 0755); err != nil { log.Fatalf("failed to create log dir: %v", err) } // 使用按天轮转的日志文件,保留最近 30 个文件 var err error rl, err = rotatelogs.New( "./log/charge.%Y-%m-%d.log", rotatelogs.WithRotationTime(24*time.Hour), rotatelogs.WithRotationCount(30), ) if err != nil { log.Fatalf("failed to initialize log rotator: %v", err) } // 打开一个普通的最新日志文件(不使用 symlink),用于提供固定路径的最新日志 currFile, err := os.OpenFile("./log/charge.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { // 如果打开失败,仍然继续使用轮转器+控制台 util.LogStructured("warn", "failed to open current log file", map[string]any{"error": err.Error()}) logWriter = io.MultiWriter(rl, os.Stdout) errWriter = io.MultiWriter(rl, os.Stderr) } else { // 同时输出到轮转日志、固定最新日志文件和控制台 logWriter = io.MultiWriter(rl, currFile, os.Stdout) errWriter = io.MultiWriter(rl, currFile, os.Stderr) } log.SetOutput(logWriter) // 保持全局默认 writer(以兼容其他调用) gin.DefaultWriter = os.Stdout gin.DefaultErrorWriter = errWriter common.Init() } func requestLogger() gin.HandlerFunc { return func(c *gin.Context) { startedAt := time.Now() c.Next() util.LogStructured("info", "http request", map[string]any{ "clientIp": c.ClientIP(), "latencyMs": time.Since(startedAt).Milliseconds(), "method": c.Request.Method, "path": c.Request.URL.Path, "query": c.Request.URL.RawQuery, "status": c.Writer.Status(), "userAgent": c.Request.UserAgent(), }) } } func main() { // 使用 gin.New 并显式注入写入器,确保中间件把日志写到轮转器 // gin.SetMode(gin.ReleaseMode) r := gin.New() r.Use(requestLogger()) r.Use(gin.RecoveryWithWriter(errWriter)) ChargeApi := r.Group("/api") { // 充值发货 ChargeApi.POST("test/charge", test.Charge) ChargeApi.POST("tuyou/charge", tuyou.Charge) //小游戏奖励 ChargeApi.POST("tuyou/reward", minigame.Reward) } server := &http.Server{ Addr: fmt.Sprintf(":%d", shipcommon.AppConf.Port), Handler: r, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 15 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, } shutdownCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM) defer stop() serverErr := make(chan error, 1) go func() { util.LogStructured("info", "ship sdk started", map[string]any{ "port": shipcommon.AppConf.Port, "version": shipcommon.AppConf.Version, }) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { serverErr <- err } close(serverErr) }() select { case err, ok := <-serverErr: if ok && err != nil { util.LogStructured("error", "ship sdk start failed", map[string]any{"error": err.Error()}) os.Exit(1) } case <-shutdownCtx.Done(): util.LogStructured("info", "ship sdk shutting down", map[string]any{"reason": shutdownCtx.Err().Error()}) } gracefulCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := server.Shutdown(gracefulCtx); err != nil { util.LogStructured("error", "ship sdk shutdown failed", map[string]any{"error": err.Error()}) os.Exit(1) } util.LogStructured("info", "ship sdk stopped", nil) }