diff --git a/pkg/distlock/service/internal/lease_actor.go b/pkg/distlock/service/internal/lease_actor.go index 8c2a408..9c23430 100644 --- a/pkg/distlock/service/internal/lease_actor.go +++ b/pkg/distlock/service/internal/lease_actor.go @@ -115,7 +115,7 @@ func (a *LeaseActor) Serve() error { err := a.mainActor.Release(reqID) if err != nil { - logger.Std.Warnf("releasing lock request: %w", err) + logger.Std.Warnf("releasing lock request: %s", err.Error()) } } } diff --git a/pkg/distlock/service/service.go b/pkg/distlock/service/service.go index eab0049..44b4459 100644 --- a/pkg/distlock/service/service.go +++ b/pkg/distlock/service/service.go @@ -92,7 +92,7 @@ func (svc *Service) Acquire(req distlock.LockRequest, opts ...AcquireOption) (st // TODO 不影响结果,但考虑打日志 err := svc.leaseActor.Add(reqID, time.Duration(opt.LeaseTimeSec)*time.Second) 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 不影响结果,但考虑打日志 err2 := svc.leaseActor.Remove(reqID) if err2 != nil { - logger.Std.Warnf("removing lease: %w", err2) + logger.Std.Warnf("removing lease: %s", err2.Error()) } return err diff --git a/utils/serder/any_to_any.go b/utils/serder/any_to_any.go new file mode 100644 index 0000000..9258488 --- /dev/null +++ b/utils/serder/any_to_any.go @@ -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 +} diff --git a/utils/serder/serder.go b/utils/serder/serder.go index 017b07b..09d2abb 100644 --- a/utils/serder/serder.go +++ b/utils/serder/serder.go @@ -4,10 +4,6 @@ import ( "encoding/json" "fmt" "reflect" - "time" - - mp "github.com/mitchellh/mapstructure" - myreflect "gitlink.org.cn/cloudream/common/utils/reflect" ) func ObjectToJSON(obj any) ([]byte, error) { @@ -28,84 +24,14 @@ type TypedSerderOption struct { 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 { return AnyToAny(m, obj) } 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) { @@ -128,7 +54,7 @@ func TypedMapToObject(m map[string]any, opt TypedSerderOption) (any, error) { val := reflect.New(typ) valPtr := val.Interface() - err = MapToObject(m, valPtr) + err = AnyToAny(m, valPtr) if err != nil { 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) { - mp, err := ObjectToMap(obj) + var mp map[string]any + err := AnyToAny(obj, &mp) if err != nil { return nil, err } diff --git a/utils/serder/serder_test.go b/utils/serder/serder_test.go index 92aef70..27d92a1 100644 --- a/utils/serder/serder_test.go +++ b/utils/serder/serder_test.go @@ -1,18 +1,19 @@ package serder import ( + "fmt" + "reflect" "testing" - "time" . "github.com/smartystreets/goconvey/convey" myreflect "gitlink.org.cn/cloudream/common/utils/reflect" ) -type SpecialString struct { +type FromAnyString struct { Str string } -func (a *SpecialString) FromAny(val any) (bool, error) { +func (a *FromAnyString) FromAny(val any) (bool, error) { if str, ok := val.(string); ok { a.Str = "@" + str return true, nil @@ -21,6 +22,61 @@ func (a *SpecialString) FromAny(val any) (bool, error) { 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) { Convey("包含用字符串保存的int数据", t, func() { type Struct struct { @@ -37,7 +93,7 @@ func Test_MapToObject(t *testing.T) { var st Struct - err := MapToObject(mp, &st) + err := AnyToAny(mp, &st) So(err, ShouldBeNil) So(st.A, ShouldEqual, "a") @@ -45,35 +101,25 @@ func Test_MapToObject(t *testing.T) { So(st.C, ShouldEqual, 1234) }) - Convey("包含Time,先从结构体转为JSON,再从JSON转为Map,最后变回结构体", t, func() { + Convey("只有FromAny", t, func() { 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(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 { - Special SpecialString `json:"str"` + Special *FromAnyString `json:"str"` } mp := map[string]any{ @@ -86,6 +132,68 @@ func Test_MapToObject(t *testing.T) { 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) {