diff --git a/go.mod b/go.mod index 4e0d2a0..fc87255 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,13 @@ module gitlink.org.cn/cloudream/common -go 1.20 +go 1.22 + +toolchain go1.23.2 require ( github.com/antonfisher/nested-logrus-formatter v1.3.1 + github.com/aws/aws-sdk-go-v2 v1.36.3 + github.com/aws/aws-sdk-go-v2/credentials v1.17.62 github.com/google/uuid v1.3.0 github.com/hashicorp/go-multierror v1.1.1 github.com/imdario/mergo v0.3.15 @@ -21,14 +25,16 @@ require ( golang.org/x/exp v0.0.0-20230519143937-03e91628a987 ) +require github.com/aws/smithy-go v1.22.2 // indirect + require ( github.com/benbjohnson/clock v1.3.0 // indirect github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/go-querystring v1.1.0 github.com/gopherjs/gopherjs v1.17.2 // indirect - github.com/gorilla/websocket v1.5.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect diff --git a/go.sum b/go.sum index c51be4b..f099e44 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,11 @@ github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ= github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA= +github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= +github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.62 h1:fvtQY3zFzYJ9CfixuAQ96IxDrBajbBWGqjNTCa79ocU= +github.com/aws/aws-sdk-go-v2/credentials v1.17.62/go.mod h1:ElETBxIQqcxej++Cs8GyPBbgMys5DgQPTwo7cUPDKt8= +github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= +github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= @@ -15,15 +21,17 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -51,6 +59,7 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/otiai10/copy v1.12.0 h1:cLMgSQnXBs1eehF0Wy/FAGsgDTDmAqFR7rQylBb1nDY= github.com/otiai10/copy v1.12.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= +github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -75,6 +84,7 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= +github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -150,6 +160,7 @@ google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sdks/storage/cdsapi/presigned.go b/sdks/storage/cdsapi/presigned.go new file mode 100644 index 0000000..ec7f357 --- /dev/null +++ b/sdks/storage/cdsapi/presigned.go @@ -0,0 +1,143 @@ +package cdsapi + +import ( + "context" + "fmt" + "net/http" + "net/url" + "time" + + v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/google/go-querystring/query" + cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" +) + +const ( + AuthService = "jcs" + AuthRegion = "any" +) + +type PresignedService struct { + *Client + accessKey string + secretKey string +} + +func (c *Client) Presigned(accessKey string, secretKey string) *PresignedService { + return &PresignedService{ + Client: c, + accessKey: accessKey, + secretKey: secretKey, + } +} + +const PresignedObjectDownloadByPathPath = "/v1/presigned/object/downloadByPath" + +type PresignedObjectDownloadByPath struct { + UserID cdssdk.UserID `form:"userID" url:"userID" binding:"required"` + PackageID cdssdk.PackageID `form:"packageID" url:"packageID" binding:"required"` + Path string `form:"path" url:"path" binding:"required"` + Offset int64 `form:"offset" url:"offset,omitempty"` + Length *int64 `form:"length" url:"length,omitempty"` +} + +func (c *PresignedService) ObjectDownload(req PresignedObjectDownloadByPath, expireIn int) (string, error) { + return c.presign(req, PresignedObjectDownloadByPathPath, http.MethodGet, expireIn) +} + +const PresignedObjectUploadPath = "/v1/presigned/object/upload" + +type PresignedObjectUpload struct { + UserID cdssdk.UserID `form:"userID" binding:"required" url:"userID"` + PackageID cdssdk.PackageID `form:"packageID" binding:"required" url:"packageID"` + Path string `form:"path" binding:"required" url:"path"` + Affinity cdssdk.StorageID `form:"affinity" url:"affinity,omitempty"` + LoadTo []cdssdk.StorageID `form:"loadTo" url:"loadTo,omitempty"` + LoadToPath []string `form:"loadToPath" url:"loadToPath,omitempty"` +} + +type PresignedObjectUploadResp struct { + Object cdssdk.Object `json:"object"` +} + +func (c *PresignedService) ObjectUpload(req PresignedObjectUpload, expireIn int) (string, error) { + return c.presign(req, PresignedObjectUploadPath, http.MethodPost, expireIn) +} + +const PresignedObjectNewMultipartUploadPath = "/v1/presigned/object/newMultipartUpload" + +type PresignedObjectNewMultipartUpload struct { + UserID cdssdk.UserID `form:"userID" binding:"required" url:"userID"` + PackageID cdssdk.PackageID `form:"packageID" binding:"required" url:"packageID"` + Path string `form:"path" binding:"required" url:"path"` +} + +type PresignedObjectNewMultipartUploadResp struct { + Object cdssdk.Object `json:"object"` +} + +func (c *PresignedService) ObjectNewMultipartUpload(req PresignedObjectNewMultipartUpload, expireIn int) (string, error) { + return c.presign(req, PresignedObjectNewMultipartUploadPath, http.MethodPost, expireIn) +} + +const PresignedObjectUploadPartPath = "/v1/presigned/object/uploadPart" + +type PresignedObjectUploadPart struct { + UserID cdssdk.UserID `form:"userID" binding:"required" url:"userID"` + ObjectID cdssdk.ObjectID `form:"objectID" binding:"required" url:"objectID"` + Index int `form:"index" binding:"required" url:"index"` +} + +type PresignedUploadPartResp struct{} + +func (c *PresignedService) ObjectUploadPart(req PresignedObjectUploadPart, expireIn int) (string, error) { + return c.presign(req, PresignedObjectUploadPartPath, http.MethodPost, expireIn) +} + +const PresignedObjectCompleteMultipartUploadPath = "/v1/presigned/object/completeMultipartUpload" + +type PresignedObjectCompleteMultipartUpload struct { + UserID cdssdk.UserID `form:"userID" binding:"required" url:"userID"` + ObjectID cdssdk.ObjectID `form:"objectID" binding:"required" url:"objectID"` + Indexes []int `form:"indexes" binding:"required" url:"indexes"` +} + +type PresignedObjectCompleteMultipartUploadResp struct { + Object cdssdk.Object `json:"object"` +} + +func (c *PresignedService) ObjectCompleteMultipartUpload(req PresignedObjectCompleteMultipartUpload, expireIn int) (string, error) { + return c.presign(req, PresignedObjectCompleteMultipartUploadPath, http.MethodPost, expireIn) +} + +func (c *PresignedService) presign(req any, path string, method string, expireIn int) (string, error) { + u, err := url.Parse(c.baseURL) + if err != nil { + return "", err + } + u = u.JoinPath(path) + + us, err := query.Values(req) + if err != nil { + return "", err + } + us.Add("X-Expires", fmt.Sprintf("%v", expireIn)) + + u.RawQuery = us.Encode() + + prod := credentials.NewStaticCredentialsProvider(c.accessKey, c.secretKey, "") + cred, err := prod.Retrieve(context.TODO()) + if err != nil { + return "", err + } + + r, err := http.NewRequest(method, u.String(), nil) + if err != nil { + return "", err + } + + signer := v4.NewSigner() + signedURL, _, err := signer.PresignHTTP(context.Background(), cred, r, "", AuthService, AuthRegion, time.Now()) + return signedURL, err +} diff --git a/sdks/storage/cdsapi/presigned_test.go b/sdks/storage/cdsapi/presigned_test.go new file mode 100644 index 0000000..ae34e3d --- /dev/null +++ b/sdks/storage/cdsapi/presigned_test.go @@ -0,0 +1,125 @@ +package cdsapi + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" + "gitlink.org.cn/cloudream/common/pkgs/types" +) + +func Test_Presigned(t *testing.T) { + cli := NewClient(&Config{ + URL: "http://localhost:7890", + }) + + Convey("下载文件", t, func() { + pre := cli.Presigned("123456", "123456") + url, err := pre.ObjectDownload(PresignedObjectDownloadByPath{ + UserID: 1, + PackageID: 3, + Path: "example.java", + Offset: 1, + Length: types.Ref(int64(100)), + }, 100) + So(err, ShouldEqual, nil) + t.Logf("url: %s", url) + }) + + Convey("上传文件", t, func() { + pre := cli.Presigned("123456", "123456") + url, err := pre.ObjectUpload(PresignedObjectUpload{ + UserID: 1, + PackageID: 3, + Path: "example.java", + }, 100) + So(err, ShouldEqual, nil) + t.Logf("url: %s", url) + }) +} + +func Test_PresignedObjectDownload(t *testing.T) { + cli := NewClient(&Config{ + URL: "http://localhost:7890", + }) + + Convey("下载文件", t, func() { + pre := cli.Presigned("123456", "123456") + url, err := pre.ObjectDownload(PresignedObjectDownloadByPath{ + UserID: 1, + PackageID: 3, + Path: "example.java", + // Offset: 1, + // Length: types.Ref(int64(100)), + }, 100) + So(err, ShouldEqual, nil) + t.Logf("url: %s", url) + }) +} + +func Test_PresignedObjectUpload(t *testing.T) { + cli := NewClient(&Config{ + URL: "http://localhost:7890", + }) + + Convey("上传文件", t, func() { + pre := cli.Presigned("123456", "123456") + url, err := pre.ObjectUpload(PresignedObjectUpload{ + UserID: 1, + PackageID: 3, + Path: "example.java", + }, 100) + So(err, ShouldEqual, nil) + t.Logf("url: %s", url) + }) +} + +func Test_PresignedNewMultipartUpload(t *testing.T) { + cli := NewClient(&Config{ + URL: "http://localhost:7890", + }) + + Convey("启动分片上传", t, func() { + pre := cli.Presigned("123456", "123456") + url, err := pre.ObjectNewMultipartUpload(PresignedObjectNewMultipartUpload{ + UserID: 1, + PackageID: 3, + Path: "example.java", + }, 600) + So(err, ShouldEqual, nil) + t.Logf("url: %s", url) + }) +} + +func Test_PresignedObjectUploadPart(t *testing.T) { + cli := NewClient(&Config{ + URL: "http://localhost:7890", + }) + + Convey("上传分片", t, func() { + pre := cli.Presigned("123456", "123456") + url, err := pre.ObjectUploadPart(PresignedObjectUploadPart{ + UserID: 1, + ObjectID: 7, + Index: 3, + }, 600) + So(err, ShouldEqual, nil) + t.Logf("url: %s", url) + }) +} + +func Test_PresignedCompleteMultipartUpload(t *testing.T) { + cli := NewClient(&Config{ + URL: "http://localhost:7890", + }) + + Convey("合并分片", t, func() { + pre := cli.Presigned("123456", "123456") + url, err := pre.ObjectCompleteMultipartUpload(PresignedObjectCompleteMultipartUpload{ + UserID: 1, + ObjectID: 7, + Indexes: []int{1, 2, 3}, + }, 600) + So(err, ShouldEqual, nil) + t.Logf("url: %s", url) + }) +} diff --git a/utils/serder/serder.go b/utils/serder/serder.go index c665201..9967ae5 100644 --- a/utils/serder/serder.go +++ b/utils/serder/serder.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "reflect" + "strings" jsoniter "github.com/json-iterator/go" "github.com/mitchellh/mapstructure" @@ -189,3 +190,79 @@ func ObjectToMap(obj any) (map[string]any, error) { } return mp, dec.Decode(obj) } + +// 1. 尝试解开所有引用 +// +// 2. nil值将会是空字符串 +func ObjectToMapString(obj any) (map[string]string, error) { + if obj == nil { + return make(map[string]string), nil + } + + v := reflect.ValueOf(obj) + for v.Kind() == reflect.Ptr { + v = v.Elem() + } + if !v.IsValid() { + return make(map[string]string), nil + } + if v.Kind() != reflect.Struct { + return nil, fmt.Errorf("type %v is not a struct", v.Type()) + } + + mp := make(map[string]string) + objectToMapString(v, mp) + return mp, nil +} + +func objectToMapString(val reflect.Value, mp map[string]string) { + typ := val.Type() + for i := 0; i < val.NumField(); i++ { + vf := val.Field(i) + tf := typ.Field(i) + if tf.Anonymous { + objectToMapString(vf, mp) + continue + } + + fieldName := tf.Name + + omitEmpty := false + jsonTag := tf.Tag.Get("json") + if jsonTag != "" { + tagParts := strings.Split(jsonTag, ",") + fieldName = strings.TrimSpace(tagParts[0]) + if len(tagParts) > 1 { + for _, tagPart := range tagParts[1:] { + tagPart = strings.TrimSpace(tagPart) + if tagPart == "omitempty" { + omitEmpty = true + } + } + } + } + + for vf.Kind() == reflect.Ptr { + vf = vf.Elem() + } + + if !vf.IsValid() { + if omitEmpty { + continue + } + + mp[fieldName] = "" + continue + } + + if vf.IsZero() && omitEmpty { + continue + } + + if vf.Kind() == reflect.Array { + + } + + mp[fieldName] = fmt.Sprintf("%v", vf) + } +} diff --git a/utils/serder/serder_test.go b/utils/serder/serder_test.go index abd7aef..e0d1f4c 100644 --- a/utils/serder/serder_test.go +++ b/utils/serder/serder_test.go @@ -694,3 +694,108 @@ func Test_ObjectToJSONEx4(t *testing.T) { So(ret[0].(*StCallback).Value, ShouldEqual, "called") }) } + +type StStringer struct { +} + +func (s StStringer) String() string { + return "StStringer" +} + +func Test_ObjectToMapString(t *testing.T) { + Convey("结构体", t, func() { + type StEmb struct { + IntRef *int `json:"intRef,omitempty"` + BoolRef **bool `json:"boolRef"` + StrRef *string `json:"strRef"` + } + + type St struct { + StEmb + Int int + Bool bool + Str string + StStringer *StStringer + } + + st := St{ + StEmb: StEmb{ + IntRef: types.Ref(123), + BoolRef: types.Ref(types.Ref(true)), + }, + Int: 456, + Bool: false, + Str: "test", + StStringer: &StStringer{}, + } + + mp, err := ObjectToMapString(st) + So(err, ShouldBeNil) + + So(mp["intRef"], ShouldEqual, "123") + So(mp["boolRef"], ShouldEqual, "true") + So(mp["strRef"], ShouldEqual, "") + So(mp["Int"], ShouldEqual, "456") + So(mp["Bool"], ShouldEqual, "false") + So(mp["Str"], ShouldEqual, "test") + So(mp["StStringer"], ShouldEqual, "StStringer") + }) + + Convey("结构体引用", t, func() { + type StEmb struct { + IntRef *int `json:"intRef,omitempty"` + BoolRef **bool `json:"boolRef"` + StrRef *string `json:"strRef"` + } + + type St struct { + StEmb + Int int + Bool bool + Str string + StStringer *StStringer + } + + st := St{ + StEmb: StEmb{ + IntRef: types.Ref(123), + BoolRef: types.Ref(types.Ref(true)), + }, + Int: 456, + Bool: false, + Str: "test", + StStringer: &StStringer{}, + } + + mp, err := ObjectToMapString(&st) + So(err, ShouldBeNil) + + So(mp["intRef"], ShouldEqual, "123") + So(mp["boolRef"], ShouldEqual, "true") + So(mp["strRef"], ShouldEqual, "") + So(mp["Int"], ShouldEqual, "456") + So(mp["Bool"], ShouldEqual, "false") + So(mp["Str"], ShouldEqual, "test") + So(mp["StStringer"], ShouldEqual, "StStringer") + }) + + Convey("nil", t, func() { + mp, err := ObjectToMapString(nil) + So(err, ShouldBeNil) + So(mp, ShouldResemble, map[string]string{}) + }) + + Convey("nil指针", t, func() { + type St struct{} + var st *St + mp, err := ObjectToMapString(st) + So(err, ShouldBeNil) + So(mp, ShouldResemble, map[string]string{}) + }) + + Convey("非结构体", t, func() { + mp, err := ObjectToMapString(123) + So(err, ShouldNotBeNil) + So(mp, ShouldBeNil) + }) +}