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  }