Files
trading_assistant/trading_assistant_api/common/db/postgres.go
2026-02-06 16:44:33 +08:00

136 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// common/db/postgres.go
package db
import (
"context"
"database/sql"
"fmt"
"sync"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// 全局单例连接,避免重复创建连接(连接池全局复用)
var (
pgInstance *gorm.DB
pgOnce sync.Once
pgErr error
)
// InitPostgres 初始化PostgreSQL连接单例模式仅执行一次
// 入参:选项模式的配置函数,用户可灵活配置
// 返回:原生*gorm.DB + 错误业务层可直接使用GORM所有方法
func InitPostgres(opts ...PostgresOption) (*gorm.DB, error) {
pgOnce.Do(func() {
// 加载默认配置 + 覆盖用户自定义配置
options := defaultPostgresOptions()
for _, opt := range opts {
opt(options)
}
// 校验必传配置(账号、密码、数据库名不能为空)
if options.User == "" || options.Password == "" || options.DBName == "" {
pgErr = fmt.Errorf("postgres config error: user/password/dbname can not be empty")
return
}
// 拼接PostgreSQL DSN数据源名称GORM官方规范
dsn := fmt.Sprintf(
"host=%s port=%s user=%s password=%s dbname=%s sslmode=%s TimeZone=%s",
options.Host,
options.Port,
options.User,
options.Password,
options.DBName,
options.SSLMode,
options.TimeZone,
)
// 配置GORM日志按传入的日志级别生产环境可关闭
gormConfig := &gorm.Config{
Logger: logger.Default.LogMode(options.LogLevel),
}
// 建立GORM连接
pgInstance, pgErr = gorm.Open(postgres.Open(dsn), gormConfig)
if pgErr != nil {
pgErr = fmt.Errorf("postgres connect failed: %w", pgErr)
return
}
// 获取底层*sql.DB配置连接池关键避免连接泄漏提升性能
sqlDB, err := pgInstance.DB()
if err != nil {
pgErr = fmt.Errorf("get postgres sql.DB failed: %w", err)
return
}
// 设置连接池参数
sqlDB.SetMaxOpenConns(options.MaxOpenConns)
sqlDB.SetMaxIdleConns(options.MaxIdleConns)
sqlDB.SetConnMaxLifetime(options.ConnMaxLifetime)
sqlDB.SetConnMaxIdleTime(options.ConnMaxIdleTime)
// 测试连接ping一下确保连接有效
if err := sqlDB.PingContext(context.Background()); err != nil {
pgErr = fmt.Errorf("postgres ping failed: %w", err)
pgInstance = nil // 连接失败,置空实例
return
}
})
// 单例执行完成后,返回实例和错误
return pgInstance, pgErr
}
// GetPostgres 获取全局PostgreSQL单例连接
// 业务层初始化后可通过此方法直接获取连接无需重复调用InitPostgres
func GetPostgres() (*gorm.DB, error) {
if pgInstance == nil {
return nil, fmt.Errorf("postgres not initialized, please call InitPostgres first")
}
return pgInstance, nil
}
// ClosePostgres 关闭PostgreSQL连接应用退出时调用释放资源
// 一般在main函数的defer中调用defer db.ClosePostgres()
func ClosePostgres() error {
if pgInstance == nil {
return fmt.Errorf("postgres not initialized")
}
// 获取底层*sql.DB执行关闭
sqlDB, err := pgInstance.DB()
if err != nil {
return fmt.Errorf("get postgres sql.DB failed: %w", err)
}
if err := sqlDB.Close(); err != nil {
return fmt.Errorf("close postgres failed: %w", err)
}
// 关闭后重置单例,避免重复关闭
pgOnce = sync.Once{}
pgInstance = nil
pgErr = nil
return nil
}
// PingPostgres 测试PostgreSQL连接是否有效
// 业务层可定时调用(如健康检查),确保连接未断开
func PingPostgres() error {
db, err := GetPostgres()
if err != nil {
return err
}
sqlDB, err := db.DB()
if err != nil {
return fmt.Errorf("get postgres sql.DB failed: %w", err)
}
return sqlDB.PingContext(context.Background())
}