From 20d29122746ec7a9e229c6bfcaf7d001b0d75cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Mon, 13 Oct 2025 11:40:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AD=97=E7=AC=A6=E4=B8=B2=20->=20?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E6=95=B0=E6=8D=AE=E7=B1=BB=E5=9E=8B=E8=BD=AC?= =?UTF-8?q?=E6=8D=A2,=20=E4=BD=BF=E7=94=A8=E6=B3=9B=E5=9E=8B=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- define/result.go | 22 +++ go.mod | 9 +- go.sum | 8 +- map_test.go | 6 +- op_string/base.go | 50 ++++++ op_string/base_test.go | 32 ++++ tool/convert.go | 394 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 515 insertions(+), 6 deletions(-) create mode 100644 define/result.go create mode 100644 op_string/base.go create mode 100644 op_string/base_test.go create mode 100644 tool/convert.go diff --git a/define/result.go b/define/result.go new file mode 100644 index 0000000..0b38608 --- /dev/null +++ b/define/result.go @@ -0,0 +1,22 @@ +// Package define ... +// +// Description : define ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2025-10-13 11:22 +package define + +import "git.zhangdeman.cn/zhangdeman/op_type" + +// BaseValueResult 基础类型转换结果 +type BaseValueResult[BaseType op_type.BaseType] struct { + Value BaseType `json:"result"` // 转换结果 + Err error `json:"err"` // 错误信息 +} + +// BaseValuePtrResult 基础类型指针转换结果 +type BaseValuePtrResult[BaseType op_type.BaseType] struct { + Value *BaseType `json:"result"` // 转换结果 + Err error `json:"err"` // 错误信息 +} diff --git a/go.mod b/go.mod index c57913c..de8c903 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module git.zhangdeman.cn/zhangdeman/wrapper -go 1.23.0 +go 1.24.0 require ( git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250916024308-d378e6c57772 @@ -8,6 +8,7 @@ require ( git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20250504055908-8d68e6106ea9 git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 + github.com/smartystreets/goconvey v1.8.1 github.com/spaolacci/murmur3 v1.1.0 github.com/stretchr/testify v1.11.1 github.com/tidwall/gjson v1.18.0 @@ -17,11 +18,17 @@ require ( github.com/BurntSushi/toml v1.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-ini/ini v1.67.0 // indirect + github.com/gopherjs/gopherjs v1.17.2 // indirect + github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mozillazg/go-pinyin v0.20.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sbabiv/xml2map v1.2.1 // indirect + github.com/smarty/assertions v1.15.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect + golang.org/x/mod v0.9.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/tools v0.7.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b2efcfc..e4de9ae 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,6 @@ github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sS github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= @@ -46,6 +44,12 @@ github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/map_test.go b/map_test.go index 58652ee..68a9f3a 100644 --- a/map_test.go +++ b/map_test.go @@ -22,11 +22,11 @@ func TestMap_Exist(t *testing.T) { func TestMap_IsNil(t *testing.T) { var ( - m Map - m1 *Map + m Map + //m1 *Map ) fmt.Println(m.Set("a", 1)) - fmt.Println(m.IsNil(), m1.IsNil()) + //fmt.Println(m.IsNil(), m1.IsNil()) } func TestMap_IsMasher(t *testing.T) { diff --git a/op_string/base.go b/op_string/base.go new file mode 100644 index 0000000..1eb8d27 --- /dev/null +++ b/op_string/base.go @@ -0,0 +1,50 @@ +// Package op_string ... +// +// Description : op_string ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2025-10-13 11:18 +package op_string + +import ( + "git.zhangdeman.cn/zhangdeman/op_type" + "git.zhangdeman.cn/zhangdeman/wrapper/define" + "git.zhangdeman.cn/zhangdeman/wrapper/tool" +) + +// ToBaseValue 转换为基础数据类型 +func ToBaseValue[BaseType op_type.BaseType](str string) define.BaseValueResult[BaseType] { + var ( + err error + target BaseType + ) + if err = tool.ConvertAssign(&target, str); nil != err { + return define.BaseValueResult[BaseType]{ + Value: target, + Err: err, + } + } + return define.BaseValueResult[BaseType]{ + Value: target, + Err: err, + } +} + +// ToBaseValuePtr 转换为基础数据类型指针 +func ToBaseValuePtr[BaseType op_type.BaseType](str string) define.BaseValuePtrResult[BaseType] { + var ( + err error + target BaseType + ) + if err = tool.ConvertAssign(&target, str); nil != err { + return define.BaseValuePtrResult[BaseType]{ + Value: nil, + Err: err, + } + } + return define.BaseValuePtrResult[BaseType]{ + Value: &target, + Err: err, + } +} diff --git a/op_string/base_test.go b/op_string/base_test.go new file mode 100644 index 0000000..6b8a55f --- /dev/null +++ b/op_string/base_test.go @@ -0,0 +1,32 @@ +// Package op_string ... +// +// Description : op_string ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2025-10-13 11:28 +package op_string + +import ( + "reflect" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestToBaseValue(t *testing.T) { + Convey("测试ToBaseValue - uint64 转换成功", t, func() { + res := ToBaseValue[uint64]("12345") + So(res.Err, ShouldBeNil) + So(res.Value, ShouldEqual, uint64(12345)) + So(reflect.TypeOf(res.Value).Kind(), ShouldEqual, reflect.Uint64) + So(reflect.TypeOf(res.Value).Kind(), ShouldNotEqual, reflect.Uint32) + }) + Convey("测试ToBaseValue - uint64 转换失败", t, func() { + res := ToBaseValue[uint64]("s12345") + So(res.Err, ShouldNotBeNil) + So(res.Value, ShouldEqual, 0) + So(reflect.TypeOf(res.Value).Kind(), ShouldEqual, reflect.Uint64) + So(reflect.TypeOf(res.Value).Kind(), ShouldNotEqual, reflect.Uint32) + }) +} diff --git a/tool/convert.go b/tool/convert.go new file mode 100644 index 0000000..739e94e --- /dev/null +++ b/tool/convert.go @@ -0,0 +1,394 @@ +// Package tool ... +// +// Description : 任意类型之间的相互转换 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2021-02-23 10:23 下午 +package tool + +/* + Desc : 在处理一些参数的时候,可能需要将参数转换为各种类型,这里实现一个通用的转换函数,实现各种类型之间的相互转换。 + + 当然,如果源数据格式和目标数据类型不一致,是会返回错误的。例如将字符串“一二三”转换为数值类型则会报错,而将字符串“123”转换为数值类型则OK。 + + 这段代码实际抄自go自带的“database/sql”库,只是源代码作为内部函数无法在外面调用,可以单独把需要的功能拎出来使用: + + 代码中有一个Scaner接口,可以自行实现,然后通过"convertAssign()"函数,作为dst参数传入。 + + Author : zhangdeman001@ke.com<白茶清欢> + +*/ +import ( + "errors" + "fmt" + "reflect" + "strconv" + "time" +) + +// RawBytes is a byte slice that holds a reference to memory owned by +// the database itself. After a Scan into a RawBytes, the slice is only +// valid until the next call to Next, Scan, or Close. +type RawBytes []byte + +var errNilPtr = errors.New("destination pointer is nil") // embedded in descriptive error + +// ConvertAssign ... +// convertAssign copies to dest the value in src, converting it if possible. +// An error is returned if the copy would result in loss of information. +// dest should be a pointer type. +func ConvertAssign(dest, src any) error { + // Common cases, without reflect. + switch s := src.(type) { + case string: + switch d := dest.(type) { + case *string: + if d == nil { + return errNilPtr + } + *d = s + return nil + case *[]byte: + if d == nil { + return errNilPtr + } + *d = []byte(s) + return nil + case *RawBytes: + if d == nil { + return errNilPtr + } + *d = append((*d)[:0], s...) + return nil + } + case []byte: + switch d := dest.(type) { + case *string: + if d == nil { + return errNilPtr + } + *d = string(s) + return nil + case *any: + if d == nil { + return errNilPtr + } + *d = cloneBytes(s) + return nil + case *[]byte: + if d == nil { + return errNilPtr + } + *d = cloneBytes(s) + return nil + case *RawBytes: + if d == nil { + return errNilPtr + } + *d = s + return nil + } + case time.Time: + switch d := dest.(type) { + case *time.Time: + *d = s + return nil + case *string: + *d = s.Format(time.RFC3339Nano) + return nil + case *[]byte: + if d == nil { + return errNilPtr + } + *d = []byte(s.Format(time.RFC3339Nano)) + return nil + case *RawBytes: + if d == nil { + return errNilPtr + } + *d = s.AppendFormat((*d)[:0], time.RFC3339Nano) + return nil + } + case nil: + switch d := dest.(type) { + case *any: + if d == nil { + return errNilPtr + } + *d = nil + return nil + case *[]byte: + if d == nil { + return errNilPtr + } + *d = nil + return nil + case *RawBytes: + if d == nil { + return errNilPtr + } + *d = nil + return nil + } + } + + var sv reflect.Value + + switch d := dest.(type) { + case *string: + sv = reflect.ValueOf(src) + switch sv.Kind() { + case reflect.Bool, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Float32, reflect.Float64: + *d = asString(src) + return nil + } + case *[]byte: + sv = reflect.ValueOf(src) + if b, ok := asBytes(nil, sv); ok { + *d = b + return nil + } + case *RawBytes: + sv = reflect.ValueOf(src) + if b, ok := asBytes([]byte(*d)[:0], sv); ok { + *d = RawBytes(b) + return nil + } + case *bool: + bv, err := Bool.ConvertValue(src) + if err == nil { + *d = bv.(bool) + } + return err + case *any: + *d = src + return nil + } + + if scanner, ok := dest.(Scanner); ok { + return scanner.Scan(src) + } + + dpv := reflect.ValueOf(dest) + if dpv.Kind() != reflect.Ptr { + return errors.New("destination not a pointer") + } + if dpv.IsNil() { + return errNilPtr + } + + if !sv.IsValid() { + sv = reflect.ValueOf(src) + } + + dv := reflect.Indirect(dpv) + if sv.IsValid() && sv.Type().AssignableTo(dv.Type()) { + switch b := src.(type) { + case []byte: + dv.Set(reflect.ValueOf(cloneBytes(b))) + default: + dv.Set(sv) + } + return nil + } + + if dv.Kind() == sv.Kind() && sv.Type().ConvertibleTo(dv.Type()) { + dv.Set(sv.Convert(dv.Type())) + return nil + } + + // The following conversions use a string value as an intermediate representation + // to convert between various numeric types. + // + // This also allows scanning into user defined types such as "type Int int64". + // For symmetry, also check for string destination types. + switch dv.Kind() { + case reflect.Ptr: + if src == nil { + dv.Set(reflect.Zero(dv.Type())) + return nil + } + dv.Set(reflect.New(dv.Type().Elem())) + return ConvertAssign(dv.Interface(), src) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + s := asString(src) + i64, err := strconv.ParseInt(s, 10, dv.Type().Bits()) + if err != nil { + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) + } + dv.SetInt(i64) + return nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + s := asString(src) + u64, err := strconv.ParseUint(s, 10, dv.Type().Bits()) + if err != nil { + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) + } + dv.SetUint(u64) + return nil + case reflect.Float32, reflect.Float64: + s := asString(src) + f64, err := strconv.ParseFloat(s, dv.Type().Bits()) + if err != nil { + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) + } + dv.SetFloat(f64) + return nil + case reflect.String: + switch v := src.(type) { + case string: + dv.SetString(v) + return nil + case []byte: + dv.SetString(string(v)) + return nil + } + } + + return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, dest) +} + +func strconvErr(err error) error { + if ne, ok := err.(*strconv.NumError); ok { + return ne.Err + } + return err +} + +func cloneBytes(b []byte) []byte { + if b == nil { + return nil + } + c := make([]byte, len(b)) + copy(c, b) + return c +} + +func ToString(src any) string { + return asString(src) +} + +func asString(src any) string { + switch v := src.(type) { + case string: + return v + case []byte: + return string(v) + } + rv := reflect.ValueOf(src) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.FormatInt(rv.Int(), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return strconv.FormatUint(rv.Uint(), 10) + case reflect.Float64: + return strconv.FormatFloat(rv.Float(), 'g', -1, 64) + case reflect.Float32: + return strconv.FormatFloat(rv.Float(), 'g', -1, 32) + case reflect.Bool: + return strconv.FormatBool(rv.Bool()) + } + return fmt.Sprintf("%v", src) +} + +func asBytes(buf []byte, rv reflect.Value) (b []byte, ok bool) { + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.AppendInt(buf, rv.Int(), 10), true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return strconv.AppendUint(buf, rv.Uint(), 10), true + case reflect.Float32: + return strconv.AppendFloat(buf, rv.Float(), 'g', -1, 32), true + case reflect.Float64: + return strconv.AppendFloat(buf, rv.Float(), 'g', -1, 64), true + case reflect.Bool: + return strconv.AppendBool(buf, rv.Bool()), true + case reflect.String: + s := rv.String() + return append(buf, s...), true + } + return +} + +// Value is a value that drivers must be able to handle. +// It is either nil, a type handled by a database driver's NamedValueChecker +// interface, or an instance of one of these types: +// +// int64 +// float64 +// bool +// []byte +// string +// time.Time +type Value any + +type boolType struct{} + +var Bool boolType + +func (boolType) String() string { return "Bool" } +func (boolType) ConvertValue(src any) (Value, error) { + switch s := src.(type) { + case bool: + return s, nil + case string: + b, err := strconv.ParseBool(s) + if err != nil { + return nil, fmt.Errorf("sql/driver: couldn't convert %q into type bool", s) + } + return b, nil + case []byte: + b, err := strconv.ParseBool(string(s)) + if err != nil { + return nil, fmt.Errorf("sql/driver: couldn't convert %q into type bool", s) + } + return b, nil + } + + sv := reflect.ValueOf(src) + switch sv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + iv := sv.Int() + if iv == 1 || iv == 0 { + return iv == 1, nil + } + return nil, fmt.Errorf("sql/driver: couldn't convert %d into type bool", iv) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + uv := sv.Uint() + if uv == 1 || uv == 0 { + return uv == 1, nil + } + return nil, fmt.Errorf("sql/driver: couldn't convert %d into type bool", uv) + } + + return nil, fmt.Errorf("sql/driver: couldn't convert %v (%T) into type bool", src, src) +} + +type Scanner interface { + // Scan assigns a value from a database driver. + // + // The src value will be of one of the following types: + // + // int64 + // float64 + // bool + // []byte + // string + // time.Time + // nil - for NULL values + // + // An error should be returned if the value cannot be stored + // without loss of information. + // + // Reference types such as []byte are only valid until the next call to Scan + // and should not be retained. Their underlying memory is owned by the driver. + // If retention is necessary, copy their values before the next call to Scan. + Scan(src any) error +}