docs(constitution): 新增数据库设计原则(v2.4.0)
在项目宪章中新增第九条原则"数据库设计原则",明确禁止使用数据库外键约束和ORM关联标签。 主要变更: - 新增原则IX:数据库设计原则(Database Design Principles) - 强制要求:数据库表不得使用外键约束 - 强制要求:GORM模型不得使用ORM关联标签(foreignKey、hasMany等) - 强制要求:表关系必须通过ID字段手动维护 - 强制要求:关联数据查询必须显式编写,避免ORM魔法 - 强制要求:时间字段由GORM处理,不使用数据库触发器 设计理念: - 提升业务逻辑灵活性(无数据库约束限制) - 优化高并发性能(无外键检查开销) - 增强代码可读性(显式查询,无隐式预加载) - 简化数据库架构和迁移流程 - 支持分布式和微服务场景 版本升级:2.3.0 → 2.4.0(MINOR)
This commit is contained in:
172
pkg/database/postgres.go
Normal file
172
pkg/database/postgres.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/config"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
// InitPostgreSQL 初始化 PostgreSQL 数据库连接
|
||||
func InitPostgreSQL(cfg *config.DatabaseConfig, log *zap.Logger) (*gorm.DB, error) {
|
||||
// 构建 DSN (数据源名称)
|
||||
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
||||
cfg.Host,
|
||||
cfg.Port,
|
||||
cfg.User,
|
||||
cfg.Password,
|
||||
cfg.DBName,
|
||||
cfg.SSLMode,
|
||||
)
|
||||
|
||||
// 配置 GORM
|
||||
gormConfig := &gorm.Config{
|
||||
// 使用自定义日志器(集成 Zap)
|
||||
Logger: newGormLogger(log),
|
||||
// 禁用自动创建表(使用迁移脚本管理)
|
||||
DisableAutomaticPing: false,
|
||||
SkipDefaultTransaction: true, // 提高性能,手动管理事务
|
||||
PrepareStmt: true, // 预编译语句
|
||||
}
|
||||
|
||||
// 连接数据库
|
||||
db, err := gorm.Open(postgres.Open(dsn), gormConfig)
|
||||
if err != nil {
|
||||
log.Error("PostgreSQL 连接失败",
|
||||
zap.String("host", cfg.Host),
|
||||
zap.Int("port", cfg.Port),
|
||||
zap.String("dbname", cfg.DBName),
|
||||
zap.Error(err))
|
||||
return nil, fmt.Errorf("failed to connect to PostgreSQL: %w", err)
|
||||
}
|
||||
|
||||
// 获取底层 SQL DB 对象
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
log.Error("获取 SQL DB 失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("failed to get SQL DB: %w", err)
|
||||
}
|
||||
|
||||
// 配置连接池
|
||||
maxOpenConns := cfg.MaxOpenConns
|
||||
if maxOpenConns <= 0 {
|
||||
maxOpenConns = constants.DefaultMaxOpenConns
|
||||
}
|
||||
|
||||
maxIdleConns := cfg.MaxIdleConns
|
||||
if maxIdleConns <= 0 {
|
||||
maxIdleConns = constants.DefaultMaxIdleConns
|
||||
}
|
||||
|
||||
connMaxLifetime := cfg.ConnMaxLifetime
|
||||
if connMaxLifetime <= 0 {
|
||||
connMaxLifetime = constants.DefaultConnMaxLifetime
|
||||
}
|
||||
|
||||
sqlDB.SetMaxOpenConns(maxOpenConns)
|
||||
sqlDB.SetMaxIdleConns(maxIdleConns)
|
||||
sqlDB.SetConnMaxLifetime(connMaxLifetime)
|
||||
|
||||
// 验证连接
|
||||
if err := sqlDB.Ping(); err != nil {
|
||||
log.Error("PostgreSQL Ping 失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("failed to ping PostgreSQL: %w", err)
|
||||
}
|
||||
|
||||
log.Info("PostgreSQL 连接成功",
|
||||
zap.String("host", cfg.Host),
|
||||
zap.Int("port", cfg.Port),
|
||||
zap.String("dbname", cfg.DBName),
|
||||
zap.Int("max_open_conns", maxOpenConns),
|
||||
zap.Int("max_idle_conns", maxIdleConns),
|
||||
zap.Duration("conn_max_lifetime", connMaxLifetime))
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// gormLogger 自定义 GORM 日志器,集成 Zap
|
||||
type gormLogger struct {
|
||||
zap *zap.Logger
|
||||
slowQueryThreshold time.Duration
|
||||
ignoreRecordNotFound bool
|
||||
logLevel logger.LogLevel
|
||||
}
|
||||
|
||||
// newGormLogger 创建新的 GORM 日志器
|
||||
func newGormLogger(log *zap.Logger) logger.Interface {
|
||||
return &gormLogger{
|
||||
zap: log,
|
||||
slowQueryThreshold: constants.SlowQueryThreshold,
|
||||
ignoreRecordNotFound: true,
|
||||
logLevel: logger.Info,
|
||||
}
|
||||
}
|
||||
|
||||
// LogMode 设置日志级别
|
||||
func (l *gormLogger) LogMode(level logger.LogLevel) logger.Interface {
|
||||
newLogger := *l
|
||||
newLogger.logLevel = level
|
||||
return &newLogger
|
||||
}
|
||||
|
||||
// Info 记录 Info 级别日志
|
||||
func (l *gormLogger) Info(ctx context.Context, msg string, data ...interface{}) {
|
||||
if l.logLevel >= logger.Info {
|
||||
l.zap.Sugar().Infof(msg, data...)
|
||||
}
|
||||
}
|
||||
|
||||
// Warn 记录 Warn 级别日志
|
||||
func (l *gormLogger) Warn(ctx context.Context, msg string, data ...interface{}) {
|
||||
if l.logLevel >= logger.Warn {
|
||||
l.zap.Sugar().Warnf(msg, data...)
|
||||
}
|
||||
}
|
||||
|
||||
// Error 记录 Error 级别日志
|
||||
func (l *gormLogger) Error(ctx context.Context, msg string, data ...interface{}) {
|
||||
if l.logLevel >= logger.Error {
|
||||
l.zap.Sugar().Errorf(msg, data...)
|
||||
}
|
||||
}
|
||||
|
||||
// Trace 记录 SQL 查询日志
|
||||
func (l *gormLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
|
||||
if l.logLevel <= logger.Silent {
|
||||
return
|
||||
}
|
||||
|
||||
elapsed := time.Since(begin)
|
||||
sql, rows := fc()
|
||||
|
||||
switch {
|
||||
case err != nil && l.logLevel >= logger.Error && (!l.ignoreRecordNotFound || err != gorm.ErrRecordNotFound):
|
||||
// 查询错误
|
||||
l.zap.Error("SQL 查询失败",
|
||||
zap.String("sql", sql),
|
||||
zap.Int64("rows", rows),
|
||||
zap.Duration("elapsed", elapsed),
|
||||
zap.Error(err))
|
||||
|
||||
case elapsed > l.slowQueryThreshold && l.logLevel >= logger.Warn:
|
||||
// 慢查询
|
||||
l.zap.Warn("慢查询检测",
|
||||
zap.String("sql", sql),
|
||||
zap.Int64("rows", rows),
|
||||
zap.Duration("elapsed", elapsed),
|
||||
zap.Duration("threshold", l.slowQueryThreshold))
|
||||
|
||||
case l.logLevel >= logger.Info:
|
||||
// 正常查询
|
||||
l.zap.Debug("SQL 查询",
|
||||
zap.String("sql", sql),
|
||||
zap.Int64("rows", rows),
|
||||
zap.Duration("elapsed", elapsed))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user