diff --git a/abstract/api_sql_manager.go b/abstract/api_sql_manager.go new file mode 100644 index 0000000..1ec1cd9 --- /dev/null +++ b/abstract/api_sql_manager.go @@ -0,0 +1,24 @@ +// Package abstract ... +// +// Description : abstract ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2024-08-20 17:17 +package abstract + +import ( + "context" +) + +// IManager 接口 => sql 数据管理实例约束 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 17:17 2024/8/20 +type IManager interface { + // SetDatabaseClient 设置数据库连接的客户端 + SetDatabaseClient(database IWrapperClient) + // Execute 执行 + Execute(ctx context.Context, databaseFlag string) (any, error) +} diff --git a/abstract/database.go b/abstract/database.go index 7b0d052..51b546b 100644 --- a/abstract/database.go +++ b/abstract/database.go @@ -8,7 +8,7 @@ package abstract import ( - "git.zhangdeman.cn/zhangdeman/database" + "git.zhangdeman.cn/zhangdeman/database/define" "gorm.io/gorm" ) @@ -19,21 +19,21 @@ import ( // Date : 15:06 2023/10/14 type IDatabase interface { // Create 创建数据 - Create(dbInstance *gorm.DB, data any, optionList ...database.SetOption) error + Create(dbInstance *gorm.DB, data any, optionList ...define.SetOption) error // Update 更新数据 - Update(dbInstance *gorm.DB, updateData any, optionFuncList ...database.SetOption) (int64, error) + Update(dbInstance *gorm.DB, updateData any, optionFuncList ...define.SetOption) (int64, error) // UpdateOne 更新一条数据 - UpdateOne(dbInstance *gorm.DB, updateData any, optionFuncList ...database.SetOption) (int64, error) + UpdateOne(dbInstance *gorm.DB, updateData any, optionFuncList ...define.SetOption) (int64, error) // List 查询数据列表 - List(dbInstance *gorm.DB, result any, optionFuncList ...database.SetOption) error + List(dbInstance *gorm.DB, result any, optionFuncList ...define.SetOption) error // Delete 删除数据 - Delete(dbInstance *gorm.DB, dataModel any, optionFuncList ...database.SetOption) (int64, error) + Delete(dbInstance *gorm.DB, dataModel any, optionFuncList ...define.SetOption) (int64, error) // Detail 数据详情 - Detail(dbInstance *gorm.DB, result any, optionFuncList ...database.SetOption) error + Detail(dbInstance *gorm.DB, result any, optionFuncList ...define.SetOption) error // IsNotFound 错误是否为数据不存在 IsNotFound(err error) bool // Count 查询数据数量 - Count(dbInstance *gorm.DB, optionFuncList ...database.SetOption) (int64, error) + Count(dbInstance *gorm.DB, optionFuncList ...define.SetOption) (int64, error) // Tx 执行事务 Tx(dbInstance *gorm.DB, txFunc func(dbInstance *gorm.DB) error) error // Begin 开启事务 diff --git a/abstract/wrapper_client.go b/abstract/wrapper_client.go new file mode 100644 index 0000000..5f12266 --- /dev/null +++ b/abstract/wrapper_client.go @@ -0,0 +1,35 @@ +// Package abstract ... +// +// Description : abstract ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2024-08-20 17:26 +package abstract + +import ( + "context" + "git.zhangdeman.cn/zhangdeman/database/define" + "go.uber.org/zap" + "gorm.io/gorm" +) + +// IWrapperClient 包装的客户端, 包装 IWrapperDatabaseClient +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 17:26 2024/8/20 +type IWrapperClient interface { + // AddWithConfigFile 通过配置文件增加数据库实例 + AddWithConfigFile(cfgFilePath string, logInstance *zap.Logger, extraFieldList []string) error + // AddWithConfig 通过具体的配置增加数据库实例 + AddWithConfig(flag string, logInstance *zap.Logger, databaseConfig *define.Database, extraFieldList []string) error + // BatchAddWithConfigDir 通过具体的配置列表增加数据库实例 + BatchAddWithConfigDir(cfgDir string, logInstance *zap.Logger, extraFieldList []string) error + // GetDBClient 基于数据库标识获取数据库实例 + GetDBClient(dbFlag string) (IWrapperDatabaseClient, error) + // GetMasterClient 获取主库连接 + GetMasterClient(ctx context.Context, dbFlag string) (*gorm.DB, error) + // GetSlaveClient 获取从库连接 + GetSlaveClient(ctx context.Context, dbFlag string) (*gorm.DB, error) +} diff --git a/abstract/wrapper_db_client.go b/abstract/wrapper_db_client.go new file mode 100644 index 0000000..4ab2bb6 --- /dev/null +++ b/abstract/wrapper_db_client.go @@ -0,0 +1,37 @@ +// Package abstract ... +// +// Description : abstract ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2024-08-20 17:59 +package abstract + +import ( + "context" + "git.zhangdeman.cn/zhangdeman/database/define" + "go.uber.org/zap" + "gorm.io/gorm" +) + +// IWrapperDatabaseClient 包装gorm +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 18:00 2024/8/20 +type IWrapperDatabaseClient interface { + // Init 初始化客户端连接 + Init(databaseConfig *define.Database, cacheTableStructureConfig *define.CacheTableStructureConfig) error + // GetMaster 获取master连接 + GetMaster(ctx context.Context) *gorm.DB + // GetSlave 获取slave连接 + GetSlave(ctx context.Context) *gorm.DB + // GetDatabaseClient 获取数据库连接 + GetDatabaseClient(conf *define.Driver, logInstance *zap.Logger) (*gorm.DB, error) + // CacheDataTableStructureConfig 缓存数据表结构的配置 + CacheDataTableStructureConfig() *define.CacheTableStructureConfig + // GetTableFieldList 获取指定表数据字段列表 + GetTableFieldList(tableName string) ([]*define.ColumnConfig, error) + // SetTableStructure 设置数据表结构 + SetTableStructure(tableConfigTable map[string][]*define.ColumnConfig) +} diff --git a/api2sql/execute.go b/api2sql/execute.go new file mode 100644 index 0000000..4dbb4b2 --- /dev/null +++ b/api2sql/execute.go @@ -0,0 +1,281 @@ +// Package api2sql ... +// +// Description : api2sql ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2024-08-21 20:45 +package api2sql + +import ( + "context" + "errors" + "fmt" + "git.zhangdeman.cn/zhangdeman/consts" + "git.zhangdeman.cn/zhangdeman/database" + "git.zhangdeman.cn/zhangdeman/database/abstract" + "git.zhangdeman.cn/zhangdeman/database/define" + "git.zhangdeman.cn/zhangdeman/wrapper" + "gorm.io/gorm" +) + +var ( + Exec = &execute{} +) + +type execute struct { + databaseClientManager abstract.IWrapperClient // 全部数据库管理的实例 + baseDao database.BaseDao // 基础dao +} + +// SetDatabaseClientManager 设置数据库连接管理实例 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 21:06 2024/8/21 +func (e *execute) SetDatabaseClientManager(databaseClientManager abstract.IWrapperClient) { + e.databaseClientManager = databaseClientManager +} + +// Run 执行 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 20:48 2024/8/21 +func (e *execute) Run(ctx context.Context, inputParam *define.Api2SqlParam) (any, error) { + if err := e.formatAndValidateInputParam(inputParam); nil != err { + return nil, err + } + if len(inputParam.InputSql) > 0 { + // 基于表达式执行 + return e.Express(ctx, inputParam) + } + switch inputParam.SqlType { + case consts.SqlTypeCount: + return e.Count(ctx, inputParam) + case consts.SqlTypeDetail: + return e.Detail(ctx, inputParam) + case consts.SqlTypeList: + return e.List(ctx, inputParam) + case consts.SqlTypeInsert: + return e.Insert(ctx, inputParam) + case consts.SqlTypeUpdate: + return e.Update(ctx, inputParam) + case consts.SqlTypeDelete: + return e.Delete(ctx, inputParam) + default: + return nil, errors.New(inputParam.SqlType + " : sql type is not support") + + } +} + +// List 列表 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 20:52 2024/8/21 +func (e *execute) List(ctx context.Context, inputParam *define.Api2SqlParam) (any, error) { + var ( + err error + tx *gorm.DB + optionList []define.SetOption + ) + if tx, err = e.getTx(ctx, inputParam); nil != err { + return nil, err + } + if optionList, err = e.getOptionList(ctx, inputParam); nil != err { + return nil, err + } + // 动态生成结果解析结构体 + st := wrapper.NewDynamic() + for _, columnConfig := range inputParam.ColumnList { + tag := fmt.Sprintf(`gorm:"%v" json:"%v"`, columnConfig.Column, columnConfig.Alias) + column := wrapper.String(columnConfig.Column).SnakeCaseToCamel() + switch columnConfig.Type { + case "int", "int8", "int16", "int32", "int64": + st.AddInt(column, tag, "") + case "uint", "uint8", "uint16", "uint32", "uint64": + st.AddUint(column, tag, "") + case "bool": + st.AddBool(column, tag, "") + case "float": + st.AddBool(column, tag, "") + case "string": + st.AddString(column, tag, "") + case "map": + st.AddMap(column, tag, "") + case "slice": + st.AddSlice(column, tag, "") + } + } + val := st.ToStructDefaultSliceValue() + if err = e.baseDao.List(tx, &val, optionList...); nil != err { + return nil, err + } + return val, nil +} + +// Detail 详情 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 20:52 2024/8/21 +func (e *execute) Detail(ctx context.Context, inputParam *define.Api2SqlParam) (any, error) { + return nil, nil +} + +// Insert 插入 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 20:52 2024/8/21 +func (e *execute) Insert(ctx context.Context, inputParam *define.Api2SqlParam) (any, error) { + return nil, nil +} + +// Update 更新 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 20:52 2024/8/21 +func (e *execute) Update(ctx context.Context, inputParam *define.Api2SqlParam) (any, error) { + return nil, nil +} + +// Count 数量 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 20:52 2024/8/21 +func (e *execute) Count(ctx context.Context, inputParam *define.Api2SqlParam) (any, error) { + return nil, nil +} + +// Delete 删除 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 20:51 2024/8/21 +func (e *execute) Delete(ctx context.Context, inputParam *define.Api2SqlParam) (any, error) { + return nil, nil +} + +// Express 基于表达式执行 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 20:51 2024/8/21 +func (e *execute) Express(ctx context.Context, inputParam *define.Api2SqlParam) (any, error) { + if nil == e.databaseClientManager { + return nil, errors.New("database client is nil, please use `SetDatabaseClientManager` set instance") + } + // 格式化 inputParam + return nil, nil +} + +// formatAndValidateInputParam 格式化并校验输入参数 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 10:38 2024/8/22 +func (e *execute) formatAndValidateInputParam(inputParam *define.Api2SqlParam) error { + if nil == inputParam { + return errors.New("inputParam is nil") + } + if len(inputParam.DatabaseFlag) == 0 { + return errors.New("databaseFlag is empty") + } + if len(inputParam.Table) == 0 { + return errors.New("table is empty") + } + if len(inputParam.SqlType) == 0 { + return errors.New("sqlType is empty") + } + databaseClient, err := e.databaseClientManager.GetDBClient(inputParam.DatabaseFlag) + if nil != err { + return errors.New(inputParam.DatabaseFlag + " : database client get fail -> " + err.Error()) + } + // 尝试获取表结构, api 转 sql 要求必须开启表结构缓存, 否则无法确定相关数据如何解析 + if inputParam.TableColumnConfig, err = databaseClient.GetTableFieldList(inputParam.Table); nil != err { + return errors.New(inputParam.DatabaseFlag + " : get table field list fail -> " + err.Error()) + } + if len(inputParam.TableColumnConfig) == 0 { + return errors.New(inputParam.DatabaseFlag + " : table field list is empty, please enable `CacheDataTableStructureConfig` or `SetTableColumnConfig`") + } + // 操作字段列表为空 + if err = e.validateColumn(inputParam); nil != err { + return err + } + // 验证 force no limit + if inputParam.ForceNoLimit { + inputParam.Limit = 0 + inputParam.WithCount = false + } + return nil +} + +// validateColumn 验证表字段相关 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 11:48 2024/8/22 +func (e *execute) validateColumn(inputParam *define.Api2SqlParam) error { + if len(inputParam.ColumnList) == 0 && wrapper.ArrayType[string]([]string{ + consts.SqlTypeList, consts.SqlTypeDetail, + }).Has(inputParam.SqlType) >= 0 { + return errors.New("column list is empty") + } + // 验证字段是否都正确 + tableColumnTable := make(map[string]bool) + for _, itemColumn := range inputParam.TableColumnConfig { + tableColumnTable[itemColumn.Column] = true + } + for _, columnConfig := range inputParam.ColumnList { + if !tableColumnTable[columnConfig.Column] { + return errors.New(columnConfig.Column + " : input column not found in table column list") + } + if len(columnConfig.Alias) == 0 { + columnConfig.Alias = columnConfig.Column + } + } + return nil +} + +// getTx 获取数据库连接 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 12:20 2024/8/23 +func (e *execute) getTx(ctx context.Context, inputParam *define.Api2SqlParam) (*gorm.DB, error) { + if nil != inputParam.Tx { + return inputParam.Tx, nil + } + if inputParam.ForceMaster || // 强制操作主库 + wrapper.ArrayType[string]([]string{consts.SqlTypeDelete, consts.SqlTypeInsert, consts.SqlTypeUpdate}).Has(inputParam.SqlType) >= 0 { // 写操作 + return e.databaseClientManager.GetMasterClient(ctx, inputParam.DatabaseFlag) + } + return e.databaseClientManager.GetSlaveClient(ctx, inputParam.DatabaseFlag) +} + +// getOptionList 设置where条件 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 12:31 2024/8/23 +func (e *execute) getOptionList(ctx context.Context, inputParam *define.Api2SqlParam) ([]define.SetOption, error) { + optionList := []define.SetOption{ + database.WithTable(inputParam.Table), + } + // 设置 limit offset + if !inputParam.ForceNoLimit && inputParam.Limit > 0 { + optionList = append(optionList, database.WithLimit(inputParam.Limit, inputParam.Offset)) + } + for _, item := range inputParam.ConditionList { + optionFunc, err := database.WithAnyCondition(item.Column, item.Operate, item.Value) + if nil != err { + return nil, err + } + optionList = append(optionList, optionFunc) + } + return optionList, nil +} diff --git a/api2sql/execute_test.go b/api2sql/execute_test.go new file mode 100644 index 0000000..ceb4192 --- /dev/null +++ b/api2sql/execute_test.go @@ -0,0 +1,74 @@ +// Package api2sql ... +// +// Description : api2sql ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2024-08-23 17:36 +package api2sql + +import ( + "context" + "encoding/json" + "fmt" + "git.zhangdeman.cn/zhangdeman/consts" + "git.zhangdeman.cn/zhangdeman/database" + "git.zhangdeman.cn/zhangdeman/database/define" + "reflect" + "testing" +) + +func Test_execute_Run(t *testing.T) { + clientManager := database.NewWrapperClient() + if err := clientManager.AddWithConfig("TEST_DATABASE", nil, &define.Database{ + Master: &define.Driver{ + DBType: "sqlite3", + Host: "/tmp/gateway.db", + }, + Slave: &define.Driver{ + DBType: "sqlite3", + Host: "/tmp/gateway.db", + }, + }, []string{}); nil != err { + panic(err.Error()) + } + dbClient, _ := clientManager.GetDBClient("TEST_DATABASE") + + dbClient.SetTableStructure(map[string][]*define.ColumnConfig{ + "project": []*define.ColumnConfig{ + &define.ColumnConfig{ + Column: "flag", + Alias: "project_flag", + Type: "string", + }, + }, + }) + Exec.SetDatabaseClientManager(clientManager) + res, err := Exec.List(context.Background(), &define.Api2SqlParam{ + DatabaseFlag: "TEST_DATABASE", + Table: "project", + ForceMaster: false, + InputSql: "", + TableSplitConfig: define.TableSplitConfig{}, + SqlType: consts.SqlTypeList, + ColumnList: []*define.ColumnConfig{ + &define.ColumnConfig{ + Column: "flag", + Alias: "project_flag", + Type: "string", + }, + }, + Limit: 0, + Offset: 0, + ForceNoLimit: false, + OrderField: "id", + OrderRule: "desc", + WithCount: false, + ConditionList: nil, + TableColumnConfig: nil, + Tx: nil, + }) + byteData, _ := json.Marshal(res) + tt := reflect.TypeOf(res) + fmt.Println(tt.String(), res, err, string(byteData)) +} diff --git a/base.go b/base.go index 0b20801..b6ae157 100644 --- a/base.go +++ b/base.go @@ -7,6 +7,7 @@ package database import ( "errors" + "git.zhangdeman.cn/zhangdeman/database/define" "gorm.io/gorm" ) @@ -25,8 +26,8 @@ type BaseDao struct { // Author : go_developer@163.com<白茶清欢> // // Date : 8:06 下午 2021/8/8 -func (b *BaseDao) Create(dbInstance *gorm.DB, data any, optionList ...SetOption) error { - o := &Option{} +func (b *BaseDao) Create(dbInstance *gorm.DB, data any, optionList ...define.SetOption) error { + o := &define.Option{} for _, itemFunc := range optionList { itemFunc(o) } @@ -42,7 +43,7 @@ func (b *BaseDao) Create(dbInstance *gorm.DB, data any, optionList ...SetOption) // Author : go_developer@163.com<白茶清欢> // // Date : 8:12 下午 2021/8/8 -func (b *BaseDao) Update(dbInstance *gorm.DB, updateData any, optionFuncList ...SetOption) (int64, error) { +func (b *BaseDao) Update(dbInstance *gorm.DB, updateData any, optionFuncList ...define.SetOption) (int64, error) { dbInstance = b.setTxCondition(dbInstance, optionFuncList...) r := dbInstance.Updates(updateData) return r.RowsAffected, r.Error @@ -53,7 +54,7 @@ func (b *BaseDao) Update(dbInstance *gorm.DB, updateData any, optionFuncList ... // Author : go_developer@163.com<白茶清欢> // // Date : 17:05 2024/1/13 -func (b *BaseDao) UpdateOne(dbInstance *gorm.DB, updateData any, optionFuncList ...SetOption) (int64, error) { +func (b *BaseDao) UpdateOne(dbInstance *gorm.DB, updateData any, optionFuncList ...define.SetOption) (int64, error) { optionFuncList = append(optionFuncList, WithLimit(1, 0)) return b.Update(dbInstance, updateData, optionFuncList...) } @@ -63,7 +64,7 @@ func (b *BaseDao) UpdateOne(dbInstance *gorm.DB, updateData any, optionFuncList // Author : go_developer@163.com<白茶清欢> // // Date : 8:14 下午 2021/8/8 -func (b *BaseDao) List(dbInstance *gorm.DB, result any, optionFuncList ...SetOption) error { +func (b *BaseDao) List(dbInstance *gorm.DB, result any, optionFuncList ...define.SetOption) error { dbInstance = b.setTxCondition(dbInstance, optionFuncList...) return dbInstance.Find(result).Error } @@ -73,7 +74,7 @@ func (b *BaseDao) List(dbInstance *gorm.DB, result any, optionFuncList ...SetOpt // Author : go_developer@163.com<白茶清欢> // // Date : 11:46 2023/2/9 -func (b *BaseDao) Delete(dbInstance *gorm.DB, dataModel any, optionFuncList ...SetOption) (int64, error) { +func (b *BaseDao) Delete(dbInstance *gorm.DB, dataModel any, optionFuncList ...define.SetOption) (int64, error) { dbInstance = dbInstance.Model(dataModel) dbInstance = b.setTxCondition(dbInstance, optionFuncList...).Delete(dataModel) return dbInstance.RowsAffected, dbInstance.Error @@ -84,7 +85,7 @@ func (b *BaseDao) Delete(dbInstance *gorm.DB, dataModel any, optionFuncList ...S // Author : go_developer@163.com<白茶清欢> // // Date : 8:25 下午 2021/8/8 -func (b *BaseDao) Detail(dbInstance *gorm.DB, result any, optionFuncList ...SetOption) error { +func (b *BaseDao) Detail(dbInstance *gorm.DB, result any, optionFuncList ...define.SetOption) error { dbInstance = b.setTxCondition(dbInstance, optionFuncList...) return dbInstance.First(result).Error } @@ -118,7 +119,7 @@ func (b *BaseDao) IsNotFound(err error) bool { // Author : go_developer@163.com<白茶清欢> // // Date : 8:25 下午 2021/8/8 -func (b *BaseDao) Count(dbInstance *gorm.DB, optionFuncList ...SetOption) (int64, error) { +func (b *BaseDao) Count(dbInstance *gorm.DB, optionFuncList ...define.SetOption) (int64, error) { dbInstance = b.setTxCondition(dbInstance, optionFuncList...) var cnt int64 return cnt, dbInstance.Count(&cnt).Error @@ -173,10 +174,10 @@ func (b *BaseDao) Rollback(db *gorm.DB) error { // Author : go_developer@163.com<白茶清欢> // // Date : 17:38 2022/5/15 -func (b *BaseDao) setTxCondition(tx *gorm.DB, optionFuncList ...SetOption) *gorm.DB { +func (b *BaseDao) setTxCondition(tx *gorm.DB, optionFuncList ...define.SetOption) *gorm.DB { // 构建查询条件 - o := &Option{} + o := &define.Option{} for _, fn := range optionFuncList { fn(o) } @@ -199,7 +200,72 @@ func (b *BaseDao) setTxCondition(tx *gorm.DB, optionFuncList ...SetOption) *gorm tx = tx.Offset(o.Offset) } } - sqlBlockList := make([]string, 0) + // in 语句 + if nil != o.In { + tx = tx.Where(o.In) + } + + // not in 语句 + if nil != o.NotIn { + for field, value := range o.NotIn { + tx = tx.Where(field+" NOT IN ? ", value) + } + } + + // like 语句 + if nil != o.Like { + for field, value := range o.Like { + tx = tx.Where(field+" LIKE ? ", "%"+value+"%") + } + } + + // NOT LIKE 语句 + if nil != o.NotLike { + for field, value := range o.NotLike { + tx = tx.Where(field+" NOT LIKE ? ", "%"+value+"%") + } + } + + // >= + if nil != o.Start { + for field, value := range o.Start { + tx = tx.Where(field+" >= ?", value) + } + } + + // < + if nil != o.End { + for field, value := range o.End { + tx = tx.Where(field+" < ?", value) + } + } + + // between + for field, betweenVal := range o.Between { + tx = tx.Where("`"+field+"` BETWEEN ? AND ?", betweenVal[0], betweenVal[1]) + } + // not between + for field, notBetweenVal := range o.NotBetween { + tx = tx.Where("`"+field+"` NOT BETWEEN ? AND ?", notBetweenVal[0], notBetweenVal[1]) + } + + // 排序 + for _, orderRule := range o.Order { + tx = tx.Order(orderRule) + } + + // or 语句 + if nil != o.OR { + for _, itemOr := range o.OR { + orOption := &define.Option{} + for _, fn := range itemOr { + fn(orOption) + } + orSql, orBindVal := optionToSql(orOption) + tx.Or(orSql, orBindVal) + } + } + /*sqlBlockList := make([]string, 0) bindValueList := make([]any, 0) sql, bindVal := optionToSql(o) tx.Or(sql, bindVal...) @@ -212,6 +278,6 @@ func (b *BaseDao) setTxCondition(tx *gorm.DB, optionFuncList ...SetOption) *gorm } orSql, orBindVal := optionToSql(orOption) tx.Or(orSql, orBindVal...) - } + }*/ return tx } diff --git a/client.go b/client.go deleted file mode 100644 index 1e910c5..0000000 --- a/client.go +++ /dev/null @@ -1,327 +0,0 @@ -// Package database ... -// -// Description : mysql客户端 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 2021-03-01 9:20 下午 -package database - -import ( - "context" - "errors" - "fmt" - "git.zhangdeman.cn/zhangdeman/consts" - "git.zhangdeman.cn/zhangdeman/serialize" - "path/filepath" - "strings" - "sync" - - "git.zhangdeman.cn/zhangdeman/logger/wrapper" - "go.uber.org/zap" - - gormLogger "gorm.io/gorm/logger" - - "gorm.io/driver/mysql" - "gorm.io/driver/sqlite" - "gorm.io/gorm" -) - -var ( - // Client mysql客户端 - Client *client -) - -func init() { - Client = &client{ - lock: &sync.RWMutex{}, - clientTable: make(map[string]*DBClient), - } -} - -type client struct { - lock *sync.RWMutex - clientTable map[string]*DBClient - logger *zap.Logger -} - -// AddWithConfigFile 使用文件生成新的客户端,文件名去掉后缀作为flag -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 19:19 2022/6/5 -func (c *client) AddWithConfigFile(cfgFilePath string, logInstance *zap.Logger, extraFieldList []string) error { - var ( - err error - cfg *cfgFile - ) - - if cfg, err = c.getCfg(cfgFilePath); nil != err { - return err - } - if nil == cfg { - // 不支持的配置文件格式 - return nil - } - return c.AddWithConfig(cfg.Flag, logInstance, cfg.Config, extraFieldList) -} - -// AddWithConfig ... -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 20:41 2023/4/18 -func (c *client) AddWithConfig(flag string, logInstance *zap.Logger, databaseConfig *Database, extraFieldList []string) error { - dbClient := &DBClient{ - dbFlag: flag, - loggerInstance: logInstance, - master: nil, - slave: nil, - extraFieldList: extraFieldList, - cfg: Driver{}, - } - var err error - if dbClient.master, err = c.GetDatabaseClient(databaseConfig.Master, logInstance); nil != err { - return err - } - if dbClient.slave, err = c.GetDatabaseClient(databaseConfig.Slave, logInstance); nil != err { - return err - } - c.lock.Lock() - c.clientTable[dbClient.dbFlag] = dbClient - c.lock.Unlock() - return nil -} - -// BatchAddWithConfigDir 自动读取目录下配置文件, 生成客户端 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 19:19 2022/6/5 -func (c *client) BatchAddWithConfigDir(cfgDir string, logInstance *zap.Logger, extraFieldList []string) error { - filepathNames, _ := filepath.Glob(filepath.Join(cfgDir, "*")) - for i := range filepathNames { - if err := c.AddWithConfigFile(filepathNames[i], logInstance, extraFieldList); nil != err { - return err - } - } - return nil -} - -// getCfg 读取配置 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 18:05 2022/6/11 -func (c *client) getCfg(cfgPath string) (*cfgFile, error) { - fileArr := strings.Split(cfgPath, ".") - if len(fileArr) < 2 { - // 获取不到类型 - return nil, errors.New("文件格式必须是JSON或者YAML") - } - fileType := strings.ToLower(fileArr[len(fileArr)-1]) - fileFlagArr := strings.Split(fileArr[0], string(filepath.Separator)) - result := &cfgFile{ - Path: cfgPath, - Type: "", - Flag: fileFlagArr[len(fileFlagArr)-1], - Config: &Database{}, - } - var ( - err error - cfgInfo Database - ) - switch fileType { - case consts.FileTypeYaml: - fallthrough - case consts.FileTypeYml: - result.Type = consts.FileTypeYaml - if err = serialize.File.ReadYmlContent(cfgPath, &result.Config); nil != err { - return nil, fmt.Errorf("%s 配置文件解析失败, 原因 : %s", cfgPath, err.Error()) - } - case consts.FileTypeJson: - result.Type = consts.FileTypeJson - if err = serialize.File.ReadJSONContent(cfgPath, &cfgInfo); nil != err { - return nil, fmt.Errorf("%s 配置文件解析失败, 原因 : %s", cfgPath, err.Error()) - } - default: - // 不是JSON , 也不是YML, 跳过 - return nil, nil - } - if len(result.Config.Master.Timezone) == 0 { - // 默认使用本地时区 - result.Config.Master.Timezone = "Local" - } else { - result.Config.Slave.Timezone = result.Config.Master.Timezone - } - return result, nil -} - -// GetDBClient 获取db client -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 19:32 2022/6/5 -func (c *client) GetDBClient(dbFlag string) (*DBClient, error) { - c.lock.RLock() - defer c.lock.RUnlock() - var ( - exist bool - dbClient *DBClient - ) - if dbClient, exist = c.clientTable[dbFlag]; !exist { - return nil, fmt.Errorf("%s 标识的数据库实例不存在! ", dbFlag) - } - return dbClient, nil -} - -// GetMasterClient 获取主库客户端 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 19:36 2022/6/5 -func (c *client) GetMasterClient(ctx context.Context, dbFlag string) (*gorm.DB, error) { - var ( - err error - dbClient *DBClient - ) - if dbClient, err = c.GetDBClient(dbFlag); nil != err { - return nil, err - } - - session := dbClient.master.Session(&gorm.Session{}) - session.Logger = dbClient.getLogger(ctx, session, dbFlag+"-master") - return session, nil -} - -// GetSlaveClient 获取从库客户端 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 19:37 2022/6/5 -func (c *client) GetSlaveClient(ctx context.Context, dbFlag string) (*gorm.DB, error) { - var ( - err error - dbClient *DBClient - ) - if dbClient, err = c.GetDBClient(dbFlag); nil != err { - return nil, err - } - - session := dbClient.slave.Session(&gorm.Session{}) - session.Logger = dbClient.getLogger(ctx, session, dbFlag+"-slave") - return session, nil -} - -// getGormClient 获取GORM client方法 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 11:24 2022/6/6 -func (c *client) getGormClient() (*gorm.DB, error) { - return nil, nil -} - -// GetDatabaseClient 获取数据库连接 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 18:41 2022/6/11 -func (c *client) GetDatabaseClient(conf *Driver, logInstance *zap.Logger) (*gorm.DB, error) { - var ( - instance *gorm.DB - err error - ) - - if conf.DBType == consts.DatabaseDriverMysql { - if instance, err = gorm.Open(mysql.Open(c.buildConnectionDSN(conf)), &gorm.Config{}); nil != err { - return nil, err - } - } else if conf.DBType == consts.DatabaseDriverSqlite3 { - if instance, err = gorm.Open(sqlite.Open(c.buildConnectionDSN(conf)), &gorm.Config{}); nil != err { - return nil, err - } - } else { - return nil, fmt.Errorf("%v : db driver is not support", conf.DBType) - } - - instance.Logger = wrapper.NewGormLoggerWithInstance(nil, instance, logInstance, "", nil) - - return instance, nil -} - -// buildConnectionDSN 构建连接信息 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 18:42 2022/6/11 -func (c *client) buildConnectionDSN(conf *Driver) string { - if conf.DBType == consts.DatabaseDriverSqlite3 { - // 兼容sqlite3 - return conf.Host - } - return fmt.Sprintf( - "%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=%s", - conf.Username, - conf.Password, - conf.Host, - conf.Port, - conf.Database, - conf.Charset, - conf.Timezone, - ) -} - -// DBClient 包装日志实例 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 3:09 PM 2021/12/24 -type DBClient struct { - dbFlag string // 数据库标识 - loggerInstance *zap.Logger // 日志实例 - master *gorm.DB // 主库 - slave *gorm.DB // 从库 - extraFieldList []string // 提取的字段 - cfg Driver // 数据库配置 -} - -// SetFlag 设置数据库标识 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 16:18 2022/6/5 -func (dc *DBClient) SetFlag(dbFlag string) { - dc.dbFlag = dbFlag -} - -// GetMaster 获取主库连接 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 3:28 PM 2021/12/24 -func (dc *DBClient) GetMaster(ctx context.Context) *gorm.DB { - session := dc.master.Session(&gorm.Session{}) - session.Logger = dc.getLogger(ctx, session, "slave") - return session -} - -// GetSlave 获取从库链接 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 3:29 PM 2021/12/24 -func (dc *DBClient) GetSlave(ctx context.Context) *gorm.DB { - session := dc.slave.Session(&gorm.Session{}) - session.Logger = dc.getLogger(ctx, session, "slave") - return session -} - -// getLogger 获取日志实例 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 3:45 PM 2021/12/24 -func (dc *DBClient) getLogger(ctx context.Context, dbClient *gorm.DB, node string) gormLogger.Interface { - return wrapper.NewGormLoggerWithInstance(ctx, dbClient, dc.loggerInstance, dc.dbFlag+"|"+node, dc.extraFieldList) -} diff --git a/define/api2sql.go b/define/api2sql.go new file mode 100644 index 0000000..b96c7a8 --- /dev/null +++ b/define/api2sql.go @@ -0,0 +1,72 @@ +// Package define ... +// +// Description : define ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2024-08-21 16:05 +package define + +import ( + "gorm.io/gorm" +) + +// Api2SqlParam 接口转sql的输入配置 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:06 2024/8/21 +// +// 一个合法的 sql 配置格式 : +// +// select 语句 : SELECT {FIELD_LIST} FROM {TABLE} WHERE {WHERE} ORDER BY {ORDER_FIELD} {ORDER_RULE} LIMIT {LIMIT} OFFSET {OFFSET} +// +// insert 语句 : INSERT INTO {TABLE} ({FIELD_LIST}) VALUES ({VALUE_LIST}) +// +// update 语句 : UPDATE {TABLE} SET {SET} WHERE {WHERE} LIMIT {LIMIT} OFFSET {OFFSET} ORDER BY {ORDER_FIELD} {ORDER_RULE} +// +// delete 语句 : DELETE FROM {TABLE} WHERE {WHERE} LIMIT {LIMIT} OFFSET {OFFSET} ORDER BY {ORDER_FIELD} {ORDER_RULE} +// +// count 语句 : SELECT COUNT(*) as count FROM {TABLE} WHERE {WHERE} +type Api2SqlParam struct { + DatabaseFlag string `json:"database_flag"` // 数据库标识 + Table string `json:"table"` // 操作的数据表 + ForceMaster bool `json:"force_master"` // 针对查询语句, 是否强制读主, 如果查询语句在事务中, 默认强制读主 + InputSql string `json:"input_sql"` // 输入的sql模板, 仅依赖 ValueList 解析字段值, 依赖 split 相关解析分表配置 + TableSplitConfig TableSplitConfig `json:"table_split_config"` // 分表配置 + SqlType string `json:"sql_type"` // sql语句类型 : detail - 查询详情 list - 查询列表 count - 查询数量 update - 更新 insert - 插入 delete - 删除 + ColumnList []*ColumnConfig `json:"column_list"` // 仅针对 select / detail 有效, 查询的字段列表, 字段名 => 字段别名, 不设置, 则以字段名输出 + Limit int `json:"limit"` // 操作数量 + Offset int `json:"offset"` // 操作偏移量 + ForceNoLimit bool `json:"force_no_limit"` // 强制允许不限制 : 正常操作 select / delete / update 均需要指定本次操作数据量, 如果确定不限制, 此参数设置为 `true` , sqlType = list , 且 ForceNoLimit = true 时, WithCount 参数无效 + OrderField string `json:"order_field"` // 排序字段, 仅 sqlType = list 生效 + OrderRule string `json:"order_rule"` // 排序规则, Asc / Desc + WithCount bool `json:"with_count"` // 是否返回数据总量, 仅 sqlType = list 生效 + ConditionList []SqlCondition `json:"value_list"` // 字段列表 + TableColumnConfig []*ColumnConfig `json:"table_column_config"` // 表字段配置 + Tx *gorm.DB `json:"-"` // 前后已有的数据库连接, 直接复用 +} + +// TableSplitConfig 分表配置 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 14:42 2024/8/23 +type TableSplitConfig struct { + IsSplit bool `json:"is_split"` // 是否分表 + SplitField string `json:"split_field"` // 分表字段, 仅分表时有效, 分表字段要求在 ValueList 必须存在 + FieldValue any `json:"field_value"` // 分表字段值 + SplitStrategy string `json:"split_strategy"` // 分表策略, 仅分表时有效, 支持注册自动以策略 + TableCnt int64 `json:"table_cnt"` // 一共分表多少张 +} + +// SqlCondition sql条件 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 14:46 2024/8/23 +type SqlCondition struct { + Column string `json:"column"` // 表字段 + Operate string `json:"operate"` // 操作 : == / !== / in / not in / like / not like + Value any `json:"value"` // 数据值 +} diff --git a/define.go b/define/define.go similarity index 98% rename from define.go rename to define/define.go index 1c09d03..49076c7 100644 --- a/define.go +++ b/define/define.go @@ -1,11 +1,11 @@ -// package database... +// Package define ... // // Description : 数据定义 // // Author : go_developer@163.com<白茶清欢> // // Date : 2021-03-01 9:27 下午 -package database +package define // DBConfig 数据库连接的配置 // @@ -22,12 +22,12 @@ type DBConfig struct { Connection Connection `json:"connection" yaml:"connection"` // 连接数量配置 } -// cfgFile 配置文件定义 +// CfgFile 配置文件定义 // // Author : go_developer@163.com<白茶清欢> // // Date : 14:47 2022/6/9 -type cfgFile struct { +type CfgFile struct { Flag string `json:"flag"` // 数据库标识 Path string `json:"path"` // 配置文件路径 Type string `json:"type"` // 配置文件类型 diff --git a/define/sql_option.go b/define/sql_option.go new file mode 100644 index 0000000..7518d2f --- /dev/null +++ b/define/sql_option.go @@ -0,0 +1,39 @@ +// Package define ... +// +// Description : define ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2024-08-23 17:46 +package define + +// SetOption 设置选项 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 11:46 2022/5/15 +type SetOption func(o *Option) + +// Option 扩展选项 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 8:05 下午 2021/8/8 +type Option struct { + Model any `json:"-"` // 操作model + Table string `json:"table"` // 查询的数据表 + Limit int `json:"limit"` // 限制数量 + Offset int `json:"offset"` // 偏移量 + In map[string]any `json:"in"` // in语句 + NotIn map[string]any `json:"not_in"` // not in语句 + Where map[string]any `json:"where"` // where 条件 + Between map[string][2]any `json:"between"` // between 条件 + NotBetween map[string][2]any `json:"not_between"` // not between 条件 + Start map[string]any `json:"start"` // >= 条件 + End map[string]any `json:"end"` // < 条件 + Like map[string]string `json:"like"` // like 语句 + NotLike map[string]string `json:"not_like"` // not like 语句 + NotEqual map[string]any `json:"not_equal"` // != 语句 + Order []string `json:"order"` // 排序规则 + OR [][]SetOption `json:"or"` // or 语句, or 和其他属性 and 关系, 内部 OR 关系 +} diff --git a/define/table.go b/define/table.go new file mode 100644 index 0000000..32138b9 --- /dev/null +++ b/define/table.go @@ -0,0 +1,29 @@ +// Package define ... +// +// Description : define ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2024-08-21 14:56 +package define + +// CacheTableStructureConfig 缓存表结构的配置 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 14:56 2024/8/21 +type CacheTableStructureConfig struct { + Enable bool `json:"enable"` // 是否启用表结构缓存 + SyncTimeInterval int `json:"sync_time_interval"` // 开启的情况向, 多久同步一次表结构, 默认值 3600, 单位 : s +} + +// ColumnConfig ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 16:42 2024/8/23 +type ColumnConfig struct { + Column string `json:"column"` // 字段名 + Alias string `json:"alias"` // 字段别名 + Type string `json:"type"` // 字段类型 +} diff --git a/go.mod b/go.mod index f31d51f..4d06dc9 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.21 toolchain go1.21.3 require ( - git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240817091513-491f455a23c0 + git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240823041145-d4df71cf37e5 git.zhangdeman.cn/zhangdeman/logger v0.0.0-20240725055115-98eb52ae307a git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20240618035451-8d48a6bd39dd - git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20240813083016-da44ae07ab9b + git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20240823103024-c38d16dc28d3 github.com/pkg/errors v0.9.1 github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 go.uber.org/zap v1.27.0 diff --git a/go.sum b/go.sum index 22fec99..35d64db 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,10 @@ git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240726024939-e424db29c5c4 h1:mibnyz git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240726024939-e424db29c5c4/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240817091513-491f455a23c0 h1:U12XDtyRrmsqb/wRvRZG9+SBKMCGFNADpiLogsp5POw= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240817091513-491f455a23c0/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= +git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240821082758-8bf75bab08fb h1:dNnVYynCQhLSbtNUVQsvDIthkNgpTrAJF4dAEj08FdE= +git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240821082758-8bf75bab08fb/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= +git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240823041145-d4df71cf37e5 h1:pmIHln0gWW+5xAB762h3WDsRkZuYLUDndvJDsGMKoOY= +git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240823041145-d4df71cf37e5/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= git.zhangdeman.cn/zhangdeman/easylock v0.0.0-20230731062340-983985c12eda h1:bMD6r9gjRy7cO+T4zRQVYAesgIblBdTnhzT1vN5wjvI= git.zhangdeman.cn/zhangdeman/easylock v0.0.0-20230731062340-983985c12eda/go.mod h1:dT0rmHcJ9Z9IqWeMIt7YzR88nKkNV2V3dfG0j9Q6lK0= git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20240311030808-e2a2e6a3c211 h1:I/wOsRpCSRkU9vo1u703slQsmK0wnNeZzsWQOGtIAG0= @@ -28,6 +32,10 @@ git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20240806072320-3533617196fd h1:zcmfm git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20240806072320-3533617196fd/go.mod h1:gnaF3v9/om6gaxFKeNVuKeFTYM61gHyW7vign7vrwyo= git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20240813083016-da44ae07ab9b h1:CcO2t7ssBSZwE7BDTOkCSgOvTGATarOZ0tajZ00McEc= git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20240813083016-da44ae07ab9b/go.mod h1:gnaF3v9/om6gaxFKeNVuKeFTYM61gHyW7vign7vrwyo= +git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20240821101656-59ba4ebfa5c5 h1:+P+7IDkfOYII4K7BaPiZhnzYgPpJ7mv2hhUSZcxCWiI= +git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20240821101656-59ba4ebfa5c5/go.mod h1:KcojKP22mv9/IZrQWlIBfa1EuBxtEOqfWMgN3SYK2N8= +git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20240823103024-c38d16dc28d3 h1:RcWNxrHmhZksZWrP/HLEwAM8uIIHYlPLQ20HnLzC+j0= +git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20240823103024-c38d16dc28d3/go.mod h1:KcojKP22mv9/IZrQWlIBfa1EuBxtEOqfWMgN3SYK2N8= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ= diff --git a/option.go b/option.go index a253d34..3da670a 100644 --- a/option.go +++ b/option.go @@ -9,47 +9,23 @@ package database import ( "encoding/json" + "errors" + "fmt" + "git.zhangdeman.cn/zhangdeman/consts" + "git.zhangdeman.cn/zhangdeman/database/define" "git.zhangdeman.cn/zhangdeman/op_type" + "git.zhangdeman.cn/zhangdeman/serialize" "reflect" "strings" ) -// SetOption 设置选项 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 11:46 2022/5/15 -type SetOption func(o *Option) - -// Option 扩展选项 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 8:05 下午 2021/8/8 -type Option struct { - Model any `json:"-"` // 操作model - Table string `json:"table"` // 查询的数据表 - Limit int `json:"limit"` // 限制数量 - Offset int `json:"offset"` // 偏移量 - In map[string]any `json:"in"` // in语句 - NotIn map[string]any `json:"not_in"` // not in语句 - Where map[string]any `json:"where"` // where 条件 - Start map[string]any `json:"start"` // >= 条件 - End map[string]any `json:"end"` // < 条件 - Like map[string]string `json:"like"` // like 语句 - NotLike map[string]string `json:"not_like"` // not like 语句 - NotEqual map[string]any `json:"not_equal"` // != 语句 - Order []string `json:"order"` // 排序规则 - OR [][]SetOption `json:"or"` // or 语句, or 和其他属性 and 关系, 内部 OR 关系 -} - // WithModel ... // // Author : go_developer@163.com<白茶清欢> // // Date : 15:18 2024/1/15 -func WithModel(model any) SetOption { - return func(o *Option) { +func WithModel(model any) define.SetOption { + return func(o *define.Option) { o.Model = model } } @@ -59,8 +35,8 @@ func WithModel(model any) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 20:56 2023/3/22 -func WithTable(tableName string) SetOption { - return func(o *Option) { +func WithTable(tableName string) define.SetOption { + return func(o *define.Option) { o.Table = tableName } } @@ -70,8 +46,8 @@ func WithTable(tableName string) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 12:17 2022/5/15 -func WithWhere[T op_type.BaseType](where map[string]T) SetOption { - return func(o *Option) { +func WithWhere[T op_type.BaseType](where map[string]T) define.SetOption { + return func(o *define.Option) { if nil == o.Where { o.Where = make(map[string]any) } @@ -86,8 +62,8 @@ func WithWhere[T op_type.BaseType](where map[string]T) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 12:00 2022/5/15 -func WithLimit[T op_type.Int](limit T, offset T) SetOption { - return func(o *Option) { +func WithLimit[T op_type.Int](limit T, offset T) define.SetOption { + return func(o *define.Option) { o.Limit = int(limit) o.Offset = int(offset) } @@ -98,8 +74,8 @@ func WithLimit[T op_type.Int](limit T, offset T) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 21:42 2023/10/14 -func WithLimitByPageAndSize[T op_type.Int](page T, size T) SetOption { - return func(o *Option) { +func WithLimitByPageAndSize[T op_type.Int](page T, size T) define.SetOption { + return func(o *define.Option) { if size > 0 { o.Limit = int(size) } @@ -115,8 +91,8 @@ func WithLimitByPageAndSize[T op_type.Int](page T, size T) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 12:23 2022/5/15 -func WithIn[T op_type.Array](field string, value T) SetOption { - return func(o *Option) { +func WithIn[T op_type.Array](field string, value T) define.SetOption { + return func(o *define.Option) { if nil == value { return } @@ -135,8 +111,8 @@ func WithIn[T op_type.Array](field string, value T) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 12:24 2022/5/15 -func WithBatchIn[T op_type.Array](batchIn map[string]T) SetOption { - return func(o *Option) { +func WithBatchIn[T op_type.Array](batchIn map[string]T) define.SetOption { + return func(o *define.Option) { if nil == o.In { o.In = make(map[string]any) } @@ -151,8 +127,8 @@ func WithBatchIn[T op_type.Array](batchIn map[string]T) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 16:18 2022/5/15 -func WithNotIn[T op_type.Array](field string, value T) SetOption { - return func(o *Option) { +func WithNotIn[T op_type.Array](field string, value T) define.SetOption { + return func(o *define.Option) { if nil == o.NotIn { o.NotIn = make(map[string]any) } @@ -168,8 +144,8 @@ func WithNotIn[T op_type.Array](field string, value T) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 16:23 2022/5/15 -func WithBatchNotIn[T op_type.Array](data map[string]T) SetOption { - return func(o *Option) { +func WithBatchNotIn[T op_type.Array](data map[string]T) define.SetOption { + return func(o *define.Option) { if nil == o.NotIn { o.NotIn = make(map[string]any) } @@ -187,8 +163,8 @@ func WithBatchNotIn[T op_type.Array](data map[string]T) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 17:01 2022/5/15 -func WithStart(field string, value any) SetOption { - return func(o *Option) { +func WithStart(field string, value any) define.SetOption { + return func(o *define.Option) { if nil == o.Start { o.Start = make(map[string]any) } @@ -201,8 +177,8 @@ func WithStart(field string, value any) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 17:03 2022/5/15 -func WithBatchStart(data map[string]any) SetOption { - return func(o *Option) { +func WithBatchStart(data map[string]any) define.SetOption { + return func(o *define.Option) { if nil == o.Start { o.Start = make(map[string]any) } @@ -217,8 +193,8 @@ func WithBatchStart(data map[string]any) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 17:07 2022/5/15 -func WithEnd(field string, value any) SetOption { - return func(o *Option) { +func WithEnd(field string, value any) define.SetOption { + return func(o *define.Option) { if nil == o.End { o.End = make(map[string]any) } @@ -231,8 +207,8 @@ func WithEnd(field string, value any) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 17:10 2022/5/15 -func WithBatchEnd(data map[string]any) SetOption { - return func(o *Option) { +func WithBatchEnd(data map[string]any) define.SetOption { + return func(o *define.Option) { if nil == o.End { o.End = make(map[string]any) } @@ -247,8 +223,8 @@ func WithBatchEnd(data map[string]any) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 17:16 2022/5/15 -func WithLike(field string, value string) SetOption { - return func(o *Option) { +func WithLike(field string, value string) define.SetOption { + return func(o *define.Option) { if nil == o.Like { o.Like = make(map[string]string) } @@ -264,8 +240,8 @@ func WithLike(field string, value string) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 17:19 2022/5/15 -func WithBatchLike(data map[string]string) SetOption { - return func(o *Option) { +func WithBatchLike(data map[string]string) define.SetOption { + return func(o *define.Option) { if nil == o.Like { o.Like = make(map[string]string) } @@ -283,8 +259,8 @@ func WithBatchLike(data map[string]string) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 17:50 2022/5/15 -func WithNotLike(field string, value string) SetOption { - return func(o *Option) { +func WithNotLike(field string, value string) define.SetOption { + return func(o *define.Option) { if nil == o.NotLike { o.NotLike = make(map[string]string) } @@ -300,8 +276,8 @@ func WithNotLike(field string, value string) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 17:52 2022/5/15 -func WithBatchNotLike(data map[string]string) SetOption { - return func(o *Option) { +func WithBatchNotLike(data map[string]string) define.SetOption { + return func(o *define.Option) { if nil == o.NotLike { o.NotLike = make(map[string]string) } @@ -319,8 +295,8 @@ func WithBatchNotLike(data map[string]string) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 17:31 2022/5/15 -func WithNotEqual[T op_type.BaseType](field string, value T) SetOption { - return func(o *Option) { +func WithNotEqual[T op_type.BaseType](field string, value T) define.SetOption { + return func(o *define.Option) { if nil == o.NotEqual { o.NotEqual = make(map[string]any) } @@ -333,8 +309,8 @@ func WithNotEqual[T op_type.BaseType](field string, value T) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 17:33 2022/5/15 -func WithBatchNotEqual[T op_type.BaseType](data map[string]T) SetOption { - return func(o *Option) { +func WithBatchNotEqual[T op_type.BaseType](data map[string]T) define.SetOption { + return func(o *define.Option) { if nil == o.NotEqual { o.NotEqual = make(map[string]any) } @@ -349,10 +325,10 @@ func WithBatchNotEqual[T op_type.BaseType](data map[string]T) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 20:03 2022/7/23 -func WithOR(orConditionList ...SetOption) SetOption { - return func(o *Option) { +func WithOR(orConditionList ...define.SetOption) define.SetOption { + return func(o *define.Option) { if nil == o.OR { - o.OR = make([][]SetOption, 0) + o.OR = make([][]define.SetOption, 0) } o.OR = append(o.OR, orConditionList) } @@ -363,8 +339,8 @@ func WithOR(orConditionList ...SetOption) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 20:15 2022/10/20 -func WithOrder(orderRuleList ...string) SetOption { - return func(o *Option) { +func WithOrder(orderRuleList ...string) define.SetOption { + return func(o *define.Option) { o.Order = orderRuleList } } @@ -374,8 +350,8 @@ func WithOrder(orderRuleList ...string) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 15:09 2023/4/3 -func WithOrderDesc(field string) SetOption { - return func(o *Option) { +func WithOrderDesc(field string) define.SetOption { + return func(o *define.Option) { if nil == o.Order { o.Order = make([]string, 0) } @@ -388,8 +364,8 @@ func WithOrderDesc(field string) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 15:09 2023/4/3 -func WithOrderAsc(field string) SetOption { - return func(o *Option) { +func WithOrderAsc(field string) define.SetOption { + return func(o *define.Option) { if nil == o.Order { o.Order = make([]string, 0) } @@ -402,8 +378,8 @@ func WithOrderAsc(field string) SetOption { // Author : go_developer@163.com<白茶清欢> // // Date : 17:46 2024/8/9 -func newOption(setOptionList ...SetOption) *Option { - o := &Option{} +func newOption(setOptionList ...define.SetOption) *define.Option { + o := &define.Option{} for _, item := range setOptionList { item(o) } @@ -415,7 +391,7 @@ func newOption(setOptionList ...SetOption) *Option { // Author : go_developer@163.com<白茶清欢> // // Date : 17:46 2024/8/9 -func optionToSql(o *Option) (sqlBuildResult string, bindValue []any) { +func optionToSql(o *define.Option) (sqlBuildResult string, bindValue []any) { bindValue = make([]any, 0) sqlBuildResultBlockList := make([]string, 0) // 设置where条件 @@ -484,6 +460,17 @@ func optionToSql(o *Option) (sqlBuildResult string, bindValue []any) { bindValue = append(bindValue, fieldValue) } + // between + for field, betweenVal := range o.Between { + sqlBuildResultBlockList = append(sqlBuildResultBlockList, "`"+field+"` BETWEEN ? AND ?") + bindValue = append(bindValue, betweenVal[0], betweenVal[1]) + } + // not between + for field, notBetweenVal := range o.NotBetween { + sqlBuildResultBlockList = append(sqlBuildResultBlockList, "`"+field+"` NOT BETWEEN ? AND ?") + bindValue = append(bindValue, notBetweenVal[0], notBetweenVal[1]) + } + if len(bindValue) > 0 { sqlBuildResult = strings.Join(sqlBuildResultBlockList, " AND ") } @@ -508,3 +495,66 @@ func parseInSql(fieldValue any) (string, []any) { } return strings.Join(placeholderList, ","), dataList } + +// WithBetween between 语句 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 15:52 2024/8/23 +func WithBetween(field string, left any, right any) define.SetOption { + return func(o *define.Option) { + if nil == o.Between { + o.Between = map[string][2]any{} + } + o.Between[field] = [2]any{left, right} + } +} + +// WithNotBetween not between 语句 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 15:52 2024/8/23 +func WithNotBetween(field string, left any, right any) define.SetOption { + return func(o *define.Option) { + if nil == o.NotBetween { + o.NotBetween = map[string][2]any{} + } + o.NotBetween[field] = [2]any{left, right} + } +} + +// WithAnyCondition 设置任意查询条件, 仅 where 子句 in / not in / like / not like / == / != +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 14:48 2024/8/23 +func WithAnyCondition(column string, operate string, value any) (define.SetOption, error) { + if nil == value { + return nil, errors.New("value is nil") + } + switch operate { + case consts.WhereOperateEqual: + return WithWhere(map[string]any{column: value}), nil + case consts.WhereOperateNotEqual: + return WithNotEqual(column, value), nil + case consts.WhereOperateIn: + var target []any + if err := serialize.JSON.Transition(value, &target); nil != err { + return nil, err + } + return WithIn[[]any](column, target), nil + case consts.WhereOperateNotIn: + var target []any + if err := serialize.JSON.Transition(value, &target); nil != err { + return nil, err + } + return WithNotIn[[]any](column, target), nil + case consts.WhereOperateLike: + return WithLike(column, fmt.Sprintf("%v", value)), nil + case consts.WhereOperateNotLike: + return WithNotLike(column, fmt.Sprintf("%v", value)), nil + default: + return nil, errors.New(operate + " : operate is not support") + } +} diff --git a/option_test.go b/option_test.go index db8b325..db067a7 100644 --- a/option_test.go +++ b/option_test.go @@ -9,14 +9,12 @@ package database import ( "fmt" + "git.zhangdeman.cn/zhangdeman/database/define" "testing" ) func Test_optionToSql(t *testing.T) { - type args struct { - o *Option - } - o := &Option{ + o := &define.Option{ In: nil, NotIn: nil, Where: map[string]any{ diff --git a/system.go b/system.go index ae9cecd..670cf71 100644 --- a/system.go +++ b/system.go @@ -12,6 +12,7 @@ import ( "gorm.io/driver/mysql" + "git.zhangdeman.cn/zhangdeman/database/define" "gorm.io/gorm" ) @@ -99,10 +100,10 @@ func (sd *SystemDao) GetCreateTableSQL(dbInstance *gorm.DB, table string) (strin // Author : go_developer@163.com<白茶清欢> // // Date : 22:37 2023/8/16 -func (sd *SystemDao) GetTableDesc(dbInstance *gorm.DB, database string, tableName string) ([]*DescTableItem, error) { +func (sd *SystemDao) GetTableDesc(dbInstance *gorm.DB, database string, tableName string) ([]*define.DescTableItem, error) { var ( err error - result []*DescTableItem + result []*define.DescTableItem ) if err = dbInstance.Raw("DESC `" + tableName + "`").Scan(&result).Error; nil != err { @@ -125,9 +126,9 @@ func (sd *SystemDao) GetTableDesc(dbInstance *gorm.DB, database string, tableNam // Author : go_developer@163.com<白茶清欢> // // Date : 23:10 2023/8/16 -func (sd *SystemDao) GetTableInfo(dbInstance *gorm.DB, database string, tableName string) ([]*ColumnInfo, error) { +func (sd *SystemDao) GetTableInfo(dbInstance *gorm.DB, database string, tableName string) ([]*define.ColumnInfo, error) { var ( - list []*ColumnInfo + list []*define.ColumnInfo err error ) diff --git a/wrapper_client.go b/wrapper_client.go new file mode 100644 index 0000000..113ae6e --- /dev/null +++ b/wrapper_client.go @@ -0,0 +1,205 @@ +// Package database ... +// +// Description : mysql客户端 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2021-03-01 9:20 下午 +package database + +import ( + "context" + "errors" + "fmt" + "git.zhangdeman.cn/zhangdeman/consts" + "git.zhangdeman.cn/zhangdeman/database/abstract" + "git.zhangdeman.cn/zhangdeman/database/define" + "git.zhangdeman.cn/zhangdeman/serialize" + "go.uber.org/zap" + "path/filepath" + "strings" + "sync" + + "gorm.io/gorm" +) + +var ( + // WrapperClient 包装后的数据库客户端 + WrapperClient abstract.IWrapperClient +) + +func init() { + WrapperClient = NewWrapperClient() +} + +func NewWrapperClient() *wrapperClient { + return &wrapperClient{ + lock: &sync.RWMutex{}, + clientTable: make(map[string]abstract.IWrapperDatabaseClient), + } +} + +type wrapperClient struct { + lock *sync.RWMutex + clientTable map[string]abstract.IWrapperDatabaseClient + logger *zap.Logger +} + +// AddWithConfigFile 使用文件生成新的客户端,文件名去掉后缀作为flag +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 19:19 2022/6/5 +func (c *wrapperClient) AddWithConfigFile(cfgFilePath string, logInstance *zap.Logger, extraFieldList []string) error { + var ( + err error + cfg *define.CfgFile + ) + + if cfg, err = c.getCfg(cfgFilePath); nil != err { + return err + } + if nil == cfg { + // 不支持的配置文件格式 + return nil + } + return c.AddWithConfig(cfg.Flag, logInstance, cfg.Config, extraFieldList) +} + +// AddWithConfig ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 20:41 2023/4/18 +func (c *wrapperClient) AddWithConfig(flag string, logInstance *zap.Logger, databaseConfig *define.Database, extraFieldList []string) error { + dbClient := &DBClient{ + DbFlag: flag, + LoggerInstance: logInstance, + ExtraFieldList: extraFieldList, + Cfg: define.Driver{}, + } + + if err := dbClient.Init(databaseConfig, nil); nil != err { + return err + } + c.lock.Lock() + c.clientTable[dbClient.DbFlag] = dbClient + c.lock.Unlock() + return nil +} + +// BatchAddWithConfigDir 自动读取目录下配置文件, 生成客户端 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 19:19 2022/6/5 +func (c *wrapperClient) BatchAddWithConfigDir(cfgDir string, logInstance *zap.Logger, extraFieldList []string) error { + filepathNames, _ := filepath.Glob(filepath.Join(cfgDir, "*")) + for i := range filepathNames { + if err := c.AddWithConfigFile(filepathNames[i], logInstance, extraFieldList); nil != err { + return err + } + } + return nil +} + +// getCfg 读取配置 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 18:05 2022/6/11 +func (c *wrapperClient) getCfg(cfgPath string) (*define.CfgFile, error) { + fileArr := strings.Split(cfgPath, ".") + if len(fileArr) < 2 { + // 获取不到类型 + return nil, errors.New("文件格式必须是JSON或者YAML") + } + fileType := strings.ToLower(fileArr[len(fileArr)-1]) + fileFlagArr := strings.Split(fileArr[0], string(filepath.Separator)) + result := &define.CfgFile{ + Path: cfgPath, + Type: "", + Flag: fileFlagArr[len(fileFlagArr)-1], + Config: &define.Database{}, + } + var ( + err error + cfgInfo define.Database + ) + switch fileType { + case consts.FileTypeYaml: + fallthrough + case consts.FileTypeYml: + result.Type = consts.FileTypeYaml + if err = serialize.File.ReadYmlContent(cfgPath, &result.Config); nil != err { + return nil, fmt.Errorf("%s 配置文件解析失败, 原因 : %s", cfgPath, err.Error()) + } + case consts.FileTypeJson: + result.Type = consts.FileTypeJson + if err = serialize.File.ReadJSONContent(cfgPath, &cfgInfo); nil != err { + return nil, fmt.Errorf("%s 配置文件解析失败, 原因 : %s", cfgPath, err.Error()) + } + default: + // 不是JSON , 也不是YML, 跳过 + return nil, nil + } + if len(result.Config.Master.Timezone) == 0 { + // 默认使用本地时区 + result.Config.Master.Timezone = "Local" + } else { + result.Config.Slave.Timezone = result.Config.Master.Timezone + } + return result, nil +} + +// GetDBClient 获取db client +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 19:32 2022/6/5 +func (c *wrapperClient) GetDBClient(dbFlag string) (abstract.IWrapperDatabaseClient, error) { + c.lock.RLock() + defer c.lock.RUnlock() + var ( + exist bool + dbClient abstract.IWrapperDatabaseClient + ) + if dbClient, exist = c.clientTable[dbFlag]; !exist { + return nil, fmt.Errorf("%s 标识的数据库实例不存在! ", dbFlag) + } + return dbClient, nil +} + +// GetMasterClient 获取主库客户端 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 19:36 2022/6/5 +func (c *wrapperClient) GetMasterClient(ctx context.Context, dbFlag string) (*gorm.DB, error) { + var ( + err error + dbClient abstract.IWrapperDatabaseClient + ) + if dbClient, err = c.GetDBClient(dbFlag); nil != err { + return nil, err + } + + return dbClient.GetMaster(ctx), nil +} + +// GetSlaveClient 获取从库客户端 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 19:37 2022/6/5 +func (c *wrapperClient) GetSlaveClient(ctx context.Context, dbFlag string) (*gorm.DB, error) { + var ( + err error + dbClient abstract.IWrapperDatabaseClient + ) + if dbClient, err = c.GetDBClient(dbFlag); nil != err { + return nil, err + } + + return dbClient.GetSlave(ctx), nil +} diff --git a/wrapper_db_client.go b/wrapper_db_client.go new file mode 100644 index 0000000..6ea26d6 --- /dev/null +++ b/wrapper_db_client.go @@ -0,0 +1,267 @@ +// Package database ... +// +// Description : define ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2024-08-20 17:36 +package database + +import ( + "context" + "errors" + "fmt" + "git.zhangdeman.cn/zhangdeman/consts" + "git.zhangdeman.cn/zhangdeman/database/define" + "git.zhangdeman.cn/zhangdeman/logger/wrapper" + "go.uber.org/zap" + "gorm.io/driver/mysql" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + gormLogger "gorm.io/gorm/logger" + "strings" + "sync" + "time" +) + +// DBClient 包装日志实例 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 3:09 PM 2021/12/24 +type DBClient struct { + DbFlag string // 数据库标识 + LoggerInstance *zap.Logger // 日志实例 + master *gorm.DB // 主库 + slave *gorm.DB // 从库 + ExtraFieldList []string // 提取的字段 + Cfg define.Driver // 数据库配置 + cacheTableStructureConfig *define.CacheTableStructureConfig // 缓存配置 + lock *sync.RWMutex // 操作锁 + tableStructureCache map[string][]*define.ColumnConfig // 表结构缓存 +} + +// Init 初始化客户端 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 17:44 2024/8/20 +func (dc *DBClient) Init(databaseConfig *define.Database, cacheTableStructureConfig *define.CacheTableStructureConfig) error { + dc.lock = &sync.RWMutex{} + dc.cacheTableStructureConfig = cacheTableStructureConfig + var err error + if dc.master, err = dc.GetDatabaseClient(databaseConfig.Master, dc.LoggerInstance); nil != err { + return err + } + if dc.slave, err = dc.GetDatabaseClient(databaseConfig.Slave, dc.LoggerInstance); nil != err { + return err + } + if err = dc.syncDbTableStructure(false); nil != err { // 同步缓存表结构 + return err + } + // 启动异步任务 + go func() { + if !dc.CacheDataTableStructureConfig().Enable { + // 未启用 + return + } + for { + select { + case <-time.After(time.Second * time.Duration(dc.CacheDataTableStructureConfig().SyncTimeInterval)): + _ = dc.syncDbTableStructure(true) + } + } + }() + return nil +} + +// GetMaster 获取主库连接 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 3:28 PM 2021/12/24 +func (dc *DBClient) GetMaster(ctx context.Context) *gorm.DB { + session := dc.master.Session(&gorm.Session{}) + session.Logger = dc.getLogger(ctx, session, "master") + return session +} + +// GetSlave 获取从库链接 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 3:29 PM 2021/12/24 +func (dc *DBClient) GetSlave(ctx context.Context) *gorm.DB { + session := dc.slave.Session(&gorm.Session{}) + session.Logger = dc.getLogger(ctx, session, "slave") + return session +} + +// getLogger 获取日志实例 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 3:45 PM 2021/12/24 +func (dc *DBClient) getLogger(ctx context.Context, dbClient *gorm.DB, node string) gormLogger.Interface { + return wrapper.NewGormLoggerWithInstance(ctx, dbClient, dc.LoggerInstance, dc.DbFlag+"|"+node, dc.ExtraFieldList) +} + +// GetDatabaseClient 获取数据库连接 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 18:41 2022/6/11 +func (dc *DBClient) GetDatabaseClient(conf *define.Driver, logInstance *zap.Logger) (*gorm.DB, error) { + var ( + instance *gorm.DB + err error + ) + + if nil == logInstance { + logInstance = dc.LoggerInstance + } + + if conf.DBType == consts.DatabaseDriverMysql { + if instance, err = gorm.Open(mysql.Open(dc.buildConnectionDSN(conf)), &gorm.Config{}); nil != err { + return nil, err + } + } else if conf.DBType == consts.DatabaseDriverSqlite3 { + if instance, err = gorm.Open(sqlite.Open(dc.buildConnectionDSN(conf)), &gorm.Config{}); nil != err { + return nil, err + } + } else { + return nil, fmt.Errorf("%v : db driver is not support", conf.DBType) + } + + instance.Logger = wrapper.NewGormLoggerWithInstance(nil, instance, logInstance, "", nil) + + return instance, nil +} + +// buildConnectionDSN 构建连接信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 18:42 2022/6/11 +func (dc *DBClient) buildConnectionDSN(conf *define.Driver) string { + if conf.DBType == consts.DatabaseDriverSqlite3 { + // 兼容sqlite3 + return conf.Host + } + return fmt.Sprintf( + "%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=%s", + conf.Username, + conf.Password, + conf.Host, + conf.Port, + conf.Database, + conf.Charset, + conf.Timezone, + ) +} + +// CacheDataTableStructureConfig ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 15:03 2024/8/21 +func (dc *DBClient) CacheDataTableStructureConfig() *define.CacheTableStructureConfig { + if nil == dc.cacheTableStructureConfig { + return &define.CacheTableStructureConfig{Enable: false, SyncTimeInterval: 3600} + } + if dc.cacheTableStructureConfig.SyncTimeInterval <= 0 { + dc.cacheTableStructureConfig.SyncTimeInterval = 3600 + } + return dc.cacheTableStructureConfig +} + +// GetTableFieldList 获取表结构配置 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 15:07 2024/8/21 +func (dc *DBClient) GetTableFieldList(tableName string) ([]*define.ColumnConfig, error) { + if !dc.CacheDataTableStructureConfig().Enable { + // 未启用缓存, 返回空list + return make([]*define.ColumnConfig, 0), nil + } + dc.lock.RLock() + defer dc.lock.RUnlock() + if _, exist := dc.tableStructureCache[tableName]; !exist { + return nil, errors.New(tableName + " : cache result not found") + } + return dc.tableStructureCache[tableName], nil +} + +// syncDbTableStructure 缓存表结构 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 15:17 2024/8/21 +func (dc *DBClient) syncDbTableStructure(ignoreErr bool) error { + if !dc.CacheDataTableStructureConfig().Enable { + // 自动同步不可用 + return nil + } + var ( + err error + tableList []string + ) + + c := dc.GetMaster(context.Background()) + systemDao := &SystemDao{} + if tableList, err = systemDao.GetTableList(c); nil != err { + return err + } + tableStructCache := make(map[string][]*define.ColumnConfig) + for _, itemTableName := range tableList { + fieldList, loadTableErr := systemDao.GetTableInfo(c, dc.Cfg.Database, itemTableName) + if nil != loadTableErr { + if ignoreErr { + continue + } + return loadTableErr + } + tableStructCache[itemTableName] = make([]*define.ColumnConfig, 0) + for _, itemColumn := range fieldList { + fieldType := "string" + if strings.Contains(itemColumn.ColumnType, "int") { + if strings.Contains(itemColumn.ColumnType, "unsigned") { + fieldType = "uint" + } else { + fieldType = "int" + } + } + tableStructCache[itemTableName] = append(tableStructCache[itemTableName], &define.ColumnConfig{ + Column: itemColumn.ColumnName, + Alias: itemColumn.ColumnName, + Type: fieldType, + }) + } + } + // 更新缓存结果 + dc.lock.Lock() + defer dc.lock.Unlock() + dc.tableStructureCache = tableStructCache + return nil +} + +// SetTableStructure 设置表结构, 一旦调用人工设置, 则将终止自动同步 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 17:06 2024/8/23 +func (dc *DBClient) SetTableStructure(tableConfigTable map[string][]*define.ColumnConfig) { + if nil != dc.cacheTableStructureConfig { + // 关闭自动同步 + dc.cacheTableStructureConfig.Enable = false + } + dc.lock.Lock() + if nil == dc.tableStructureCache { + dc.tableStructureCache = make(map[string][]*define.ColumnConfig) + } + for table, columnConfig := range tableConfigTable { + dc.tableStructureCache[table] = columnConfig + } + dc.lock.Unlock() +}