| @@ -115,7 +115,7 @@ func (a *LeaseActor) Serve() error { | |||||
| err := a.mainActor.Release(reqID) | err := a.mainActor.Release(reqID) | ||||
| if err != nil { | if err != nil { | ||||
| logger.Std.Warnf("releasing lock request: %w", err) | |||||
| logger.Std.Warnf("releasing lock request: %s", err.Error()) | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -92,7 +92,7 @@ func (svc *Service) Acquire(req distlock.LockRequest, opts ...AcquireOption) (st | |||||
| // TODO 不影响结果,但考虑打日志 | // TODO 不影响结果,但考虑打日志 | ||||
| err := svc.leaseActor.Add(reqID, time.Duration(opt.LeaseTimeSec)*time.Second) | err := svc.leaseActor.Add(reqID, time.Duration(opt.LeaseTimeSec)*time.Second) | ||||
| if err != nil { | if err != nil { | ||||
| logger.Std.Warnf("adding lease: %w", err) | |||||
| logger.Std.Warnf("adding lease: %s", err.Error()) | |||||
| } | } | ||||
| } | } | ||||
| @@ -111,7 +111,7 @@ func (svc *Service) Release(reqID string) error { | |||||
| // TODO 不影响结果,但考虑打日志 | // TODO 不影响结果,但考虑打日志 | ||||
| err2 := svc.leaseActor.Remove(reqID) | err2 := svc.leaseActor.Remove(reqID) | ||||
| if err2 != nil { | if err2 != nil { | ||||
| logger.Std.Warnf("removing lease: %w", err2) | |||||
| logger.Std.Warnf("removing lease: %s", err2.Error()) | |||||
| } | } | ||||
| return err | return err | ||||
| @@ -0,0 +1,138 @@ | |||||
| package serder | |||||
| import ( | |||||
| "reflect" | |||||
| mp "github.com/mitchellh/mapstructure" | |||||
| myreflect "gitlink.org.cn/cloudream/common/utils/reflect" | |||||
| ) | |||||
| type Converter func(srcType reflect.Type, dstType reflect.Type, data interface{}) (interface{}, error) | |||||
| type AnyToAnyOption struct { | |||||
| NoFromAny bool // 不判断目的字段是否实现了FromAny接口 | |||||
| NoToAny bool // 不判断源字段是否实现了ToAny接口 | |||||
| Converters []Converter // 字段类型转换函数 | |||||
| } | |||||
| type FromAny interface { | |||||
| FromAny(val any) (ok bool, err error) | |||||
| } | |||||
| type ToAny interface { | |||||
| ToAny(typ reflect.Type) (val any, ok bool, err error) | |||||
| } | |||||
| // AnyToAny 相同结构的任意类型对象之间的转换 | |||||
| func AnyToAny(src any, dst any, opts ...AnyToAnyOption) error { | |||||
| var opt AnyToAnyOption | |||||
| if len(opts) > 0 { | |||||
| opt = opts[0] | |||||
| } | |||||
| var hooks []mp.DecodeHookFunc | |||||
| if !opt.NoToAny { | |||||
| hooks = append(hooks, toAny) | |||||
| } | |||||
| if !opt.NoFromAny { | |||||
| hooks = append(hooks, fromAny) | |||||
| } | |||||
| for _, c := range opt.Converters { | |||||
| hooks = append(hooks, c) | |||||
| } | |||||
| config := &mp.DecoderConfig{ | |||||
| TagName: "json", | |||||
| Squash: true, | |||||
| WeaklyTypedInput: true, | |||||
| Result: dst, | |||||
| DecodeHook: mp.ComposeDecodeHookFunc(hooks...), | |||||
| } | |||||
| decoder, err := mp.NewDecoder(config) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| return decoder.Decode(src) | |||||
| } | |||||
| // fromAny 如果目的字段实现的FromAny接口,那么通过此接口实现字段类型转换 | |||||
| func fromAny(srcType reflect.Type, targetType reflect.Type, data interface{}) (interface{}, error) { | |||||
| if myreflect.TypeOfValue(data) == targetType { | |||||
| return data, nil | |||||
| } | |||||
| if targetType.Implements(myreflect.TypeOf[FromAny]()) { | |||||
| // 非pointer receiver的FromAny没有意义,因为修改不了receiver的内容,所以这里只支持指针类型 | |||||
| if targetType.Kind() == reflect.Pointer { | |||||
| val := reflect.New(targetType.Elem()) | |||||
| anyIf := val.Interface().(FromAny) | |||||
| ok, err := anyIf.FromAny(data) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if !ok { | |||||
| return data, nil | |||||
| } | |||||
| return val.Interface(), nil | |||||
| } | |||||
| } else if reflect.PointerTo(targetType).Implements(myreflect.TypeOf[FromAny]()) { | |||||
| val := reflect.New(targetType) | |||||
| anyIf := val.Interface().(FromAny) | |||||
| ok, err := anyIf.FromAny(data) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if !ok { | |||||
| return data, nil | |||||
| } | |||||
| return val.Interface(), nil | |||||
| } | |||||
| return data, nil | |||||
| } | |||||
| // 如果源字段实现了ToAny接口,那么通过此接口实现字段类型转换 | |||||
| func toAny(srcType reflect.Type, targetType reflect.Type, data interface{}) (interface{}, error) { | |||||
| dataType := myreflect.TypeOfValue(data) | |||||
| if dataType == targetType { | |||||
| return data, nil | |||||
| } | |||||
| if dataType.Implements(myreflect.TypeOf[ToAny]()) { | |||||
| anyIf := data.(ToAny) | |||||
| dstVal, ok, err := anyIf.ToAny(targetType) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if !ok { | |||||
| return data, nil | |||||
| } | |||||
| return dstVal, nil | |||||
| } else if reflect.PointerTo(dataType).Implements(myreflect.TypeOf[ToAny]()) { | |||||
| dataVal := reflect.ValueOf(data) | |||||
| dataPtrVal := reflect.New(dataType) | |||||
| dataPtrVal.Elem().Set(dataVal) | |||||
| anyIf := dataPtrVal.Interface().(ToAny) | |||||
| dstVal, ok, err := anyIf.ToAny(targetType) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if !ok { | |||||
| return data, nil | |||||
| } | |||||
| return dstVal, nil | |||||
| } | |||||
| return data, nil | |||||
| } | |||||
| @@ -4,10 +4,6 @@ import ( | |||||
| "encoding/json" | "encoding/json" | ||||
| "fmt" | "fmt" | ||||
| "reflect" | "reflect" | ||||
| "time" | |||||
| mp "github.com/mitchellh/mapstructure" | |||||
| myreflect "gitlink.org.cn/cloudream/common/utils/reflect" | |||||
| ) | ) | ||||
| func ObjectToJSON(obj any) ([]byte, error) { | func ObjectToJSON(obj any) ([]byte, error) { | ||||
| @@ -28,84 +24,14 @@ type TypedSerderOption struct { | |||||
| TypeFieldName string | TypeFieldName string | ||||
| } | } | ||||
| type FromAny interface { | |||||
| FromAny(val any) (ok bool, err error) | |||||
| } | |||||
| func parseTimeHook(srcType reflect.Type, targetType reflect.Type, data interface{}) (interface{}, error) { | |||||
| if targetType != reflect.TypeOf(time.Time{}) { | |||||
| return data, nil | |||||
| } | |||||
| switch srcType.Kind() { | |||||
| case reflect.String: | |||||
| return time.Parse(time.RFC3339, data.(string)) | |||||
| case reflect.Float64: | |||||
| return time.Unix(0, int64(data.(float64))*int64(time.Millisecond)), nil | |||||
| case reflect.Int64: | |||||
| return time.Unix(0, data.(int64)*int64(time.Millisecond)), nil | |||||
| default: | |||||
| return data, nil | |||||
| } | |||||
| } | |||||
| // fromAny 如果目的字段实现的FromAny接口,那么通过此接口实现字段类型转换 | |||||
| func fromAny(srcType reflect.Type, targetType reflect.Type, data interface{}) (interface{}, error) { | |||||
| if reflect.PointerTo(targetType).Implements(myreflect.TypeOf[FromAny]()) { | |||||
| val := reflect.New(targetType) | |||||
| anyIf := val.Interface().(FromAny) | |||||
| ok, err := anyIf.FromAny(data) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if !ok { | |||||
| return data, nil | |||||
| } | |||||
| return val.Interface(), nil | |||||
| } | |||||
| return data, nil | |||||
| } | |||||
| // AnyToAny 相同结构的任意类型对象之间的转换 | |||||
| func AnyToAny(src any, dst any) error { | |||||
| config := &mp.DecoderConfig{ | |||||
| TagName: "json", | |||||
| Squash: true, | |||||
| WeaklyTypedInput: true, | |||||
| Result: dst, | |||||
| DecodeHook: mp.ComposeDecodeHookFunc(fromAny, parseTimeHook), | |||||
| } | |||||
| decoder, err := mp.NewDecoder(config) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| return decoder.Decode(src) | |||||
| } | |||||
| func MapToObject(m map[string]any, obj any) error { | func MapToObject(m map[string]any, obj any) error { | ||||
| return AnyToAny(m, obj) | return AnyToAny(m, obj) | ||||
| } | } | ||||
| func ObjectToMap(obj any) (map[string]any, error) { | func ObjectToMap(obj any) (map[string]any, error) { | ||||
| var retMap map[string]any | |||||
| config := &mp.DecoderConfig{ | |||||
| TagName: "json", | |||||
| Squash: true, | |||||
| WeaklyTypedInput: true, | |||||
| Result: &retMap, | |||||
| } | |||||
| decoder, err := mp.NewDecoder(config) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| err = decoder.Decode(obj) | |||||
| return retMap, err | |||||
| var m map[string]any | |||||
| err := AnyToAny(obj, &m) | |||||
| return m, err | |||||
| } | } | ||||
| func TypedMapToObject(m map[string]any, opt TypedSerderOption) (any, error) { | func TypedMapToObject(m map[string]any, opt TypedSerderOption) (any, error) { | ||||
| @@ -128,7 +54,7 @@ func TypedMapToObject(m map[string]any, opt TypedSerderOption) (any, error) { | |||||
| val := reflect.New(typ) | val := reflect.New(typ) | ||||
| valPtr := val.Interface() | valPtr := val.Interface() | ||||
| err = MapToObject(m, valPtr) | |||||
| err = AnyToAny(m, valPtr) | |||||
| if err != nil { | if err != nil { | ||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| @@ -137,7 +63,8 @@ func TypedMapToObject(m map[string]any, opt TypedSerderOption) (any, error) { | |||||
| } | } | ||||
| func ObjectToTypedMap(obj any, opt TypedSerderOption) (map[string]any, error) { | func ObjectToTypedMap(obj any, opt TypedSerderOption) (map[string]any, error) { | ||||
| mp, err := ObjectToMap(obj) | |||||
| var mp map[string]any | |||||
| err := AnyToAny(obj, &mp) | |||||
| if err != nil { | if err != nil { | ||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| @@ -1,18 +1,19 @@ | |||||
| package serder | package serder | ||||
| import ( | import ( | ||||
| "fmt" | |||||
| "reflect" | |||||
| "testing" | "testing" | ||||
| "time" | |||||
| . "github.com/smartystreets/goconvey/convey" | . "github.com/smartystreets/goconvey/convey" | ||||
| myreflect "gitlink.org.cn/cloudream/common/utils/reflect" | myreflect "gitlink.org.cn/cloudream/common/utils/reflect" | ||||
| ) | ) | ||||
| type SpecialString struct { | |||||
| type FromAnyString struct { | |||||
| Str string | Str string | ||||
| } | } | ||||
| func (a *SpecialString) FromAny(val any) (bool, error) { | |||||
| func (a *FromAnyString) FromAny(val any) (bool, error) { | |||||
| if str, ok := val.(string); ok { | if str, ok := val.(string); ok { | ||||
| a.Str = "@" + str | a.Str = "@" + str | ||||
| return true, nil | return true, nil | ||||
| @@ -21,6 +22,61 @@ func (a *SpecialString) FromAny(val any) (bool, error) { | |||||
| return false, nil | return false, nil | ||||
| } | } | ||||
| type ToAnyString struct { | |||||
| Str string | |||||
| } | |||||
| func (a *ToAnyString) ToAny(typ reflect.Type) (val any, ok bool, err error) { | |||||
| if typ == myreflect.TypeOf[map[string]any]() { | |||||
| return map[string]any{ | |||||
| "str": "@" + a.Str, | |||||
| }, true, nil | |||||
| } | |||||
| return nil, false, nil | |||||
| } | |||||
| type FromAnySt struct { | |||||
| Value string | |||||
| } | |||||
| func (a *FromAnySt) FromAny(val any) (bool, error) { | |||||
| if st, ok := val.(ToAnySt); ok { | |||||
| a.Value = "From:" + st.Value | |||||
| return true, nil | |||||
| } | |||||
| return false, nil | |||||
| } | |||||
| type ToAnySt struct { | |||||
| Value string | |||||
| } | |||||
| func (a *ToAnySt) ToAny(typ reflect.Type) (val any, ok bool, err error) { | |||||
| if typ == myreflect.TypeOf[FromAnySt]() { | |||||
| return FromAnySt{ | |||||
| Value: "To:" + a.Value, | |||||
| }, true, nil | |||||
| } | |||||
| return nil, false, nil | |||||
| } | |||||
| type DirToAnySt struct { | |||||
| Value string | |||||
| } | |||||
| func (a DirToAnySt) ToAny(typ reflect.Type) (val any, ok bool, err error) { | |||||
| if typ == myreflect.TypeOf[FromAnySt]() { | |||||
| return FromAnySt{ | |||||
| Value: "DirTo:" + a.Value, | |||||
| }, true, nil | |||||
| } | |||||
| return nil, false, nil | |||||
| } | |||||
| func Test_MapToObject(t *testing.T) { | func Test_MapToObject(t *testing.T) { | ||||
| Convey("包含用字符串保存的int数据", t, func() { | Convey("包含用字符串保存的int数据", t, func() { | ||||
| type Struct struct { | type Struct struct { | ||||
| @@ -37,7 +93,7 @@ func Test_MapToObject(t *testing.T) { | |||||
| var st Struct | var st Struct | ||||
| err := MapToObject(mp, &st) | |||||
| err := AnyToAny(mp, &st) | |||||
| So(err, ShouldBeNil) | So(err, ShouldBeNil) | ||||
| So(st.A, ShouldEqual, "a") | So(st.A, ShouldEqual, "a") | ||||
| @@ -45,35 +101,25 @@ func Test_MapToObject(t *testing.T) { | |||||
| So(st.C, ShouldEqual, 1234) | So(st.C, ShouldEqual, 1234) | ||||
| }) | }) | ||||
| Convey("包含Time,先从结构体转为JSON,再从JSON转为Map,最后变回结构体", t, func() { | |||||
| Convey("只有FromAny", t, func() { | |||||
| type Struct struct { | type Struct struct { | ||||
| Time time.Time | |||||
| NilTime *time.Time | |||||
| Special FromAnyString `json:"str"` | |||||
| } | } | ||||
| var st = Struct{ | |||||
| Time: time.Now(), | |||||
| NilTime: nil, | |||||
| mp := map[string]any{ | |||||
| "str": "test", | |||||
| } | } | ||||
| data, err := ObjectToJSON(st) | |||||
| So(err, ShouldBeNil) | |||||
| var mp map[string]any | |||||
| err = JSONToObject(data, &mp) | |||||
| So(err, ShouldBeNil) | |||||
| var st2 Struct | |||||
| err = MapToObject(mp, &st2) | |||||
| var ret Struct | |||||
| err := AnyToAny(mp, &ret) | |||||
| So(err, ShouldBeNil) | So(err, ShouldBeNil) | ||||
| So(st.Time, ShouldEqual, st2.Time) | |||||
| So(st.NilTime, ShouldEqual, st2.NilTime) | |||||
| So(ret.Special.Str, ShouldEqual, "@test") | |||||
| }) | }) | ||||
| Convey("使用FromAny", t, func() { | |||||
| Convey("字段类型直接实现了FromAny", t, func() { | |||||
| type Struct struct { | type Struct struct { | ||||
| Special SpecialString `json:"str"` | |||||
| Special *FromAnyString `json:"str"` | |||||
| } | } | ||||
| mp := map[string]any{ | mp := map[string]any{ | ||||
| @@ -86,6 +132,68 @@ func Test_MapToObject(t *testing.T) { | |||||
| So(ret.Special.Str, ShouldEqual, "@test") | So(ret.Special.Str, ShouldEqual, "@test") | ||||
| }) | }) | ||||
| Convey("只有ToAny", t, func() { | |||||
| st := struct { | |||||
| Special ToAnyString `json:"str"` | |||||
| }{ | |||||
| Special: ToAnyString{ | |||||
| Str: "test", | |||||
| }, | |||||
| } | |||||
| ret := map[string]any{} | |||||
| err := AnyToAny(st, &ret) | |||||
| So(err, ShouldBeNil) | |||||
| So(ret["str"].(map[string]any)["str"], ShouldEqual, "@test") | |||||
| }) | |||||
| Convey("优先使用ToAny", t, func() { | |||||
| st1 := ToAnySt{ | |||||
| Value: "test", | |||||
| } | |||||
| st2 := FromAnySt{} | |||||
| err := AnyToAny(st1, &st2) | |||||
| So(err, ShouldBeNil) | |||||
| So(st2.Value, ShouldEqual, "To:test") | |||||
| }) | |||||
| Convey("使用Convertor", t, func() { | |||||
| type Struct1 struct { | |||||
| Value string | |||||
| } | |||||
| type Struct2 struct { | |||||
| Value string | |||||
| } | |||||
| st1 := Struct1{ | |||||
| Value: "test", | |||||
| } | |||||
| st2 := Struct2{} | |||||
| err := AnyToAny(st1, &st2, AnyToAnyOption{ | |||||
| Converters: []Converter{func(srcType reflect.Type, dstType reflect.Type, data interface{}) (interface{}, error) { | |||||
| if srcType == myreflect.TypeOf[Struct1]() && dstType == myreflect.TypeOf[Struct2]() { | |||||
| s1 := data.(Struct1) | |||||
| return Struct2{ | |||||
| Value: "@" + s1.Value, | |||||
| }, nil | |||||
| } | |||||
| return nil, fmt.Errorf("should not arrive here!") | |||||
| }}, | |||||
| }) | |||||
| So(err, ShouldBeNil) | |||||
| So(st2.Value, ShouldEqual, "@test") | |||||
| }) | |||||
| } | } | ||||
| func Test_TypedMapToObject(t *testing.T) { | func Test_TypedMapToObject(t *testing.T) { | ||||