package health import ( "context" "net/http" "time" "mengstack/internal/app/version" "github.com/gin-gonic/gin" "github.com/redis/go-redis/v9" "go.uber.org/zap" "gorm.io/gorm" ) type Handler struct { db *gorm.DB rdb *redis.Client log *zap.Logger } func NewHandler(db *gorm.DB, rdb *redis.Client, log *zap.Logger) *Handler { return &Handler{db: db, rdb: rdb, log: log} } // Handle godoc // @Summary 健康检查 // @Description 检查 PostgreSQL 和 Redis 连接状态 // @Tags System // @Produce json // @Success 200 {object} object{status=string,database=string,redis=string,timestamp=integer,version=string,trace_id=string} // @Router /health [get] func (h *Handler) Handle(c *gin.Context) { status := "ok" dbStatus := "connected" redisStatus := "connected" sqlDB, err := h.db.DB() if err != nil || sqlDB.Ping() != nil { status = "degraded" dbStatus = "disconnected" } ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second) defer cancel() if h.rdb.Ping(ctx).Err() != nil { status = "degraded" redisStatus = "disconnected" } c.JSON(http.StatusOK, gin.H{ "status": status, "database": dbStatus, "redis": redisStatus, "timestamp": time.Now().Unix(), "version": version.Version, "trace_id": c.GetString("trace_id"), }) } // Version godoc // @Summary 版本信息 // @Description 返回构建版本、提交哈希、编译时间等 // @Tags System // @Produce json // @Success 200 {object} version.Info // @Router /api/version [get] func (h *Handler) Version(c *gin.Context) { c.JSON(http.StatusOK, version.Current()) } // Healthz godoc // @Summary 存活探针 // @Description Kubernetes liveness probe,进程存活即返回 200 // @Tags System // @Produce json // @Success 200 {object} object{status=string} // @Router /healthz [get] func (h *Handler) Healthz(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "ok"}) } // Readyz godoc // @Summary 就绪探针 // @Description Kubernetes readiness probe,检查数据库和 Redis 连接 // @Tags System // @Produce json // @Success 200 {object} object{status=string,database=string,redis=string} // @Failure 503 {object} object{status=string,database=string,redis=string} // @Router /readyz [get] func (h *Handler) Readyz(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second) defer cancel() dbOK := true sqlDB, err := h.db.DB() if err != nil || sqlDB.Ping() != nil { dbOK = false } redisOK := true if h.rdb.Ping(ctx).Err() != nil { redisOK = false } body := gin.H{ "database": boolToStatus(dbOK), "redis": boolToStatus(redisOK), } if !dbOK || !redisOK { c.JSON(http.StatusServiceUnavailable, body) return } c.JSON(http.StatusOK, body) } func boolToStatus(ok bool) string { if ok { return "connected" } return "disconnected" }