Scert/backend/internal/database/database.go

91 lines
2.0 KiB
Go

package database
import (
"context"
"database/sql"
"fmt"
"path/filepath"
"strings"
"time"
"github.com/go-sql-driver/mysql"
_ "modernc.org/sqlite"
"git.pcux.cn/xuans/Scert/backend/internal/config"
)
type Database struct {
DB *sql.DB
}
func Open(ctx context.Context, cfg config.DatabaseConfig) (*Database, error) {
if cfg.Driver != "sqlite" && cfg.Driver != "mysql" {
return nil, fmt.Errorf("数据库驱动无效:%s", cfg.Driver)
}
dsn := cfg.DSN
if cfg.Driver == "mysql" {
var err error
dsn, err = normalizeMySQLDSN(dsn)
if err != nil {
return nil, err
}
}
if cfg.Driver == "sqlite" {
if cfg.DSN != ":memory:" && len(cfg.DSN) > 0 && cfg.DSN != "file::memory:?cache=shared" {
if err := ensureSQLiteDir(cfg.DSN); err != nil {
return nil, err
}
}
}
db, err := sql.Open(cfg.Driver, dsn)
if err != nil {
return nil, fmt.Errorf("打开数据库失败:%w", err)
}
db.SetMaxOpenConns(cfg.MaxOpenConns)
db.SetMaxIdleConns(cfg.MaxIdleConns)
db.SetConnMaxLifetime(time.Duration(cfg.ConnMaxLifetimeMins) * time.Minute)
pingCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
if err := db.PingContext(pingCtx); err != nil {
_ = db.Close()
return nil, fmt.Errorf("连接数据库失败:%w", err)
}
return &Database{DB: db}, nil
}
func normalizeMySQLDSN(dsn string) (string, error) {
cfg, err := mysql.ParseDSN(dsn)
if err != nil {
return "", fmt.Errorf("解析 MySQL 连接信息失败:%w", err)
}
cfg.ParseTime = true
cfg.Loc = time.UTC
return cfg.FormatDSN(), nil
}
func ensureSQLiteDir(dsn string) error {
path := dsn
if strings.HasPrefix(path, "file:") {
path = strings.TrimPrefix(strings.SplitN(path, "?", 2)[0], "file:")
}
return osMkdirAll(filepath.Dir(path))
}
var osMkdirAll = func(path string) error {
if path == "." || path == "" {
return nil
}
return mkdirAll(path)
}
var mkdirAll = func(path string) error {
return makeDirectory(path)
}
func (d *Database) Close() error {
if d == nil || d.DB == nil {
return nil
}
return d.DB.Close()
}