174 lines
4.1 KiB
Go
174 lines
4.1 KiB
Go
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())
|
||
}
|
||
}
|