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:
2025-11-13 13:40:19 +08:00
parent ea0c6a8b16
commit 984ccccc63
63 changed files with 12099 additions and 83 deletions

172
pkg/database/postgres.go Normal file
View 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))
}
}