package model import ( "fmt" "sync" "qoobing.com/gomod/gorm" "qoobing.com/gomod/log" "qoobing.com/gomod/redis" ) var ( instances = map[string]instance{} errNoModelFound = errorsWrap("named model(%s) not found") errModeAlreadyInit = errorsWrap("named model(%s) already initilized") errOpenDBFailed = errorsWrap("open database failed: %s") errOpenRedisFailed = errorsWrap("open redis failed: %s") ) func errorsWrap(base string) func(...any) error { return func(v ...any) error { return fmt.Errorf(base, v...) } } const ( tx_status_none = 0 tx_status_doing = 1 tx_status_commit = 2 tx_status_rollback = 3 ) type Model struct { mu sync.Mutex dbCfg *gorm.Config redisCfg *redis.Config db *gorm.DB dbTxStatus int redis redis.Conn redisPool *redis.Pool } func (m *Model) Begin() { if m.DB() == nil { panic("unreachable code, db is uninitialized") } else if m.dbTxStatus != tx_status_none { panic("unreachable code, begin transaction towice???") } else { m.dbTxStatus = tx_status_doing m.db = m.db.Begin() } } func (m *Model) Commit() error { if m.DB() == nil { panic("unreachable code, db is uninitialized") } else if m.dbTxStatus != tx_status_doing { return nil } else if err := m.db.Commit().Error; err != nil { m.dbTxStatus = tx_status_rollback return err } else { m.dbTxStatus = tx_status_commit return nil } } func (m *Model) Close() { if m.redis != nil { m.redis.Close() } if m.db == nil { m.dbTxStatus = tx_status_none } else if m.dbTxStatus == tx_status_doing { m.db.Rollback() m.dbTxStatus = tx_status_rollback } } func (m *Model) DB() *gorm.DB { return m.db } func (m *Model) Redis() redis.Conn { return m.redis } func (m *Model) RedisPool() *redis.Pool { return m.redisPool } type instance struct { db *gorm.DB dbCfg *gorm.Config redis *redis.Pool redisCfg *redis.Config } // OpenDefault open 'default' model func OpenDefault() (*Model, error) { return Open("default") } // OpenDefault open named model, if err is nil, caller MUST call m.Close() in defer. func Open(name string) (m *Model, err error) { // check initialize cfg, ok := instances[name] if !ok { return nil, errNoModelFound(name) } // open redis var rds redis.Conn if cfg.redisCfg == nil { // don't need to open redis } else if cfg.redis != nil { rds = cfg.redis.Get() } else if cfg.redis = redis.NewPool(*cfg.redisCfg); cfg.redis == nil { return nil, errOpenRedisFailed("new redis pool return nil") } else { rds = cfg.redis.Get() } defer func() { if m == nil && rds != nil { rds.Close() } }() // open db var db *gorm.DB if cfg.dbCfg == nil { // don't need to open database } else if cfg.db != nil { db = gorm.NewSession(cfg.db) } else if cfg.db, err = gorm.NewDB(*cfg.dbCfg); err != nil { return nil, errOpenDBFailed(err) } else { db = gorm.NewSession(cfg.db) } // success m = &Model{ dbCfg: cfg.dbCfg, redisCfg: cfg.redisCfg, db: db, redis: rds, redisPool: cfg.redis, } return m, nil } // Init init default database config & redis config func InitDefault(_defaultDb *gorm.Config, _defaultRds *redis.Config) error { return Init("default", _defaultDb, _defaultRds) } // Init init named database config & redis config func Init(name string, dbCfg *gorm.Config, rdsCfg *redis.Config) error { _, ok := instances[name] if ok { return errModeAlreadyInit(name) } instances[name] = instance{ dbCfg: dbCfg, redisCfg: rdsCfg, } if dbCfg != nil && dbCfg.Debug { log.Infof("init model(%s) database:%s", name, gorm.GetSecDsn(dbCfg)) } if rdsCfg != nil && rdsCfg.Debug { log.Infof("init model(%s) redis:%s", name, redis.) } log.Infof("init model(%s) done", name) return nil }