go项目脚手架
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sorbet/pkg/db/db.go

316 lines
8.3 KiB

package db
import (
"context"
"database/sql"
"errors"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/driver/sqlserver"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"gorm.io/plugin/optimisticlock"
"sorbet/pkg/env"
"sync"
"time"
)
var (
// TODO(hupeh): 使用原子性操作 atomic.Value
db *gorm.DB
lock sync.RWMutex
ErrNoCodeFirst = errors.New("no code first")
// 使用东八区时间
// https://cloud.tencent.com/developer/article/1805859
cstZone = time.FixedZone("CST", 8*3600)
)
type Version = optimisticlock.Version
type SessionConfig = gorm.Session
type BaseConfig struct {
TimeLocation *time.Location
NamingStrategy schema.Namer
Logger logger.Interface
Plugins map[string]gorm.Plugin
TablePrefix string
SingularTable bool
NameReplacer schema.Replacer
IdentifierMaxLength int
MaxIdleConns int
MaxOpenConns int
ConnMaxLifetime time.Duration
}
type Config struct {
BaseConfig
Driver string
StoreEngine string
DSN string
}
// DB 获取数据库操作实例
func DB() *gorm.DB {
lock.RLock()
if db != nil {
lock.RUnlock()
return db
}
lock.RUnlock()
lock.Lock()
db = New()
lock.Unlock()
return db
}
func WithContext(ctx context.Context) *gorm.DB {
return DB().WithContext(ctx)
}
// SetDB 自定义操作引擎
func SetDB(engine *gorm.DB) {
lock.Lock()
defer lock.Unlock()
db = engine
}
// New 创建数据库操作引擎,初始化参数来自环境变量
func New() *gorm.DB {
engine, err := NewWithConfig(&Config{
BaseConfig: BaseConfig{
TimeLocation: cstZone,
TablePrefix: env.String("DB_PREFIX"),
SingularTable: env.Bool("DB_SINGULAR_TABLE", false),
IdentifierMaxLength: env.Int("DB_IDENTIFIER_MAX_LENGTH", 0),
Logger: &dbLogger{200 * time.Millisecond},
MaxIdleConns: env.Int("DB_MAX_IDLE_CONNS", 0),
MaxOpenConns: env.Int("DB_MAX_OPEN_CONNS", 0),
ConnMaxLifetime: env.Duration("DB_CONN_MAX_LIFETIME", 0),
},
Driver: env.String("DB_DRIVER", "sqlite3"),
StoreEngine: env.String("DB_STORE_ENGINE", "InnoDB"),
DSN: env.String("DB_DSN", "./app.db"),
})
if err != nil {
panic(err)
}
return engine
}
// NewWithConfig 通过配置创建数据库操作引擎
func NewWithConfig(config *Config) (*gorm.DB, error) {
var dialector gorm.Dialector
switch config.Driver {
case "mysql":
dialector = mysql.Open(config.DSN)
case "pgsql":
dialector = postgres.Open(config.DSN)
case "sqlite", "sqlite3":
dialector = sqlite.Open(config.DSN)
case "sqlserver":
dialector = sqlserver.Open(config.DSN)
default:
return nil, errors.New("不支持的数据库驱动:" + config.Driver)
}
engine, err := NewWithDialector(dialector, &config.BaseConfig)
if err != nil {
return nil, err
}
if config.Driver == "mysql" && config.StoreEngine != "" {
engine = engine.Set("gorm:table_options", "ENGINE="+config.StoreEngine)
}
return engine, nil
}
// NewWithDialector 通过指定的 dialector 创建数据库操作引擎
func NewWithDialector(dialector gorm.Dialector, config *BaseConfig) (*gorm.DB, error) {
engine, err := gorm.Open(dialector, &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: config.TablePrefix,
SingularTable: config.SingularTable,
NameReplacer: config.NameReplacer,
NoLowerCase: false,
IdentifierMaxLength: config.IdentifierMaxLength,
},
Logger: config.Logger,
NowFunc: func() time.Time {
if config.TimeLocation == nil {
return time.Now()
}
return time.Now().In(config.TimeLocation)
},
QueryFields: false,
})
if err != nil {
return nil, err
}
rawDB, err := engine.DB()
if err != nil {
return nil, err
}
if config.MaxIdleConns > 0 {
rawDB.SetMaxIdleConns(config.MaxIdleConns)
}
if config.MaxOpenConns > 0 {
rawDB.SetMaxOpenConns(config.MaxOpenConns)
}
if config.ConnMaxLifetime > 0 {
rawDB.SetConnMaxLifetime(config.ConnMaxLifetime)
}
return engine, nil
}
// Sync 同步数据库结构,属于代码优先模式。
//
// 在使用该方法之前需要在环境变量中开启 "DB_CODE_FIRST" 选项。
//
// 这是非常危险的操作,必须慎之又慎,因为函数将进行如下的同步操作:
// * 自动检测和创建表,这个检测是根据表的名字
// * 自动检测和新增表中的字段,这个检测是根据字段名,同时对表中多余的字段给出警告信息
// * 自动检测,创建和删除索引和唯一索引,这个检测是根据索引的一个或多个字段名,而不根据索引名称。因此这里需要注意,如果在一个有大量数据的表中引入新的索引,数据库可能需要一定的时间来建立索引。
// * 自动转换varchar字段类型到text字段类型,自动警告其它字段类型在模型和数据库之间不一致的情况。
// * 自动警告字段的默认值,是否为空信息在模型和数据库之间不匹配的情况
//
// 以上这些警告信息需要将日志的显示级别调整为Warn级别才会显示。
func Sync(beans ...any) error {
if env.Bool("DB_CODE_FIRST") {
return DB().AutoMigrate(beans...)
}
return ErrNoCodeFirst
}
// Ping ping 一下数据库连接
func Ping() error {
raw, err := DB().DB()
if err != nil {
return err
}
return raw.Ping()
}
// Stats 返回数据库统计信息
func Stats() (*sql.DBStats, error) {
raw, err := DB().DB()
if err != nil {
return nil, err
}
stats := raw.Stats()
return &stats, nil
}
// Now 这是个工具函数,返回当前时间
func Now() time.Time {
return DB().Config.NowFunc()
}
// Session 会话模式
//
// 在该模式下会创建并缓存预编译语句,从而提高后续的调用速度
func Session(config *SessionConfig) *gorm.DB {
return DB().Session(config)
}
// Model 通过模型进行下一步操作
func Model(value any) *gorm.DB {
return DB().Model(value)
}
// Table 通过数据表面进行下一步操作
func Table(name string, args ...any) *gorm.DB {
return DB().Table(name, args...)
}
// Create 通过模型创建记录
//
// 使用模型创建一条记录:
//
// user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
// ok, err := db.Create(&user) // 通过数据的指针来创建
//
// 我们还可以使用模型切边创建多项记录:
//
// users := []*User{
// User{Name: "Jinzhu", Age: 18, Birthday: time.Now()},
// User{Name: "Jackson", Age: 19, Birthday: time.Now()},
// }
// ok, err := db.Create(users) // 通过 slice 创建多条记录
func Create(value any) (bool, error) {
result := DB().Create(value)
if err := result.Error; err != nil {
return false, err
}
return result.RowsAffected > 0, nil
}
// Save 保存模型数据,由以下两点需要注意:
//
// - 该函数会保存所有的字段,即使字段是零值。
// - 如果模型中的主键值是零值,将会创建该数据。
func Save(value any) (bool, error) {
result := DB().Save(value)
if err := result.Error; err != nil {
return false, err
}
return result.RowsAffected > 0, nil
}
func Upsert(bean any, conflict clause.OnConflict) (bool, error) {
result := DB().Clauses(conflict).Create(bean)
if err := result.Error; err != nil {
return false, err
}
return result.RowsAffected > 0, nil
}
// Transaction 自动事务管理
//
// 如果在 fc 中开启了新的事务,必须确保这个内嵌的事务被提交或被回滚。
func Transaction(fc func(tx *gorm.DB) error, opts ...*sql.TxOptions) error {
return DB().Transaction(fc, opts...)
}
// Begin 开启事务
//
// 使用示例
//
// tx := db.Begin() // 开始事务
// tx.Create() // 执行一些数据库操作
// tx.Rollback() // 遇到错误时回滚事务
// tx.Commit() // 否则,提交事务
//
// 事务一旦开始,就应该使用返回的 tx 对象处理数据
func Begin(opts ...*sql.TxOptions) (tx *gorm.DB) {
return DB().Begin(opts...)
}
// Raw 执行 SQL 查询语句
func Raw(sql string, values ...any) *gorm.DB {
return DB().Raw(sql, values...)
}
// Exec 执行Insert, Update, Delete 等命令的 SQL 语句,
// 如果需要查询数据请使用 Query 函数
func Exec(sql string, values ...any) *gorm.DB {
return DB().Exec(sql, values...)
}
func Unscoped() *gorm.DB {
return DB().Unscoped()
}
// Migrator 返回迁移接口
func Migrator() gorm.Migrator {
return DB().Migrator()
}