diff --git a/net/ghttp/ghttp_z_unit_feature_request_file_test.go b/net/ghttp/ghttp_z_unit_feature_request_file_test.go index 8b40ba6d4..9d3544617 100644 --- a/net/ghttp/ghttp_z_unit_feature_request_file_test.go +++ b/net/ghttp/ghttp_z_unit_feature_request_file_test.go @@ -9,11 +9,12 @@ package ghttp_test import ( "context" "fmt" - "github.com/gogf/gf/v2/internal/json" "strings" "testing" "time" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/os/gfile" @@ -173,7 +174,7 @@ func Test_Params_File_Batch(t *testing.T) { }) } -func Test_Params_Strict_Route_File_Single(t *testing.T) { +func Test_Params_Strict_Route_File_Single_Ptr_Attrr(t *testing.T) { type Req struct { gmeta.Meta `method:"post" mime:"multipart/form-data"` File *ghttp.UploadFile `type:"file"` @@ -219,6 +220,48 @@ func Test_Params_Strict_Route_File_Single(t *testing.T) { }) } +func Test_Params_Strict_Route_File_Single_Struct_Attr(t *testing.T) { + type Req struct { + gmeta.Meta `method:"post" mime:"multipart/form-data"` + File ghttp.UploadFile `type:"file"` + } + type Res struct{} + + dstDirPath := gfile.Temp(gtime.TimestampNanoStr()) + s := g.Server(guid.S()) + s.BindHandler("/upload/single", func(ctx context.Context, req *Req) (res *Res, err error) { + var ( + r = g.RequestFromCtx(ctx) + file = req.File + ) + name, err := file.Save(dstDirPath) + if err != nil { + r.Response.WriteExit(err) + } + r.Response.WriteExit(name) + return + }) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + time.Sleep(100 * time.Millisecond) + // normal name + gtest.C(t, func(t *gtest.T) { + client := g.Client() + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())) + + srcPath := gtest.DataPath("upload", "file1.txt") + dstPath := gfile.Join(dstDirPath, "file1.txt") + content := client.PostContent(ctx, "/upload/single", g.Map{ + "file": "@file:" + srcPath, + }) + t.AssertNE(content, "") + t.AssertNE(content, "upload failed") + t.Assert(content, "file1.txt") + t.Assert(gfile.GetContents(dstPath), gfile.GetContents(srcPath)) + }) +} + func Test_Params_File_Upload_Required(t *testing.T) { type Req struct { gmeta.Meta `method:"post" mime:"multipart/form-data"` @@ -274,3 +317,101 @@ func Test_Params_File_MarshalJSON(t *testing.T) { t.Assert(strings.Contains(content, "file1.txt"), true) }) } + +// Select only one file when batch uploading +func Test_Params_Strict_Route_File_Batch_Up_One(t *testing.T) { + type Req struct { + gmeta.Meta `method:"post" mime:"multipart/form-data"` + Files ghttp.UploadFiles `type:"file"` + } + type Res struct{} + + dstDirPath := gfile.Temp(gtime.TimestampNanoStr()) + s := g.Server(guid.S()) + s.BindHandler("/upload/batch", func(ctx context.Context, req *Req) (res *Res, err error) { + var ( + r = g.RequestFromCtx(ctx) + files = req.Files + ) + if len(files) == 0 { + r.Response.WriteExit("upload file cannot be empty") + } + names, err := files.Save(dstDirPath) + if err != nil { + r.Response.WriteExit(err) + } + r.Response.WriteExit(gstr.Join(names, ",")) + return + }) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + time.Sleep(100 * time.Millisecond) + // normal name + gtest.C(t, func(t *gtest.T) { + client := g.Client() + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())) + + srcPath := gtest.DataPath("upload", "file1.txt") + dstPath := gfile.Join(dstDirPath, "file1.txt") + content := client.PostContent(ctx, "/upload/batch", g.Map{ + "files": "@file:" + srcPath, + }) + t.AssertNE(content, "") + t.AssertNE(content, "upload file cannot be empty") + t.AssertNE(content, "upload failed") + t.Assert(content, "file1.txt") + t.Assert(gfile.GetContents(dstPath), gfile.GetContents(srcPath)) + }) +} + +// Select multiple files during batch upload +func Test_Params_Strict_Route_File_Batch_Up_Multiple(t *testing.T) { + type Req struct { + gmeta.Meta `method:"post" mime:"multipart/form-data"` + Files ghttp.UploadFiles `type:"file"` + } + type Res struct{} + + dstDirPath := gfile.Temp(gtime.TimestampNanoStr()) + s := g.Server(guid.S()) + s.BindHandler("/upload/batch", func(ctx context.Context, req *Req) (res *Res, err error) { + var ( + r = g.RequestFromCtx(ctx) + files = req.Files + ) + if len(files) == 0 { + r.Response.WriteExit("upload file cannot be empty") + } + names, err := files.Save(dstDirPath) + if err != nil { + r.Response.WriteExit(err) + } + r.Response.WriteExit(gstr.Join(names, ",")) + return + }) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + time.Sleep(100 * time.Millisecond) + // normal name + gtest.C(t, func(t *gtest.T) { + client := g.Client() + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())) + + srcPath1 := gtest.DataPath("upload", "file1.txt") + srcPath2 := gtest.DataPath("upload", "file2.txt") + dstPath1 := gfile.Join(dstDirPath, "file1.txt") + dstPath2 := gfile.Join(dstDirPath, "file2.txt") + content := client.PostContent(ctx, "/upload/batch", + "files=@file:"+srcPath1+ + "&files=@file:"+srcPath2, + ) + t.AssertNE(content, "") + t.AssertNE(content, "upload file cannot be empty") + t.AssertNE(content, "upload failed") + t.Assert(content, "file1.txt,file2.txt") + t.Assert(gfile.GetContents(dstPath1), gfile.GetContents(srcPath1)) + t.Assert(gfile.GetContents(dstPath2), gfile.GetContents(srcPath2)) + }) +} diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go index ac73fad04..7a532f7bd 100644 --- a/util/gconv/gconv_struct.go +++ b/util/gconv/gconv_struct.go @@ -145,9 +145,29 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string // If `params` and `pointer` are the same type, the do directly assignment. // For performance enhancement purpose. - if pointerElemReflectValue.IsValid() && pointerElemReflectValue.Type() == paramsReflectValue.Type() { - pointerElemReflectValue.Set(paramsReflectValue) - return nil + if pointerElemReflectValue.IsValid() { + switch { + // Eg: + // UploadFile => UploadFile + // *UploadFile => *UploadFile + case pointerElemReflectValue.Type() == paramsReflectValue.Type(): + pointerElemReflectValue.Set(paramsReflectValue) + return nil + + // Eg: + // UploadFile => *UploadFile + case pointerElemReflectValue.Kind() == reflect.Ptr && pointerElemReflectValue.Elem().IsValid() && + pointerElemReflectValue.Elem().Type() == paramsReflectValue.Type(): + pointerElemReflectValue.Elem().Set(paramsReflectValue) + return nil + + // Eg: + // *UploadFile => UploadFile + case paramsReflectValue.Kind() == reflect.Ptr && paramsReflectValue.Elem().IsValid() && + pointerElemReflectValue.Type() == paramsReflectValue.Elem().Type(): + pointerElemReflectValue.Set(paramsReflectValue.Elem()) + return nil + } } // Normal unmarshalling interfaces checks.