gin-base/db/core/result_mapper.go

174 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package core
import (
"database/sql"
"fmt"
"reflect"
)
// ResultSetMapper 结果集映射器 - 将查询结果映射到 Slice 或 Struct
type ResultSetMapper struct {
fieldMapper IFieldMapper
}
// NewResultSetMapper 创建结果集映射器实例
func NewResultSetMapper() *ResultSetMapper {
return &ResultSetMapper{
fieldMapper: NewFieldMapper(),
}
}
// MapToSlice 将查询结果映射到 Slice
func (rsm *ResultSetMapper) MapToSlice(rows *sql.Rows, result interface{}) error {
// 获取反射对象
resultVal := reflect.ValueOf(result)
// 必须是指针类型
if resultVal.Kind() != reflect.Ptr {
return fmt.Errorf("result 必须是指针类型")
}
elem := resultVal.Elem()
// 必须是 Slice 类型
if elem.Kind() != reflect.Slice {
return fmt.Errorf("result 必须是指向 Slice 的指针")
}
// 获取 Slice 的元素类型
sliceType := elem.Type().Elem()
var isPtr bool
if sliceType.Kind() == reflect.Ptr {
isPtr = true
sliceType = sliceType.Elem()
}
if sliceType.Kind() != reflect.Struct {
return fmt.Errorf("Slice 的元素必须是结构体")
}
// 获取列信息
columns, err := rows.Columns()
if err != nil {
return fmt.Errorf("获取列信息失败:%w", err)
}
// 建立列名到字段的映射
fieldMap := make(map[string]int)
for i := 0; i < sliceType.NumField(); i++ {
field := sliceType.Field(i)
dbTag := field.Tag.Get("db")
if dbTag != "" && dbTag != "-" {
// 使用 db 标签
fieldMap[dbTag] = i
// 同时存储小写版本用于不区分大小写的匹配
fieldMap[dbTag] = i
} else {
// 使用字段名的小写形式
fieldMap[sliceType.Field(i).Name] = i
}
}
// 循环读取每一行数据
for rows.Next() {
// 创建新的结构体实例
var item reflect.Value
if isPtr {
item = reflect.New(sliceType)
} else {
item = reflect.New(sliceType).Elem()
}
// 创建扫描目标
scanTargets := make([]interface{}, len(columns))
for i, col := range columns {
// 查找对应的字段
var fieldIndex int
found := false
// 尝试精确匹配
if idx, ok := fieldMap[col]; ok {
fieldIndex = idx
found = true
} else {
// 尝试不区分大小写匹配
colLower := col
for key, idx := range fieldMap {
if key == colLower {
fieldIndex = idx
found = true
break
}
}
}
if found {
var field reflect.Value
if isPtr {
field = item.Elem().Field(fieldIndex)
} else {
field = item.Field(fieldIndex)
}
if field.CanSet() {
scanTargets[i] = field.Addr().Interface()
} else {
// 字段不可设置,使用占位符
var dummy interface{}
scanTargets[i] = &dummy
}
} else {
// 没有找到对应字段,使用占位符
var dummy interface{}
scanTargets[i] = &dummy
}
}
// 执行扫描
if err := rows.Scan(scanTargets...); err != nil {
return fmt.Errorf("扫描数据失败:%w", err)
}
// 处理时间字段格式化(目前保持原始 time.Time 值,由 JSON 序列化时格式化)
// Go 的 database/sql 会自动将数据库时间扫描到 time.Time 类型
// 在 JSON 序列化时model.Time 的 MarshalJSON 会格式化为指定格式
// 添加到 Slice
if isPtr {
elem.Set(reflect.Append(elem, item))
} else {
elem.Set(reflect.Append(elem, item))
}
}
return nil
}
// MapToStruct 将查询结果映射到单个 Struct
func (rsm *ResultSetMapper) MapToStruct(rows *sql.Rows, result interface{}) error {
// 使用 FieldMapper 的实现
return rsm.fieldMapper.ColumnsToStruct(rows, result)
}
// ScanAll 通用扫描方法,自动识别 Slice 或 Struct
func (rsm *ResultSetMapper) ScanAll(rows *sql.Rows, result interface{}) error {
val := reflect.ValueOf(result)
if val.Kind() != reflect.Ptr {
return fmt.Errorf("result 必须是指针类型")
}
elem := val.Elem()
// 判断是 Slice 还是 Struct
switch elem.Kind() {
case reflect.Slice:
return rsm.MapToSlice(rows, result)
case reflect.Struct:
return rsm.MapToStruct(rows, result)
default:
return fmt.Errorf("不支持的目标类型:%s", elem.Kind())
}
}