feat(db,docs): 优化数据库连接池与生产日志,新增 Web 访客端接入指南

This commit is contained in:
yml2213
2026-09-11 10:50:46 +08:00
parent f88030acf5
commit e6837a4878
7 changed files with 427 additions and 20 deletions
+20 -12
View File
@@ -22,12 +22,16 @@ type ServerConfig struct {
}
type DatabaseConfig struct {
Host string
Port string
User string
Password string
Name string
SSLMode string
Host string
Port string
User string
Password string
Name string
SSLMode string
MaxOpenConns int
MaxIdleConns int
ConnMaxLifetime time.Duration
ConnMaxIdleTime time.Duration
}
type JWTConfig struct {
@@ -64,12 +68,16 @@ func Load() *Config {
Mode: getEnv("GIN_MODE", "debug"),
},
Database: DatabaseConfig{
Host: getEnv("DB_HOST", "localhost"),
Port: getEnv("DB_PORT", "5433"),
User: getEnv("DB_USER", "postgres"),
Password: getEnv("DB_PASSWORD", "postgres"),
Name: getEnv("DB_NAME", "kefu_cloud"),
SSLMode: getEnv("DB_SSLMODE", "disable"),
Host: getEnv("DB_HOST", "localhost"),
Port: getEnv("DB_PORT", "5433"),
User: getEnv("DB_USER", "postgres"),
Password: getEnv("DB_PASSWORD", "postgres"),
Name: getEnv("DB_NAME", "kefu_cloud"),
SSLMode: getEnv("DB_SSLMODE", "disable"),
MaxOpenConns: getEnvInt("DB_MAX_OPEN_CONNS", 50),
MaxIdleConns: getEnvInt("DB_MAX_IDLE_CONNS", 10),
ConnMaxLifetime: time.Duration(getEnvInt("DB_CONN_MAX_LIFETIME_MINUTES", 60)) * time.Minute,
ConnMaxIdleTime: time.Duration(getEnvInt("DB_CONN_MAX_IDLE_MINUTES", 15)) * time.Minute,
},
JWT: func() JWTConfig {
hours := getEnvInt("JWT_EXPIRE_HOURS", 24)
+85 -2
View File
@@ -1,7 +1,11 @@
package model
import (
"database/sql"
"log"
"os"
"strconv"
"time"
"gorm.io/driver/postgres"
"gorm.io/gorm"
@@ -10,21 +14,100 @@ import (
var DB *gorm.DB
// DBPoolConfig 数据库连接池配置
type DBPoolConfig struct {
MaxOpenConns int
MaxIdleConns int
ConnMaxLifetime time.Duration
ConnMaxIdleTime time.Duration
}
// DefaultDBPoolConfig 返回连接池配置,优先读取环境变量,缺省提供生产合理的默认值
func DefaultDBPoolConfig() DBPoolConfig {
maxOpen := getEnvInt("DB_MAX_OPEN_CONNS", 50)
maxIdle := getEnvInt("DB_MAX_IDLE_CONNS", 10)
lifetimeMin := getEnvInt("DB_CONN_MAX_LIFETIME_MINUTES", 60)
idleTimeMin := getEnvInt("DB_CONN_MAX_IDLE_MINUTES", 15)
if maxOpen <= 0 {
maxOpen = 50
}
if maxIdle <= 0 {
maxIdle = 10
}
if maxIdle > maxOpen {
maxIdle = maxOpen
}
return DBPoolConfig{
MaxOpenConns: maxOpen,
MaxIdleConns: maxIdle,
ConnMaxLifetime: time.Duration(lifetimeMin) * time.Minute,
ConnMaxIdleTime: time.Duration(idleTimeMin) * time.Minute,
}
}
// ApplyPoolConfig 将连接池参数应用至底层 *sql.DB
func ApplyPoolConfig(sqlDB *sql.DB, cfg DBPoolConfig) {
if sqlDB == nil {
return
}
sqlDB.SetMaxOpenConns(cfg.MaxOpenConns)
sqlDB.SetMaxIdleConns(cfg.MaxIdleConns)
sqlDB.SetConnMaxLifetime(cfg.ConnMaxLifetime)
sqlDB.SetConnMaxIdleTime(cfg.ConnMaxIdleTime)
}
// BuildGormLogger 根据 GIN_MODE 区分日志策略:生产模式仅记录慢查询与错误,避免全量 SQL 撑满磁盘
func BuildGormLogger() logger.Interface {
mode := os.Getenv("GIN_MODE")
if mode == "release" {
return logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: 200 * time.Millisecond,
LogLevel: logger.Warn,
IgnoreRecordNotFoundError: true,
Colorful: false,
},
)
}
return logger.Default.LogMode(logger.Info)
}
func getEnvInt(key string, fallback int) int {
if v := os.Getenv(key); v != "" {
if n, err := strconv.Atoi(v); err == nil {
return n
}
}
return fallback
}
func InitDB(dsn string) {
var err error
DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
Logger: BuildGormLogger(),
})
if err != nil {
log.Fatalf("数据库连接失败: %v", err)
}
sqlDB, err := DB.DB()
if err != nil {
log.Fatalf("获取底层数据库连接失败: %v", err)
}
poolCfg := DefaultDBPoolConfig()
ApplyPoolConfig(sqlDB, poolCfg)
err = Migrate(DB)
if err != nil {
log.Fatalf("数据库迁移失败: %v", err)
}
log.Println("数据库迁移完成")
log.Printf("数据库连接成功并完成迁移 [pool: max_open=%d, max_idle=%d, lifetime=%v, idletime=%v]",
poolCfg.MaxOpenConns, poolCfg.MaxIdleConns, poolCfg.ConnMaxLifetime, poolCfg.ConnMaxIdleTime)
}
func Migrate(db *gorm.DB) error {
+96
View File
@@ -0,0 +1,96 @@
package model
import (
"database/sql"
"fmt"
"os"
"testing"
"time"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
func TestDefaultDBPoolConfig(t *testing.T) {
// 默认兜底测试
cfg := DefaultDBPoolConfig()
if cfg.MaxOpenConns <= 0 || cfg.MaxIdleConns <= 0 {
t.Fatalf("连接池默认值非法: %+v", cfg)
}
if cfg.MaxIdleConns > cfg.MaxOpenConns {
t.Fatalf("MaxIdleConns 不能大于 MaxOpenConns: %+v", cfg)
}
// 环境变量覆盖测试
os.Setenv("DB_MAX_OPEN_CONNS", "120")
os.Setenv("DB_MAX_IDLE_CONNS", "30")
os.Setenv("DB_CONN_MAX_LIFETIME_MINUTES", "120")
os.Setenv("DB_CONN_MAX_IDLE_MINUTES", "30")
defer func() {
os.Unsetenv("DB_MAX_OPEN_CONNS")
os.Unsetenv("DB_MAX_IDLE_CONNS")
os.Unsetenv("DB_CONN_MAX_LIFETIME_MINUTES")
os.Unsetenv("DB_CONN_MAX_IDLE_MINUTES")
}()
customCfg := DefaultDBPoolConfig()
if customCfg.MaxOpenConns != 120 {
t.Errorf("期望 MaxOpenConns=120, 实际=%d", customCfg.MaxOpenConns)
}
if customCfg.MaxIdleConns != 30 {
t.Errorf("期望 MaxIdleConns=30, 实际=%d", customCfg.MaxIdleConns)
}
if customCfg.ConnMaxLifetime != 120*time.Minute {
t.Errorf("期望 ConnMaxLifetime=120m, 实际=%v", customCfg.ConnMaxLifetime)
}
if customCfg.ConnMaxIdleTime != 30*time.Minute {
t.Errorf("期望 ConnMaxIdleTime=30m, 实际=%v", customCfg.ConnMaxIdleTime)
}
}
func TestApplyPoolConfig(t *testing.T) {
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:pool_test_%d?mode=memory&cache=shared", time.Now().UnixNano())), &gorm.Config{})
if err != nil {
t.Fatalf("打开 sqlite 失败: %v", err)
}
sqlDB, err := db.DB()
if err != nil {
t.Fatalf("获取 sql.DB 失败: %v", err)
}
cfg := DBPoolConfig{
MaxOpenConns: 25,
MaxIdleConns: 5,
ConnMaxLifetime: 30 * time.Minute,
ConnMaxIdleTime: 10 * time.Minute,
}
ApplyPoolConfig(sqlDB, cfg)
ApplyPoolConfig(nil, cfg) // 防空指针保护
stats := sqlDB.Stats()
if stats.MaxOpenConnections != 25 {
t.Errorf("期望 MaxOpenConnections=25, 实际=%d", stats.MaxOpenConnections)
}
}
func TestBuildGormLogger(t *testing.T) {
os.Setenv("GIN_MODE", "release")
relLog := BuildGormLogger()
if relLog == nil {
t.Fatal("release logger 为空")
}
os.Setenv("GIN_MODE", "debug")
debLog := BuildGormLogger()
if debLog == nil {
t.Fatal("debug logger 为空")
}
os.Unsetenv("GIN_MODE")
}
func init() {
// 避免测试时与真实 sql.DB 产生类型告警
var _ *sql.DB
var _ = logger.Warn
}