统一时间格式和时区

This commit is contained in:
yml2213
2026-07-31 12:57:17 +08:00
parent 667bfbde06
commit 949a0eb8f7
24 changed files with 342 additions and 49 deletions
+141 -1
View File
@@ -1,11 +1,23 @@
package response
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"strings"
"time"
"affiliate_dash/internal/pkg/timeutil"
"github.com/gin-gonic/gin"
)
var (
timeType = reflect.TypeOf(time.Time{})
rawMessageType = reflect.TypeOf(json.RawMessage{})
)
type Body struct {
Code int `json:"code"`
Message string `json:"message"`
@@ -13,7 +25,11 @@ type Body struct {
}
func OK(c *gin.Context, data interface{}) {
c.JSON(http.StatusOK, Body{Code: 0, Message: "ok", Data: data})
WithStatus(c, http.StatusOK, data)
}
func WithStatus(c *gin.Context, httpStatus int, data interface{}) {
c.JSON(httpStatus, Body{Code: 0, Message: "ok", Data: NormalizeTime(data)})
}
func Fail(c *gin.Context, httpStatus int, code int, message string) {
@@ -50,3 +66,127 @@ type PageData struct {
func Page(c *gin.Context, list interface{}, total int64, page, size int) {
OK(c, PageData{List: list, Total: total, Page: page, Size: size})
}
func FormatTime(value time.Time) string {
return timeutil.FormatAPITime(value)
}
func NormalizeTime(value interface{}) interface{} {
return normalizeTimeValue(reflect.ValueOf(value))
}
func normalizeTimeValue(value reflect.Value) interface{} {
if !value.IsValid() {
return nil
}
if value.Kind() == reflect.Interface {
if value.IsNil() {
return nil
}
return normalizeTimeValue(value.Elem())
}
if value.Type() == rawMessageType {
return value.Interface()
}
if value.Type() == timeType {
if !value.CanInterface() {
return nil
}
return FormatTime(value.Interface().(time.Time))
}
switch value.Kind() {
case reflect.Pointer:
if value.IsNil() {
return nil
}
if value.Type().Elem() == timeType {
return FormatTime(value.Elem().Interface().(time.Time))
}
return normalizeTimeValue(value.Elem())
case reflect.Slice, reflect.Array:
if value.Kind() == reflect.Slice && value.IsNil() {
return nil
}
if value.Type().Elem().Kind() == reflect.Uint8 {
return value.Interface()
}
out := make([]interface{}, 0, value.Len())
for i := 0; i < value.Len(); i++ {
out = append(out, normalizeTimeValue(value.Index(i)))
}
return out
case reflect.Map:
if value.IsNil() {
return nil
}
out := make(map[string]interface{}, value.Len())
iter := value.MapRange()
for iter.Next() {
out[mapKeyString(iter.Key())] = normalizeTimeValue(iter.Value())
}
return out
case reflect.Struct:
return normalizeTimeStruct(value)
default:
if value.CanInterface() {
return value.Interface()
}
return nil
}
}
func normalizeTimeStruct(value reflect.Value) map[string]interface{} {
out := make(map[string]interface{}, value.NumField())
valueType := value.Type()
for i := 0; i < value.NumField(); i++ {
field := valueType.Field(i)
if field.PkgPath != "" {
continue
}
name, skip, omitEmpty := jsonFieldName(field)
if skip {
continue
}
fieldValue := value.Field(i)
if omitEmpty && fieldValue.IsZero() {
continue
}
out[name] = normalizeTimeValue(fieldValue)
}
return out
}
func jsonFieldName(field reflect.StructField) (string, bool, bool) {
tag := field.Tag.Get("json")
if tag == "-" {
return "", true, false
}
if tag == "" {
return field.Name, false, false
}
parts := strings.Split(tag, ",")
name := parts[0]
if name == "" {
name = field.Name
}
return name, false, hasTagOption(parts[1:], "omitempty")
}
func hasTagOption(options []string, target string) bool {
for _, option := range options {
if option == target {
return true
}
}
return false
}
func mapKeyString(value reflect.Value) string {
if value.Kind() == reflect.String {
return value.String()
}
if value.CanInterface() {
return fmt.Sprint(value.Interface())
}
return ""
}
@@ -0,0 +1,54 @@
package response
import (
"encoding/json"
"testing"
"time"
)
func TestNormalizeTimeUsesSecondPrecisionCST(t *testing.T) {
value := time.Date(2026, 7, 30, 9, 53, 58, 147355000, time.UTC)
got := NormalizeTime(map[string]interface{}{
"created_at": value,
"nested": []interface{}{
&value,
json.RawMessage(`{"kept":true}`),
},
})
raw, err := json.Marshal(got)
if err != nil {
t.Fatalf("marshal normalized response: %v", err)
}
text := string(raw)
if !json.Valid(raw) {
t.Fatalf("normalized response should stay valid json: %s", text)
}
if text != `{"created_at":"2026-07-30T17:53:58+08:00","nested":["2026-07-30T17:53:58+08:00",{"kept":true}]}` {
t.Fatalf("unexpected normalized response: %s", text)
}
}
func TestNormalizeTimeStructKeepsJSONTags(t *testing.T) {
type sample struct {
CreatedAt time.Time `json:"created_at"`
Optional string `json:"optional,omitempty"`
Hidden string `json:"-"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
}
value := time.Date(2026, 7, 30, 9, 53, 58, 147355000, time.UTC)
got := NormalizeTime(sample{
CreatedAt: value,
Hidden: "secret",
})
raw, err := json.Marshal(got)
if err != nil {
t.Fatalf("marshal normalized struct: %v", err)
}
text := string(raw)
if text != `{"created_at":"2026-07-30T17:53:58+08:00"}` {
t.Fatalf("unexpected normalized struct: %s", text)
}
}