From 27b677b0c01338aad86163244f4cefab7e8db791 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 2 Jan 2020 19:45:41 +0800 Subject: [PATCH] improve Map converting feature for gconv; improve package gproc for local shell searching; improve JSON/XML response for ghttp.Response --- .../net/ghttp/server/router/group/basic.go | 22 ++++++ .../net/ghttp/server/router/group/batch.go | 38 ++++++++++ .../net/ghttp/server/router/group/level.go | 25 +++---- .example/net/ghttp/server/router/router2.go | 7 +- .example/other/test.go | 33 ++++++--- .example/util/gconv/gconv_map_deep.go | 13 ++-- net/ghttp/ghttp_response_write.go | 24 +++++++ net/ghttp/ghttp_server_router_middleware.go | 6 ++ os/gproc/gproc.go | 8 +++ util/gconv/gconv_map.go | 72 +++++++++++-------- util/gconv/gconv_z_unit_map_test.go | 50 +++++++++++-- 11 files changed, 233 insertions(+), 65 deletions(-) create mode 100644 .example/net/ghttp/server/router/group/basic.go create mode 100644 .example/net/ghttp/server/router/group/batch.go diff --git a/.example/net/ghttp/server/router/group/basic.go b/.example/net/ghttp/server/router/group/basic.go new file mode 100644 index 000000000..899b9243e --- /dev/null +++ b/.example/net/ghttp/server/router/group/basic.go @@ -0,0 +1,22 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +func main() { + s := g.Server() + group := s.Group("/api") + group.ALL("/all", func(r *ghttp.Request) { + r.Response.Write("all") + }) + group.GET("/get", func(r *ghttp.Request) { + r.Response.Write("get") + }) + group.POST("/post", func(r *ghttp.Request) { + r.Response.Write("post") + }) + s.SetPort(8199) + s.Run() +} diff --git a/.example/net/ghttp/server/router/group/batch.go b/.example/net/ghttp/server/router/group/batch.go new file mode 100644 index 000000000..a335d2cf0 --- /dev/null +++ b/.example/net/ghttp/server/router/group/batch.go @@ -0,0 +1,38 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) + +type Object struct{} + +func (o *Object) Show(r *ghttp.Request) { + r.Response.Writeln("Show") +} + +func (o *Object) Delete(r *ghttp.Request) { + r.Response.Writeln("REST Delete") +} + +func Handler(r *ghttp.Request) { + r.Response.Writeln("Handler") +} + +func HookHandler(r *ghttp.Request) { + r.Response.Writeln("HOOK Handler") +} + +func main() { + s := g.Server() + obj := new(Object) + s.Group("/api").Bind([]ghttp.GroupItem{ + {"ALL", "*", HookHandler, ghttp.HOOK_BEFORE_SERVE}, + {"ALL", "/handler", Handler}, + {"ALL", "/obj", obj}, + {"GET", "/obj/show", obj, "Show"}, + {"REST", "/obj/rest", obj}, + }) + s.SetPort(8199) + s.Run() +} diff --git a/.example/net/ghttp/server/router/group/level.go b/.example/net/ghttp/server/router/group/level.go index 9135799e9..c8668a169 100644 --- a/.example/net/ghttp/server/router/group/level.go +++ b/.example/net/ghttp/server/router/group/level.go @@ -5,7 +5,6 @@ import ( "github.com/gogf/gf/frame/g" "github.com/gogf/gf/net/ghttp" - "github.com/gogf/gf/os/glog" ) func MiddlewareAuth(r *ghttp.Request) { @@ -24,39 +23,37 @@ func MiddlewareCORS(r *ghttp.Request) { func MiddlewareLog(r *ghttp.Request) { r.Middleware.Next() - glog.Println(r.Response.Status, r.URL.Path) + g.Log().Println(r.Response.Status, r.URL.Path) } func main() { s := g.Server() - s.Group("/", func(group *ghttp.RouterGroup) { - group.Middleware(MiddlewareLog) - }) + s.Use(MiddlewareLog) s.Group("/api.v2", func(group *ghttp.RouterGroup) { group.Middleware(MiddlewareAuth, MiddlewareCORS) - g.GET("/test", func(r *ghttp.Request) { + group.GET("/test", func(r *ghttp.Request) { r.Response.Write("test") }) - g.Group("/order", func(group *ghttp.RouterGroup) { - g.GET("/list", func(r *ghttp.Request) { + group.Group("/order", func(group *ghttp.RouterGroup) { + group.GET("/list", func(r *ghttp.Request) { r.Response.Write("list") }) - g.PUT("/update", func(r *ghttp.Request) { + group.PUT("/update", func(r *ghttp.Request) { r.Response.Write("update") }) }) - g.Group("/user", func(group *ghttp.RouterGroup) { - g.GET("/info", func(r *ghttp.Request) { + group.Group("/user", func(group *ghttp.RouterGroup) { + group.GET("/info", func(r *ghttp.Request) { r.Response.Write("info") }) - g.POST("/edit", func(r *ghttp.Request) { + group.POST("/edit", func(r *ghttp.Request) { r.Response.Write("edit") }) - g.DELETE("/drop", func(r *ghttp.Request) { + group.DELETE("/drop", func(r *ghttp.Request) { r.Response.Write("drop") }) }) - g.Group("/hook", func(group *ghttp.RouterGroup) { + group.Group("/hook", func(group *ghttp.RouterGroup) { group.Hook("/*", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) { r.Response.Write("hook any") }) diff --git a/.example/net/ghttp/server/router/router2.go b/.example/net/ghttp/server/router/router2.go index cb994e60b..285817740 100644 --- a/.example/net/ghttp/server/router/router2.go +++ b/.example/net/ghttp/server/router/router2.go @@ -1,9 +1,12 @@ package main -import "github.com/gogf/gf/net/ghttp" +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" +) func main() { - s := ghttp.GetServer() + s := g.Server() s.BindHandler("/user/:name", func(r *ghttp.Request) { r.Response.Writeln(r.Router.Uri) }) diff --git a/.example/other/test.go b/.example/other/test.go index 249e5bb78..9ce95323c 100644 --- a/.example/other/test.go +++ b/.example/other/test.go @@ -1,17 +1,32 @@ package main import ( - "fmt" "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/util/gconv" ) func main() { - s := g.Server() - s.BindHandler("/test", func(r *ghttp.Request) { - fmt.Println(r.GetBody()) - r.Response.Write(r.GetBody()) - }) - s.SetPort(8199) - s.Run() + type Ids struct { + Id int `c:"id"` + Uid int `c:"uid"` + } + type Base struct { + Ids + CreateTime string `c:"create_time"` + } + type User struct { + Base + Passport string `c:"passport"` + Password string `c:"password"` + Nickname string `c:"nickname"` + } + user := new(User) + user.Id = 1 + user.Uid = 100 + user.Nickname = "John" + user.Passport = "johng" + user.Password = "123456" + user.CreateTime = "2019" + g.Dump(gconv.Map(user)) + g.Dump(gconv.MapDeep(user)) } diff --git a/.example/util/gconv/gconv_map_deep.go b/.example/util/gconv/gconv_map_deep.go index 024869723..9ce95323c 100644 --- a/.example/util/gconv/gconv_map_deep.go +++ b/.example/util/gconv/gconv_map_deep.go @@ -7,18 +7,18 @@ import ( func main() { type Ids struct { - Id int `json:"id"` - Uid int `json:"uid"` + Id int `c:"id"` + Uid int `c:"uid"` } type Base struct { Ids - CreateTime string `json:"create_time"` + CreateTime string `c:"create_time"` } type User struct { Base - Passport string `json:"passport"` - Password string `json:"password"` - Nickname string `json:"nickname"` + Passport string `c:"passport"` + Password string `c:"password"` + Nickname string `c:"nickname"` } user := new(User) user.Id = 1 @@ -27,5 +27,6 @@ func main() { user.Passport = "johng" user.Password = "123456" user.CreateTime = "2019" + g.Dump(gconv.Map(user)) g.Dump(gconv.MapDeep(user)) } diff --git a/net/ghttp/ghttp_response_write.go b/net/ghttp/ghttp_response_write.go index 6cd53160b..9e8db6b1a 100644 --- a/net/ghttp/ghttp_response_write.go +++ b/net/ghttp/ghttp_response_write.go @@ -103,6 +103,14 @@ func (r *Response) WriteflnExit(format string, params ...interface{}) { // WriteJson writes to the response with JSON format. func (r *Response) WriteJson(content interface{}) error { + // If given string/[]byte, response it directly to client. + switch content.(type) { + case string, []byte: + r.Header().Set("Content-Type", "application/json") + r.Write(content) + return nil + } + // Else use json.Marshal function to encode the parameter. if b, err := json.Marshal(content); err != nil { return err } else { @@ -127,6 +135,14 @@ func (r *Response) WriteJsonExit(content interface{}) error { // // Note that there should be a "callback" parameter in the request for JSONP format. func (r *Response) WriteJsonP(content interface{}) error { + // If given string/[]byte, response it directly to client. + switch content.(type) { + case string, []byte: + r.Header().Set("Content-Type", "application/json") + r.Write(content) + return nil + } + // Else use json.Marshal function to encode the parameter. if b, err := json.Marshal(content); err != nil { return err } else { @@ -159,6 +175,14 @@ func (r *Response) WriteJsonPExit(content interface{}) error { // WriteXml writes to the response with XML format. func (r *Response) WriteXml(content interface{}, rootTag ...string) error { + // If given string/[]byte, response it directly to client. + switch content.(type) { + case string, []byte: + r.Header().Set("Content-Type", "application/xml") + r.Write(content) + return nil + } + // Else use gparser.VarToXml function to encode the parameter. if b, err := gparser.VarToXml(content, rootTag...); err != nil { return err } else { diff --git a/net/ghttp/ghttp_server_router_middleware.go b/net/ghttp/ghttp_server_router_middleware.go index 7113285bb..5c9e83a2c 100644 --- a/net/ghttp/ghttp_server_router_middleware.go +++ b/net/ghttp/ghttp_server_router_middleware.go @@ -41,3 +41,9 @@ func (s *Server) BindMiddlewareDefault(handlers ...HandlerFunc) { }) } } + +// Use is alias of BindMiddlewareDefault. +// See BindMiddlewareDefault. +func (s *Server) Use(handlers ...HandlerFunc) { + s.BindMiddlewareDefault(handlers...) +} diff --git a/os/gproc/gproc.go b/os/gproc/gproc.go index ca26ecaa8..bfc8839db 100644 --- a/os/gproc/gproc.go +++ b/os/gproc/gproc.go @@ -155,6 +155,14 @@ func getShell() string { case "windows": return SearchBinary("cmd.exe") default: + // Check the default binary storage path. + if gfile.Exists("/bin/bash") { + return "/bin/bash" + } + if gfile.Exists("/bin/sh") { + return "/bin/sh" + } + // Else search the env PATH. path := SearchBinary("bash") if path == "" { path = SearchBinary("sh") diff --git a/util/gconv/gconv_map.go b/util/gconv/gconv_map.go index 9f9e3e073..bebabcb0f 100644 --- a/util/gconv/gconv_map.go +++ b/util/gconv/gconv_map.go @@ -26,6 +26,19 @@ type apiMapStrAny interface { // If is a struct/*struct object, the second parameter specifies the most priority // tags that will be detected, otherwise it detects the tags in order of: gconv, json, and then the field name. func Map(value interface{}, tags ...string) map[string]interface{} { + return doMapConvert(value, false, tags...) +} + +// MapDeep does Map function recursively, which means if the attribute of +// is also a struct/*struct, calls Map function on this attribute converting it to +// a map[string]interface{} type variable. +// Also see Map. +func MapDeep(value interface{}, tags ...string) map[string]interface{} { + return doMapConvert(value, true, tags...) +} + +// doMapConvert implements the map converting. +func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]interface{} { if value == nil { return nil } @@ -121,14 +134,18 @@ func Map(value interface{}, tags ...string) map[string]interface{} { default: tagArray = append(tags, structTagPriority...) } + var rtField reflect.StructField + var rvField reflect.Value for i := 0; i < rv.NumField(); i++ { + rtField = rt.Field(i) + rvField = rv.Field(i) // Only convert the public attributes. - fieldName := rt.Field(i).Name + fieldName := rtField.Name if !utilstr.IsLetterUpper(fieldName[0]) { continue } name = "" - fieldTag := rt.Field(i).Tag + fieldTag := rtField.Tag for _, tag := range tagArray { if name = fieldTag.Get(tag); name != "" { break @@ -146,7 +163,7 @@ func Map(value interface{}, tags ...string) map[string]interface{} { if len(array) > 1 { switch strings.TrimSpace(array[1]) { case "omitempty": - if empty.IsEmpty(rv.Field(i).Interface()) { + if empty.IsEmpty(rvField.Interface()) { continue } else { name = strings.TrimSpace(array[0]) @@ -156,7 +173,30 @@ func Map(value interface{}, tags ...string) map[string]interface{} { } } } - m[name] = rv.Field(i).Interface() + switch rvField.Kind() { + case reflect.Ptr: + if rvField.Elem().Kind() == reflect.Struct { + if recursive { + for k, v := range doMapConvert(rvField.Interface(), recursive, tags...) { + m[k] = v + } + } else { + m[name] = doMapConvert(rvField.Interface(), recursive, tags...) + } + } else { + m[name] = rvField.Interface() + } + case reflect.Struct: + if recursive { + for k, v := range doMapConvert(rvField.Interface(), recursive, tags...) { + m[k] = v + } + } else { + m[name] = doMapConvert(rvField.Interface(), recursive, tags...) + } + default: + m[name] = rvField.Interface() + } } default: return nil @@ -166,30 +206,6 @@ func Map(value interface{}, tags ...string) map[string]interface{} { } } -// MapDeep does Map function recursively, which means if the attribute of -// is also a struct/*struct, calls Map function on this attribute converting it to -// a map[string]interface{} type variable. -// Also see Map. -func MapDeep(value interface{}, tags ...string) map[string]interface{} { - data := Map(value, tags...) - for key, value := range data { - rv := reflect.ValueOf(value) - kind := rv.Kind() - if kind == reflect.Ptr { - rv = rv.Elem() - kind = rv.Kind() - } - switch kind { - case reflect.Struct: - delete(data, key) - for k, v := range MapDeep(value, tags...) { - data[k] = v - } - } - } - return data -} - // MapStrStr converts to map[string]string. // Note that there might be data copy for this map type converting. func MapStrStr(value interface{}, tags ...string) map[string]string { diff --git a/util/gconv/gconv_z_unit_map_test.go b/util/gconv/gconv_z_unit_map_test.go index 1e4c4424c..82fe09c9d 100644 --- a/util/gconv/gconv_z_unit_map_test.go +++ b/util/gconv/gconv_z_unit_map_test.go @@ -113,6 +113,44 @@ func Test_Map_StructWithJsonTag(t *testing.T) { }) } +func Test_Map_StructWithCTag(t *testing.T) { + gtest.Case(t, func() { + type User struct { + Uid int + Name string + SiteUrl string `c:"-"` + NickName string `c:"nickname, omitempty"` + Pass1 string `c:"password1"` + Pass2 string `c:"password2"` + } + user1 := User{ + Uid: 100, + Name: "john", + SiteUrl: "https://goframe.org", + Pass1: "123", + Pass2: "456", + } + user2 := &user1 + map1 := gconv.Map(user1) + map2 := gconv.Map(user2) + gtest.Assert(map1["Uid"], 100) + gtest.Assert(map1["Name"], "john") + gtest.Assert(map1["SiteUrl"], nil) + gtest.Assert(map1["NickName"], nil) + gtest.Assert(map1["nickname"], nil) + gtest.Assert(map1["password1"], "123") + gtest.Assert(map1["password2"], "456") + + gtest.Assert(map2["Uid"], 100) + gtest.Assert(map2["Name"], "john") + gtest.Assert(map2["SiteUrl"], nil) + gtest.Assert(map2["NickName"], nil) + gtest.Assert(map2["nickname"], nil) + gtest.Assert(map2["password1"], "123") + gtest.Assert(map2["password2"], "456") + }) +} + func Test_Map_PrivateAttribute(t *testing.T) { type User struct { Id int @@ -126,18 +164,18 @@ func Test_Map_PrivateAttribute(t *testing.T) { func Test_Map_StructInherit(t *testing.T) { type Ids struct { - Id int `json:"id"` - Uid int `json:"uid"` + Id int `c:"id"` + Uid int `c:"uid"` } type Base struct { Ids - CreateTime string `json:"create_time"` + CreateTime string `c:"create_time"` } type User struct { Base - Passport string `json:"passport"` - Password string `json:"password"` - Nickname string `json:"nickname"` + Passport string `c:"passport"` + Password string `c:"password"` + Nickname string `c:"nickname"` } gtest.Case(t, func() { user := new(User)