在项目宪章中新增第九条原则"数据库设计原则",明确禁止使用数据库外键约束和ORM关联标签。 主要变更: - 新增原则IX:数据库设计原则(Database Design Principles) - 强制要求:数据库表不得使用外键约束 - 强制要求:GORM模型不得使用ORM关联标签(foreignKey、hasMany等) - 强制要求:表关系必须通过ID字段手动维护 - 强制要求:关联数据查询必须显式编写,避免ORM魔法 - 强制要求:时间字段由GORM处理,不使用数据库触发器 设计理念: - 提升业务逻辑灵活性(无数据库约束限制) - 优化高并发性能(无外键检查开销) - 增强代码可读性(显式查询,无隐式预加载) - 简化数据库架构和迁移流程 - 支持分布式和微服务场景 版本升级:2.3.0 → 2.4.0(MINOR)
119 lines
2.9 KiB
Go
119 lines
2.9 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/redis/go-redis/v9"
|
|
"go.uber.org/zap"
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/break/junhong_cmp_fiber/pkg/response"
|
|
)
|
|
|
|
// HealthHandler 健康检查处理器
|
|
type HealthHandler struct {
|
|
db *gorm.DB
|
|
redis *redis.Client
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewHealthHandler 创建健康检查处理器实例
|
|
func NewHealthHandler(db *gorm.DB, redis *redis.Client, logger *zap.Logger) *HealthHandler {
|
|
return &HealthHandler{
|
|
db: db,
|
|
redis: redis,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// Check 健康检查
|
|
// GET /health
|
|
func (h *HealthHandler) Check(c *fiber.Ctx) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
|
|
healthStatus := fiber.Map{
|
|
"status": "healthy",
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
"services": fiber.Map{},
|
|
}
|
|
|
|
services := healthStatus["services"].(fiber.Map)
|
|
allHealthy := true
|
|
|
|
// 检查 PostgreSQL
|
|
sqlDB, err := h.db.DB()
|
|
if err != nil {
|
|
h.logger.Error("获取 PostgreSQL DB 实例失败", zap.Error(err))
|
|
services["postgres"] = fiber.Map{
|
|
"status": "down",
|
|
"error": err.Error(),
|
|
}
|
|
allHealthy = false
|
|
} else {
|
|
if err := sqlDB.PingContext(ctx); err != nil {
|
|
h.logger.Error("PostgreSQL Ping 失败", zap.Error(err))
|
|
services["postgres"] = fiber.Map{
|
|
"status": "down",
|
|
"error": err.Error(),
|
|
}
|
|
allHealthy = false
|
|
} else {
|
|
// 获取连接池统计信息
|
|
stats := sqlDB.Stats()
|
|
services["postgres"] = fiber.Map{
|
|
"status": "up",
|
|
"open_conns": stats.OpenConnections,
|
|
"in_use": stats.InUse,
|
|
"idle": stats.Idle,
|
|
"wait_count": stats.WaitCount,
|
|
"wait_duration": stats.WaitDuration.String(),
|
|
"max_idle_close": stats.MaxIdleClosed,
|
|
"max_lifetime_close": stats.MaxLifetimeClosed,
|
|
}
|
|
}
|
|
}
|
|
|
|
// 检查 Redis
|
|
if err := h.redis.Ping(ctx).Err(); err != nil {
|
|
h.logger.Error("Redis Ping 失败", zap.Error(err))
|
|
services["redis"] = fiber.Map{
|
|
"status": "down",
|
|
"error": err.Error(),
|
|
}
|
|
allHealthy = false
|
|
} else {
|
|
// 获取 Redis 信息
|
|
poolStats := h.redis.PoolStats()
|
|
services["redis"] = fiber.Map{
|
|
"status": "up",
|
|
"hits": poolStats.Hits,
|
|
"misses": poolStats.Misses,
|
|
"timeouts": poolStats.Timeouts,
|
|
"total_conns": poolStats.TotalConns,
|
|
"idle_conns": poolStats.IdleConns,
|
|
"stale_conns": poolStats.StaleConns,
|
|
}
|
|
}
|
|
|
|
// 设置总体状态
|
|
if !allHealthy {
|
|
healthStatus["status"] = "degraded"
|
|
h.logger.Warn("健康检查失败: 部分服务不可用")
|
|
return c.Status(fiber.StatusServiceUnavailable).JSON(healthStatus)
|
|
}
|
|
|
|
h.logger.Info("健康检查成功: 所有服务正常")
|
|
return response.Success(c, healthStatus)
|
|
}
|
|
|
|
// HealthCheck 简单健康检查(保持向后兼容)
|
|
func HealthCheck(c *fiber.Ctx) error {
|
|
return response.Success(c, fiber.Map{
|
|
"status": "healthy",
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
})
|
|
}
|