This commit is contained in:
Joe
2026-01-16 15:49:34 +08:00
commit 550d3e1f42
380 changed files with 62024 additions and 0 deletions
+282
View File
@@ -0,0 +1,282 @@
package helpers
import (
"encoding/json"
"goravel/app/utils"
"reflect"
"strings"
"time"
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/support/carbon"
)
// ConvertTimesInData 递归转换数据中的时间字段到对应时区
// 使用 JSON 序列化和反序列化来确保正确处理所有类型
func ConvertTimesInData(ctx http.Context, data any) any {
if data == nil {
return nil
}
// 获取请求的时区
timezone := GetCurrentTimezone(ctx)
// 检查是否传了时区请求头
hasTimezoneHeader := ctx.Request().Header("X-Timezone", "") != "" ||
ctx.Request().Header("Timezone", "") != "" ||
ctx.Request().Input("timezone") != ""
// 如果没有传时区请求头,且时区是 UTC,直接返回原数据(不做转换)
if !hasTimezoneHeader && (timezone == carbon.UTC || timezone == "UTC") {
return data
}
// 先序列化为 JSON
jsonData, err := json.Marshal(data)
if err != nil {
// 如果序列化失败,尝试使用反射方法
return convertTimesInValue(reflect.ValueOf(data), timezone)
}
// 反序列化为 map[string]any
var result any
if err := json.Unmarshal(jsonData, &result); err != nil {
// 如果反序列化失败,返回原数据
return data
}
// 转换时间字段
converted := convertTimesInMap(result, timezone)
return converted
}
// convertTimesInMap 递归处理 map 或 slice 中的时间字段
func convertTimesInMap(data any, timezone string) any {
if data == nil {
return nil
}
switch v := data.(type) {
case map[string]any:
result := make(map[string]any)
for key, value := range v {
// 检查是否是时间字段
if isTimeField(key) {
// 尝试解析时间字符串并转换
if timeStr, ok := value.(string); ok && timeStr != "" {
// 如果时区是 UTC,直接返回原时间字符串(不做转换)
if timezone == carbon.UTC || timezone == "UTC" {
result[key] = timeStr
continue
}
// 否则进行时区转换
converted := convertTimeString(timeStr, timezone)
if converted != nil && converted != "" {
result[key] = converted
continue
}
// 如果转换失败,保留原值
result[key] = timeStr
continue
}
}
// 递归处理嵌套数据
result[key] = convertTimesInMap(value, timezone)
}
return result
case []any:
result := make([]any, len(v))
for i, item := range v {
result[i] = convertTimesInMap(item, timezone)
}
return result
default:
return data
}
}
// convertTimeString 转换时间字符串到指定时区
// 假设数据库存储的时间是 UTC 时区(如:2025-11-22 06:21:25
func convertTimeString(timeStr string, timezone string) any {
if timeStr == "" || timeStr == "null" {
return nil
}
// 如果目标时区是 UTC,直接返回原时间字符串
if timezone == carbon.UTC || timezone == "UTC" {
return timeStr
}
// 加载时区
utcLoc, _ := time.LoadLocation("UTC")
targetLoc, err := time.LoadLocation(timezone)
if err != nil {
return timeStr
}
// 解析时间字符串为 UTC(数据库存储格式)
t, err := time.ParseInLocation(utils.DateTimeFormat, timeStr, utcLoc)
if err != nil {
// 如果标准格式失败,尝试其他格式
t, err = time.Parse(time.RFC3339, timeStr)
if err != nil {
return timeStr
}
// RFC3339 格式可能带时区,转换为 UTC
t = time.Unix(t.Unix(), 0).In(utcLoc)
}
// 转换到目标时区并格式化
return t.In(targetLoc).Format(utils.DateTimeFormat)
}
// convertTimesInValue 使用反射方法处理值(作为备用方案)
func convertTimesInValue(v reflect.Value, timezone string) any {
if !v.IsValid() {
return nil
}
// 处理指针
if v.Kind() == reflect.Ptr {
if v.IsNil() {
return nil
}
return convertTimesInValue(v.Elem(), timezone)
}
// 处理时间类型
if v.Type() == reflect.TypeOf((*carbon.DateTime)(nil)).Elem() {
dt := v.Interface().(carbon.DateTime)
return dt.SetTimezone(timezone).ToDateTimeString()
}
// 处理 *carbon.DateTime
if v.Type() == reflect.TypeOf((*carbon.DateTime)(nil)) {
if v.IsNil() {
return nil
}
dt := v.Interface().(*carbon.DateTime)
if dt == nil {
return nil
}
return dt.SetTimezone(timezone).ToDateTimeString()
}
// 处理 time.Time
if v.Type() == reflect.TypeOf(time.Time{}) {
t := v.Interface().(time.Time)
dt := carbon.NewDateTime(carbon.Parse(t.Format(utils.DateTimeFormat)))
return dt.SetTimezone(timezone).ToDateTimeString()
}
// 处理 *time.Time
if v.Type() == reflect.TypeOf((*time.Time)(nil)) {
if v.IsNil() {
return nil
}
t := v.Interface().(*time.Time)
if t == nil {
return nil
}
dt := carbon.NewDateTime(carbon.Parse(t.Format(utils.DateTimeFormat)))
return dt.SetTimezone(timezone).ToDateTimeString()
}
// 处理切片
if v.Kind() == reflect.Slice {
if v.IsNil() {
return nil
}
result := make([]any, v.Len())
for i := range result {
result[i] = convertTimesInValue(v.Index(i), timezone)
}
return result
}
// 处理数组
if v.Kind() == reflect.Array {
result := make([]any, v.Len())
for i := range result {
result[i] = convertTimesInValue(v.Index(i), timezone)
}
return result
}
// 处理 map
if v.Kind() == reflect.Map {
if v.IsNil() {
return nil
}
result := make(map[string]any)
for _, key := range v.MapKeys() {
keyStr := key.String()
if key.Kind() == reflect.Interface {
keyStr = reflect.ValueOf(key.Interface()).String()
}
result[keyStr] = convertTimesInValue(v.MapIndex(key), timezone)
}
return result
}
// 处理结构体
if v.Kind() == reflect.Struct {
result := make(map[string]any)
t := v.Type()
for i := range make([]int, v.NumField()) {
field := t.Field(i)
fieldValue := v.Field(i)
// 跳过未导出字段
if !fieldValue.CanInterface() {
continue
}
fieldName := field.Name
// 检查 json tag
if jsonTag := field.Tag.Get("json"); jsonTag != "" && jsonTag != "-" {
// 解析 json tag(处理 "name,omitempty" 格式)
parts := strings.Split(jsonTag, ",")
if len(parts) > 0 && parts[0] != "" {
fieldName = parts[0]
}
}
// 只处理时间相关字段
if isTimeField(fieldName) || isTimeType(fieldValue.Type()) {
result[fieldName] = convertTimesInValue(fieldValue, timezone)
} else {
// 递归处理嵌套结构
if fieldValue.Kind() == reflect.Struct || fieldValue.Kind() == reflect.Ptr || fieldValue.Kind() == reflect.Slice || fieldValue.Kind() == reflect.Map {
result[fieldName] = convertTimesInValue(fieldValue, timezone)
} else {
result[fieldName] = fieldValue.Interface()
}
}
}
return result
}
// 其他类型直接返回
return v.Interface()
}
// isTimeField 检查字段名是否是时间字段
func isTimeField(fieldName string) bool {
return fieldName == "created_at" || fieldName == "updated_at" || fieldName == "deleted_at" ||
fieldName == "CreatedAt" || fieldName == "UpdatedAt" || fieldName == "DeletedAt"
}
// isTimeType 检查类型是否是时间类型
func isTimeType(t reflect.Type) bool {
if t == reflect.TypeOf((*carbon.DateTime)(nil)).Elem() ||
t == reflect.TypeOf((*carbon.DateTime)(nil)) ||
t == reflect.TypeOf(time.Time{}) ||
t == reflect.TypeOf((*time.Time)(nil)) {
return true
}
return false
}