mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 03:15:04 +08:00
opt: 优化数据库连接池,配置管理,错误处理
This commit is contained in:
@@ -2,7 +2,9 @@ package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ctwj/urldb/db/entity"
|
||||
@@ -45,8 +47,22 @@ func InitDB() error {
|
||||
host, port, user, password, dbname)
|
||||
|
||||
var err error
|
||||
// 配置慢查询日志
|
||||
slowThreshold := getEnvInt("DB_SLOW_THRESHOLD_MS", 200)
|
||||
logLevel := logger.Info
|
||||
if os.Getenv("ENV") == "production" {
|
||||
logLevel = logger.Warn
|
||||
}
|
||||
|
||||
DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Info),
|
||||
Logger: logger.New(
|
||||
log.New(os.Stdout, "\r\n", log.LstdFlags),
|
||||
logger.Config{
|
||||
SlowThreshold: time.Duration(slowThreshold) * time.Millisecond,
|
||||
LogLevel: logLevel,
|
||||
Colorful: true,
|
||||
},
|
||||
),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -58,10 +74,17 @@ func InitDB() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 设置连接池参数
|
||||
sqlDB.SetMaxIdleConns(10) // 最大空闲连接数
|
||||
sqlDB.SetMaxOpenConns(100) // 最大打开连接数
|
||||
sqlDB.SetConnMaxLifetime(time.Hour) // 连接最大生命周期
|
||||
// 优化数据库连接池参数
|
||||
maxOpenConns := getEnvInt("DB_MAX_OPEN_CONNS", 50)
|
||||
maxIdleConns := getEnvInt("DB_MAX_IDLE_CONNS", 20)
|
||||
connMaxLifetime := getEnvInt("DB_CONN_MAX_LIFETIME_MINUTES", 30)
|
||||
|
||||
sqlDB.SetMaxOpenConns(maxOpenConns) // 最大打开连接数
|
||||
sqlDB.SetMaxIdleConns(maxIdleConns) // 最大空闲连接数
|
||||
sqlDB.SetConnMaxLifetime(time.Duration(connMaxLifetime) * time.Minute) // 连接最大生命周期
|
||||
|
||||
utils.Info("数据库连接池配置 - 最大连接: %d, 空闲连接: %d, 生命周期: %d分钟",
|
||||
maxOpenConns, maxIdleConns, connMaxLifetime)
|
||||
|
||||
// 检查是否需要迁移(只在开发环境或首次启动时)
|
||||
if shouldRunMigration() {
|
||||
@@ -300,3 +323,19 @@ func insertDefaultDataIfEmpty() error {
|
||||
utils.Info("默认数据插入完成")
|
||||
return nil
|
||||
}
|
||||
|
||||
// getEnvInt 获取环境变量中的整数值,如果不存在则返回默认值
|
||||
func getEnvInt(key string, defaultValue int) int {
|
||||
value := os.Getenv(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
intValue, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
utils.Warn("环境变量 %s 的值 '%s' 不是有效的整数,使用默认值 %d", key, value, defaultValue)
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return intValue
|
||||
}
|
||||
|
||||
114
db/repo/pagination.go
Normal file
114
db/repo/pagination.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// PaginationResult 分页查询结果
|
||||
type PaginationResult[T any] struct {
|
||||
Data []T `json:"data"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
|
||||
// PaginationOptions 分页查询选项
|
||||
type PaginationOptions struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
OrderBy string `json:"order_by"`
|
||||
OrderDir string `json:"order_dir"` // asc or desc
|
||||
Preloads []string `json:"preloads"` // 需要预加载的关联
|
||||
Filters map[string]interface{} `json:"filters"` // 过滤条件
|
||||
}
|
||||
|
||||
// DefaultPaginationOptions 默认分页选项
|
||||
func DefaultPaginationOptions() *PaginationOptions {
|
||||
return &PaginationOptions{
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
OrderBy: "id",
|
||||
OrderDir: "desc",
|
||||
Preloads: []string{},
|
||||
Filters: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// PaginatedQuery 通用分页查询函数
|
||||
func PaginatedQuery[T any](db *gorm.DB, options *PaginationOptions) (*PaginationResult[T], error) {
|
||||
// 验证分页参数
|
||||
if options.Page < 1 {
|
||||
options.Page = 1
|
||||
}
|
||||
if options.PageSize < 1 || options.PageSize > 1000 {
|
||||
options.PageSize = 20
|
||||
}
|
||||
|
||||
// 应用预加载
|
||||
query := db.Model(new(T))
|
||||
for _, preload := range options.Preloads {
|
||||
query = query.Preload(preload)
|
||||
}
|
||||
|
||||
// 应用过滤条件
|
||||
for key, value := range options.Filters {
|
||||
// 处理特殊过滤条件
|
||||
switch key {
|
||||
case "search":
|
||||
// 搜索条件需要特殊处理
|
||||
if searchStr, ok := value.(string); ok && searchStr != "" {
|
||||
query = query.Where("title ILIKE ? OR description ILIKE ?", "%"+searchStr+"%", "%"+searchStr+"%")
|
||||
}
|
||||
case "category_id":
|
||||
if categoryID, ok := value.(uint); ok {
|
||||
query = query.Where("category_id = ?", categoryID)
|
||||
}
|
||||
case "pan_id":
|
||||
if panID, ok := value.(uint); ok {
|
||||
query = query.Where("pan_id = ?", panID)
|
||||
}
|
||||
case "is_valid":
|
||||
if isValid, ok := value.(bool); ok {
|
||||
query = query.Where("is_valid = ?", isValid)
|
||||
}
|
||||
case "is_public":
|
||||
if isPublic, ok := value.(bool); ok {
|
||||
query = query.Where("is_public = ?", isPublic)
|
||||
}
|
||||
default:
|
||||
// 通用过滤条件
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
// 应用排序
|
||||
orderClause := options.OrderBy + " " + options.OrderDir
|
||||
query = query.Order(orderClause)
|
||||
|
||||
// 计算偏移量
|
||||
offset := (options.Page - 1) * options.PageSize
|
||||
|
||||
// 获取总数
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 查询数据
|
||||
var data []T
|
||||
if err := query.Offset(offset).Limit(options.PageSize).Find(&data).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 计算总页数
|
||||
totalPages := int((total + int64(options.PageSize) - 1) / int64(options.PageSize))
|
||||
|
||||
return &PaginationResult[T]{
|
||||
Data: data,
|
||||
Total: total,
|
||||
Page: options.Page,
|
||||
PageSize: options.PageSize,
|
||||
TotalPages: totalPages,
|
||||
}, nil
|
||||
}
|
||||
@@ -68,38 +68,21 @@ func (r *ResourceRepositoryImpl) FindWithRelations() ([]entity.Resource, error)
|
||||
|
||||
// FindWithRelationsPaginated 分页查找包含关联关系的资源
|
||||
func (r *ResourceRepositoryImpl) FindWithRelationsPaginated(page, limit int) ([]entity.Resource, int64, error) {
|
||||
var resources []entity.Resource
|
||||
var total int64
|
||||
|
||||
offset := (page - 1) * limit
|
||||
|
||||
// 优化查询:只预加载必要的关联,并添加排序
|
||||
db := r.db.Model(&entity.Resource{}).
|
||||
Preload("Category").
|
||||
Preload("Pan").
|
||||
Order("updated_at DESC") // 按更新时间倒序,显示最新内容
|
||||
|
||||
// 获取总数(使用缓存键)
|
||||
cacheKey := fmt.Sprintf("resources_total_%d_%d", page, limit)
|
||||
if cached, exists := r.cache[cacheKey]; exists {
|
||||
if totalCached, ok := cached.(int64); ok {
|
||||
total = totalCached
|
||||
}
|
||||
} else {
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
// 缓存总数(5分钟)
|
||||
r.cache[cacheKey] = total
|
||||
go func() {
|
||||
time.Sleep(5 * time.Minute)
|
||||
delete(r.cache, cacheKey)
|
||||
}()
|
||||
// 使用新的分页查询功能
|
||||
options := &PaginationOptions{
|
||||
Page: page,
|
||||
PageSize: limit,
|
||||
OrderBy: "updated_at",
|
||||
OrderDir: "desc",
|
||||
Preloads: []string{"Category", "Pan"},
|
||||
}
|
||||
|
||||
// 获取分页数据
|
||||
err := db.Offset(offset).Limit(limit).Find(&resources).Error
|
||||
return resources, total, err
|
||||
result, err := PaginatedQuery[entity.Resource](r.db, options)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return result.Data, result.Total, nil
|
||||
}
|
||||
|
||||
// FindByCategoryID 根据分类ID查找
|
||||
|
||||
Reference in New Issue
Block a user