From da4875fc1677d67f37b8b592b5bc394a2db44611 Mon Sep 17 00:00:00 2001 From: Rogee Date: Thu, 11 Sep 2025 16:26:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=20PostgreSQL=20?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=EF=BC=8C=E6=B7=BB=E5=8A=A0=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E7=94=9F=E5=91=BD=E5=91=A8=E6=9C=9F=E5=92=8C=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project/providers/postgres/config.go.tpl | 65 +++++++++++++++++-- .../providers/postgres/postgres.go.tpl | 52 ++++++++++++--- 2 files changed, 105 insertions(+), 12 deletions(-) mode change 100755 => 100644 templates/project/providers/postgres/config.go.tpl diff --git a/templates/project/providers/postgres/config.go.tpl b/templates/project/providers/postgres/config.go.tpl old mode 100755 new mode 100644 index 7cfe4de..de20ce8 --- a/templates/project/providers/postgres/config.go.tpl +++ b/templates/project/providers/postgres/config.go.tpl @@ -2,9 +2,12 @@ package postgres import ( "fmt" + "strconv" + "time" "go.ipao.vip/atom/container" "go.ipao.vip/atom/opt" + "gorm.io/gorm/logger" ) const DefaultPrefix = "Database" @@ -31,6 +34,42 @@ type Config struct { Singular bool // 是否开启全局禁用复数,true表示开启 MaxIdleConns int // 空闲中的最大连接数 MaxOpenConns int // 打开到数据库的最大连接数 + // 可选:连接生命周期配置(0 表示不设置) + ConnMaxLifetimeSeconds uint + ConnMaxIdleTimeSeconds uint + + // 可选:GORM 日志与行为配置 + LogLevel string // silent|error|warn|info(默认info) + SlowThresholdMs uint // 慢查询阈值(毫秒)默认200 + ParameterizedQueries bool // 占位符输出,便于日志安全与查询归并 + PrepareStmt bool // 预编译语句缓存 + SkipDefaultTransaction bool // 跳过默认事务 + + // 可选:DSN 增强 + UseSearchPath bool // 在 DSN 中附带 search_path + ApplicationName string // application_name +} + +func (m Config) GormSlowThreshold() time.Duration { + if m.SlowThresholdMs == 0 { + return 200 * time.Millisecond // 默认200ms + } + return time.Duration(m.SlowThresholdMs) * time.Millisecond +} + +func (m Config) GormLogLevel() logger.LogLevel { + switch m.LogLevel { + case "silent": + return logger.Silent + case "error": + return logger.Error + case "warn": + return logger.Warn + case "info", "": + return logger.Info + default: + return logger.Info + } } func (m *Config) checkDefault() { @@ -64,16 +103,34 @@ func (m *Config) checkDefault() { } func (m *Config) EmptyDsn() string { + // 基本 DSN dsnTpl := "host=%s user=%s password=%s port=%d dbname=%s sslmode=%s TimeZone=%s" m.checkDefault() - - return fmt.Sprintf(dsnTpl, m.Host, m.Username, m.Password, m.Port, m.Database, m.SslMode, m.TimeZone) + base := fmt.Sprintf(dsnTpl, m.Host, m.Username, m.Password, m.Port, m.Database, m.SslMode, m.TimeZone) + // 附加可选参数 + extras := "" + if m.UseSearchPath && m.Schema != "" { + extras += " search_path=" + m.Schema + } + if m.ApplicationName != "" { + extras += " application_name=" + strconv.Quote(m.ApplicationName) + } + return base + extras } // DSN connection dsn func (m *Config) DSN() string { + // 基本 DSN dsnTpl := "host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=%s" m.checkDefault() - - return fmt.Sprintf(dsnTpl, m.Host, m.Username, m.Password, m.Database, m.Port, m.SslMode, m.TimeZone) + base := fmt.Sprintf(dsnTpl, m.Host, m.Username, m.Password, m.Database, m.Port, m.SslMode, m.TimeZone) + // 附加可选参数 + extras := "" + if m.UseSearchPath && m.Schema != "" { + extras += " search_path=" + m.Schema + } + if m.ApplicationName != "" { + extras += " application_name=" + strconv.Quote(m.ApplicationName) + } + return base + extras } diff --git a/templates/project/providers/postgres/postgres.go.tpl b/templates/project/providers/postgres/postgres.go.tpl index f0a5097..e3e7a6a 100644 --- a/templates/project/providers/postgres/postgres.go.tpl +++ b/templates/project/providers/postgres/postgres.go.tpl @@ -1,6 +1,9 @@ package postgres import ( + "context" + "time" + "github.com/sirupsen/logrus" "go.ipao.vip/atom/container" "go.ipao.vip/atom/opt" @@ -18,10 +21,24 @@ func Provide(opts ...opt.Option) error { } return container.Container.Provide(func() (*gorm.DB, *Config, error) { - dbConfig := postgres.Config{ - DSN: conf.DSN(), // DSN data source name - } - logrus.Info("Open PostgreSQL:", conf.DSN()) + dbConfig := postgres.Config{DSN: conf.DSN()} + + // 安全日志:不打印密码,仅输出关键连接信息 + logrus. + WithFields( + logrus.Fields{ + "host": conf.Host, + "port": conf.Port, + "db": conf.Database, + "schema": conf.Schema, + "ssl": conf.SslMode, + }, + ). + Info("opening PostgreSQL connection") + + // 映射日志等级 + lvl := conf.GormLogLevel() + slow := conf.GormSlowThreshold() gormConfig := gorm.Config{ NamingStrategy: schema.NamingStrategy{ @@ -29,11 +46,14 @@ func Provide(opts ...opt.Option) error { SingularTable: conf.Singular, }, DisableForeignKeyConstraintWhenMigrating: true, + PrepareStmt: conf.PrepareStmt, + SkipDefaultTransaction: conf.SkipDefaultTransaction, Logger: logger.New(logrus.StandardLogger(), logger.Config{ - SlowThreshold: 200, // 慢 SQL 阈值 - LogLevel: logger.Info, - IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误 + SlowThreshold: slow, + LogLevel: lvl, + IgnoreRecordNotFoundError: true, Colorful: false, + ParameterizedQueries: conf.ParameterizedQueries, }), } @@ -48,7 +68,23 @@ func Provide(opts ...opt.Option) error { } sqlDB.SetMaxIdleConns(conf.MaxIdleConns) sqlDB.SetMaxOpenConns(conf.MaxOpenConns) + if conf.ConnMaxLifetimeSeconds > 0 { + sqlDB.SetConnMaxLifetime(time.Duration(conf.ConnMaxLifetimeSeconds) * time.Second) + } + if conf.ConnMaxIdleTimeSeconds > 0 { + sqlDB.SetConnMaxIdleTime(time.Duration(conf.ConnMaxIdleTimeSeconds) * time.Second) + } - return db, &conf, err + // Ping 校验 + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := sqlDB.PingContext(ctx); err != nil { + return nil, nil, err + } + + // 关闭钩子 + container.AddCloseAble(func() { _ = sqlDB.Close() }) + + return db, &conf, nil }, o.DiOptions()...) }