github.com/wfusion/gofusion@v1.1.14/db/construct.go (about) 1 package db 2 3 import ( 4 "context" 5 "log" 6 "reflect" 7 "sync" 8 "syscall" 9 10 "github.com/PaesslerAG/gval" 11 "github.com/pkg/errors" 12 "gorm.io/gorm" 13 "gorm.io/gorm/logger" 14 15 "github.com/wfusion/gofusion/common/di" 16 "github.com/wfusion/gofusion/common/infra/drivers/orm" 17 "github.com/wfusion/gofusion/common/infra/drivers/orm/idgen" 18 "github.com/wfusion/gofusion/common/utils" 19 "github.com/wfusion/gofusion/common/utils/gomonkey" 20 "github.com/wfusion/gofusion/common/utils/inspect" 21 "github.com/wfusion/gofusion/config" 22 "github.com/wfusion/gofusion/db/callbacks" 23 "github.com/wfusion/gofusion/db/plugins" 24 "github.com/wfusion/gofusion/db/softdelete" 25 26 fusLog "github.com/wfusion/gofusion/log" 27 28 _ "github.com/wfusion/gofusion/log/customlogger" 29 ) 30 31 func Construct(ctx context.Context, confs map[string]*Conf, opts ...utils.OptionExtender) func() { 32 opt := utils.ApplyOptions[config.InitOption](opts...) 33 optU := utils.ApplyOptions[useOption](opts...) 34 if opt.AppName == "" { 35 opt.AppName = optU.appName 36 } 37 38 for name, conf := range confs { 39 addInstance(ctx, name, conf, opt) 40 } 41 // patch delete at 42 patches := make([]*gomonkey.Patches, 0, len(confs)) 43 patches = append(patches, softdelete.PatchGormDeleteAt()) 44 45 return func() { 46 rwlock.Lock() 47 defer rwlock.Unlock() 48 49 pid := syscall.Getpid() 50 app := config.Use(opt.AppName).AppName() 51 if appInstances != nil { 52 for _, instance := range appInstances[opt.AppName] { 53 if sqlDB, err := instance.GetProxy().DB(); err == nil { 54 if err := sqlDB.Close(); err != nil { 55 log.Printf("%v [Gofusion] %s %s close error: %s", pid, app, config.ComponentDB, err) 56 } 57 } 58 } 59 delete(appInstances, opt.AppName) 60 } 61 if len(appInstances) == 0 { 62 for _, patch := range patches { 63 if patch != nil { 64 patch.Reset() 65 } 66 } 67 softdelete.PatchGormDeleteAtOnce = new(sync.Once) 68 } 69 } 70 } 71 72 func addInstance(ctx context.Context, name string, conf *Conf, opt *config.InitOption) { 73 var logObj logger.Interface 74 if !config.Use(opt.AppName).Debug() && utils.IsStrNotBlank(conf.LoggerConfig.Logger) { 75 loggerType := inspect.TypeOf(conf.LoggerConfig.Logger) 76 loggerValue := reflect.New(loggerType) 77 if loggerValue.Type().Implements(customLoggerType) { 78 l := fusLog.Use(conf.LoggerConfig.LogInstance, fusLog.AppName(opt.AppName)) 79 loggerValue.Interface().(customLogger).Init(l, opt.AppName, name) 80 } 81 logObj = loggerValue.Interface().(logger.Interface) 82 } 83 84 // conf.Option.Password = config.CryptoDecryptFunc()(conf.Option.Password) 85 db, err := orm.Gorm.New(ctx, conf.Option, orm.WithLogger(logObj)) 86 if err != nil { 87 panic(errors.Errorf("initialize gorm db instance error: %+v", err)) 88 } 89 90 adaptMysqlAutoIncrementIncrement(db, conf) 91 mysqlSoftDelete(db, conf) 92 if config.Use(opt.AppName).Debug() { 93 db.DB = db.Debug() 94 } 95 96 // sharding 97 tablePluginMap := make(map[string]plugins.TableSharding, len(conf.Sharding)) 98 for _, shardConf := range conf.Sharding { 99 var generator idgen.Generator 100 if utils.IsStrNotBlank(shardConf.IDGen) { 101 generator = (*(*func() idgen.Generator)(inspect.FuncOf(shardConf.IDGen)))() 102 } 103 104 var expression gval.Evaluable 105 if utils.IsStrNotBlank(shardConf.ShardingKeyExpr) { 106 expression = utils.Must(gval.Full().NewEvaluable(shardConf.ShardingKeyExpr)) 107 } 108 109 tableShardingPlugin := plugins.DefaultTableSharding(plugins.TableShardingConfig{ 110 Database: name, 111 Table: shardConf.Table, 112 ShardingKeys: shardConf.Columns, 113 ShardingKeyExpr: expression, 114 ShardingKeyByRawValue: shardConf.ShardingKeyByRawValue, 115 ShardingKeysForMigrating: shardConf.ShardingKeysForMigrating, 116 NumberOfShards: shardConf.NumberOfShards, 117 CustomSuffix: shardConf.Suffix, 118 PrimaryKeyGenerator: generator, 119 }) 120 121 utils.MustSuccess(db.Use(tableShardingPlugin)) 122 tablePluginMap[shardConf.Table] = tableShardingPlugin 123 } 124 125 rwlock.Lock() 126 defer rwlock.Unlock() 127 if appInstances == nil { 128 appInstances = make(map[string]map[string]*Instance) 129 } 130 if appInstances[opt.AppName] == nil { 131 appInstances[opt.AppName] = make(map[string]*Instance) 132 } 133 if _, ok := appInstances[opt.AppName][name]; ok { 134 panic(ErrDuplicatedName) 135 } 136 appInstances[opt.AppName][name] = &Instance{db: db, name: name, tableShardingPlugins: tablePluginMap} 137 138 // ioc 139 if opt.DI != nil { 140 opt.DI.MustProvide( 141 func() *gorm.DB { 142 rwlock.RLock() 143 defer rwlock.RUnlock() 144 return appInstances[opt.AppName][name].GetProxy() 145 }, 146 di.Name(name), 147 ) 148 } 149 150 go startDaemonRoutines(ctx, opt.AppName, name, conf) 151 } 152 153 // adaptAutoIncrementIncrement patch gorm schema parse method to enable changing autoIncrementIncrement in runtime 154 // see also: https://github.com/go-gorm/gorm/issues/5814 155 func adaptMysqlAutoIncrementIncrement(db *orm.DB, conf *Conf) { 156 autoIncrIncr := conf.AutoIncrementIncrement 157 // unset, query auto increment increment 158 if autoIncrIncr == 0 && conf.Driver == orm.DriverMysql { 159 type autoConfig struct { 160 VariableName string `gorm:"column:Variable_name"` 161 Value int64 `gorm:"column:Value"` 162 } 163 164 var cfg *autoConfig 165 db.WithContext(context.Background()). 166 Raw("show variables like 'auto_increment_increment'"). 167 Scan(&cfg) 168 autoIncrIncr = cfg.Value 169 } 170 // no need to replace callbacks 171 if autoIncrIncr <= 1 { 172 return 173 } 174 175 callbacks.CreateAutoIncr(db.GetProxy(), db.GetDialector(), autoIncrIncr) 176 } 177 178 func mysqlSoftDelete(db *orm.DB, conf *Conf) { 179 callbacks.SoftDelete(db.GetProxy()) 180 } 181 182 func init() { 183 config.AddComponent(config.ComponentDB, Construct, config.WithFlag(&flagString)) 184 }