diff --git a/crud/curd.go b/crud/curd.go index bc871ee..52acb84 100644 --- a/crud/curd.go +++ b/crud/curd.go @@ -1,840 +1,861 @@ package crud -import ( - "context" - "errors" - "fmt" - "reflect" - "strings" - - "gorm.io/gorm" - "gorm.io/gorm/schema" -) - -// 定义上下文别名,保持原代码风格 -type ctx = context.Context - -// IDao -------------------------- 核心接口定义 -------------------------- -// IDao GORM 版本的Dao接口,提供GORM DB实例和表相关信息 -type IDao[T any] interface { - DB() *gorm.DB // 返回GORM的DB实例 - Table() string // 返回表名 - PrimaryKey() string // 返回主键字段名(如:id) - Ctx(ctx context.Context) *gorm.DB // 绑定上下文的DB实例 - Transaction(ctx context.Context, f func(ctx context.Context, tx *gorm.DB) error) error // 事务方法 - Column() *T -} - -// Paginate -------------------------- 分页结构体定义 -------------------------- -// Paginate 分页参数结构体(补全原代码中缺失的定义,保持功能完整) -type Paginate struct { - Page int // 页码(从1开始) - Limit int // 每页条数 -} - -// -------------------------- 全局常量 -------------------------- -// 分页相关字段,用于清理请求参数 -var pageInfo = []string{ - "page", - "size", - "num", - "limit", - "pagesize", - "pageSize", - "page_size", - "pageNum", - "pagenum", - "page_num", -} - -// Crud -------------------------- 泛型CURD核心结构体 -------------------------- -// Crud GORM 版本的泛型CURD封装,R为对应的模型结构体, C为对应模型结构体的字段结构体 -type Crud[R any, C any] struct { - Dao IDao[C] -} - -// -------------------------- 工具方法:字段名转换(保持原代码的命名风格转换) -------------------------- -// caseConvert 字段名风格转换(下划线 <-> 小驼峰) -func caseConvert(key string, toSnake bool) string { - if toSnake { - // 驼峰转下划线(参考GORM的命名策略) - return schema.NamingStrategy{}.ColumnName("", key) - } - // 下划线转小驼峰 - var result strings.Builder - upperNext := false - for i, c := range key { - if c == '_' && i < len(key)-1 { - upperNext = true - continue - } - if upperNext { - result.WriteRune(rune(strings.ToUpper(string(c))[0])) - upperNext = false - } else { - result.WriteRune(c) - } - } - return result.String() -} - -func (c Crud[R, C]) Columns() *C { - return c.Dao.Column() -} - -// BuildWhere -------------------------- 原BuildWhere对应实现:构建查询条件map -------------------------- -func (c Crud[R, C]) BuildWhere(req any, changeWhere any, subWhere any, removeFields []string, isSnake ...bool) map[string]any { - // 默认使用小写下划线方式 - toSnake := true - if len(isSnake) > 0 && !isSnake[0] { - toSnake = false - } - - // 1. 转换req为map并清理无效数据 - reqMap := convToMap(req) - cleanedReq := make(map[string]any) - - for k, v := range reqMap { - // 清理空值 - if isEmpty(v) { - continue - } - // 清理分页字段 - if strInArray(pageInfo, k) { - continue - } - // 清理指定移除字段 - if len(removeFields) > 0 && strInArray(removeFields, k) { - continue - } - // 转换字段名风格并存入 - cleanedReq[caseConvert(k, toSnake)] = v - } - - // 2. 处理changeWhere(修改查询操作符,如:eq -> gt) - if changeWhere != nil { - changeMap := convToMap(changeWhere) - for k, v := range changeMap { - // 跳过不存在于cleanedReq的字段 - if _, exists := cleanedReq[k]; !exists { - continue - } - // 跳过指定移除的字段 - if len(removeFields) > 0 && strInArray(removeFields, k) { - continue - } - - vMap := convToMap(v) - value, hasValue := vMap["value"] - op, hasOp := vMap["op"] - - if hasValue { - // 存在操作符则重构字段名(GORM支持 "字段名 >" 这种格式作为where key) - if hasOp && op != "" { - newKey := fmt.Sprintf("%s %s", k, op) - delete(cleanedReq, k) - cleanedReq[newKey] = value - } else { - cleanedReq[k] = value - } - } - } - } - - // 3. 字段名风格最终转换(确保一致性) - resultMap := make(map[string]any) - for k, v := range cleanedReq { - // 拆分字段名和操作符 - parts := strings.SplitN(k, " ", 2) - fieldName := parts[0] - opStr := "" - if len(parts) == 2 { - opStr = parts[1] - } - - // 转换字段名风格 - convertedField := caseConvert(fieldName, toSnake) - - // 重构带操作符的key - if opStr != "" { - resultMap[fmt.Sprintf("%s %s", convertedField, opStr)] = v - } else { - resultMap[convertedField] = v - } - } - - // 4. 合并subWhere附加条件 - if subWhere != nil { - subMap := convToMap(subWhere) - for k, v := range subMap { - resultMap[caseConvert(k, toSnake)] = v - } - } - - return resultMap -} - -// BuildMap -------------------------- 原 BuildMap 对应实现:构建变更条件 map -------------------------- -func (c Crud[R, C]) BuildMap(op string, value any, field ...string) map[string]any { - res := map[string]any{ - "op": op, - "field": "", - "value": value, - } - if len(field) > 0 { - res["field"] = field[0] - } - return res -} - -// WhereCondition -------------------------- AND/OR 查询条件结构体 -------------------------- -// WhereCondition 用于构建复杂的 AND/OR 查询条件 -type WhereCondition struct { - AND []interface{} // AND 条件列表 - OR []interface{} // OR 条件列表 -} - -// BuildWhereAndOr -------------------------- 新增:支持 AND 和 OR 的查询条件构建 -------------------------- -// BuildWhereAndOr 构建支持 AND 和 OR 混合使用的查询条件 -// 用法示例: // -// where := crud.BuildWhereAndOr(). -// AND(map[string]any{"status": 1}). -// OR( -// map[string]any{"age": 18}, -// map[string]any{"name": "test"}, -// ). -// AND(map[string]any{"deleted": 0}). -// Build() -func (c Crud[R, C]) BuildWhereAndOr() *WhereBuilder[R, C] { - return &WhereBuilder[R, C]{ - conditions: make([]WhereCondition, 0), - crud: c, - } -} - -// WhereBuilder -------------------------- WHERE 条件构建器 -------------------------- -// WhereBuilder 流式构建 WHERE 条件(R 为模型类型参数) -type WhereBuilder[R any, C any] struct { - conditions []WhereCondition - crud Crud[R, C] -} - -// AND 添加 AND 条件 -func (wb *WhereBuilder[R, C]) AND(conditions ...interface{}) *WhereBuilder[R, C] { - if len(conditions) > 0 { - wb.conditions = append(wb.conditions, WhereCondition{ - AND: conditions, - }) - } - return wb -} - -// OR 添加 OR 条件(OR 条件内部是或关系) -func (wb *WhereBuilder[R, C]) OR(conditions ...interface{}) *WhereBuilder[R, C] { - if len(conditions) > 0 { - wb.conditions = append(wb.conditions, WhereCondition{ - OR: conditions, - }) - } - return wb -} - -// Build 构建最终的查询条件 -// 返回格式:map[string]any 或者可以直接用于 GORM 的 Where 子句 -func (wb *WhereBuilder[R, C]) Build() interface{} { - if len(wb.conditions) == 0 { - return nil - } - - // 如果只有一个条件组,直接返回 - if len(wb.conditions) == 1 { - cond := wb.conditions[0] - if len(cond.AND) == 1 && len(cond.OR) == 0 { - return cond.AND[0] - } - if len(cond.OR) > 0 && len(cond.AND) == 0 { - return wb.buildORCondition(cond.OR) - } - } - - // 构建复杂的 AND/OR 混合条件 - var andConditions []interface{} - - for _, cond := range wb.conditions { - // 处理 AND 条件 - for _, andCond := range cond.AND { - andConditions = append(andConditions, andCond) - } - - // 处理 OR 条件(将 OR 条件作为一个整体添加到 AND 中) - if len(cond.OR) > 0 { - orCondition := wb.buildORCondition(cond.OR) - andConditions = append(andConditions, orCondition) - } - } - - // 如果只有一个条件,直接返回 - if len(andConditions) == 1 { - return andConditions[0] - } - - // 返回 AND 条件数组 - return andConditions -} - -// buildORCondition 构建 OR 条件 -func (wb *WhereBuilder[R, C]) buildORCondition(orConds []interface{}) map[string]interface{} { - if len(orConds) == 0 { - return nil - } - - // 如果只有一个 OR 条件,直接返回 - if len(orConds) == 1 { - return map[string]interface{}{ - "OR": orConds[0], - } - } - - // 多个 OR 条件 - return map[string]interface{}{ - "OR": orConds, - } -} - -// BuildWhereGORM -------------------------- 新增:GORM 原生语法构建 WHERE 条件(支持 AND/OR) -------------------------- -// BuildWhereGORM 使用 GORM 原生语法构建复杂的 AND/OR 查询条件 -// 用法示例 1 - 纯 AND 条件: +//import ( +// "context" +// "errors" +// "fmt" +// "reflect" +// "strings" // -// db.Where("age > ?", 18).Where("status = ?", 1) +// "git.magicany.cc/black1552/gin-base/orm" +//) // -// 用法示例 2 - OR 条件: +//// 定义上下文别名,保持原代码风格 +//type ctx = context.Context // -// db.Where(db.Where("name = ?", "john").Or("name = ?", "jane")) +//// Paginate -------------------------- 分页结构体定义 -------------------------- +//// Paginate 分页参数结构体(补全原代码中缺失的定义,保持功能完整) +//type Paginate struct { +// Page int // 页码(从1开始) +// Limit int // 每页条数 +//} // -// 用法示例 3 - 混合使用: +//// -------------------------- 全局常量 -------------------------- +//// 分页相关字段,用于清理请求参数 +//var pageInfo = []string{ +// "page", +// "size", +// "num", +// "limit", +// "pagesize", +// "pageSize", +// "page_size", +// "pageNum", +// "pagenum", +// "page_num", +//} // -// db.Where("status = ?", 1). -// Where(db.Where("age >= ?", 18).Or("age < ? AND vip = ?", 18, true)). -// Find(&users) -func (c Crud[R, C]) BuildWhereGORM(query interface{}, args ...interface{}) *GORMWhereBuilder[R, C] { - return &GORMWhereBuilder[R, C]{ - DB: c.Dao.DB(), - crud: c, - query: query, - args: args, - } -} - -// GORMWhereBuilder -------------------------- GORM 原生 WHERE 构建器 -------------------------- -// GORMWhereBuilder 使用 GORM 原生 API 构建复杂查询(R 为模型类型参数) -type GORMWhereBuilder[R any, C any] struct { - *gorm.DB - crud Crud[R, C] - query interface{} - args []interface{} -} - -// Where 添加 WHERE 条件(AND 关系) -func (gwb *GORMWhereBuilder[R, C]) Where(query interface{}, args ...interface{}) *GORMWhereBuilder[R, C] { - // 如果当前已经有查询条件,先应用 - if gwb.query != nil { - gwb = gwb.Where(gwb.query, gwb.args...) - gwb.query = nil - gwb.args = nil - } - return gwb.Where(query, args...) -} - -// Or 添加 OR 条件 -func (gwb *GORMWhereBuilder[R, C]) Or(query interface{}, args ...interface{}) *GORMWhereBuilder[R, C] { - // 如果当前有未应用的查询条件,先应用 - if gwb.query != nil { - gwb = gwb.Where(gwb.query, gwb.args...) - gwb.query = nil - gwb.args = nil - } - return gwb.Or(query, args...) -} - -// Not 添加 NOT 条件 -func (gwb *GORMWhereBuilder[R, C]) Not(query interface{}, args ...interface{}) *GORMWhereBuilder[R, C] { - if gwb.query != nil { - gwb = gwb.Where(gwb.query, gwb.args...) - gwb.query = nil - gwb.args = nil - } - gwb = gwb.Not(query, args...) - return gwb -} - -// Find 执行查询并返回结果 -func (gwb *GORMWhereBuilder[R, C]) Find(items interface{}) error { - // 应用剩余的查询条件 - if gwb.query != nil { - gwb = gwb.Where(gwb.query, gwb.args...) - } - return gwb.Model(new(R)).Find(items).Error -} - -// First 查询第一条记录 -func (gwb *GORMWhereBuilder[R, C]) First(result interface{}) error { - if gwb.query != nil { - gwb = gwb.Where(gwb.query, gwb.args...) - } - return gwb.Model(new(R)).First(result).Error -} - -// Count 统计记录数 -func (gwb *GORMWhereBuilder[R, C]) Count(count *int64) error { - if gwb.query != nil { - gwb = gwb.Where(gwb.query, gwb.args...) - } - return gwb.Model(new(R)).Count(count).Error -} - -// ClearField -------------------------- 原ClearField对应实现:清理请求参数并返回有效map -------------------------- -func (c Crud[R, C]) ClearField(req any, delField []string, subField ...map[string]any) map[string]any { - reqMap := convToMap(req) - resultMap := make(map[string]any) - - // 过滤无效数据和指定删除字段 - for k, v := range reqMap { - if isEmpty(v) { - continue - } - if strInArray(pageInfo, k) { - continue - } - if len(delField) > 0 && strInArray(delField, k) { - continue - } - resultMap[k] = v - } - - // 合并附加字段 - if len(subField) > 0 && subField[0] != nil { - for k, v := range subField[0] { - resultMap[k] = v - } - } - - return resultMap -} - -// ClearFieldPage -------------------------- 原ClearFieldPage对应实现:清理参数+分页查询 -------------------------- -func (c Crud[R, C]) ClearFieldPage(ctx ctx, req any, delField []string, where any, page *Paginate, order any, with map[string]func(db *gorm.DB) *gorm.DB) (items []*R, total int64, err error) { - // 1. 清理请求参数 - filterMap := c.ClearField(req, delField) - - // 2. 初始化GORM查询 - db := c.Dao.Ctx(ctx) - - if with != nil { - for k, v := range with { - db = db.Preload(k, v) - } - } - - // 3. 构建查询条件 - db = db.Model(new(R)).Where(filterMap) - if where != nil { - db = db.Where(where) - } - - // 4. 排序 - if order != nil { - db = db.Order(order) - } - - // 5. 统计总数 - if err = db.Count(&total).Error; err != nil { - return nil, 0, err - } - - // 6. 分页查询 - if page != nil && page.Limit > 0 { - offset := (page.Page - 1) * page.Limit - db = db.Offset(offset).Limit(page.Limit) - } - - // 7. 执行查询 - err = db.Find(&items).Error - return -} - -// ClearFieldList -------------------------- 原ClearFieldList对应实现:清理参数+列表查询(不分页) -------------------------- -func (c Crud[R, C]) ClearFieldList(ctx ctx, req any, delField []string, where any, order any, with map[string]func(db *gorm.DB) *gorm.DB) (items []*R, err error) { - filterMap := c.ClearField(req, delField) - db := c.Dao.Ctx(ctx).Model(new(R)) - - if with != nil { - for k, v := range with { - db = db.Preload(k, v) - } - } - if where != nil { - db = db.Where(where) - } - if order != nil { - db = db.Order(order) - } - - err = db.Where(filterMap).Find(&items).Error - return -} - -// ClearFieldOne -------------------------- 原ClearFieldOne对应实现:清理参数+单条查询 -------------------------- -func (c Crud[R, C]) ClearFieldOne(ctx ctx, req any, delField []string, where any, order any, with map[string]func(db *gorm.DB) *gorm.DB) (item *R, err error) { - item = new(R) - filterMap := c.ClearField(req, delField) - db := c.Dao.Ctx(ctx).Model(item) - - if with != nil { - for k, v := range with { - db = db.Preload(k, v) - } - } - if where != nil { - db = db.Where(where) - } - if order != nil { - db = db.Order(order) - } - - err = db.Where(filterMap).First(item).Error - // 处理记录不存在的情况(GORM会返回ErrRecordNotFound) - if errors.Is(err, gorm.ErrRecordNotFound) { - panic("未找到数据") - } - return -} - -// Value -------------------------- 原Value对应实现:查询单个字段值 -------------------------- -func (c Crud[R, C]) Value(ctx ctx, where any, field any) (interface{}, error) { - var result interface{} - db := c.Dao.Ctx(ctx).Model(new(R)).Where(where) - - // 处理字段参数 - if field != nil { - fieldStr, ok := field.(string) - if !ok || fieldStr == "" { - fieldStr = "*" - } - db = db.Select(fieldStr) - } else { - db = db.Select("*") - } - - // 执行查询(取第一条记录的指定字段) - err := db.First(&result).Error - if errors.Is(err, gorm.ErrRecordNotFound) { - panic("未找到数据") - } - return result, err -} - -// DeletePri -------------------------- 原DeletePri对应实现:按主键删除 -------------------------- -func (c Crud[R, C]) DeletePri(ctx ctx, primaryKey any) error { - db := c.Dao.Ctx(ctx).Model(new(R)) - // 按主键字段构建查询 - pk := c.Dao.PrimaryKey() - if pk == "" { - panic("主键字段未配置") - } - return db.Where(fmt.Sprintf("%s = ?", pk), primaryKey).Delete(new(R)).Error -} - -// DeleteWhere -------------------------- 原DeleteWhere对应实现:按条件删除 -------------------------- -func (c Crud[R, C]) DeleteWhere(ctx ctx, where any) error { - return c.Dao.Ctx(ctx).Model(new(R)).Where(where).Delete(new(R)).Error -} - -// Sum -------------------------- 原Sum对应实现:字段求和 -------------------------- -func (c Crud[R, C]) Sum(ctx ctx, where any, field string) float64 { - var sum float64 - if field == "" { - panic("求和字段不能为空") - } - - err := c.Dao.Ctx(ctx).Model(new(R)).Where(where).Select(fmt.Sprintf("SUM(%s) as sum", field)).Scan(&sum).Error - if errors.Is(err, gorm.ErrRecordNotFound) { - panic("未找到数据") - } - return sum -} - -// ArrayField -------------------------- 原ArrayField对应实现:查询指定字段数组 -------------------------- -func (c Crud[R, C]) ArrayField(ctx ctx, where any, field any) []interface{} { - var result []interface{} - db := c.Dao.Ctx(ctx).Model(new(R)).Where(where) - - // 处理字段参数 - if field != nil { - fieldStr, ok := field.(string) - if !ok || fieldStr == "" { - fieldStr = "*" - } - db = db.Select(fieldStr) - } else { - db = db.Select("*") - } - - // 执行查询 - err := db.Find(&result).Error - if errors.Is(err, gorm.ErrRecordNotFound) { - panic("未找到数据") - } - return result -} - -// FindPri -------------------------- 原FindPri对应实现:按主键查询单条记录 -------------------------- -func (c Crud[R, C]) FindPri(ctx ctx, primaryKey any, with map[string]func(db *gorm.DB) *gorm.DB) (model *R) { - model = new(R) - db := c.Dao.Ctx(ctx).Model(model) - pk := c.Dao.PrimaryKey() - - if pk == "" { - panic("主键字段未配置") - } - if with != nil { - for k, v := range with { - db = db.Preload(k, v) - } - } - - // 按主键查询 - err := db.Where(fmt.Sprintf("%s = ?", pk), primaryKey).First(model).Error - if errors.Is(err, gorm.ErrRecordNotFound) { - panic("未找到数据") - } - return -} - -// -------------------------- 原First对应实现:按条件查询第一条记录 -------------------------- -func (c Crud[R, C]) First(ctx ctx, where any, order any, with map[string]func(db *gorm.DB) *gorm.DB) (model *R) { - model = new(R) - db := c.Dao.Ctx(ctx).Model(model) - - if with != nil { - for k, v := range with { - db = db.Preload(k, v) - } - } - if where != nil { - db = db.Where(where) - } - if order != nil { - db = db.Order(order) - } - - err := db.First(model).Error - if errors.Is(err, gorm.ErrRecordNotFound) { - panic("未找到数据") - } - return -} - -// -------------------------- 原Exists对应实现:判断记录是否存在 -------------------------- -func (c Crud[R, C]) Exists(ctx ctx, where any) (exists bool) { - var count int64 - err := c.Dao.Ctx(ctx).Model(new(R)).Where(where).Count(&count).Error - if err != nil { - panic(fmt.Sprintf("Exists查询错误: %v", err)) - } - return count > 0 -} - -// -------------------------- 原All对应实现:查询所有符合条件的记录 -------------------------- -func (c Crud[R, C]) All(ctx ctx, where any, order any, with map[string]func(db *gorm.DB) *gorm.DB) (items []*R) { - db := c.Dao.Ctx(ctx).Model(new(R)) - - if with != nil { - for k, v := range with { - db = db.Preload(k, v) - } - } - if where != nil { - db = db.Where(where) - } - if order != nil { - db = db.Order(order) - } - - err := db.Find(&items).Error - if errors.Is(err, gorm.ErrRecordNotFound) { - panic(fmt.Sprintf("All查询错误: %v", err)) - } - return -} - -// -------------------------- 原Count对应实现:统计记录总数 -------------------------- -func (c Crud[R, C]) Count(ctx ctx, where any) (count int64) { - err := c.Dao.Ctx(ctx).Model(new(R)).Where(where).Count(&count).Error - if errors.Is(err, gorm.ErrRecordNotFound) { - panic(fmt.Sprintf("Count查询错误: %v", err)) - } - return -} - -// -------------------------- 原Save对应实现:新增/更新记录(对应GORM的Save) -------------------------- -func (c Crud[R, C]) Save(ctx ctx, data any) { - err := c.Dao.Ctx(ctx).Model(new(R)).Create(data).Error - if err != nil { - panic(fmt.Sprintf("Save保存错误: %v", err)) - } -} - -// -------------------------- 原Update对应实现:按条件更新记录 -------------------------- -func (c Crud[R, C]) Update(ctx ctx, where any, data any) (count int64) { - result := c.Dao.Ctx(ctx).Model(new(R)).Where(where).Updates(data) - if errors.Is(result.Error, gorm.ErrRecordNotFound) { - panic(fmt.Sprintf("Update更新错误: %v", result.Error.Error())) - } - return result.RowsAffected -} - -// -------------------------- 原UpdatePri对应实现:按主键更新记录 -------------------------- -func (c Crud[R, C]) UpdatePri(ctx ctx, primaryKey any, data any) (count int64) { - db := c.Dao.Ctx(ctx).Model(new(R)) - pk := c.Dao.PrimaryKey() - - if pk == "" { - panic("主键字段未配置") - } - - result := db.Where(fmt.Sprintf("%s = ?", pk), primaryKey).Updates(data) - if errors.Is(result.Error, gorm.ErrRecordNotFound) { - panic(fmt.Sprintf("UpdatePri更新错误: %v", result.Error.Error())) - } - return result.RowsAffected -} - -// -------------------------- 原Paginate对应实现:分页查询 -------------------------- -func (c Crud[R, C]) Paginate(ctx context.Context, where any, p Paginate, with map[string]func(db *gorm.DB) *gorm.DB, order any) (items []*R, total int64) { - db := c.Dao.Ctx(ctx).Model(new(R)) - - // 1. 构建查询条件 - if where != nil { - db = db.Where(where) - } - - // 2. 统计总数 - if err := db.Count(&total).Error; err != nil { - panic(fmt.Sprintf("Paginate查询错误: %v", err)) - } - - // 3. 关联查询 - if with != nil { - for k, v := range with { - db = db.Preload(k, v) - } - } - - // 4. 排序 - if order != nil { - db = db.Order(order) - } - - // 5. 分页(offset = (页码-1)*每页条数) - if p.Limit > 0 { - offset := (p.Page - 1) * p.Limit - db = db.Offset(offset).Limit(p.Limit) - } - - // 6. 执行查询 - err := db.Find(&items).Error - if err != nil || errors.Is(err, gorm.ErrRecordNotFound) { - panic(fmt.Sprintf("Paginate查询错误: %v", err)) - } - return -} - -// -------------------------- 内部辅助工具函数 -------------------------- -// convToMap 将任意类型转换为map[string]any(简化版,适配常见场景) -func convToMap(v any) map[string]any { - if v == nil { - return make(map[string]any) - } - - val := reflect.ValueOf(v) - // 处理指针类型 - if val.Kind() == reflect.Ptr { - val = val.Elem() - } - - // 只处理结构体和map类型 - if val.Kind() != reflect.Struct && val.Kind() != reflect.Map { - return make(map[string]any) - } - - result := make(map[string]any) - - if val.Kind() == reflect.Map { - // 处理map类型 - for _, key := range val.MapKeys() { - keyStr, ok := key.Interface().(string) - if !ok { - continue - } - result[keyStr] = val.MapIndex(key).Interface() - } - } else { - // 处理结构体类型 - typ := val.Type() - for i := 0; i < val.NumField(); i++ { - field := typ.Field(i) - fieldVal := val.Field(i) - - // 获取json标签作为key(优先),否则用字段名 - jsonTag := field.Tag.Get("json") - if jsonTag == "" || jsonTag == "-" { - jsonTag = field.Name - } else { - // 分割json标签(忽略omitempty等选项) - jsonTag = strings.Split(jsonTag, ",")[0] - } - - result[jsonTag] = fieldVal.Interface() - } - } - - return result -} - -// isEmpty 判断值是否为空 -func isEmpty(v any) bool { - if v == nil { - return true - } - - val := reflect.ValueOf(v) - switch val.Kind() { - case reflect.String: - return val.String() == "" - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return val.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return val.Uint() == 0 - case reflect.Float32, reflect.Float64: - return val.Float() == 0 - case reflect.Bool: - return !val.Bool() - case reflect.Slice, reflect.Array, reflect.Map, reflect.Chan: - return val.Len() == 0 - case reflect.Ptr, reflect.Interface: - return val.IsNil() - default: - return false - } -} - -// strInArray 判断字符串是否在数组中 -func strInArray(arr []string, str string) bool { - for _, v := range arr { - if strings.EqualFold(v, str) { // 忽略大小写比较 - return true - } - } - return false -} +//// Crud -------------------------- 泛型CURD核心结构体 -------------------------- +//// Crud GORM 版本的泛型CURD封装,R为对应的模型结构体, C为对应模型结构体的字段结构体 +//type Crud[R any] struct { +// Dao orm.IDao[R] +//} +// +//// -------------------------- 工具方法:字段名转换(保持原代码的命名风格转换) -------------------------- +//// caseConvert 字段名风格转换(下划线 <-> 小驼峰) +//func caseConvert(key string, toSnake bool) string { +// if toSnake { +// // 驼峰转下划线 +// return toSnakeCase(key) +// } +// // 下划线转小驼峰 +// return toCamelCase(key) +//} +// +//// toSnakeCase 驼峰转下划线 +//func toSnakeCase(s string) string { +// if s == "" { +// return "" +// } +// +// var result strings.Builder +// for i, r := range s { +// if r >= 'A' && r <= 'Z' { +// if i > 0 { +// result.WriteRune('_') +// } +// result.WriteRune(r + 'a' - 'A') +// } else { +// result.WriteRune(r) +// } +// } +// return result.String() +//} +// +//// toCamelCase 下划线转小驼峰 +//func toCamelCase(s string) string { +// if s == "" { +// return "" +// } +// +// var result strings.Builder +// upperNext := false +// for i, r := range s { +// if r == '_' && i < len(s)-1 { +// upperNext = true +// continue +// } +// if upperNext { +// if r >= 'a' && r <= 'z' { +// result.WriteRune(r - 'a' + 'A') +// } else { +// result.WriteRune(r) +// } +// upperNext = false +// } else { +// result.WriteRune(r) +// } +// } +// return result.String() +//} +// +//// BuildWhere -------------------------- 原BuildWhere对应实现:构建查询条件map -------------------------- +//func (c Crud[R]) BuildWhere(req any, changeWhere any, subWhere any, removeFields []string, isSnake ...bool) map[string]any { +// // 默认使用小写下划线方式 +// toSnake := true +// if len(isSnake) > 0 && !isSnake[0] { +// toSnake = false +// } +// +// // 1. 转换req为map并清理无效数据 +// reqMap := convToMap(req) +// cleanedReq := make(map[string]any) +// +// for k, v := range reqMap { +// // 清理空值 +// if isEmpty(v) { +// continue +// } +// // 清理分页字段 +// if strInArray(pageInfo, k) { +// continue +// } +// // 清理指定移除字段 +// if len(removeFields) > 0 && strInArray(removeFields, k) { +// continue +// } +// // 转换字段名风格并存入 +// cleanedReq[caseConvert(k, toSnake)] = v +// } +// +// // 2. 处理changeWhere(修改查询操作符,如:eq -> gt) +// if changeWhere != nil { +// changeMap := convToMap(changeWhere) +// for k, v := range changeMap { +// // 跳过不存在于cleanedReq的字段 +// if _, exists := cleanedReq[k]; !exists { +// continue +// } +// // 跳过指定移除的字段 +// if len(removeFields) > 0 && strInArray(removeFields, k) { +// continue +// } +// +// vMap := convToMap(v) +// value, hasValue := vMap["value"] +// op, hasOp := vMap["op"] +// +// if hasValue { +// // 存在操作符则重构字段名(GORM支持 "字段名 >" 这种格式作为where key) +// if hasOp && op != "" { +// newKey := fmt.Sprintf("%s %s", k, op) +// delete(cleanedReq, k) +// cleanedReq[newKey] = value +// } else { +// cleanedReq[k] = value +// } +// } +// } +// } +// +// // 3. 字段名风格最终转换(确保一致性) +// resultMap := make(map[string]any) +// for k, v := range cleanedReq { +// // 拆分字段名和操作符 +// parts := strings.SplitN(k, " ", 2) +// fieldName := parts[0] +// opStr := "" +// if len(parts) == 2 { +// opStr = parts[1] +// } +// +// // 转换字段名风格 +// convertedField := caseConvert(fieldName, toSnake) +// +// // 重构带操作符的key +// if opStr != "" { +// resultMap[fmt.Sprintf("%s %s", convertedField, opStr)] = v +// } else { +// resultMap[convertedField] = v +// } +// } +// +// // 4. 合并subWhere附加条件 +// if subWhere != nil { +// subMap := convToMap(subWhere) +// for k, v := range subMap { +// resultMap[caseConvert(k, toSnake)] = v +// } +// } +// +// return resultMap +//} +// +//// BuildMap -------------------------- 原 BuildMap 对应实现:构建变更条件 map -------------------------- +//func (c Crud[R]) BuildMap(op string, value any, field ...string) map[string]any { +// res := map[string]any{ +// "op": op, +// "field": "", +// "value": value, +// } +// if len(field) > 0 { +// res["field"] = field[0] +// } +// return res +//} +// +//// WhereCondition -------------------------- AND/OR 查询条件结构体 -------------------------- +//// WhereCondition 用于构建复杂的 AND/OR 查询条件 +//type WhereCondition struct { +// AND []interface{} // AND 条件列表 +// OR []interface{} // OR 条件列表 +//} +// +//// BuildWhereAndOr -------------------------- 新增:支持 AND 和 OR 的查询条件构建 -------------------------- +//// BuildWhereAndOr 构建支持 AND 和 OR 混合使用的查询条件 +//// 用法示例: +//// +//// where := crud.BuildWhereAndOr(). +//// AND(map[string]any{"status": 1}). +//// OR( +//// map[string]any{"age": 18}, +//// map[string]any{"name": "test"}, +//// ). +//// AND(map[string]any{"deleted": 0}). +//// Build() +//func (c Crud[R]) BuildWhereAndOr() *WhereBuilder[R] { +// return &WhereBuilder[R]{ +// conditions: make([]WhereCondition, 0), +// crud: Crud[R]{ +// Dao: c.Dao, +// }, +// } +//} +// +//// WhereBuilder -------------------------- WHERE 条件构建器 -------------------------- +//// WhereBuilder 流式构建 WHERE 条件(R 为模型类型参数) +//type WhereBuilder[R any] struct { +// conditions []WhereCondition +// crud Crud[R] +//} +// +//// AND 添加 AND 条件 +//func (wb *WhereBuilder[R]) AND(conditions ...interface{}) *WhereBuilder[R] { +// if len(conditions) > 0 { +// wb.conditions = append(wb.conditions, WhereCondition{ +// AND: conditions, +// }) +// } +// return wb +//} +// +//// OR 添加 OR 条件(OR 条件内部是或关系) +//func (wb *WhereBuilder[R]) OR(conditions ...interface{}) *WhereBuilder[R] { +// if len(conditions) > 0 { +// wb.conditions = append(wb.conditions, WhereCondition{ +// OR: conditions, +// }) +// } +// return wb +//} +// +//// Build 构建最终的查询条件 +//// 返回格式:map[string]any 或者可以直接用于 GORM 的 Where 子句 +//func (wb *WhereBuilder[R]) Build() interface{} { +// if len(wb.conditions) == 0 { +// return nil +// } +// +// // 如果只有一个条件组,直接返回 +// if len(wb.conditions) == 1 { +// cond := wb.conditions[0] +// if len(cond.AND) == 1 && len(cond.OR) == 0 { +// return cond.AND[0] +// } +// if len(cond.OR) > 0 && len(cond.AND) == 0 { +// return wb.buildORCondition(cond.OR) +// } +// } +// +// // 构建复杂的 AND/OR 混合条件 +// var andConditions []interface{} +// +// for _, cond := range wb.conditions { +// // 处理 AND 条件 +// for _, andCond := range cond.AND { +// andConditions = append(andConditions, andCond) +// } +// +// // 处理 OR 条件(将 OR 条件作为一个整体添加到 AND 中) +// if len(cond.OR) > 0 { +// orCondition := wb.buildORCondition(cond.OR) +// andConditions = append(andConditions, orCondition) +// } +// } +// +// // 如果只有一个条件,直接返回 +// if len(andConditions) == 1 { +// return andConditions[0] +// } +// +// // 返回 AND 条件数组 +// return andConditions +//} +// +//// buildORCondition 构建 OR 条件 +//func (wb *WhereBuilder[R]) buildORCondition(orConds []interface{}) map[string]interface{} { +// if len(orConds) == 0 { +// return nil +// } +// +// // 如果只有一个 OR 条件,直接返回 +// if len(orConds) == 1 { +// return map[string]interface{}{ +// "OR": orConds[0], +// } +// } +// +// // 多个 OR 条件 +// return map[string]interface{}{ +// "OR": orConds, +// } +//} +// +//// BuildWhereGORM -------------------------- 新增:GORM 原生语法构建 WHERE 条件(支持 AND/OR) -------------------------- +//// BuildWhereGORM 使用 GORM 原生语法构建复杂的 AND/OR 查询条件 +//// 用法示例 1 - 纯 AND 条件: +//// +//// db.Where("age > ?", 18).Where("status = ?", 1) +//// +//// 用法示例 2 - OR 条件: +//// +//// db.Where(db.Where("name = ?", "john").Or("name = ?", "jane")) +//// +//// 用法示例 3 - 混合使用: +//// +//// db.Where("status = ?", 1). +//// Where(db.Where("age >= ?", 18).Or("age < ? AND vip = ?", 18, true)). +//// Find(&users) +//func (c Crud[R]) BuildWhereGORM(query interface{}, args ...interface{}) *GORMWhereBuilder[R] { +// return &GORMWhereBuilder[R]{ +// Pool: c.Dao.DB(), +// crud: c, +// query: query, +// args: args, +// } +//} +// +//// GORMWhereBuilder -------------------------- GORM 原生 WHERE 构建器 -------------------------- +//// GORMWhereBuilder 使用 GORM 原生 API 构建复杂查询(R 为模型类型参数) +//type GORMWhereBuilder[R any] struct { +// *orm.Pool +// crud Crud[R] +// query interface{} +// args []interface{} +//} +// +//// Where 添加 WHERE 条件(AND 关系) +//func (gwb *GORMWhereBuilder[R]) Where(query interface{}, args ...interface{}) *GORMWhereBuilder[R] { +// // 如果当前已经有查询条件,先应用 +// if gwb.query != nil { +// gwb = gwb.Where(gwb.query, gwb.args...) +// gwb.query = nil +// gwb.args = nil +// } +// return gwb.Where(query, args...) +//} +// +//// Or 添加 OR 条件 +//func (gwb *GORMWhereBuilder[R]) Or(query interface{}, args ...interface{}) *GORMWhereBuilder[R] { +// // 如果当前有未应用的查询条件,先应用 +// if gwb.query != nil { +// gwb = gwb.Where(gwb.query, gwb.args...) +// gwb.query = nil +// gwb.args = nil +// } +// return gwb.Or(query, args...) +//} +// +//// Not 添加 NOT 条件 +//func (gwb *GORMWhereBuilder[R]) Not(query interface{}, args ...interface{}) *GORMWhereBuilder[R] { +// if gwb.query != nil { +// gwb = gwb.Where(gwb.query, gwb.args...) +// gwb.query = nil +// gwb.args = nil +// } +// gwb = gwb.Not(query, args...) +// return gwb +//} +// +//// Find 执行查询并返回结果 +//func (gwb *GORMWhereBuilder[R]) Find(items interface{}) error { +// // 应用剩余的查询条件 +// if gwb.query != nil { +// gwb = gwb.Where(gwb.query, gwb.args...) +// } +// return gwb.Model(new(R)).Find(items).Error +//} +// +//// First 查询第一条记录 +//func (gwb *GORMWhereBuilder[R]) First(result interface{}) error { +// if gwb.query != nil { +// gwb = gwb.Where(gwb.query, gwb.args...) +// } +// +// return gwb.Model(new(R)).First(result) +//} +// +//// Count 统计记录数 +//func (gwb *GORMWhereBuilder[R]) Count(count *int64) error { +// if gwb.query != nil { +// gwb = gwb.Where(gwb.query, gwb.args...) +// } +// result := gwb.Model(new(R)).Count(count) +// return result.Error +//} +// +//// ClearField -------------------------- 原ClearField对应实现:清理请求参数并返回有效map -------------------------- +//func (c Crud[R]) ClearField(req any, delField []string, subField ...map[string]any) map[string]any { +// reqMap := convToMap(req) +// resultMap := make(map[string]any) +// +// // 过滤无效数据和指定删除字段 +// for k, v := range reqMap { +// if isEmpty(v) { +// continue +// } +// if strInArray(pageInfo, k) { +// continue +// } +// if len(delField) > 0 && strInArray(delField, k) { +// continue +// } +// resultMap[k] = v +// } +// +// // 合并附加字段 +// if len(subField) > 0 && subField[0] != nil { +// for k, v := range subField[0] { +// resultMap[k] = v +// } +// } +// +// return resultMap +//} +// +//// ClearFieldPage -------------------------- 原 ClearFieldPage 对应实现:清理参数 + 分页查询 -------------------------- +//func (c Crud[R]) ClearFieldPage(ctx ctx, req any, delField []string, where any, page *Paginate, order string, with map[string]interface{}) (items []*R, total int64, err error) { +// // 1. 清理请求参数 +// filterMap := c.ClearField(req, delField) +// +// // 2. 初始化 ORM 查询 +// db := c.Dao.Ctx(ctx) +// +// // 注意:orm 包不支持 Preload,如需关联查询需手动处理 +// _ = with // 忽略 with 参数 +// +// // 3. 构建查询条件 +// db = db.Where(filterMap) +// if where != nil { +// db = db.Where(where) +// } +// +// // 4. 排序 +// if order != "" { +// db = db.OrderBy(order) +// } +// +// // 5. 统计总数 +// total, err = db.Count(&total) +// if err != nil { +// return nil, 0, err +// } +// +// // 6. 分页查询 +// if page != nil && page.Limit > 0 { +// offset := (page.Page - 1) * page.Limit +// db = db.Offset(offset).Limit(page.Limit) +// } +// +// // 7. 执行查询 +// results, err := db.Find(&items) +// if err != nil { +// return nil, 0, results.Error +// } +// return +//} +// +//// ClearFieldList -------------------------- 原 ClearFieldList 对应实现:清理参数 + 列表查询(不分页) -------------------------- +//func (c Crud[R]) ClearFieldList(ctx ctx, req any, delField []string, where any, order any, with map[string]interface{}) (items []*R, err error) { +// filterMap := c.ClearField(req, delField) +// db := c.Dao.Ctx(ctx) +// +// // 注意:orm 包不支持 Preload +// _ = with +// +// if where != nil { +// db = db.Where(where) +// } +// if order != nil { +// db = db.OrderBy(order) +// } +// +// results, err := db.Where(filterMap).Find(&items) +// if err != nil { +// return nil, results.Error +// } +// return +//} +// +//// ClearFieldOne -------------------------- 原 ClearFieldOne 对应实现:清理参数 + 单条查询 -------------------------- +//func (c Crud[R]) ClearFieldOne(ctx ctx, req any, delField []string, where any, order any, with map[string]interface{}) (item *R, err error) { +// item = new(R) +// filterMap := c.ClearField(req, delField) +// db := c.Dao.Ctx(ctx) +// +// // 注意:orm 包不支持 Preload +// _ = with +// +// if where != nil { +// db = db.Where(where) +// } +// if order != nil { +// db = db.OrderBy(order) +// } +// +// err = db.Where(filterMap).First(item) +// if err != nil { +// panic("未找到数据") +// } +// return +//} +// +//// Value -------------------------- 原Value对应实现:查询单个字段值 -------------------------- +//func (c Crud[R]) Value(ctx ctx, where any, field any) (interface{}, error) { +// var result interface{} +// db := c.Dao.Ctx(ctx).Model(new(R)).Where(where) +// +// // 处理字段参数 +// if field != nil { +// fieldStr, ok := field.(string) +// if !ok || fieldStr == "" { +// fieldStr = "*" +// } +// db = db.Select(fieldStr) +// } else { +// db = db.Select("*") +// } +// +// // 执行查询(取第一条记录的指定字段) +// err := db.First(&result).Error +// if errors.Is(err, gorm.ErrRecordNotFound) { +// panic("未找到数据") +// } +// return result, err +//} +// +//// DeletePri -------------------------- 原DeletePri对应实现:按主键删除 -------------------------- +//func (c Crud[R]) DeletePri(ctx ctx, primaryKey any) error { +// db := c.Dao.Ctx(ctx).Model(new(R)) +// // 按主键字段构建查询 +// pk := c.Dao.PrimaryKey() +// if pk == "" { +// panic("主键字段未配置") +// } +// return db.Where(fmt.Sprintf("%s = ?", pk), primaryKey).Delete(new(R)).Error +//} +// +//// DeleteWhere -------------------------- 原DeleteWhere对应实现:按条件删除 -------------------------- +//func (c Crud[R]) DeleteWhere(ctx ctx, where any) error { +// return c.Dao.Ctx(ctx).Model(new(R)).Where(where).Delete(new(R)).Error +//} +// +//// Sum -------------------------- 原Sum对应实现:字段求和 -------------------------- +//func (c Crud[R]) Sum(ctx ctx, where any, field string) float64 { +// var sum float64 +// if field == "" { +// panic("求和字段不能为空") +// } +// +// err := c.Dao.Ctx(ctx).Model(new(R)).Where(where).Select(fmt.Sprintf("SUM(%s) as sum", field)).Scan(&sum).Error +// if errors.Is(err, gorm.ErrRecordNotFound) { +// panic("未找到数据") +// } +// return sum +//} +// +//// ArrayField -------------------------- 原ArrayField对应实现:查询指定字段数组 -------------------------- +//func (c Crud[R]) ArrayField(ctx ctx, where any, field any) []interface{} { +// var result []interface{} +// db := c.Dao.Ctx(ctx).Model(new(R)).Where(where) +// +// // 处理字段参数 +// if field != nil { +// fieldStr, ok := field.(string) +// if !ok || fieldStr == "" { +// fieldStr = "*" +// } +// db = db.Select(fieldStr) +// } else { +// db = db.Select("*") +// } +// +// // 执行查询 +// err := db.Find(&result).Error +// if errors.Is(err, gorm.ErrRecordNotFound) { +// panic("未找到数据") +// } +// return result +//} +// +//// FindPri -------------------------- 原FindPri对应实现:按主键查询单条记录 -------------------------- +//func (c Crud[R]) FindPri(ctx ctx, primaryKey any, with map[string]func(db *gorm.DB) *gorm.DB) (model *R) { +// model = new(R) +// db := c.Dao.Ctx(ctx).Model(model) +// pk := c.Dao.PrimaryKey() +// +// if pk == "" { +// panic("主键字段未配置") +// } +// if with != nil { +// for k, v := range with { +// db = db.Preload(k, v) +// } +// } +// +// // 按主键查询 +// err := db.Where(fmt.Sprintf("%s = ?", pk), primaryKey).First(model).Error +// if errors.Is(err, gorm.ErrRecordNotFound) { +// panic("未找到数据") +// } +// return +//} +// +//// -------------------------- 原First对应实现:按条件查询第一条记录 -------------------------- +//func (c Crud[R]) First(ctx ctx, where any, order any, with map[string]func(db *gorm.DB) *gorm.DB) (model *R) { +// model = new(R) +// db := c.Dao.Ctx(ctx).Model(model) +// +// if with != nil { +// for k, v := range with { +// db = db.Preload(k, v) +// } +// } +// if where != nil { +// db = db.Where(where) +// } +// if order != nil { +// db = db.Order(order) +// } +// +// err := db.First(model).Error +// if errors.Is(err, gorm.ErrRecordNotFound) { +// panic("未找到数据") +// } +// return +//} +// +//// -------------------------- 原Exists对应实现:判断记录是否存在 -------------------------- +//func (c Crud[R]) Exists(ctx ctx, where any) (exists bool) { +// var count int64 +// err := c.Dao.Ctx(ctx).Model(new(R)).Where(where).Count(&count).Error +// if err != nil { +// panic(fmt.Sprintf("Exists查询错误: %v", err)) +// } +// return count > 0 +//} +// +//// -------------------------- 原All对应实现:查询所有符合条件的记录 -------------------------- +//func (c Crud[R]) All(ctx ctx, where any, order any, with map[string]func(db *gorm.DB) *gorm.DB) (items []*R) { +// db := c.Dao.Ctx(ctx).Model(new(R)) +// +// if with != nil { +// for k, v := range with { +// db = db.Preload(k, v) +// } +// } +// if where != nil { +// db = db.Where(where) +// } +// if order != nil { +// db = db.Order(order) +// } +// +// err := db.Find(&items).Error +// if errors.Is(err, gorm.ErrRecordNotFound) { +// panic(fmt.Sprintf("All查询错误: %v", err)) +// } +// return +//} +// +//// -------------------------- 原Count对应实现:统计记录总数 -------------------------- +//func (c Crud[R]) Count(ctx ctx, where any) (count int64) { +// err := c.Dao.Ctx(ctx).Model(new(R)).Where(where).Count(&count).Error +// if errors.Is(err, gorm.ErrRecordNotFound) { +// panic(fmt.Sprintf("Count查询错误: %v", err)) +// } +// return +//} +// +//// -------------------------- 原Save对应实现:新增/更新记录(对应GORM的Save) -------------------------- +//func (c Crud[R]) Save(ctx ctx, data any) { +// err := c.Dao.Ctx(ctx).Model(new(R)).Create(data).Error +// if err != nil { +// panic(fmt.Sprintf("Save保存错误: %v", err)) +// } +//} +// +//// -------------------------- 原Update对应实现:按条件更新记录 -------------------------- +//func (c Crud[R]) Update(ctx ctx, where any, data any) (count int64) { +// result := c.Dao.Ctx(ctx).Model(new(R)).Where(where).Updates(data) +// if errors.Is(result.Error, gorm.ErrRecordNotFound) { +// panic(fmt.Sprintf("Update更新错误: %v", result.Error.Error())) +// } +// return result.RowsAffected +//} +// +//// -------------------------- 原UpdatePri对应实现:按主键更新记录 -------------------------- +//func (c Crud[R]) UpdatePri(ctx ctx, primaryKey any, data any) (count int64) { +// db := c.Dao.Ctx(ctx).Model(new(R)) +// pk := c.Dao.PrimaryKey() +// +// if pk == "" { +// panic("主键字段未配置") +// } +// +// result := db.Where(fmt.Sprintf("%s = ?", pk), primaryKey).Updates(data) +// if errors.Is(result.Error, gorm.ErrRecordNotFound) { +// panic(fmt.Sprintf("UpdatePri更新错误: %v", result.Error.Error())) +// } +// return result.RowsAffected +//} +// +//// -------------------------- 原Paginate对应实现:分页查询 -------------------------- +//func (c Crud[R]) Paginate(ctx context.Context, where any, p Paginate, with map[string]func(db *gorm.DB) *gorm.DB, order any) (items []*R, total int64) { +// db := c.Dao.Ctx(ctx).Model(new(R)) +// +// // 1. 构建查询条件 +// if where != nil { +// db = db.Where(where) +// } +// +// // 2. 统计总数 +// if err := db.Count(&total).Error; err != nil { +// panic(fmt.Sprintf("Paginate查询错误: %v", err)) +// } +// +// // 3. 关联查询 +// if with != nil { +// for k, v := range with { +// db = db.Preload(k, v) +// } +// } +// +// // 4. 排序 +// if order != nil { +// db = db.Order(order) +// } +// +// // 5. 分页(offset = (页码-1)*每页条数) +// if p.Limit > 0 { +// offset := (p.Page - 1) * p.Limit +// db = db.Offset(offset).Limit(p.Limit) +// } +// +// // 6. 执行查询 +// err := db.Find(&items).Error +// if err != nil || errors.Is(err, gorm.ErrRecordNotFound) { +// panic(fmt.Sprintf("Paginate查询错误: %v", err)) +// } +// return +//} +// +//// -------------------------- 内部辅助工具函数 -------------------------- +//// convToMap 将任意类型转换为map[string]any(简化版,适配常见场景) +//func convToMap(v any) map[string]any { +// if v == nil { +// return make(map[string]any) +// } +// +// val := reflect.ValueOf(v) +// // 处理指针类型 +// if val.Kind() == reflect.Ptr { +// val = val.Elem() +// } +// +// // 只处理结构体和map类型 +// if val.Kind() != reflect.Struct && val.Kind() != reflect.Map { +// return make(map[string]any) +// } +// +// result := make(map[string]any) +// +// if val.Kind() == reflect.Map { +// // 处理map类型 +// for _, key := range val.MapKeys() { +// keyStr, ok := key.Interface().(string) +// if !ok { +// continue +// } +// result[keyStr] = val.MapIndex(key).Interface() +// } +// } else { +// // 处理结构体类型 +// typ := val.Type() +// for i := 0; i < val.NumField(); i++ { +// field := typ.Field(i) +// fieldVal := val.Field(i) +// +// // 获取json标签作为key(优先),否则用字段名 +// jsonTag := field.Tag.Get("json") +// if jsonTag == "" || jsonTag == "-" { +// jsonTag = field.Name +// } else { +// // 分割json标签(忽略omitempty等选项) +// jsonTag = strings.Split(jsonTag, ",")[0] +// } +// +// result[jsonTag] = fieldVal.Interface() +// } +// } +// +// return result +//} +// +//// isEmpty 判断值是否为空 +//func isEmpty(v any) bool { +// if v == nil { +// return true +// } +// +// val := reflect.ValueOf(v) +// switch val.Kind() { +// case reflect.String: +// return val.String() == "" +// case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: +// return val.Int() == 0 +// case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: +// return val.Uint() == 0 +// case reflect.Float32, reflect.Float64: +// return val.Float() == 0 +// case reflect.Bool: +// return !val.Bool() +// case reflect.Slice, reflect.Array, reflect.Map, reflect.Chan: +// return val.Len() == 0 +// case reflect.Ptr, reflect.Interface: +// return val.IsNil() +// default: +// return false +// } +//} +// +//// strInArray 判断字符串是否在数组中 +//func strInArray(arr []string, str string) bool { +// for _, v := range arr { +// if strings.EqualFold(v, str) { // 忽略大小写比较 +// return true +// } +// } +// return false +//} diff --git a/db/core/dao.go b/db/core/dao.go index 117eef5..e882485 100644 --- a/db/core/dao.go +++ b/db/core/dao.go @@ -13,22 +13,26 @@ type DAO struct { } // NewDAO 创建 DAO 基类实例 -func NewDAO(db *Database) *DAO { - return &DAO{db: db} +// 自动使用全局默认 Database 实例 +func NewDAO() *DAO { + return &DAO{ + db: GetDefaultDatabase(), + } } // NewDAOWithModel 创建带模型类型的 DAO 基类实例 // 参数: -// - db: 数据库连接实例 // - model: 模型实例(指针类型),用于获取表结构信息 -func NewDAOWithModel(db *Database, model interface{}) *DAO { +// 自动使用全局默认 Database 实例 +func NewDAOWithModel(model interface{}) *DAO { return &DAO{ - db: db, + db: GetDefaultDatabase(), modelType: model, } } // Create 创建记录(通用方法) +// 自动使用 DAO 中已关联的 Database 实例 func (dao *DAO) Create(ctx context.Context, model interface{}) error { // 使用事务来插入数据 tx, err := dao.db.Begin() @@ -46,11 +50,13 @@ func (dao *DAO) Create(ctx context.Context, model interface{}) error { } // GetByID 根据 ID 查询单条记录(通用方法) +// 自动使用 DAO 中已关联的 Database 实例 func (dao *DAO) GetByID(ctx context.Context, model interface{}, id int64) error { return dao.db.Model(model).Where("id = ?", id).First(model) } // Update 更新记录(通用方法) +// 自动使用 DAO 中已关联的 Database 实例 func (dao *DAO) Update(ctx context.Context, model interface{}, data map[string]interface{}) error { pkValue := getFieldValue(model, "ID") @@ -62,6 +68,7 @@ func (dao *DAO) Update(ctx context.Context, model interface{}, data map[string]i } // Delete 删除记录(通用方法) +// 自动使用 DAO 中已关联的 Database 实例 func (dao *DAO) Delete(ctx context.Context, model interface{}) error { pkValue := getFieldValue(model, "ID") @@ -73,16 +80,19 @@ func (dao *DAO) Delete(ctx context.Context, model interface{}) error { } // FindAll 查询所有记录(通用方法) +// 自动使用 DAO 中已关联的 Database 实例 func (dao *DAO) FindAll(ctx context.Context, model interface{}) error { return dao.db.Model(model).Find(model) } // FindByPage 分页查询(通用方法) +// 自动使用 DAO 中已关联的 Database 实例 func (dao *DAO) FindByPage(ctx context.Context, model interface{}, page, pageSize int) error { return dao.db.Model(model).Limit(pageSize).Offset((page - 1) * pageSize).Find(model) } // Count 统计记录数(通用方法) +// 自动使用 DAO 中已关联的 Database 实例 func (dao *DAO) Count(ctx context.Context, model interface{}, where ...string) (int64, error) { var count int64 @@ -100,6 +110,7 @@ func (dao *DAO) Count(ctx context.Context, model interface{}, where ...string) ( } // Exists 检查记录是否存在(通用方法) +// 自动使用 DAO 中已关联的 Database 实例 func (dao *DAO) Exists(ctx context.Context, model interface{}) (bool, error) { count, err := dao.Count(ctx, model) if err != nil { @@ -109,6 +120,7 @@ func (dao *DAO) Exists(ctx context.Context, model interface{}) (bool, error) { } // First 查询第一条记录(通用方法) +// 自动使用 DAO 中已关联的 Database 实例 func (dao *DAO) First(ctx context.Context, model interface{}) error { return dao.db.Model(model).First(model) } @@ -123,14 +135,14 @@ func (dao *DAO) First(ctx context.Context, model interface{}) error { // *core.DAO // } // -// func NewUserDAO(db *core.Database) *UserDAO { +// func NewUserDAO() *UserDAO { // return &UserDAO{ -// DAO: core.NewDAOWithModel(db, &model.User{}), +// DAO: core.NewDAOWithModel(&model.User{}), // } // } // // // 使用 -// dao := NewUserDAO(db) +// dao := NewUserDAO() // cols := dao.Columns() // 返回 *struct{ID string; Username string; ...} func (dao *DAO) Columns() interface{} { // 检查是否有模型类型信息 diff --git a/db/core/database.go b/db/core/database.go index 9490e43..37dfdb5 100644 --- a/db/core/database.go +++ b/db/core/database.go @@ -8,6 +8,9 @@ import ( "git.magicany.cc/black1552/gin-base/db/driver" ) +// defaultDatabase 全局默认数据库连接实例 +var defaultDatabase *Database + // NewDatabase 创建数据库连接 - 初始化数据库连接和相关组件 func NewDatabase(config *Config) (*Database, error) { db := &Database{ @@ -59,6 +62,9 @@ func NewDatabase(config *Config) (*Database, error) { fmt.Println("[Magic-ORM] 数据库连接成功") } + // 设置为全局默认实例 + defaultDatabase = db + return db, nil } @@ -126,3 +132,13 @@ func loadAndConnect(configPath string, debug bool) (*Database, error) { // 简单方案:返回错误,提示使用 config 包 return nil, fmt.Errorf("请使用 config.AutoConnect() 方法") } + +// GetDefaultDatabase 获取全局默认数据库实例 +func GetDefaultDatabase() *Database { + return defaultDatabase +} + +// SetDefaultDatabase 设置全局默认数据库实例 +func SetDefaultDatabase(db *Database) { + defaultDatabase = db +} diff --git a/db/generator/generator.go b/db/generator/generator.go index 8f0f798..51cfdbe 100644 --- a/db/generator/generator.go +++ b/db/generator/generator.go @@ -139,19 +139,15 @@ import ( "git.magicany.cc/black1552/gin-base/db/model" ) -// {{.ModelName}}DAO {{.TableName}} 数据访问对象 -// 嵌入 core.DAO,自动获得所有 CRUD 方法 -type {{.ModelName}}DAO struct { - *core.DAO +var {{.ModelName}}Dao = &s{{.ModelName}}DAO{ + DAO: core.NewDAOWithModel(&model.{{.ModelName}}), } -// New{{.ModelName}}DAO 创建 {{.ModelName}}DAO 实例 -func New{{.ModelName}}DAO(db *core.Database) *{{.ModelName}}DAO { - return &{{.ModelName}}DAO{ - DAO: core.NewDAOWithModel(db, &model.{{.ModelName}}{}), - } -} -` +// {{.ModelName}}DAO {{.TableName}} 数据访问对象 +// 嵌入 core.DAO,自动获得所有 CRUD 方法 +type s{{.ModelName}}DAO struct { + *core.DAO +}` data := struct { ModelName string diff --git a/go.mod b/go.mod index d27b1e3..d53ded8 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.magicany.cc/black1552/gin-base go 1.25.0 require ( - git.magicany.cc/black1552/gf-common v1.0.1017 + git.magicany.cc/black1552/gf-common v1.0.1018 github.com/eclipse/paho.mqtt.golang v1.5.1 github.com/fsnotify/fsnotify v1.9.0 github.com/gin-gonic/gin v1.12.0 @@ -21,6 +21,7 @@ require ( require ( filippo.io/edwards25519 v1.2.0 // indirect github.com/BurntSushi/toml v1.6.0 // indirect + github.com/VictoriaMetrics/easyproto v0.1.4 // indirect github.com/bytedance/gopkg v0.1.4 // indirect github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic/loader v0.5.1 // indirect @@ -29,12 +30,14 @@ require ( github.com/clipperhouse/displaywidth v0.10.0 // indirect github.com/clipperhouse/uax29/v2 v2.6.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect + github.com/denisenkom/go-mssqldb v0.12.3 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect github.com/fatih/color v1.19.0 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -44,19 +47,30 @@ require ( github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/goccy/go-json v0.10.6 // indirect github.com/goccy/go-yaml v1.19.2 // indirect + github.com/godror/godror v0.50.0 // indirect + github.com/godror/knownpb v0.3.0 // indirect + github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grokify/html-strip-tags-go v0.1.0 // indirect + github.com/ibmdb/go_ibm_db v0.5.4 // indirect + github.com/ibmruntimes/go-recordio/v2 v2.0.0-20240416213906-ae0ad556db70 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.6 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lib/pq v1.12.0 // indirect github.com/magiconair/properties v1.8.10 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.21 // indirect + github.com/mattn/go-sqlite3 v1.14.38 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/montanaflynn/stats v0.7.1 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect github.com/olekukonko/errors v1.2.0 // indirect @@ -73,6 +87,11 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.1 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.2.0 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + go.mongodb.org/mongo-driver v1.17.9 // indirect go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel v1.42.0 // indirect @@ -81,6 +100,7 @@ require ( go.opentelemetry.io/otel/trace v1.42.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/arch v0.25.0 // indirect + golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect golang.org/x/net v0.52.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.42.0 // indirect diff --git a/go.sum b/go.sum index d7a8d69..8920dd2 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,14 @@ filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= git.magicany.cc/black1552/gf-common v1.0.1017 h1:KP0e32CSOzIYg8Nfqj7zwGakO6Co9HYTuiDZupK7LsU= git.magicany.cc/black1552/gf-common v1.0.1017/go.mod h1:ln6bd5oXxPNsktr8xI3itmsqpVBn1j+4W7iaS0g7S0Q= +git.magicany.cc/black1552/gf-common v1.0.1018/go.mod h1:ln6bd5oXxPNsktr8xI3itmsqpVBn1j+4W7iaS0g7S0Q= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/VictoriaMetrics/easyproto v0.1.4 h1:r8cNvo8o6sR4QShBXQd1bKw/VVLSQma/V2KhTBPf+Sc= +github.com/VictoriaMetrics/easyproto v0.1.4/go.mod h1:QlGlzaJnDfFd8Lk6Ci/fuLxfTo3/GThPs2KH23mv710= github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM= github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4= github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= @@ -23,6 +29,9 @@ github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gE github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= +github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE= @@ -45,6 +54,8 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -66,10 +77,20 @@ github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/godror/godror v0.50.0 h1:c0ZnGSDFT12E8HJfQwxtqcmybaIkbqACNk4lIfkkESc= +github.com/godror/godror v0.50.0/go.mod h1:kTMcxZzRw73RT5kn9v3JkBK4kHI6dqowHotqV72ebU8= +github.com/godror/knownpb v0.3.0 h1:+caUdy8hTtl7X05aPl3tdL540TvCcaQA6woZQroLZMw= +github.com/godror/knownpb v0.3.0/go.mod h1:PpTyfJwiOEAzQl7NtVCM8kdPCnp3uhxsZYIzZ5PV4zU= github.com/gogf/gf/v2 v2.10.0 h1:rzDROlyqGMe/eM6dCalSR8dZOuMIdLhmxKSH1DGhbFs= github.com/gogf/gf/v2 v2.10.0/go.mod h1:Svl1N+E8G/QshU2DUbh/3J/AJauqCgUnxHurXWR4Qx0= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -83,12 +104,18 @@ github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtg github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/ibmdb/go_ibm_db v0.5.4 h1:cveEOt1J2PoQivQdxIQB0f8ugDJYKaSmh7RUKAaJyAE= +github.com/ibmdb/go_ibm_db v0.5.4/go.mod h1:BA12Alfe+h5BMGZGE+b0pqP4leILZkpoxe5qr/iMoHw= +github.com/ibmruntimes/go-recordio/v2 v2.0.0-20240416213906-ae0ad556db70 h1:muF5XqVkHnMdbMDXusPdKtuT8qWzefBgSuLH1JVHcC4= +github.com/ibmruntimes/go-recordio/v2 v2.0.0-20240416213906-ae0ad556db70/go.mod h1:NSpUK0x9IyEoM1EjTp2/S8ErxZfRHoA2DfwiYobFSkc= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= +github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -97,6 +124,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo= +github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= @@ -105,11 +134,16 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w= github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mattn/go-sqlite3 v1.14.38 h1:tDUzL85kMvOrvpCt8P64SbGgVFtJB11GPi2AdmITgb4= +github.com/mattn/go-sqlite3 v1.14.38/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= @@ -122,6 +156,7 @@ github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVW github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY= github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM= github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= @@ -147,6 +182,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -159,6 +195,17 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs= +github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU= +go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ= go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= @@ -181,21 +228,54 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE= golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= +golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -203,7 +283,10 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=