From c91b83969cc96bb54c5b60897e428580b937d8cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E6=B9=9B?= Date: Wed, 5 Jan 2022 14:33:20 +0800 Subject: [PATCH 01/17] WIP: add cookie security configuration --- net/ghttp/ghttp_server_config.go | 12 ++++++++++ net/ghttp/ghttp_server_config_cookie.go | 23 ++++++++++++++++++ net/ghttp/ghttp_server_cookie.go | 24 ++++++++++++++++--- net/ghttp/ghttp_z_unit_feature_config_test.go | 9 +++++++ net/ghttp/ghttp_z_unit_feature_cookie_test.go | 12 ++++++++++ 5 files changed, 77 insertions(+), 3 deletions(-) diff --git a/net/ghttp/ghttp_server_config.go b/net/ghttp/ghttp_server_config.go index 7d3944718..85370b71a 100644 --- a/net/ghttp/ghttp_server_config.go +++ b/net/ghttp/ghttp_server_config.go @@ -145,6 +145,18 @@ type ServerConfig struct { // It also affects the default storage for session id. CookieDomain string `json:"cookieDomain"` + // CookieSameSite specifies cookie SameSite property. + // It also affects the default storage for session id. + CookieSameSite string `json:"sameSite"` + + // CookieSameSite specifies cookie Secure property. + // It also affects the default storage for session id. + CookieSecure bool `json:"cookieSecure"` + + // CookieSameSite specifies cookie HttpOnly property. + // It also affects the default storage for session id. + CookieHttpOnly bool `json:"CookieHttpOnly"` + // ====================================================================================================== // Session. // ====================================================================================================== diff --git a/net/ghttp/ghttp_server_config_cookie.go b/net/ghttp/ghttp_server_config_cookie.go index 266462028..2282abf05 100644 --- a/net/ghttp/ghttp_server_config_cookie.go +++ b/net/ghttp/ghttp_server_config_cookie.go @@ -7,6 +7,7 @@ package ghttp import ( + "net/http" "time" ) @@ -39,3 +40,25 @@ func (s *Server) GetCookiePath() string { func (s *Server) GetCookieDomain() string { return s.config.CookieDomain } + +// GetCookieSameSite return CookieSameSite of server. +func (s *Server) GetCookieSameSite() http.SameSite { + switch s.config.CookieSameSite { + case "lax": + return http.SameSiteLaxMode + case "none": + return http.SameSiteNoneMode + case "strict": + return http.SameSiteStrictMode + default: + return http.SameSiteDefaultMode + } +} + +func (s *Server) GetCookieSecure() bool { + return s.config.CookieSecure +} + +func (s *Server) GetCookieHttpOnly() bool { + return s.config.CookieHttpOnly +} diff --git a/net/ghttp/ghttp_server_cookie.go b/net/ghttp/ghttp_server_cookie.go index 8275593d9..d9b7ac440 100644 --- a/net/ghttp/ghttp_server_cookie.go +++ b/net/ghttp/ghttp_server_cookie.go @@ -7,6 +7,7 @@ package ghttp import ( + "github.com/gogf/gf/v2/util/gconv" "net/http" "time" @@ -88,17 +89,27 @@ func (c *Cookie) Set(key, value string) { c.request.Server.GetCookieDomain(), c.request.Server.GetCookiePath(), c.request.Server.GetCookieMaxAge(), + map[string]interface{}{ + "sameSite": c.request.Server.GetCookieSameSite(), + "secure": c.request.Server.GetCookieSecure(), + "httpOnly": c.request.Server.GetCookieHttpOnly(), + }, ) } // SetCookie sets cookie item with given domain, path and expiration age. // The optional parameter `httpOnly` specifies if the cookie item is only available in HTTP, // which is usually empty. -func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration, httpOnly ...bool) { +func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration, extra ...map[string]interface{}) { c.init() isHttpOnly := false - if len(httpOnly) > 0 { - isHttpOnly = httpOnly[0] + sameSite := http.SameSiteDefaultMode + secure := false + if len(extra) > 0 { + config := extra[0] + isHttpOnly = gconv.Bool(config["httpOnly"]) + sameSite = http.SameSite(gconv.Int(config["sameSite"])) + secure = gconv.Bool(config["secure"]) } httpCookie := &http.Cookie{ Name: key, @@ -106,6 +117,8 @@ func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration Path: path, Domain: domain, HttpOnly: isHttpOnly, + SameSite: sameSite, + Secure: secure, } if maxAge != 0 { httpCookie.Expires = time.Now().Add(maxAge) @@ -136,6 +149,11 @@ func (c *Cookie) SetSessionId(id string) { c.request.Server.GetCookieDomain(), c.request.Server.GetCookiePath(), c.server.GetSessionCookieMaxAge(), + map[string]interface{}{ + "sameSite": c.request.Server.GetCookieSameSite(), + "secure": c.request.Server.GetCookieSecure(), + "httpOnly": c.request.Server.GetCookieHttpOnly(), + }, ) } diff --git a/net/ghttp/ghttp_z_unit_feature_config_test.go b/net/ghttp/ghttp_z_unit_feature_config_test.go index 593b6d21f..271225de2 100644 --- a/net/ghttp/ghttp_z_unit_feature_config_test.go +++ b/net/ghttp/ghttp_z_unit_feature_config_test.go @@ -28,6 +28,9 @@ func Test_ConfigFromMap(t *testing.T) { "indexFiles": g.Slice{"index.php", "main.php"}, "errorLogEnabled": true, "cookieMaxAge": "1y", + "sameSite": "lax", + "cookieSecure": true, + "CookieHttpOnly": true, } config, err := ghttp.ConfigFromMap(m) t.Assert(err, nil) @@ -38,6 +41,9 @@ func Test_ConfigFromMap(t *testing.T) { t.Assert(config.CookieMaxAge, d2) t.Assert(config.IndexFiles, m["indexFiles"]) t.Assert(config.ErrorLogEnabled, m["errorLogEnabled"]) + t.Assert(config.CookieSameSite, m["sameSite"]) + t.Assert(config.CookieSecure, m["cookieSecure"]) + t.Assert(config.CookieHttpOnly, m["CookieHttpOnly"]) }) } @@ -54,6 +60,9 @@ func Test_SetConfigWithMap(t *testing.T) { "SessionIdName": "MySessionId", "SessionPath": "/tmp/MySessionStoragePath", "SessionMaxAge": 24 * time.Hour, + "sameSite": "lax", + "cookieSecure": true, + "CookieHttpOnly": true, } s := g.Server() err := s.SetConfigWithMap(m) diff --git a/net/ghttp/ghttp_z_unit_feature_cookie_test.go b/net/ghttp/ghttp_z_unit_feature_cookie_test.go index f6d0aa0bf..e087ca88e 100644 --- a/net/ghttp/ghttp_z_unit_feature_cookie_test.go +++ b/net/ghttp/ghttp_z_unit_feature_cookie_test.go @@ -104,3 +104,15 @@ func Test_SetHttpCookie(t *testing.T) { //t.Assert(client.GetContent(ctx, "/get?k=key2"), "200") }) } + +func Test_CookieSameSite(t *testing.T) { + // todo: 补充测试 +} + +func Test_CookieSecure(t *testing.T) { + // todo: 补充测试 +} + +func Test_CookieHttpOnly(t *testing.T) { + // todo: 补充测试 +} From 572e71d76ab644b524baf8bec3c9c0b84d64ebc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E6=B9=9B?= Date: Fri, 7 Jan 2022 17:10:21 +0800 Subject: [PATCH 02/17] add CookieOptions add UnitTest --- net/ghttp/ghttp_server_config.go | 4 +- net/ghttp/ghttp_server_cookie.go | 43 +++++------ net/ghttp/ghttp_z_unit_feature_config_test.go | 12 ++-- net/ghttp/ghttp_z_unit_feature_cookie_test.go | 71 +++++++++++++++++-- 4 files changed, 94 insertions(+), 36 deletions(-) diff --git a/net/ghttp/ghttp_server_config.go b/net/ghttp/ghttp_server_config.go index 85370b71a..edb955c40 100644 --- a/net/ghttp/ghttp_server_config.go +++ b/net/ghttp/ghttp_server_config.go @@ -147,7 +147,7 @@ type ServerConfig struct { // CookieSameSite specifies cookie SameSite property. // It also affects the default storage for session id. - CookieSameSite string `json:"sameSite"` + CookieSameSite string `json:"cookieSameSite"` // CookieSameSite specifies cookie Secure property. // It also affects the default storage for session id. @@ -155,7 +155,7 @@ type ServerConfig struct { // CookieSameSite specifies cookie HttpOnly property. // It also affects the default storage for session id. - CookieHttpOnly bool `json:"CookieHttpOnly"` + CookieHttpOnly bool `json:"cookieHttpOnly"` // ====================================================================================================== // Session. diff --git a/net/ghttp/ghttp_server_cookie.go b/net/ghttp/ghttp_server_cookie.go index d9b7ac440..b37492a4c 100644 --- a/net/ghttp/ghttp_server_cookie.go +++ b/net/ghttp/ghttp_server_cookie.go @@ -7,7 +7,6 @@ package ghttp import ( - "github.com/gogf/gf/v2/util/gconv" "net/http" "time" @@ -22,6 +21,13 @@ type Cookie struct { response *Response // Belonged HTTP response. } +// CookieOptions provides security config for cookies +type CookieOptions struct { + sameSite http.SameSite // cookie SameSite property + secure bool // cookie Secure property + httpOnly bool // cookie HttpOnly property +} + // cookieItem is the item stored in Cookie. type cookieItem struct { *http.Cookie // Underlying cookie items. @@ -89,10 +95,10 @@ func (c *Cookie) Set(key, value string) { c.request.Server.GetCookieDomain(), c.request.Server.GetCookiePath(), c.request.Server.GetCookieMaxAge(), - map[string]interface{}{ - "sameSite": c.request.Server.GetCookieSameSite(), - "secure": c.request.Server.GetCookieSecure(), - "httpOnly": c.request.Server.GetCookieHttpOnly(), + CookieOptions{ + sameSite: c.request.Server.GetCookieSameSite(), + secure: c.request.Server.GetCookieSecure(), + httpOnly: c.request.Server.GetCookieHttpOnly(), }, ) } @@ -100,25 +106,20 @@ func (c *Cookie) Set(key, value string) { // SetCookie sets cookie item with given domain, path and expiration age. // The optional parameter `httpOnly` specifies if the cookie item is only available in HTTP, // which is usually empty. -func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration, extra ...map[string]interface{}) { +func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration, options ...CookieOptions) { c.init() - isHttpOnly := false - sameSite := http.SameSiteDefaultMode - secure := false - if len(extra) > 0 { - config := extra[0] - isHttpOnly = gconv.Bool(config["httpOnly"]) - sameSite = http.SameSite(gconv.Int(config["sameSite"])) - secure = gconv.Bool(config["secure"]) + config := CookieOptions{} + if len(options) > 0 { + config = options[0] } httpCookie := &http.Cookie{ Name: key, Value: value, Path: path, Domain: domain, - HttpOnly: isHttpOnly, - SameSite: sameSite, - Secure: secure, + HttpOnly: config.httpOnly, + SameSite: config.sameSite, + Secure: config.secure, } if maxAge != 0 { httpCookie.Expires = time.Now().Add(maxAge) @@ -149,10 +150,10 @@ func (c *Cookie) SetSessionId(id string) { c.request.Server.GetCookieDomain(), c.request.Server.GetCookiePath(), c.server.GetSessionCookieMaxAge(), - map[string]interface{}{ - "sameSite": c.request.Server.GetCookieSameSite(), - "secure": c.request.Server.GetCookieSecure(), - "httpOnly": c.request.Server.GetCookieHttpOnly(), + CookieOptions{ + sameSite: c.request.Server.GetCookieSameSite(), + secure: c.request.Server.GetCookieSecure(), + httpOnly: c.request.Server.GetCookieHttpOnly(), }, ) } diff --git a/net/ghttp/ghttp_z_unit_feature_config_test.go b/net/ghttp/ghttp_z_unit_feature_config_test.go index 271225de2..78bd844e3 100644 --- a/net/ghttp/ghttp_z_unit_feature_config_test.go +++ b/net/ghttp/ghttp_z_unit_feature_config_test.go @@ -28,9 +28,9 @@ func Test_ConfigFromMap(t *testing.T) { "indexFiles": g.Slice{"index.php", "main.php"}, "errorLogEnabled": true, "cookieMaxAge": "1y", - "sameSite": "lax", + "cookieSameSite": "lax", "cookieSecure": true, - "CookieHttpOnly": true, + "cookieHttpOnly": true, } config, err := ghttp.ConfigFromMap(m) t.Assert(err, nil) @@ -41,9 +41,9 @@ func Test_ConfigFromMap(t *testing.T) { t.Assert(config.CookieMaxAge, d2) t.Assert(config.IndexFiles, m["indexFiles"]) t.Assert(config.ErrorLogEnabled, m["errorLogEnabled"]) - t.Assert(config.CookieSameSite, m["sameSite"]) + t.Assert(config.CookieSameSite, m["cookieSameSite"]) t.Assert(config.CookieSecure, m["cookieSecure"]) - t.Assert(config.CookieHttpOnly, m["CookieHttpOnly"]) + t.Assert(config.CookieHttpOnly, m["cookieHttpOnly"]) }) } @@ -60,9 +60,9 @@ func Test_SetConfigWithMap(t *testing.T) { "SessionIdName": "MySessionId", "SessionPath": "/tmp/MySessionStoragePath", "SessionMaxAge": 24 * time.Hour, - "sameSite": "lax", + "cookieSameSite": "lax", "cookieSecure": true, - "CookieHttpOnly": true, + "cookieHttpOnly": true, } s := g.Server() err := s.SetConfigWithMap(m) diff --git a/net/ghttp/ghttp_z_unit_feature_cookie_test.go b/net/ghttp/ghttp_z_unit_feature_cookie_test.go index e087ca88e..e9d30ff9c 100644 --- a/net/ghttp/ghttp_z_unit_feature_cookie_test.go +++ b/net/ghttp/ghttp_z_unit_feature_cookie_test.go @@ -9,6 +9,7 @@ package ghttp_test import ( "fmt" "net/http" + "strings" "testing" "time" @@ -105,14 +106,70 @@ func Test_SetHttpCookie(t *testing.T) { }) } -func Test_CookieSameSite(t *testing.T) { - // todo: 补充测试 +func Test_CookieOptionsDefault(t *testing.T) { + p, _ := ports.PopRand() + s := g.Server(p) + s.BindHandler("/test", func(r *ghttp.Request) { + r.Cookie.Set(r.Get("k").String(), r.Get("v").String()) + }) + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + gtest.C(t, func(t *gtest.T) { + client := g.Client() + client.SetBrowserMode(true) + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + r1, e1 := client.Get(ctx, "/test?k=key1&v=100") + if r1 != nil { + defer r1.Close() + } + + t.Assert(e1, nil) + t.Assert(r1.ReadAllString(), "") + + parts := strings.Split(r1.Header.Get("Set-Cookie"), "; ") + + t.AssertEQ(len(parts), 3) + }) } -func Test_CookieSecure(t *testing.T) { - // todo: 补充测试 -} +func Test_CookieOptions(t *testing.T) { + p, _ := ports.PopRand() + s := g.Server(p) + s.SetConfigWithMap(g.Map{ + "cookieSameSite": "lax", + "cookieSecure": true, + "cookieHttpOnly": true, + }) + s.BindHandler("/test", func(r *ghttp.Request) { + r.Cookie.Set(r.Get("k").String(), r.Get("v").String()) + }) + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() -func Test_CookieHttpOnly(t *testing.T) { - // todo: 补充测试 + time.Sleep(100 * time.Millisecond) + gtest.C(t, func(t *gtest.T) { + client := g.Client() + client.SetBrowserMode(true) + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + r1, e1 := client.Get(ctx, "/test?k=key1&v=100") + if r1 != nil { + defer r1.Close() + } + + t.Assert(e1, nil) + t.Assert(r1.ReadAllString(), "") + + parts := strings.Split(r1.Header.Get("Set-Cookie"), "; ") + + t.AssertEQ(len(parts), 6) + t.Assert(parts[3], "HttpOnly") + t.Assert(parts[4], "Secure") + t.Assert(parts[5], "SameSite=Lax") + }) } From d045b4d2f5fa3e9957fe402927349079d3fbc36d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E6=B9=9B?= Date: Fri, 7 Jan 2022 18:52:21 +0800 Subject: [PATCH 03/17] make unit test compatible with go 1.15 --- net/ghttp/ghttp_z_unit_feature_cookie_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ghttp/ghttp_z_unit_feature_cookie_test.go b/net/ghttp/ghttp_z_unit_feature_cookie_test.go index e9d30ff9c..26652fee7 100644 --- a/net/ghttp/ghttp_z_unit_feature_cookie_test.go +++ b/net/ghttp/ghttp_z_unit_feature_cookie_test.go @@ -132,7 +132,7 @@ func Test_CookieOptionsDefault(t *testing.T) { parts := strings.Split(r1.Header.Get("Set-Cookie"), "; ") - t.AssertEQ(len(parts), 3) + t.AssertIN(len(parts), []int{3, 4}) // For go < 1.16 cookie always output "SameSite", see: https://github.com/golang/go/commit/542693e00529fbb4248fac614ece68b127a5ec4d }) } From 072d5f9760cc3f32868c2b997f8372d23331c967 Mon Sep 17 00:00:00 2001 From: yuzhan Date: Wed, 2 Mar 2022 09:56:58 +0800 Subject: [PATCH 04/17] make options public --- net/ghttp/ghttp_server_cookie.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/net/ghttp/ghttp_server_cookie.go b/net/ghttp/ghttp_server_cookie.go index b37492a4c..1e519d957 100644 --- a/net/ghttp/ghttp_server_cookie.go +++ b/net/ghttp/ghttp_server_cookie.go @@ -23,9 +23,9 @@ type Cookie struct { // CookieOptions provides security config for cookies type CookieOptions struct { - sameSite http.SameSite // cookie SameSite property - secure bool // cookie Secure property - httpOnly bool // cookie HttpOnly property + SameSite http.SameSite // cookie SameSite property + Secure bool // cookie Secure property + HttpOnly bool // cookie HttpOnly property } // cookieItem is the item stored in Cookie. @@ -96,15 +96,15 @@ func (c *Cookie) Set(key, value string) { c.request.Server.GetCookiePath(), c.request.Server.GetCookieMaxAge(), CookieOptions{ - sameSite: c.request.Server.GetCookieSameSite(), - secure: c.request.Server.GetCookieSecure(), - httpOnly: c.request.Server.GetCookieHttpOnly(), + SameSite: c.request.Server.GetCookieSameSite(), + Secure: c.request.Server.GetCookieSecure(), + HttpOnly: c.request.Server.GetCookieHttpOnly(), }, ) } // SetCookie sets cookie item with given domain, path and expiration age. -// The optional parameter `httpOnly` specifies if the cookie item is only available in HTTP, +// The optional parameter `options` specifies extra security configurations, // which is usually empty. func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration, options ...CookieOptions) { c.init() @@ -117,9 +117,9 @@ func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration Value: value, Path: path, Domain: domain, - HttpOnly: config.httpOnly, - SameSite: config.sameSite, - Secure: config.secure, + HttpOnly: config.HttpOnly, + SameSite: config.SameSite, + Secure: config.Secure, } if maxAge != 0 { httpCookie.Expires = time.Now().Add(maxAge) @@ -151,9 +151,9 @@ func (c *Cookie) SetSessionId(id string) { c.request.Server.GetCookiePath(), c.server.GetSessionCookieMaxAge(), CookieOptions{ - sameSite: c.request.Server.GetCookieSameSite(), - secure: c.request.Server.GetCookieSecure(), - httpOnly: c.request.Server.GetCookieHttpOnly(), + SameSite: c.request.Server.GetCookieSameSite(), + Secure: c.request.Server.GetCookieSecure(), + HttpOnly: c.request.Server.GetCookieHttpOnly(), }, ) } From 5c23c0cecd64a94c17b3a28afc3d2f9ba45b6414 Mon Sep 17 00:00:00 2001 From: med-one Date: Wed, 2 Mar 2022 11:00:04 +0800 Subject: [PATCH 05/17] fix(fix bug , add nexttime feature): ServeFileDownload filename double quotes cause underscores before and after the final file --- net/ghttp/ghttp_response.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ghttp/ghttp_response.go b/net/ghttp/ghttp_response.go index 01312f0fd..6b01be340 100644 --- a/net/ghttp/ghttp_response.go +++ b/net/ghttp/ghttp_response.go @@ -92,7 +92,7 @@ func (r *Response) ServeFileDownload(path string, name ...string) { } r.Header().Set("Content-Type", "application/force-download") r.Header().Set("Accept-Ranges", "bytes") - r.Header().Set("Content-Disposition", fmt.Sprintf(`attachment;filename="%s"`, url.QueryEscape(downloadName))) + r.Header().Set("Content-Disposition", fmt.Sprintf(`attachment;filename=%s`, url.QueryEscape(downloadName))) r.Server.serveFile(r.Request, serveFile) } From 3bff71b3fc412a898807a75ca9d9fa0573a18bfe Mon Sep 17 00:00:00 2001 From: yuzhan Date: Wed, 2 Mar 2022 15:33:58 +0800 Subject: [PATCH 06/17] merge master and update unit test --- net/ghttp/ghttp_z_unit_feature_cookie_test.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/net/ghttp/ghttp_z_unit_feature_cookie_test.go b/net/ghttp/ghttp_z_unit_feature_cookie_test.go index 4271165cb..b6aa8fa8c 100644 --- a/net/ghttp/ghttp_z_unit_feature_cookie_test.go +++ b/net/ghttp/ghttp_z_unit_feature_cookie_test.go @@ -104,12 +104,10 @@ func Test_SetHttpCookie(t *testing.T) { } func Test_CookieOptionsDefault(t *testing.T) { - p, _ := ports.PopRand() - s := g.Server(p) + s := g.Server(guid.S()) s.BindHandler("/test", func(r *ghttp.Request) { r.Cookie.Set(r.Get("k").String(), r.Get("v").String()) }) - s.SetPort(p) s.SetDumpRouterMap(false) s.Start() defer s.Shutdown() @@ -118,7 +116,7 @@ func Test_CookieOptionsDefault(t *testing.T) { gtest.C(t, func(t *gtest.T) { client := g.Client() client.SetBrowserMode(true) - client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())) r1, e1 := client.Get(ctx, "/test?k=key1&v=100") if r1 != nil { defer r1.Close() @@ -134,8 +132,7 @@ func Test_CookieOptionsDefault(t *testing.T) { } func Test_CookieOptions(t *testing.T) { - p, _ := ports.PopRand() - s := g.Server(p) + s := g.Server(guid.S()) s.SetConfigWithMap(g.Map{ "cookieSameSite": "lax", "cookieSecure": true, @@ -144,7 +141,6 @@ func Test_CookieOptions(t *testing.T) { s.BindHandler("/test", func(r *ghttp.Request) { r.Cookie.Set(r.Get("k").String(), r.Get("v").String()) }) - s.SetPort(p) s.SetDumpRouterMap(false) s.Start() defer s.Shutdown() @@ -153,7 +149,7 @@ func Test_CookieOptions(t *testing.T) { gtest.C(t, func(t *gtest.T) { client := g.Client() client.SetBrowserMode(true) - client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())) r1, e1 := client.Get(ctx, "/test?k=key1&v=100") if r1 != nil { defer r1.Close() From 4e2d3781455d5d8a577d8b13376d849d9b463985 Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 2 Mar 2022 21:15:16 +0800 Subject: [PATCH 07/17] improve file uploading using strict route feature --- net/ghttp/ghttp_request_param_file.go | 6 +-- net/ghttp/ghttp_request_param_request.go | 18 +++++-- .../ghttp_z_unit_feature_request_file_test.go | 48 +++++++++++++++++++ protocol/goai/goai.go | 1 + protocol/goai/goai_parameter.go | 3 +- protocol/goai/goai_path.go | 10 +++- 6 files changed, 75 insertions(+), 11 deletions(-) diff --git a/net/ghttp/ghttp_request_param_file.go b/net/ghttp/ghttp_request_param_file.go index 0b624cb76..84952e274 100644 --- a/net/ghttp/ghttp_request_param_file.go +++ b/net/ghttp/ghttp_request_param_file.go @@ -23,8 +23,8 @@ import ( // UploadFile wraps the multipart uploading file with more and convenient features. type UploadFile struct { - *multipart.FileHeader - ctx context.Context + *multipart.FileHeader `json:"-"` + ctx context.Context } // UploadFiles is array type for *UploadFile. @@ -32,7 +32,7 @@ type UploadFiles []*UploadFile // Save saves the single uploading file to directory path and returns the saved file name. // -// The parameter `dirPath` should be a directory path or it returns error. +// The parameter `dirPath` should be a directory path, or it returns error. // // Note that it will OVERWRITE the target file if there's already a same name file exist. func (f *UploadFile) Save(dirPath string, randomlyRename ...bool) (filename string, err error) { diff --git a/net/ghttp/ghttp_request_param_request.go b/net/ghttp/ghttp_request_param_request.go index 035ba2c72..8874fa79c 100644 --- a/net/ghttp/ghttp_request_param_request.go +++ b/net/ghttp/ghttp_request_param_request.go @@ -66,14 +66,10 @@ func (r *Request) GetRequestMap(kvMap ...map[string]interface{}) map[string]inte var ( ok, filter bool ) - var length int if len(kvMap) > 0 && kvMap[0] != nil { - length = len(kvMap[0]) filter = true - } else { - length = len(r.routerMap) + len(r.queryMap) + len(r.formMap) + len(r.bodyMap) + len(r.paramsMap) } - m := make(map[string]interface{}, length) + m := make(map[string]interface{}) for k, v := range r.routerMap { if filter { if _, ok = kvMap[0][k]; !ok { @@ -114,6 +110,16 @@ func (r *Request) GetRequestMap(kvMap ...map[string]interface{}) map[string]inte } m[k] = v } + // File uploading. + if r.MultipartForm != nil { + for name := range r.MultipartForm.File { + if uploadFiles := r.GetUploadFiles(name); len(uploadFiles) == 1 { + m[name] = uploadFiles[0] + } else { + m[name] = uploadFiles + } + } + } // Check none exist parameters and assign it with default value. if filter { for k, v := range kvMap[0] { @@ -171,9 +177,11 @@ func (r *Request) doGetRequestStruct(pointer interface{}, mapping ...map[string] if data == nil { data = map[string]interface{}{} } + // Default struct values. if err = r.mergeDefaultStructValue(data, pointer); err != nil { return data, nil } + return data, gconv.Struct(data, pointer, mapping...) } 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 bf273f62a..509a01897 100644 --- a/net/ghttp/ghttp_z_unit_feature_request_file_test.go +++ b/net/ghttp/ghttp_z_unit_feature_request_file_test.go @@ -7,6 +7,7 @@ package ghttp_test import ( + "context" "fmt" "testing" "time" @@ -18,6 +19,7 @@ import ( "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/test/gtest" "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gmeta" "github.com/gogf/gf/v2/util/guid" ) @@ -169,3 +171,49 @@ func Test_Params_File_Batch(t *testing.T) { t.Assert(gfile.GetContents(dstPath2), gfile.GetContents(srcPath2)) }) } + +func Test_Params_Strict_Route_File_Single(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 + ) + if file == nil { + r.Response.WriteExit("upload file cannot be empty") + } + 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 := gdebug.TestDataPath("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 file cannot be empty") + t.AssertNE(content, "upload failed") + t.Assert(content, "file1.txt") + t.Assert(gfile.GetContents(dstPath), gfile.GetContents(srcPath)) + }) +} diff --git a/protocol/goai/goai.go b/protocol/goai/goai.go index 85409e922..90c4bf020 100644 --- a/protocol/goai/goai.go +++ b/protocol/goai/goai.go @@ -82,6 +82,7 @@ const ( TagNamePath = `path` TagNameMethod = `method` TagNameMime = `mime` + TagNameConsumes = `consumes` TagNameType = `type` TagNameDomain = `domain` TagNameValidate = `v` diff --git a/protocol/goai/goai_parameter.go b/protocol/goai/goai_parameter.go index 298b75821..082bd60e4 100644 --- a/protocol/goai/goai_parameter.go +++ b/protocol/goai/goai_parameter.go @@ -54,7 +54,8 @@ func (oai *OpenApiV3) newParameterRefWithStructMethod(field gstructs.Field, path parameter.Name = field.Name() } if len(tagMap) > 0 { - if err := gconv.Struct(oai.fileMapWithShortTags(tagMap), parameter); err != nil { + err := gconv.Struct(oai.fileMapWithShortTags(tagMap), parameter) + if err != nil { return nil, gerror.Wrap(err, `mapping struct tags to Parameter failed`) } } diff --git a/protocol/goai/goai_path.go b/protocol/goai/goai_path.go index d60b912e4..62f29f142 100644 --- a/protocol/goai/goai_path.go +++ b/protocol/goai/goai_path.go @@ -80,6 +80,7 @@ func (oai *OpenApiV3) addPath(in addPathInput) error { } var ( + mime string path = Path{} inputMetaMap = gmeta.Data(inputObject.Interface()) outputMetaMap = gmeta.Data(outputObject.Interface()) @@ -126,12 +127,17 @@ func (oai *OpenApiV3) addPath(in addPathInput) error { } if len(inputMetaMap) > 0 { - if err := gconv.Struct(oai.fileMapWithShortTags(inputMetaMap), &path); err != nil { + inputMetaMap = oai.fileMapWithShortTags(inputMetaMap) + if err := gconv.Struct(inputMetaMap, &path); err != nil { return gerror.Wrap(err, `mapping struct tags to Path failed`) } - if err := gconv.Struct(oai.fileMapWithShortTags(inputMetaMap), &operation); err != nil { + if err := gconv.Struct(inputMetaMap, &operation); err != nil { return gerror.Wrap(err, `mapping struct tags to Operation failed`) } + // Allowed request mime. + if mime = inputMetaMap[TagNameMime]; mime == "" { + mime = inputMetaMap[TagNameConsumes] + } } // ================================================================================================================= From 3fcd6ef877565e39f46def153fe69c40c878499f Mon Sep 17 00:00:00 2001 From: John Guo Date: Thu, 3 Mar 2022 21:03:42 +0800 Subject: [PATCH 08/17] fix issue orphan value parsing for sructured arguments of command for package gcmd --- os/gcmd/gcmd_command_object.go | 19 +++++++--- os/gcmd/gcmd_z_unit_feature_object1_test.go | 41 ++++++++++++++++++++- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/os/gcmd/gcmd_command_object.go b/os/gcmd/gcmd_command_object.go index 9924f743e..faf9ce706 100644 --- a/os/gcmd/gcmd_command_object.go +++ b/os/gcmd/gcmd_command_object.go @@ -221,9 +221,7 @@ func newCommandFromMethod(object interface{}, method reflect.Value) (command *Co return } - var ( - inputObject reflect.Value - ) + var inputObject reflect.Value if method.Type().In(1).Kind() == reflect.Ptr { inputObject = reflect.New(method.Type().In(1).Elem()).Elem() } else { @@ -264,8 +262,19 @@ func newCommandFromMethod(object interface{}, method reflect.Value) (command *Co } } else { // Read argument from command line option name. - if arg.Orphan && parser.GetOpt(arg.Name) != nil { - data[arg.Name] = "true" + if arg.Orphan { + if orphanValue := parser.GetOpt(arg.Name); orphanValue != nil { + if orphanValue.String() == "" { + // Eg: gf -f + data[arg.Name] = "true" + } else { + // Adapter with common user habits. + // Eg: + // `gf -f=0`: which parameter `f` is parsed as false + // `gf -f=1`: which parameter `f` is parsed as true + data[arg.Name] = orphanValue.Bool() + } + } } } } diff --git a/os/gcmd/gcmd_z_unit_feature_object1_test.go b/os/gcmd/gcmd_z_unit_feature_object1_test.go index dcde693de..4ee725e2c 100644 --- a/os/gcmd/gcmd_z_unit_feature_object1_test.go +++ b/os/gcmd/gcmd_z_unit_feature_object1_test.go @@ -243,7 +243,7 @@ func Test_Command_Pointer(t *testing.T) { t.AssertNil(err) t.Assert(value, `{"Content":"john"}`) }) - return + gtest.C(t, func(t *gtest.T) { var ( ctx = gctx.New() @@ -257,3 +257,42 @@ func Test_Command_Pointer(t *testing.T) { t.Assert(value, `{"Content":"john"}`) }) } + +type TestCommandOrphan struct { + g.Meta `name:"root" root:"root"` +} + +type TestCommandOrphanIndexInput struct { + g.Meta `name:"index"` + Orphan1 bool `short:"n1" orphan:"true"` + Orphan2 bool `short:"n2" orphan:"true"` + Orphan3 bool `short:"n3" orphan:"true"` +} +type TestCommandOrphanIndexOutput struct { + Orphan1 bool + Orphan2 bool + Orphan3 bool +} + +func (c *TestCommandOrphan) Index(ctx context.Context, in TestCommandOrphanIndexInput) (out *TestCommandOrphanIndexOutput, err error) { + out = &TestCommandOrphanIndexOutput{ + Orphan1: in.Orphan1, + Orphan2: in.Orphan2, + Orphan3: in.Orphan3, + } + return +} +func Test_Command_Orphan_Parameter(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var ctx = gctx.New() + cmd, err := gcmd.NewFromObject(TestCommandOrphan{}) + t.AssertNil(err) + + os.Args = []string{"root", "index", "-n1", "-n2=0", "-n3=1"} + value, err := cmd.RunWithValueError(ctx) + t.AssertNil(err) + t.Assert(value.(*TestCommandOrphanIndexOutput).Orphan1, true) + t.Assert(value.(*TestCommandOrphanIndexOutput).Orphan2, false) + t.Assert(value.(*TestCommandOrphanIndexOutput).Orphan3, true) + }) +} From 84c0f456c05ba884df9185a29055ca91fe9a2042 Mon Sep 17 00:00:00 2001 From: John Guo Date: Thu, 3 Mar 2022 21:43:01 +0800 Subject: [PATCH 09/17] template pack update --- cmd/gf/internal/packed/template-mono.go | 2 +- cmd/gf/internal/packed/template-single.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/gf/internal/packed/template-mono.go b/cmd/gf/internal/packed/template-mono.go index 480a35936..b21df6ddc 100644 --- a/cmd/gf/internal/packed/template-mono.go +++ b/cmd/gf/internal/packed/template-mono.go @@ -3,7 +3,7 @@ package packed import "github.com/gogf/gf/v2/os/gres" func init() { - if err := gres.Add("H4sIAAAAAAAC/+y9BVSVS9s+/pAiIKASBikhvWmkW+kuKemGDWwaCSmlU0AQUUQUkRKREhCkW0C6ke5G8r/O+77nHLZHcbPB979+3/ex1jksXa77muueeeaZ575m5pKXQkLGBdAANIATrKUMHPohBM4C9kZWNpZ69kYMVmBrMIjRxMxez97ezkzfwd4IoqKMAiD0dhDqtynW1jVKgZroGT83STRKgWSk5fIcNs8gA8DBgbzUGbR2eWZuQgAA8AEA+DncpR/CmZlYg+2M/oISbcqx+siEifJ5P2zUW+vaVWevjzFoATkMF66NvyEhpWnTsQ1rfuG0Ts7zitPqU6r8XZ7al+4SEuEyVlsWGuO68uKpofiYrs/V8arpnvca4rr1C1MNqDKyydz5TMO9ax6sdOb5bmQ3R2rYHkQuYeXgWbR4NWTKK2PtYXB0zbfrf9J5Uccg7AUAQNqRdAj+QUdRTEhURoxRRvQvNoqqNi1OdJKNMipoEk0MtRL0Eio2DSDFpaaWBmxJhtpJxL9SaDlfGk0FAADpIUzgH5jn/4GpZ2PzF9ofcQ7/65/HIf5RHJCejRnDn3/7y6D/TMj1XwYFyehZGBmbWf4dHX1lEzvAaRlTUV5ePltF/jN1s3hTnQI1tdznryuQNYdzWDb2jihSNC9SUiljJqKpbQsLULe2blX7pAiNBqA8KqJMK0ThCqBMk9Ri3G8ezBS6QVHIshoJkVRSxFtbW1tfW2O9EJW7HrUKgayurtnlIiD8me5B0g4NewAAAo9ME8WvGenZmDkyw5EsBhhDg0yNLC3BjCbgvzAsQ3NkfZgxfSe3aejlstm8meVa/fxCMiZqbK/pX7jw0tzP2amDhYz5ib3TUJeLmwneVx1ZbKDK9WqoUzY5w5OuWQ8L/Hdm6PEV08wp5VRCZxcQdttQsBUt3GLkHvN3Tmyh8AwIBLAS87iF4q7kCU29YW0b4YxztgZHKD+2YWQljhUHWenRGnAPcqjRcFds3mnbvW9YRRHZ0CPimO5rZzyrck2GZFwEDBL4K9VWt6yG3gAAMHtkqmEYPGbW9kZ21nqWcIx3OtijgwysDOHoT5bjIfzx3+FOtQ8vN+9nwvFvaVPC5O4pz3l6S4Tx7LvClGgGd680PlXRFxGX6voEe5xAMfkU9xfqGgWSPN2lq78RcHtzMWrsxEUpT71jgYxKgr+YZgSvrjvscDXTaMT2mw+YtyTN0V6q44Zc1H3dWdIZDyZPU+esGfcUfBGHpFoUFhBV9ZjB2PMAlPeE7qxBdvGrFBzx4IU8/udz+6Cpy8S6IJIkxeDaBpKMDrVgNoS4B0+anj7cVmj9vEbmjCs3i5yqHI1ezIcbySrR5zHo/ER3wHlhYhu1izxErsbJK1hRfDizZCfjHJ89BSJFZMp9bWG8OOKPIUOa4K58091rZg2JOcY3tgf4P2nzDDtLalKa1eaJ5O1tjcZStoJxZ7BW+Jxv6iDWBUjKar06WOp6PvnCItdVYGhLmizlWdW5Uq0m2askkcIOF1E3hOgcLr8rmtI5OPfnmJt2ZyaXQgAAIcSjRgXjcfoMbA2xh8AxMG4cG+Q/vw4PD1px6WZZaTkV8eYWOkaaib9fGx8JMBvxAADAPnL4sxyvDfZ2YEtLIzs4yPLABfTDOc6alQzHd7bvZXfSTPNb7L1ppduBisW2MrXEXCjqH+Zuh9KXuVX2mCXjmg4nnGet+iZHgMZnghcagawr6yeWc7+ZTZqAHUg8m+tBD34+aYh0v86/7knSHAVjxLmJMC9F2s808xdM3mfiztZ/2Hk2b7sP1gVu8JhZZeGSj7Fur9uVsEuVofMBqg+rZtdRmRh6DBZtIU6oWi82XctrRm09uxjQE21yHUMsZgRfIrg92w7qZIgU56FH0nmGMXZPFkPF/bysW8m7xfvLgnk2mWzERqqBhZybtrditvZbMFvPTl+7bL1E9GcPorM8958EAEAL4ageZDhGYq3AhkaWcHQex3Ex/rV+szAy+nsF8tek/0c8lF9Oy2zHRjSytjezd4GDHB+cUCfmeJxJxkbPwMLI8DdPMv8G+c+vH00ywOc0Gq0/1r//prmKav7l4i+nGNAxWgAxsnM0MzD6zTz/g3LiDoQH8wRLFd4TwIEM9cBwZFXshJAnTjHPiRoAD2XRkyEek/E/8al+jW+lZ2Z9+PEMDhVCr2DCEetKVPJ9EynGOsxnP4JiKN1+aU5aOPE16aA2bUGbX1/LZEkX9ROa65+UkZ/apOdeOTNQ3q219o5Q5E1w4cR5dtsEcaUWFVpFC/GMtSS8INN4PyqtCLNsYIDgWpjzgG6JbqhrvgDWAfc1bHzejwXid/N5wMStYsQVMYG+e9esHXy1i7gWR85m/TU5CAa7xkUAADB90sW9lZ61mbERxB6OJ4YR9uh/LDqMzUzgGDDcxwb5zy9GFz2rv+cBtmAFdERmTNEDSCDZmVYPSdkRPMRzs5LdMSmaWSA8p1Jj50apIDMyZNMMvUfeeDG3aPzOmg5gze8o1Uh9dJrfmU+cV6cWKLLCJ9iWo1p5Xx9ZJANBUx/wjFXo40ImlyW/to36Z8/cScMKNQYAwPPUcmdoZGMJdoGjg24cGwRk4QCxB1uZuRrBAcd3AjiQvh4EnheTzEkx//O3VkbW9tAjxj6sRLaGCbNyk7eqRNCvyFhE33Cq0loRg/4xdhVqR/4thECNjgMZhebNdiJ2l1FrRSnkPGMnEohz1NubvDbtRUaMxCw8GV9xkeSpIcib5lEQa8lmBHlX47wRA919EvUxtsZmGcoIsUYEJlVZnR05+qiXXTVIk5HIcy1DHc96w58AnDgfdUypv4xS+O2QcbgviopS0XiWeRm6FsZ5vo11+vLidQrDqI6AWS5OKeKfQ89GNiH9jynCBOGolMmfOGV//lHP3gxsDZ01+vqccxVMOCit+6lLiMyS9z8g8pXY8k81LGt/hVzzm6YR/5YSy8n/OTGVpmPgPrvH+aDphCT1RWKzfAMPva+3aR0kRmwzeE1piazdQEsUSurZGY4cUw9MrNOlyz9j4+/SXCv7awasQqur/uM5Ax85PiRPTPY/byFomlohzOiIQphiO4+vEai3yra53kOp4m/0MLQxucCi1wLiCdHd3Fr8pJQFqnzV6RdG524+5Evy4Dm+vMhMappwo/ZV3/McMaKr2hfZCMftrSPGfI2Sl+aFGxuJY1QtCTwGuwpTqq8W7ssi8Uhq1HbM7A89Bf9FO9H0cbgjAADJRz6KQiehDXY0srPUc4HAMQVInAYuyNDI0cgSbAPHdKB+mvj/eZFY6dlAd75lmIzUJ0Ec/8VveCJ+yferUz+rogZ6E8/KfJDLjcq23/g0IPFsOCy2Wvmc8+NNHyfiwI1Epffl5ITpo7olQs5YpXkDdFUVOZFklPQAL9fNYdwV4R4xsnExVgqiqtHIbdX6i1xCW/QAkDnsF70R7WK/I2ruAzrTFUyMReDlmzlmXwj4htGjMZFH9ITFf/HVOc+2zhvorzkjdsCWIfA4aCFaAFkJLfDL69LiO2DiCbneylnzPJrY8bdvsjNjZxP6Mt2jWy2dnpE9uiX5aOjN2LzLmFPPUuli9ch5MOeHXZJ+XGNBhMdPpQX/nlVAe/wuswAAhB45q+ieaub/npQZ/nj+jOy+e/yamF9/EsREbvWQYyZgShp63Xy5DcMN7VqMqrMfWT42lmXgqwtg3ZvtC7z+6zimE1s8B33t+hf749w8uZ7H59q3K5oJvlL6+DzsxVShRU7IV1eji5kkca4Dvc87vGviEOmTGNG0GErR42bHnIQ5nvS7lSL9mQvXsnhaBwAAso4chVqnmoujZttGGXNfQZxby+6JySDdgOJWNbTbKG9BfWWprftP0wltZAUwx/Y3n7DtiXBmUPrvsWXdThpWbnh9dfFOtUZNDdsno2TthgufNjMq8/CTsdJN5LLFRXSJH9XcxmTzbaGQGpNaEeqobBeY6yq+wSEjMHV18Iu3Ywfd8y2GIEP1KN9L+G/siGVF19D/TM7ui9rdQAAA0k9v5QM2sICrHsZ1bBCQ6L9+QYkkdhgrmyq1dXLSNJ1yjTS1jVJ5LJ25w6OKqYzDiqqkAO+etwih+11CEd69vVfWPLHpd+KECzzD84NMcPybtgNQULRCGRnRxdKqvr0Qq0ubQld+yr1e/nptTUoRgrEGYVmVpBu9CFFqlF6DXGRdxXktyJv5VF7A72tw/y4uyjw6G83rnVDdblpqauT7N1D95AeM1cMreGvH5zDQSbM5/1ZUkq3DJp4BAHAb8YQf8N8n5N+/GCGmh1S1Drn6xnY5Sfrm4VFFVbS6WnrVfwtHtfSq4hniMowMEjIK4pIyDSBJFfE6uWZZBkVx2qZGic9jiEiCf73CInIVl0UAABA86beLnREE7GBnAM9alR726CAz5hvWcIxB9mNC/DcqXn8B2jjoW5oZwJE41mODgEztreApi/LCh3TiNLIfH9fG0sHEDJ4xwg8v1olZch4f+QSPGw/8aCADCOT3FJqOQjxxfvlOgG9mpWcCz8fwzZNinpg19wlaYA5PN4ucCPDEfJmOAX+CHR5ccMCcmNuP96qYg/VPf6/K4aCnulfl9jcJ+Peq3Hk3jvfrvSowMDr1DRQ/jH6qGyh+ivD9BgrfiM9BHUw41UlRZfqNSm7RyHe1Cs4siuIqV1aZgBJSe5ybGlkkWpB6DrQjY1ZvaL0Ib5vmXN0prasrcrvKW8Amebk9oEDwup+db5UAPqVf8psUXNpKOy2sXgWWexWGFYbEOgkBVitUr4IQ66kK2PZvffQ9UH0c9ziyJBo7Jr1cjuOalghRBpiGMmgMRFNpkrCLJUh5VeGc4gNwD2qZm2rByNRLolda9ASvhvWxl3yw5heLUQawUlG0J+vJLTVb82fdvKLG5y5nXtmU2pl6tto4/CjA7RWVaCsJjX3JvtfFL1xI/SryLwTHBtdVNtxfaTOgXJzhHCsI/ILHy2deMGXsw/d5cPvTBZ1RZ6HgS6rDhYm25t1qXV62g8bZIAyDDE5tvuB7WfyVWrwi/IxGKUlot4LG5m7nR6/Lvp51x1zs2vZod2iChCJQz7pjmk7GWh8UJWTsFWoaDBtTOWFD8hRzBbm2BULtEjLz8zhluq50iSs+WCWtur2x4vTXUhpXIETUFQEAZuHYb/GTLj7d/RZHgZz+fguGY7ThdNX6IzB+k1p/FOIpq/W/hvpN3y4/Bj5ltf4okP+OWv/jFpy2Wn8kym9S64/GPHW1Hja4U1XrYYf8TWo9rA04PbUeZsTfpNZD4f9TrWcOrWDCEZttpKFXczwf9m6zm2ED0XT0RnX4oxxvJPEHxc9JFYSEdtbXB9Nxw6Kj9GgR5Qtm2oJ9rE0sGa3KsMhoDTp59aS0TMzTTPDEXuNhc69l4QW9e4fWVEDWbopQxCn6ZGnXx7WCsO+Lx427sw94NZaAdtrlnoUPhUIELphlyhS7WaMct7akCCb5+kfO/8k3oWNULQpOvf47vqes1/8w+mnr9UeB/FCvr6+Tk5NjeDuqKF8rwSDVpJCtKC/ZWKcwqihPzShRB2LIUaSmoZVB+Wt9kGdsV0j/rxFzWhk4ZdX9KJDfoLrDBne6qvsxMP9PdT9+yv6fV92PQ/Z/kOoOI+1TV92PiXvqqjuc+P+nup9YdYc38/8TVXd4c/G/QnX/SXJOV3U/CuR/pep+ZEL+f1bdf1z0hzganH7R/3DQUy36VxnVwV/0z2zNnIH3gCoUI3gPqNLDGBpkZGAKdXZLqzlHtpIJs3q50AW94aL41LVLuUykqKgxs5WqUUXWyGZ2AZo0wh94AtVlOTWHWg6k94N879ndIA45y/wy2cYENfrp7L7LCo2GxFv123PLhrHCERlPCzlvaho7r1tcSUv8hqjSsIDn0Uj60kkVMY5leqb17Z2ojA6c6BTuOfJLhYTlrMKG2u1z8urnZ4jJH04skc/Lp/F9vLrGJWvsLJGwkrqn0I2c+klvpIEYvQUt1+mGok98EQGHS3Xd7qBqZ3q9ZiaGSfPQud2DF0ief/VFB62BWzMAAGQIx/9ohkrYqQswP4x+qgLMTxH+eYK1xbyfCaca6gTrHkSCRnMbUHJ5g5d3U58CjGQ0VKI+GEkYH/UI7LG/ZsBSdGfqrNbjPCzm62mtZkZao5KgL6YZwdjrH/YItQM7r5epl3U3nClGu/l28AMGk8psS97EZoTyGyfbfRJ5mgk8uoGayjr95vda/XtlHaiZFMHpA2KUGXQBn0s6CaIHDi4jfUKaGx9TCjzvsv/84XZgBBVwkTn78jtFAXTX+EHksAumjz5GByrq5d8XtiMz65O7Wnw2JkR7cgajP5SNn2gIgUWS7F7xxkI+x1prBFq4eCSx41kVXVdAwANcoj2UvJybwuBUHT8ytbmD6TTvNUZj9TCFLB/ry8YYJG7yocdzeafwrHKCry6UXAp1ARjr4H1XTF4nK9WsyasCRZArGr3dszJz75ZHqJU59YazzV9k+qiqDcyW/TXowM0D8tIIACAIh6Tyk047XUnlKJBfSyqjiO3oV7Qeh6KgPKFBf49+tspI7K/J8qxW/KGK9hiP5032f43P4x9x/Wkb4T/iyg0X0D9mScPQFnMfQZzqlmYlTOJA1LE2i2cv2whMn+jySAcrO3GMUZ/vcQJZtElB1PlLBXYdk1+SDDFHIitdRlX18ghO9sfDNQr9ZkwpqOBYoj2xGGNon2hvYsJbXX6V5NbZ0bvB1hpTW7HcJDvjeb5XdJfj9ZLsz9+9VV/0Qqv7yrcFW5bEuvfINk+Q9MScu4OuyHlk1Fpv3XixV2DhU6/GO1f2zsVlZZbgjsWb6Jcv75qHq6xezLD1Ee4xPre14pk/v6ULvjzEL23OtIZEsfXXgWuATrK24Y/xCMcp1h/n7nR1sSMwfpMudhTiKetiv4b6TbrYj4FPWRc7CuS/o4v9uAWnrYsdifKbdLGjMU9dF4MN7lR1Mdghf5MuBmsDTk8XgxnxN+liUPgw6mJxN6qvsb6/cJ2UbCHUWxQFZb6ryy3rQhKNGHk0HqkUA4QVq8gnhya/3RVTC1lJgvw9tUMyvhuNRrh5ldHYfp5+J77Ji7NOhoT0gpv+4uTWAur40v2ZG59GNM806JYjBaev5A64QHxCjdDKotl3s2Ycb20FQetiXGpvyeDVxb7je8q62A+jn7YudhTIT8+xVjDhiB5AAvWwn26ZbOLq6JGJZNDLdMvMrlMmr7J/MtBhkfXH/2h+zpjWWHQx3ygC8V3zwPZG9rJy7ZdtRxejvuv1+Rl0Y2EeSpLLHA3R3HLcuPoLQw2v5i8Fnj/z9+1B78ZKs4wAAHA7tdSdsqB2FMhvENRggztdQe0YmP8nqB0/Zf/PC2rHIfs/SFCDkfapC2rHxD11QQ1O/P8T1E4sqMGb+f+Jghq8ufhfIaj9JDmnK6gdBfK/UlA7MiH/PwtqdMdoMcQWnsIb2/EQQA6QP9JxCCq4NUfykyBhzXKJ3PJQvJePtzcpUeZ0WvnXa8ss9jNEdWlCrmdjg5oZ25rE3rRWSQzM4TBMrVJ8zg+/kt1G0w7evns/adxAlFm+w4kpm/di4L3PCLq2laQxvd29bGSxt+9SiSFXPhdErEHAlQcbXmyoYaTCpW/ZG7B231SUwd94WpxjmcLb0CZqhhqr5TepdbmPZ4L2bWpp4MsVx5xItYDA3rqV9VSPD9YpwZ8w6mqSE98QD85MupCLUBbgk2deFtXCIApDwVziR3/soClrPD55i9394Al+SFP7qxcZxmIOAgcmYe59Yvs4oUs4wePzxjXI0liW3m7vo+ueHqi9xYrw9DYDdb0sdktM+qtfuZlXyDAQAKD4pKrTqR9P/mH00z2e/HOIExctcP8BaAJmtAL/XbA0rG8650eKCQy7vdRFqG9puCqeo3zB/KH8hBOwmDSll0iY79IbKkaZbXcu6utCHwXIlQZIl4rbB3yILcZmC/0fDtRfYt5W8Fj66yomVoQaJpl/LaeO3S6Ig9Vf7XpXJJGfLN63ySOQlDzByHleiOBxXjOAcP48HgquyZo7NqLHdm7/7Za9qcwi1k1N8DgFJMzjgI4xi+MB9fRY+d7Sa2/1c1Y9SfcZZR+U0q4ILAz7bHt5Vi4dPCTye3hhmfaJ8BlN7ceXaYqfYHA44rNSZUZdCHhVL005EHIwdLAqde28ConYuEB5hX3eQ3e64ZsSKXYDxFTlevmsJRrgFQuz+FeOF+WuGDu1DX3Uaf0iexWnHZdHW7r/M4nTGoJAa5RZUmbOlSvJrJjRUebn2UI/bwu+NGJ8GjKMX5w+cZllvvD9GsNEUGHMXZRk2RlpRwn10lGfhO4aiojIdAfPgsSpWWuGWzoJ6PcXiBKLZW9TGYniKm1ff7T/NKm2/uOSNapi8qzvlFV/jcuCnJG9YccjHnpHjOwr5WMfHK7JbmOtVHU7J2nFY6SMik0wsHk2ncXUU3vJ9PD9l4A3qKmgsyUa/Uhr1m8W+5FAgQGvNBwdmPZt75MsIX3wyn8TYrXovSHRL+ancr4qsfEb8/WiueyXkQNUfE71Fc85KfbZt1hLF2hiLyDZJ6wwio9sMMXbJpFsKbVHBHH0dXNQKO3oxxHOuvtsgeOu5EFiKeVsrkZ7LBWoiWxi3llCiEEjBZiuIaSoR2rbkWEAa2R2U0tpj0heBYlnBHT2YiAVmqVoE3uxcS04poeiDI4PY+dz84gE7Dmx3QmdPZfqUMQ90Bze9cZnbLU5caKP3Z3+6RUVOjtcmdjCyQERl02DqTUJaWcVnbwkfgqBjxfvO53p83ntzouf+/qc2xtN8f4kRjcnNebiq+KORDq43/ypau6M6AQ3Ot5960AStjpCpeHmgj87zGRF4yjC3DTDffPdWwwttxJ+3x6ePLp0/rOTB7vLhkokcxjcRGMLQoWEXBdqxtIo4/neTV4jtAnr0S7EfydZtqmi8XJ2hsd0AWWP+uqjyBQUMwz+RcJECeH8KBkgtfSRxznXBMVkV/nhb6kHV2QO0HZGJ6Ynqttk9hSJsro80ApdwjNnhk3wZ6ioEart764r3qrPjwNNXKGoaxurd7Tet568vdOY23//Q04PzkTl0zbUputMZA8V2Ydak8onrxMtjw3x7I+FCpDuPuX3LJQxMhmZr6WfAeSbe6kNbD/Vjk4Pt652Dllm7BlGWAQyJy8K3cG8yclmba12JwtlmbYu7Gb6B+k3m7nucvKyRC+fcFlpxogELdMVW6y8GKLafEoZWGix8pIzjuT8hwvBcSwdJWYE94VZ+RKVI7dzUmc5AiZ2G3Wy0qL9KoxviA+llMbRo4e5jhEyMqhm+tktgL/YgMZS0e1ky54PmFfn3HvjzJq1Pxo6OM2S3BCl38Mn41Jnh0aqSyYS5aUZiYuFMFmcOVsy4w+p1A+0YpngBU8YlIEqZW6/c9KHmCa8EcDAuIeR55e91/AsOfxBhUBClVLWeu11m0S8YgdCIl9NrvwHvTiO9bezF5otC4y4cgSwJmuuXX+bs+px7i5zR1puqUkwb8GaN0sntv+HrCu5hDmOVZ/FMqxXcQPWHQZu9RJYL0SfV8hXuVMosrXG67rKGzWL0lAYtBtvOsJ37YMmQrMdXmpgqzpdLjhs7cMXdDGvO893QLoz7TP5/cuIJiyfdMcwcC+BBqYu3roZQd6kIFGsepG76usLLHEz1tHFNoGPNSYot9CuRerj5BmqJ3nzB/AhLwVtpKzEygCd9vKosbO9116MmAf4y1cGzWcrWoVf//h5gg68NNThkXjHsXCtVTEwzOU9njPXU2UrysH8em+S4lJ2C4uDcGdB4tn3O1SJ20lREeGiFF+pXaf1UcMf3L25MAt2ZJbATtdJhexyqzfO5kwWBTkookg92nzs1P/m/aT79MKlDQvvg8QNi89Zd1czQwm6HFOal6uCdcIQ5nPmGHvSO+8pVbqRkzvgZA13DauBVfTZO654frxfnF2ypKOKcDvm4VTl47h6Jct8njB6ifyXCt/w4jxynQeINjoDBCCofTRBifr4BORrc5aQp4TjLPz6LVyrXzZwaHuyypNYF6dtlNV2HF7dOqMkVdF0V+vVe7cspa6szh3rkVdvnJvakS+V2TPuTfikv9hJySPJq3p/vk7yprFXZDw6dvyjELUD2u267DObSmrddz6qRJ9pUZzEW8/9UNICqIN5mV3fGUclMJI9f26tFUkWnl5j431JKWLRYjWOuW7BAvEABcPzzAZfxUI3525ieh21Eu9oaclkUPgzBt+7nMVFX6iZzQxxPnuwuzMU3MleRNoue2DEn5A481oYIsBSn8zQmUDMlK/fubODLnfXcXlsvd87obDM5U5MWtnYHp3xe4knPJWXCrTHjYQOTPz5HmFUtBJ+2qB2T1ybcZyx73yCFImf28C36jSorXrVihmBdbfXdv/JN/1M9wquECGuzXsek3jkwsshxRrM421rwixnaybbcfx7qfb1rTWNWh7RLgTd7Vm3UgS5XZ+JUVh/zuMpa0nKNrpeAAHNRdRiBeTMAIUL702CeMzEye2FeaK9+dM0UPZbhrOiWl10e1/jmCJqdPlkcsUSxWuj6u+/qwhQYfZ1Hr3xZLlsDtt2fvzL8MWSqLtYdMMM/kQEHLc4Vc2H4lIsywqby7lSFpftuTCIPt/xjhGu2WaaHnczx5Udik3KWSacJOLEfoL/CXvh7PyVnPjMTJKlYp9upAjs994qUQULBZp4AxT78VhEn3rwv8lMBZUjQSgyJ0m5U/0JpHeT9Rcm3q9LQQDlNyoOH0M6adLyJ6t33BzbuNwFDqaHutCW+5FJcJkDFkwJqdadUxa/1bWP3CsrPHeBQjKRJyu503jvzhsPck/Z6lz2peZ+5nSZG5Rm9jz81SguDWdcO8lV+K9d27B3Gntr0ryPtVKlrhrzsPhR3MOU26rMfsgqVF8m4t9qJYOVh0Qt3xuNysZwzSzWzcujGoxuLmNvTS6+p7sRilXH8Xi31yQYwqxeUW13lo94ZiS+j2EB2a/V3Qyh9Iw7szOS+A0rDDUKRu9Xc+O3XLu4DKKqnvK9TlhPFoxwsT6T3zFYbMzF5bGcNPxiMMaHnvphQRRWuZ6mus6bqwrRXfP+Vu/tCs9ncPt0KiwRypTvyj3aOksXxiCSz2fQ3rd++wu2pYFyV0yGjwEFy0pcsFczq9OEg8g211zVR7Fpdh4RTy0Mrk5rN9YbxIkGkA9Zqeey7KvyroLNx+s+eXtip9FivyVLr39zbdvkSrBV25l57JceOthEEXO6ihRpzPfw8o0YmellRcaUquLjHvBqJPbwNRTrEzoPnSm7RExicbNJrqYjEX8ZpeXRYHkEm9oCg9yjpqeWfEr81ZdbjBfvaT7UFhaVeMRH191VRT10bvw1+Zx3T8bU1dje6gCjzjpd3CjTByJO4UgiQ/SMscrbl3GT84u2xD+P2KXgP7TB1H2uRfctUqzXrFGp6R2KwFUeJCEvupfNHpJGHvS5D/rqraTUOl8/va9VgMz3iW9tzlbCfPa1MhfrraLLOG0Gi3vom/opnto4EoXkFSWdc6tn0Aq0b2TdeH51TJNBfV+V3DrYAVQDOjd6Jo/Wu9ZFsSc/6o77Ov7ZClcT1Svl6LMZDmuG9B8l7u/i91vwO1v24RXnTZbtROwj+S26U/UgMRqP8F1YYyGt0o5ldC66kvtM/carfB8/WxxVG0fOnZmXHQJqVlSSCdLlEna9pk5Dq7Ka5/Jj4tFjgwtpUHI50Ns2H3pd229OAhd0mStWIxaXokZaK60Z9KZ/XVanMd6tRcPM/NZIVFI+DukpKyEpYfS+377dojF+XS/u+kISw/LZF5VfqCV1QS2ueb0FPAP8oSar6md7x0WzUu5HnidrBDNmWQz5lLqqDGRbqmYYuLEoq7u9ZFHKpWJ7/QpCzlE0z5AwI5vsTj4469CVL7RYxaBQOL32/GrpN73EHkLD6PVkInfmjq1Gfey9G2x0s58l1fmZV1w1gttSJDzD5n0WFsGBUxjxos4z5hIpdJZOBHvmZ0WGfTY8Ogn8cuRNwSvo84OrZRS5AruaocQ0kuWp1I531QdQW7j1MM/sP0ZgkL7F26MZOqutsTS3SHon2KjzXKbsYABVn+ME4GzaxMGXGJlGlJXiOn5JtLLSSHpneK/eiDjSZKh+r8Isl2TzbBY/6RhdKPEGk+ZuRoLd08RrPd2SfqUabm7PQ+fjVpFa+Ivp1yFzO4PyweO0GBzmXoyVMjPrKBgZouzLWMmIn5iuY8cqeOX3f2oJVHFXdpDfECLGFRpqJpEQU3YPL6u0MrhEyZoj/151s/4sJy0zz/VcdQFc80miDc7zOD0O9dNjN79RLlZJ8/PdZq5pR2aQnHNR6Hcdq1zb0kk35+GpQvx6PQ0ziHVc7IBIyt5jQ3/G3LPDcjVbYGFA+7Kpe9YTJT7+S99cnXC4Lj5r0vE3T5+L2PtjrX/rGR7nN5fU63EvysfmFsU3LhVbKEn1WTvNjDK4ooUNcxf1yrW6zzqXB5VXu3zR7wl3ZS/oi5cmJREK+aQldef9+4e4OTm302Lsr+rPJHSoRWVVv7Cd0LWa0MToHeVfeL+Brm/+rP/bOf+OQhPaagkSyU1fAaeVsNchDwKWegsX0g9KSHwJSRFuor3FAfH50rAArZOPu751ym1GXM8LxwGaMG4jR2nHWcpovL78KuHR3XKZeadERP4sRc9MVATfcB82CRDzrFscBkLKdFduk8Q7jkK3XJfuAN+m0UzUJ1ESfR0gEM2jVNya8fHeBmGhb35XEL2E0IQihb5GvtD2ZfZ6N1YiG1t8365jzaZzw5aexIA1F5soo5OnvnJe9lwg3eNxbfqbxnfcFdwQ9+2wPyOUSJGB1rRs3gSoJ1iEYEJoP7Bfn35L5K4ZHb8V3W9NdbBZsDmwmjRWP0m7Q5Jv8BEBNYIC2ebs5alvuFdtmNbuWDRgLZz1mSNyjwroCqXs63Oca3KWCTXsJhjrZtc1wt9Ne+5BW4WALv9AlCIl9p5U8ROpTzhcWV0FfPkROzhgrJ0ALMyODco+pnufqb0H4lTTHA1JwHSKWatTBIqNYVLyHxklCOQxMRGx28F3bpuOGF72V1jS9DcKqbG+noAxnFqD1G30SS9WHplhs0/q1l2ZaE4aZCAEAHClnlE2CUWu9eBs7c/mJ6cSP36Gx0GP7iisvIqSYzLRo0gfsmp+SzmUIkxtRfXCVNK+KpqgFympGbptQ3g1mxhC20h317PsdFsEjgQxv4Rzl2pmTYc0LB/XiVQoPzUyBHlNrkYHNenuK6MjouuJI8SMxsRhCLdNTnlVZSXOZY8Xr6sUfO1u5N14jIZanjapZ2cS8kI9dDhjt4hfxofKsCy8wsv/EpkgA1HNvdKG7MdndMjD6a4rdozXCug7vQiyZ7XSi7dLXZ6KYhxSmHH5YnvdPt90Z1IRR3htN4kiFpMg/PHs1ptF98GLZ+tjjPp1lmUNs1VNabjVpFGvOnztsGKyut8uO2LoqNT+fC2Mbf/hmD1kuoQSdJbmAUJKHOXaZRx5TC/i1e3EDTYzBaa2TkdXTASZFE2R5PukGUp0AsiiK6Zq9N2qZEGE85Ma/axOCMlMyA2TZyUArq+ftzhN3VG5vTBJJd4ZduMwyaKZbWZwH1ho4JAiAwE3rjAi1Ok+0MLVbduJfVcEyovxeYCIm7t27yqALxRm6EOQlmtbOar9mK9CzZTgqn1/lXvME4ILfsF2CxveLmtKoIixMv+bmxsWZOIi6La4m9HeretENZ+awRHJw7kPhQUZuWIoz0tFeo3prHL676yNovSvv4tySCvONshY8FOeqPRIwihkf+PU1kSwmbmW1+nx0DXwM64Uq1TMKG8sxcBHW88g3F2F7uVcWhR+Inniq85km5oppK6h3IR+sZlyEfRnLnehlJeB/ACvcISkUK0ieikI7uiNWOEyMXSyC2+97ulOtyOMhSOseK26yYpdu0AmKmQeYNaix9c7brrI6YuC6EWOUMkWFGc0daa0BjFW32aLJ4W/Rs+LyTCWLFUrZkwYpLsEDncNHZzKPedD6cclCsFKRTjjGdSRG6Lre14DEV3h3SIXsN28GOkd52GN64WhhX4mDmuIKtdTLpCsPFc3XPesMKM9zphvnLvVtS/MQRavrPriGJqe4qvq3ASh758nLrCaXVn3IOHyBwUFLju826P1Q9jEdIzX2I7TIwrE5AvF3N/aXdJumsnRlXzmnW4HHgyzK4yR8MGXoaOmZgvq6wu5sBG14dC6+6S6zO4xv2nQJtMtzLuhs6Z12wiTtqTDdw/YN/qj+e5XpN0JiA2ZttOmobpL+mZTMWsGrwj3IoMy0myK9bLDxVmfaQiH252qjRB/ylVd4dfSuQyXOXqNFNwam0E1N8ii3xFz8vG0fHJuPaBbdLB4Bn54a+qlTh4jNh//Gt184fPA9XiJXqTN4SUKV6Ev5+xWhlwayxo/NbR0XcARFiTfemSNU0yBIhzmUNESoDYY8sax4m5tgzijYVZ2sk8xv2XmhtYtO416I7+pqBsxX7sQhvtYQPcjO3f6bAobNlIeS08XyefcRpv9zGhtNpZnhDTdpzu+z1B8L8tc5aHn5eekQThfSTXsSO0vBfN+Zcr6fPutm95SCZ6CZFhdZFAinY5lERaoOvO2C6IiWJq7zymEdn+0HKTfn4tOLqoXr3MN6/5HCFn1kkqjNmnAhgtPaWhXX3csZ/XXhIfeS9cEdcpV3FF3gzwHltruVKxqYyI8eNJOdhYnX5TxY8tM7Wj+B4zAhKdqGXRxHSWsr5ZsCklCu87gaQ07F8vXCDcW6BF8ddxjrzogRx0iZln4erOPwkLSfG1vAjn/hVPEVR+d2815zjeCZvUp61VKl7Db1WZfPrVK33DnNyDDYD53XoObMNU9EvHN2Id6GuQ93Rv9Dx/P6JJ3uz5Off/QzL/JfBwzVhnsqGl/w99vjx1z/+bCav0D7w4Ipa2ZOHPoogHWAQcXMnUtQrxhzOPZ+6FXmeJSa4fINw02fOO8Acbpj2MFOFGylzz4sR7QTr32FX/TyYlB061kT7KgMv0e+OLMYXgp0D9WXjJ3o5Qxfajr22TN62TeLWeFvSFOgSwf6mFSj/dNRsMIOOG15ft9eVEFGxe4e3voQBHq6dgLtl3+uOSPg43jKkfapkXSZxFU+Jbyxc67NitjSoBoSCAUY0hDRoP8B6EbqpA8Bq9YWj1R42fczD7MrGacWUmP+D7KNjIijszcPUhB0xNFvOD9Ygyx5VkFDk+jhcCKMLnJnsIQY0VF2nQz183n2UEcap9we9ik5JqqIJF0Vk29LWIjZxBJbL4JLH4FNpXvz7EA9rX6zUiEq/FBu8niKetN8gnyTOw3jV7mpi4W+BSRa1F1LgYBdrccckrmX9yZKfUUSe4u6ZN/Mf6GE1JHVTjxNWxJkMP74VzQhP/bW5FEeFnsWzOxXzcHGerYQa4Q/Ci3FnoxyOy4jW1DUZ9OdSNZfjrauOj1WU8dm73oxt11IeP4muWRu5x3VQK1ISYpTkmjcrQEebMq5wq+vS0o4NBrLOLxOHPQQ0ylIxiVM2NtNxozZRi9h6d89Yp0ceajEb3RwsHuCPYIkyGh0ksN7IIgTD902Zr98oIvQiDHjW7QFTJzwd4e8qxYP+Tkrwt8dQ747hSUI1X3Yt37R6SMDJ60C1SGi65dQCD5jFDhhZjUgjihnp18+yntJXVvn23NZlSTVsZm/SqblOImBoGWcR105fmkgA+E4q/iKvPe7z7/SkRpYU6IXR0HdhhBJY1SJRspdKsvat1A+mC619K+JleYtbgWcmbwZb6Mo56tc6d71+NoeyPn89gk2R/zZdAY/cyrP3A4gWpLafsFbD015IS8qMXbMCliS1vCHxjU4aDOKRncqNMcksK6ekux95YXo/p1F3aMmcTzz7daVVFn6dUXz29vC7Q5c4UOXczfEh+redSrt96vU1mBpvqoonUUjWPA0hq1rj4rnLGaIK2HZiWMRO7l5z41X/WR2tnXpsEpmhns7dReQV1V16wkmL139jqC170nnlFi13t+UjigWuDrOsiy7QjDxF3GDYnUzfV2T8LnoE2zXh+utaqzktusk5sorPA+j/Xa4ECtOej2deaGgeBx52+KwbYuiwH261OJGty1o3r61gt5EmHBXXUv9gkIv9Uk3i1870WBjNZqU+f3QA/xPkZ3/guFF/5cBiRzkuGhTR/FMe49m1vC4ktsIrZM718IuD1HoEhhJvBwe7kjSYRQWCrQwDTQ8dKF7IWXsbbkqiXkRPcZwO80bBE6dC6Rb4SG5/aQ8obmscbl1BO99kxZlgtbPTjzp6CwtvWGBPUSAIBuHCXeXPiHoGCqZ2ABh0RD+MNAIAOwlRXYmtHq75CPHyqA+zhwapIy5Gzl8yqEyW2v+vN1m6q2bDzW1calMLuXzIiowSvhS6MYZfryyewiP+G0aIia5ceVp/0RTh8O9pyf+hDYpPE0PNRfvFLO4M2uS4dcxeCInwoGlV953kZaUlqbyR9MqsZS+5AQxYeTSEPcBrNBd0k+MlHotqCRVXvIJboXGRzpbJzsdDK3s9/qMtB+JXJsTGr2dWmttymvH+W/4ohHi/ICoUNT3bDHarelqq5p7Zp/LCjNWOfLzY/pLcodWSlv6AW3PUh7iNT6rpiSo3kTMGUw3UHqGqWZSyBKrRkYpttJGXvrZYgx+RHZ+tEgNYrAk7JdhNK7CLc/1oTh7CEGVAuMuG2tmUmLs9MjeZUDq/WCBUxly1e0HXrucWwo4ASyTtGDGdgy6s77dVAuhbCHIfRLPm9nO5cYLNTUJR6ZqFabYwG5d0WzqMnvnjDt3FMmhkaqywlGlpkZ74pL75TLkBa4DG4Ny9wWDOidL2L1HBCnFOz8UMmK002du/rkU+FYXnJPHadVUG+Fa25FP20RR88V7fYrVVyFIWYMNK7DGU8pjOwyn064ULJvVBD6f3SXGy5W0loeXI90TQoMdhup+Gph6a54p6Lw3s6TNZJztg14/UMy+EAW2ZV6xOS9AUPbGz7XP7XYEm6OdC1ag7ZtVrBMSoQikDeoqcxIbwvF2QznbX27Ls4trwjmb/evKjpLloNzzqRnbfNb096Xr2EuvIEbFNNMWKUD8T7SY4oQOkhmB1a8jNr5cQP1oBAuhHBF8dYE0eJbmBT7Dm1bkbkHtqt3muIbsHbLFMKsPNZLX3KUuS6SuYkM4z80u1BcfQNLFlWPIWXs0jnw27kFD0eXi6zCNZ0JzfEa+34GrrSVuV7xFrcLztROTxk86tFP38o0+PyEjYj6mxmBKtbmtzJcqQB/uRVcwLn9FRFFladxee47/74P3qIibClBZSVPpCeXI0iYjT7bcJR5JLgvQYQwK/ZY5q40xo6YlmGmITe93DR/p2XTUPJyC11l0mrEIGZkzGDWA1XHvauqYUnQk0x7d+jvk1zXYxQuIAEADepRTyvePx4yB3szS/iOiRD/LNaJ97if+z7yEa1DQMRF+rt9hz2C/5hQ/vxJuffH/2GyIf4+5GGf3ktQIcE/DPmd1fD34Q5b8BJAheNHAH5p9ft3tB/b9v77BweYF3r7j2jQJr4/j0MMFWf7R3F+ZuILTfWw/e11KKpyiMCxTXyPajIFVJPJkIDjGupCN/ywmSwDVMPvwhj6n2aTRzX/OlTzg5EBOExqfx6dDir6DOzRvzviC52jw+anLFA5kkYBTmJSexQVRigqwBkAfmdVaDaHb9y8AcXG6NggPziWehQnFihO08eD++cJU2heh30/eaB4KaIBp2KiehQ1Bihq6WcBuN1FoVkdfotwQLHCRgdO6i56FCE2KEL3jg32jyOZP+fFB8VrDk6o49GDfryMMAD4PUWhiR0+Fwn9eNUfG+QHpzGP4gSC4iSNCZzAQPTnvQVN6u3xUY7XUTegSFGdA07DO/TncLxQcA9PAPfdqcufJ1QMKqFoWMApe4ceRZcHim7AibBhYysKxXbnZIhHkf039GE/TSooaDds4LiOobAvbMxwADgMOmGdrmpgj/7Pg43Q+TnsaskNlR/688BpGHTCzgrpAgC/dSasE4jpsUF+eObw53B8UHBdJ4D7/swhdMcdPkwnA9VxUheB32Sd+X0boM64QbXBAxf4fV6U3zfj8JkzSahmWOABv8Ml8qj+F4Lqf3184FT9Gn+OKwGF23sauD846Aad+MNHr9ShEq9BAPx+v8bvm3P49JMuVHM6LgH/PRPD75t1+NyRFlSzhi8D/x0/Qdgn4b0rAPwuftC8D5/84YLirX8VOAUXv+/xDp/bObxInb8HIQRObpIH+8ufkgiAw+Hu59HpoaL7wB79+yMkP1+VsUP1z8QxIU7y6aVGDMBvbvdzEFYokA/HBvne3O7nqeOFSh0VCXA65nZHkWOHIvfk+JD/8LX7OT1+KHropMBp+dodRZATiqDv8UGP9XxBfwStwI/2naUdrB9BhmTA6Vrawb4+/XoC6O/d7H5O9yYUXfVrwGm72R1FmBt6YXQCcHPYOlcEejVODpyqkd1RVJmgqNYfA/mXAsdhgtAvc2YK4BQ87GBXaVJ/iPcT+7rvPnYPGb9BqzQ7vwx6PJUGekHQRAnAYSUHq8xBRwXAaSUHnZ3DnmPQMkfI8RDglzlsqAH4Dc1glTm+HBvkmDIHtBZwmwaA27sMVi2g5LgYJ9ECQLTASW3LYNUC4uGEOsmC9CIdAL9jGaxagPOxQU6kBWwfAw5+LUCXHji5PRnspbwOOOBOoAWIMACn5UwGqxbw/oSQJ9ECOBmBUzIlg3UZnHYyxF9rAYe9uqC1ABoQcFw/Mtjf/peZADjMv2Cdrpxgj/4rLeCw1xa0FtB/bJATagEuzAD8hl6wTiCDxwY5kRYgwwKcmqEXrFpA+UkxT0ELWGMFfp9DFuxawAgb8Du8q2DXArrZgVN1kYJVC5DnAH6LixSsWkDraeKfXAuQvAH896yVYNcCVLmA/47LEeyTMIQbgN9bCFYtoPvYICfTAqZ5gJNb98BerUjgBWD33YHmcNixBrpasfTLoCfZU1rBBxzXAwe64YftXeihGk7ED8DngQP7aktWAIDDNQbWWssb2KMfWWs5bEYCXWvBEgRO4hoD+9P9SQiA3+kEms1hRxHoT0JqYeDkTiewbyltPB7cr7aUHjbhgF4Qs4sAp2FaAnsVyVYUgNvpA9Yq0pfjYpykinRLDDipyQesVaS3cEKdpIpEdxOA398D1ipS+LFBTlRFunALOIGZB6xVJOfjo5ykijQHB9wJqkia4sBp+XjAWkVqPiHkSapIchLAKVl4wFpFKj8Z4q+rSIedLaCrSEKSwHHdO2Bf1zBLAXBYZcA6XYXBHv1XVaTDBhPQL821Y4OcsIpULw3A72IB6wQCkgFOw8UC1irSkxPAwVtFuiAL/CYXC9irSGJywO+zhYC9isQiD/wOwwbYq0g0CsCpWifAWkV6dhq4J6gikSkCv986AfYqUoIS8N/zE4C9ivRSGfjvXO0P+yRcqQLAf6E+rFUkGlXgFC7Uh72KxK0GnPy+etjLDnPHgIO+bB6a0OEr0NmgEiirDpzssnnYVzKVtwE4bliHdXvsNQ0A3hvWYd0e63dMiF+vKQ9faI4LhbXxD6zvLlf/PtThq0ygQ4VqAkffh35Uki9AJXkIAhx9E8p31a5DFzZAX1VAbQ/AdhPKUW3Dg2pbiBPwy3sfft7TxFDN6/lZrON9E5071MADIVZn4KdXPqCg/vGvEoAEYEwIAJyc//jT/xcAAP//bP+6O3C6AAA="); err != nil { + if err := gres.Add("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"); err != nil { panic("add binary content to resource manager failed: " + err.Error()) } } diff --git a/cmd/gf/internal/packed/template-single.go b/cmd/gf/internal/packed/template-single.go index 9419e4682..152642675 100644 --- a/cmd/gf/internal/packed/template-single.go +++ b/cmd/gf/internal/packed/template-single.go @@ -3,7 +3,7 @@ package packed import "github.com/gogf/gf/v2/os/gres" func init() { - if err := gres.Add("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"); err != nil { + if err := gres.Add("H4sIAAAAAAAC/7R7BVRUXdv2GboklJLuZgakWwSlpBukGbq7U1BEWhpEEVEECZFWFBTpFOnu7s5/Pd//Pn6MgYDvx1p6FgpX3Pfe++w519kKMvAIuAAKgAKoWxupACe+yABUwAlqbWdl4ARldTS3MbWCQsCm5k4GTk4O5obOTlBHVRVEANRVQ2HYoVRX3yQDaWYBtzdLNclAZG/LFzlvIyMAwPGxggwyylcFdn4SAADwAQD4PSHRbwjNTW1sHaDfycSbqy5Vs2EEjVZIzFbpBlziByyuxCQUF+AJQL3FccZ2Kj/t25O+MdVOh+uiem5uuhEP0uqmfwF0ghEr6RIJ4JzCI9VSAHn5pMSRibiiKzRCt1MFlpK4GS5zv0tXz3+NndOB0hF715zqXfM239v1Y+RAIZTwm0QzKMTdZms7K1gBWM9cgX99ub2HGN4DAKD8VF+Ev/Ala2AJNTG3+l9XKap6tv3c2L7FwjX+hfDoYRlFhFv61JrvexwDihQjMxADylEIV2lwMiVl01M8zCv37R5Z8+kWg9YDvXzdP+yvSurWri4ebowRASMIe5/4JZ4/8cKtdOXb5a0oS0ppTglFve3+jGU9szAba3VQqKzepoABr4SwU8kULZEnrNUnTee4cHgfBHayLlNqf6FkF6p/qYfEP90C3ZQc/JHo2Q3IO/4IyPKtSIZZWunwd0P7RKwIvMFB3Z5NkuGUMpQlxTkSio+kknmwDAwfMHBoEkHNVZ7e0DUiLcqWqBa0gJbef7Ph029jAmyiTdl7jR8P8pYJC2yPzty3oHM3QHi3e+umtgwhlLGumByli9hSFeATtNhiqTVGvROq05NNGkycV0JP7pTyHDtnBJfKP1uSR7qq4uuzkpVDgTrcSi9Q13GQ6BOcngiMSh/SK9lE+GTX8+5J2fg1N9u+u458vetJ2KDhDXuMosnYoDfXUNMj+eGHNAovjagRkQZTOkp6IOpRPXXRYqrN/cpnkWMmNo2klJUf3KnClvq5nTPcpE+6YkkLf8AtIDn+/faQKaPxy9Tu17uW1LGA5gpGhKh+VlP5N5JnlroMRGkZWUyd+HHN8HRq7S31DTMBVrjTI3GZb+7ORKkHRD+SKg0IoC/r7AE5croteX78oF2Dt9yJr/RCOcoe+U6CcYhqcmes3avpSuTD7XulY2hLtvTrh/CJUugNxtjvgigkZVXtPKuRQlCQ+QIpnig7dSRJWNnxMCAZ2gnv8JYaVC7Xq4yTswn6FGhYl1lboAR0qKAygbneCQw2xy5/CLhLI4L75o3lsBir8Z505O2nx04CiSLkn+R6MpYgHpuzU+9C6cPGiuFudlBY1aFLo5D5IZK4UfocoQYQPeps5nFoW6ymyG8jXxoUOHp5LM6VVjs0rz0aHPClYkSN7k4W1yPZ1K8qVtVMLM1VwUG4Y85UshI8l6+PyAe8YeU7JI8pd+W+U9x9FMbfZxKfJtBlEp+mHactOoIWy+g9LEDY9zwy/NORnWfvPk47PmtyX9KNoxCj8t7PoX4Blonl+x0GM4bUhdXSq86km2korRy0y+9WRl4KH8YbUrK30xVhACtvVCxROo6bAgjMaUukMP2Dj+y21XaJl+63vhQZ8Mz2GvMTYtXQMXyj/+xFzZK5RREcK8HAmpryAUh1sCE9TDN/EdUhrytZdBHSw3FIaL2nB/p3YkdSYRw8gAcAGeTTJvbVX0xspZui4rI3wbLi32e2kppdqyuzdJOsKopUM2udFIuUql0jRAmlpUmmVeqSNGvdNNz3hXI5xBSeAQAAqhMLCvATL94veA3szF3Yv3P+g3XyN863yv8PFsQMamVlCza1/Q5qFV4gF8iOcXd6j5FFPp8zgF2+LSgo7PVUrT2V4eXLLyyC3Fw7r1GyP3ZyHe529zTFm9CVwwJqPIjDXfOpWR93z/tY4r81R0uqnmXPqKITRV0CHXQgYilZesbJpwh3Te0gCgyKhHCQCXiG464Vic7kcHSM8iS42dhGqaTYgTnI4iUh1gZMRvxD3OqM/NXb5G0HyMY1NNGNvWIu2XcdTOZVqWTJJ8Vs6UW+N/EdFWJXDgAAc6cWAP8XBTC1BVvbGn83btyog+NHgQGMbD9/AozXckSOJQz4KxtP8PiBKunuqV278yVLgM48PKiQuHOp/NIb27LSy0AoKqGan1xE3BFByuBDk1sbM9by6F7fpeXeHpOSAwBA6yLSHJ2tv0t7Wy5Vki7Zvy0gkpY+BebBESVIKWoBQDg4eIi4phveWHA+e4UDmq2HM7nlHNvatpM0jhE+x8zgPO77DLPjVYcrrwI0Lln3pt0Dy92vZFoTWRoJ3PPz/bxyHEsaFHt5lenxDWRtnZSrjBWP0bld8DnocmMuh7xsuE07GHY8fLwuQ4WjSn5zUqSq2qko1pt55JZUhsMgGV2VQQnHey3bNUvzpJcuV+SJTFw7hj/qtn2TI8b+iiugc3ugndx1AyTSFmOelltARJTOgfEoxgKHM7x97/oLKPhJ2Ah+RfbU1WuLZcUbrFOhZXFeiOlyc7ddpDQqxwKTe2ppoqKznX1LU2fmbVgldJPR7i2RplbIadJBxXGV9+gTj56k1TV8XLFBUkqfvztjPVDrviQPdTLuTBRgcUHPJ6oaf+dMJbeHuVbT45Z2Jwk9Y+zmFCunbzMqhoH6C7bY4m8hOUiZENT3WgPwGzY5ywPwkAchL7VcnNmO7O+Rr8C/8yvJCbNeDtiSGrgZpIpTk9q0y05fvpD/InqQTsi1ofoZD80R1w5H5RJj/GV4p+Q1sOToFluSfRr5jvLXqFDu/h5uGuV9wwSSee/AHdsEoiLHeFp5O+JHPiul6mLbGHoroDgUCoCNCpShEa3jQIkObFA6zKxkJZK/DJV8HdLVhw5fZp6hQ+bHybfkkh2OODQ5glXCLyAWcujKqRc+fynTuZx/sCWyOydwfL0ldaqfy5vlCZEqswOubHzZ9KCY+7bRzIbUbTdV3aI0YRqRj1fuuSL3B77yFsQvfHXJM0dbciAN7Omqzl5BLOlCqou7G0xXqzeq+7DJxeuNM3nE+iidlqc7/vwImzWjixh78xz/rbdv0O94vhe+2ytQxJwtjDp9fLBqrEy+gM5POr4kWkbCd7l2PIs2SejtNBWJXUSvThn+W+kP26paL+bnBMyWEA8ZiBOjMxDN0YWXSVKlbpTEyAKZlYk+lzySldI9FEZ2M4+JZI9R9semZqe+dMgeKpHmdfuglLlH5s6NmOLP0TGAvjh5bSpJNJQkQKaIaOo7xhtcbI5spjX3mwoH7r0r6MWe+vykA6mZno0yVolruC2tapqedHV8WOBoPFyE4uCJsG+ZLNR0dLGOZQ5QaOljMLL/VDc2O9K23jVs9frQOMryAXv6sqgexi0eThsbdb08xFWm+ohb2e9u52wXessryJG+eMxnrR0nFrrKXGG59nyYbvsJ7YMyy7UXPAnkOO8uP0y41vnenODeDQ6hVJXovYLMee6QqYMm3bysR0HVJrySwxmVCSxoER7jJGBWtdwghyXbb3aQ8Uw0B7kPzwYtvhT457hx5B2NhQ/NXktvjDHsFZJ1r3dAodCnFIvx047GxQRNV+TOv58Ldvxs+MD62pSg7ZTRB8hnWc23roaOZsk5Iujo/uhFQfmHjU/TI+9XiyTXKOdt1tHbpeJVOJOQ3tXmK7nfh+3SoJm/1GJVCuUrEMGcrqWif1Ow7nPJi70zq7DS9KFg6UbAtS6s4Hd5RIUkBS417Tdf26zjhmw6D0r0EdgsPcJRLFHVKxPb2RD0WBeMmUdsLAs9SDIbFaJ6pw1qccDLfNCmwVxoG7Hx7hvaTT+9Z/sQ/bmvcyUDq3Cm1z7pj6PjEkIGZ65I3IqiblaUqlC7wl8z8RxT0pxjbLlD5GOtKaIEClW0IXaRsUZagHCIEMJK6FbGWrws0OWkgBQ/30f1fNQiJFjhc+hivpJ1JP3H9ilm25XhTp9UPZeyjTalBxHuxXhufE9UrGmHShoCyCsquSwtjyPdrpPNF+/Tpe6lxURFitNMMHjMGiJF3ve6tTRv68IuhZWtm+l4wK/RNF8wXR7qrIQok7id4jqQUzztPbtEuGUZcJy6Zdme57WeG07Q7ZLRslrzUDcCtFiwAO7N7vJX/uxJTe2MnTfSPaJuq2rI1Unk+/FeRf77FV01kGZc7MznlIQGZasSgQgWqZIXirt4CT6FboOkW10hIo5I/YyhqYb4BNQbC1aOT0gmrwkbtvKtf9vCZurNq0rjWJ61U1Hfd34pgawsU93sdedlsWeecnde177N6Msct+avCIQfnMCHU4HZz/czisiLaopx6qVvmfhFJ6FhJSWGqR8z7dXnI28rq/fofVR9hNyqNI23WfjufSugYSvI7vHWJCYZTPnsmc2daMrI7Fq7AELlqGXL9QT2+iVLuGNEdF/kLaHqpR6eg9TsegZlwbHK99OhkU9Z73rxVJR/Y2A3N8Zu9+HyZi3Vy1+G3/twHyqcnDr36oajyLWGdNauZDK2EsOu/X00eS+X1fHNgYDksg/uenFZH8YPmU2KpR4LfCYs1ZmEih6bBgslole3kXzaYvBO3ZhzmXPqegwfjV/YKLTuOqSjRmzNDuI46LM/erxrmOtdzRcmyrft7zONR31jNaxCi32yY+PGNdTa6a/YwX10R4Y22tDWRKalUK/eTWsliCf9XJzi5jMBXzkrCs6xzVJHyEJUHWZIwRxQtlRsGipgLkntdEPgUYBwlhbiUetIXkybu37fK2wzOK3uwFy+eNIkHSTDo7fVIarsd93GeB+vfljAsl+c/DZy5X2MFybzCGswKQG3BI+axXBChtWHspYqvozlVSc+dNJ2vYC4G7V7bLOTnha4csPxaQWrJNOkPFiP8T9hLaEuEhUk5eaSr1QE9sBHYRUHqMaULpVq4w3SHCVhkn7qxd+VnQmtgnekyZ2m4M8MJrh9kG64NFW8KeMIqOSoOn8M62LMKpn+su/p0sHnLXI8O9yNsjqAQI7LHrJkRkK36ZaxvFv/ddT/Q9mlyzTSqQJ56V0mh3o5PtS+cl8KuVZaBtizZXlpzZ0EhL8gujcie3RRqwpTUW05uY6/MW05wlyr0VCLi61ITIjN0FRjD0JQpfs2lfTmTrqtyrC4VTF0TC6Ob265flEByWhsexVrZ3q5mJk3HLOeO+Wgz/ShI7tG9RcHVCGyudGkftYlhKA2b3NQJbI3uxu8JK81ujoNOODlwqSERzefUUzNE6FXyZvp16PcbZBLOocqTPj4fFbTRp4PxQWyMMSWxmBWGWhr6OYQKz7qXgy2LnYow3nNH9iluEIiW3Ugn7iDyhzBKlYiZPS1f1PzG5aVkUp33OtAI5prawkP/Vo4XKecxfb4Fmo+3pzlEhDzvYPO12XjycFLlmrk+C4v81KeU00Rsa3FZP2nAF+sLCasN5TZDTlUe6ZED607kBexXvjoYpFGLegr0WSx++OVQMHsLHJi48o1SQn3BbVSe4UaKwxJ3IaRPxCSkVveapav7UzFX0VsTRyqiuJUX2KVT2x+YiWkLPzlaqvJsr92rM4NcalEIeae7hqG4UuTr6gXAnpfzxDH930JgXbV6+PGmN0Xc42EFxtmAcer7F3FTS8p35FsH3XIwI+1w9B/dod5N/pmn3mTcvNbRBFiAXhRP+YXLT7SUB+Wwvv9DdYy6l2vnty7U4og9EloY8FeymL+lQofh0T5VewOo+VDtG3DDF8dbKky6ur3XQvryCilOrx5vM+Ix7VZNY7UqG0eOkNqIZfGkIuYAurclXpLYvS8N/FRqz1M1Yiq0OZfO28Ys3yUuneAP2Ap7GbVj1dRNP1hP+oIPmjZm64XHmwyKnR54xpFjU482K2cqPCpBu/LksAge2w1Oxee/bkXnSLq1nTSyberpBz6zFyH1+W0L5XEJaHFPyxjRCzkRuvYjvWjOmpJsy3ttlD6AldRiRRto7xh1Jc9sarBaHJQh4KRu9tE+r5q0rH3w3vy9+CAe1/3WrUm6Q0S6JfSWFdRn3/+xiCtD2n1KOorFRgUDjdd10DtmxTPy7gXjUPZZAvOsxwOrPRQHcy3Untt5HlNRcPzxTXlQjrOVy8dqbnLF1mT5+TSvamH5p27S0SXa1gVy2Y3nhFX7hqk9pIYP9pMJ/Vm79xpMsQ65OVknm+X1hBmX/PQetiRIeUbsRi4tGz7YAY9SdxtzkIqg9nKleDQAlVsJHDLp4sgqEDBzHYNbXFo/QNNociBdjgZo3RVJoOLl8YgUiu/AQbyUQqI9baEYK92+LyO1srCMoXeQ2jXpVy5oRC6fpcpwM2smVsoNTqLNC/DY5JQ/PNn6O39kcMGKFm06XDDYbV5Ifk2ap4wxThzONkWm/bB62SHJ6lUvT3SQZVanp7PwhcT1uFbhStYNh0X9ocUHk4yoXNb+IE/y85tIqK/FudaxUyH+8RGjxWv6Fcy8Kn1gaq3irPCligZruhwC7nUTRXvyA+frY0IaTkKFIrVthtQeZjYBegLNURwLaZJt3hwsHudG2bHb+3SLtfcFhbSZK/9isAqveCuOOAx/nljRzfbQkCgBm6CPgsjlGPy5jGpjJPPluGchW+n1Xq+yNKgzlUz77zHykLChLserth8V5426wZbZC9EHf6z15d4isez655Jn/C8anxhWXKLsMJSWabfxnVujNUDJWKEv7xPvs173q0qtOqL+zfD3kgPrtL+pNsU5KJhn+7I6BUXx+IWFGhmxTkRG84ld6rH5H15bj+lbz2ljd43JrxUvIVmaPF0YPdScGeZKdMXKXLp7bsirmsRr8Luh6z0lS1lH78nv0tCAbqF8gYbInSX8RrQNp3Svdslvx1FXxSJDTSjayLE6CRYyWq9uvoyOdGrSnbRNRVOOE/JNxcJdDcykFMKwj7vmYAOypjtLmyWestd5lno3hNyt3ksF+lxjFR/JwTCmJiJWzs52dd4Q3Q3iAjOTxRFNFp0Ivq5zl12v7fj7+XiK+45dG7YdW3Zs5AbcRRikb7uEmj4vCh36QFzyqQOyy0TPW9FT7gjB6x20HsZSsjGHbucEI1kyzAMR6Z3XPSzb0i9tR8l7TwasKE73i7dHlxPG2+YZtonLzH6CEKKokGwQ706s4tLbMe2oWfZiLmEGrhA6h0T0h1O29/vstDsJhtu3EMw3sOlD8U/yHrmw1QDQlO4L06TEe8vU/FY5hM2X153qVBJ1D62LeZ+CCZG5xZtP5t/O0PAYIJalosxuS2zUt76DIFSU4SMwkewFIECBgYc1ldbPU2zUeOrwYor2sHQsFob+mT0kcxa+B7oJ4N4BQTW7X4ZCS/ZRzyMCEAYAODKPKVtFo3e6MXeOZovSc8kS3mKx82C5nJDZR2xwHSqV4klbN1CQiWcJkJ9Te3yTNqRGsp1PwoKczT7xsgvnDdBHaM93U/zs+1B3Mk3g5IvEdbOmw1rWaXUi1WrPIEaQ/ym1x+FNusfqaDBoRlIguLG4hLQb3RMz/jV5KUu5E9WbKqWTvQ0CW6loCBVZU0bOJiGPdcIH3l9UC4sG0hn/CGy2i+YkPI6K2mtf2VjfgqyLnUkM71S52SdiKHr81AnDmuDJIfM1ZkY8LDinPs3e3qnErP9aSXsGxsHaTTxGASRKfM7OcveQ1dQG+KgA7qrcsb5amaM/Oq3kYidJzqt2azvfZUbNXZR/vpsI4LzKHbcyXH2PS0ElfE+KCOBduMqtgKGH9n6XuoWp7kiW0eXiwcGSDZDWyz9HsVrZWYRBPE1M3WWHjXKUJLFaa0BDldQOhtC4zSqFMA30b7DY+aNxO+HQSH11rgHm00OxXz7Nf+xpRY2BQIQwksEBtXr37+Dq9+xH/+2HFIUF3gfDrdww58YwBeNMA4kyCq0/zymkyJUrW5GQOw0UOMd95jgctBDh6WtAPcNZUjU+IfgW9tblpSSYmj2uNuPAto2SWs/tdhGpY8Uxt64DuaLo8WRifYb113nCd7fGEMc2Hwb45xVkW/0eilIZeqzTxp6GVeOa0czwXbuRlGXT6zHg3ZcGQ6ZuDHBeJrBj/a+obgHij2rhUyIwqQKZMRulNvaGRQe4fwkQfG58lEsyFe7Eas+QIIAv0hQWvidchYZR9wx3vgbH26iUV5+4+evP/sVNB4JWvNb95S7SXWZUlzUIsS81UCob9JsmecuIpwfNegzZ2gCdAa5shYu3tBuRyBDuNbAj804njLzTtz4DYj+im2kR/jQTOGlQNogPnFHzEwQsm9oZ2GY/l0cLTg0xbfLfMBey3J0QIKPDa4f+h005ATMYbpCX/kHlFWF+pH6qDfATtjjdxO8ram+sYdavrTuT2BtfoKvpnsLgnaEQ1ZqPb+26UPOFwwJfbDq/PaQKQi0jeGSpLWXYED6AEMoHONo52BFp3muQF/6aUC2g+1QhENZnFQgviwzAwNnaH9/2OWtmC3ntoPHXz44pAibhW6zSWB4hc+b1e+Bpu0pRryOubYGHgndq87SC4kPm3XQYaTzosjZVsqbwyvHvcKqAj+fYbPqfGU+cNaR21OvZissmHZd/8ar24WsV7n7oIqeTS2QWl7KR2/JeIQEWj+5tR0zLztbPrWNlZh5oVsExhIS3mBeLHv2YDNJqg9+e2SFxkP02yWHtWH3pg9Nnxpbuy9j37hOvZNog11Bg3gjwrm6NUR9KCzHpdqrrlESbJyXnx5YIWyVu3VHwkGrARo0E8MbN9ENGum/BrkX3bXfb1fWuJWRcnu2XKFAE2W+HWxjPl4EhZ/t1588Yq3wz7NQjfW9+owiFHuCQsuBwonwoeAEW1675htPg5X3eIrSEfXRoanMulblmJAvuZrucEq2t/n7XcOYjsaqIIYDhWjU4gZJulSY9z46Un5ZUW3SoQjZcheoDO/u74nn+TKRHBuwQnVdt0rVG+kg1HdwpUOvel0HA3T/8VdKVOwScfDH1rm6sZJ36A+Sn6i/Zk7ofM/xcsWujDy8GxnvzohbhULtjaZSA4IJl0OummNqpGGya0sTt/ppLKUtNg6nEEqeu0YRB+pqthS58YbOG9I2qFauYH1Vn3/xxDp7y1vYiBKd/RKOFj9Jpnc0XM74uwZGhEN93oHYlDl96h6PlMziWPPgZotJjHgVWxdtJ97goEMujKNbS+sN9wM6HWntzSXZw5eNMI+5+RAY6kBJxnEp8/fCidkSMuuGqbeNtu4mBADg2Y/jpdgxcoQ+wpj3mWZe3ZXM6eJBZ+xRdiJfUp0tBr65cRsTPgiOV5Au3KoEZw93707XvkoX3HFTPBzmEckLZBih8Cluho6AsCPrqo76i2JKty7z9/UyQ6I0srGW7LuDcalTHpokfB7tmBXLngepCq2U3MTxaFHBkIIwkjvSjMMPQ4eEj8O31ByLWP3imQzETZ7ysweyc5jz5KUlCn2UawLDjc55HWegGIjDXQ54Pg7X+rQaW6DJUmTtBrXpoeIwuLo6a7aF79az/FBu9U+4vZwy8s01jtHM1s19rTdHkeHI7XZFlieAbZV7C9cApzrDFniS9aTQg3TJjM1mhWQFNq5b0BeFmculgeXUd+i6lkMBBwnngveLz/XmKn3F0nve9ys8n8zhcaynK5uaiFi5zh0QuxA6FfxGIpoUL49rZy5+YnuItZ4L4uGIH+PZynLTcX7Szr6xvF/3SxNlSTbKpDj9vK+u3eGjpoNNUZOk2tVRLx4v1Qc6jqYZrmlj8kwERfOql0p335SWchs0lQv4IB/3ktHpXo8pmLNxGIubMX50iKdCTHS7Ijdx1GCsbKgniivKdFi0krCR6zoEIwhNrvaoqvSbKMRlqwdCRGlxva+XOi8+CCF9Ykmo3hnfm4Z2tMY/3ntgVAZq9PiryOdI8Y3LIPJ2ULUfXFor3JRGfrrmEyZCjYDAPe0WJNM2cIthjV1GRTOrSOukLprKYlrIOxLJlwmfi4oPnk2Q0lpakGB9SbB1HkWiiFGjHC3zbChv24J/Z3bY+nVDvixveSMMeehFiayLgb1bl3d3yiMnqBsOFnn+xxJZFHCQxZd33K6QukqmARF7Xy15UT8GyQ4MmvjK1sj7RvXYSAvKRrz12sMymMQSSn0SfmANencu9LlUnGc7bWpI8ywayzh7eyIdbnzhw1dKdiTHaxP7DDYHdD9Xo6glVreNoXAPWtkg1TfkRYK/EGT1Mq5FkMu/aO9Xv6sxWjf/yuxhhvZrrq8MfqHdNVTWUuwB+4edDzcDpp7SYjX4flI8plsS6j7Os++MwMBdxQ2L1i8M8E7D52bKstkcqbOut5bfrpefKqsOwMF8ZXSs3hKqSc/eOPhw0m1X6aG9+3KI0+ZMqhZ/3ZiBoc1SkVTEw+7650cEJLu1qV5lxX40CChtdvVB9w3g7qH3lDxXfB7MZ0S+IB0Z3vxREt3/6cIKplBqM5lV9sBSiOYCgRKNuUjs3mpnmhjJDZkHRmYPXAgv5y+9iLenVntPTXqP1fatlj2oU5eQeis8srCXQjC8iCOhoIH0lW/GqnzE+jHyv5HCxk4OORIhAEB4T4uOfpVFm9s4QR1sDKz+mB79DEdyChzEyNr4AoEU9R8g//lzMpVyiqx60M+GHdzaoYzB3xvKLBoR/H7hRkX8o+gOZIXJAiqmsCZJOXjaZV0W5Ggxx2vrrdN65IUa2/0eCPF0jGJa19RnMhTAyjUyqkpok2JCrz+8bazfWTAdN3U8sjIpRg1TlwdVWxxkdU3yRYeugmWP4ZkyafsepElTkLcjvKozftbbxFt7P8cYMtm7RueV7z+CV8x9+Fj5Dedh7A2vQlQJINaKgkuS0gd/3t4LZYh3RDCA6ZtDkCwB2trVMusPORO3EtQdUrMJSnVuflgeBuE3Il7V7asY7m8Tu9fk15To2UMj7r+K9+6o3aOi8uYe87Tz7CPFT0PLJVd1+2o22PPwyihLMNu39rAzGcfIG3sWJ4R28jlvTlDH0SpXHY3mlVxX7jaxF0k7+ki43jaGpGdrUhvrIvWl/BooiCGoFFPpDhRyjPjv+CFWyrglDgKAa3CnNZz81O7Y2jg6OV6g54x/Rv3P5WTnmSRvt8jdlleVbGllBjNO/W+K+pEAowkPAACsU8cu9R9InRxsraygDheww3I25F8mrDYbFNh3LZeVF+ShctuB0cLfpBTnLHPpjGVz9Efiiibr1iokivMeKD0RHX5o8oL3eBLuaasUb60kRhsWbaE+elgfW3M6QITnsUsyHNYaeInPidvJxaOYtMLfSsEX4W7w5dS2474X2PXlHfLyaJwfJ9yyfZ8RclQ+GRZpLNdc05kB5aNF6MbhPSGOqe0an7XjXOrwSFxlb7+DKUT9lVR+n7XwRa5Td8+1b3pvH0cllMRrjsPJsqmXR6hMxq7bRblnd09JF02o1A+UEjzIUq0t1Z7aWZU3N0swnkBgPvjeHe7lQPRRAABkQad1h+y0GlrbGkOtLtAY+j+C/s8LR5ZQqN139O+Z7T8AiH9cEWn/TAG1cTJ3cr+AfPBZsf/axanT3M7AyBJq/N+e5v8f9T+XX01zoD2L8Y7f93ev1pEsvl354ySnOI3SEergYm4E/W87+Q/sXzfhTCR/cfNmPQ8+xNjA9gKF4j4vx19XjeV8jBcxxXVOinN6+pmQ4BeE1gbmNjD7oBb23Go2jC+rZcI3HzFe1WV57/nxI6KCpM4VRQHVebjLY0svcz4eeYscvrgDTqyXykVTgFoUlgGD7e65LrryWZyiNoJPom4bce9pv1qT1Phm1iU4ZqKwDCqPwMJyO6wtpiCo2IcEpplFDYkhEKqxLpdjD8KXU3n3Tb6azbfoaXv7Qvs+zuMVtH99+Qb6f3kAAEAjcN7dqLWBjbkJ1NHpAgOa/BS4f+67JuamF+g2859R/3MBuxtY/+9E5HyoiAbHjiF+7PiAErnNR1puFA/u0rx0T1yGdh4Ez7XSxK1JJtScEsHstUFiAF6cBGMQqtkg5uK+cq3MR9fF/cXURQ0GkXJrfII9ebq14obocllHFI1B33jFfj4Eajlqqj2kf4utl4UZbgIAgO/Fq2MMtbOydb9AzRn/jAqxdHZ0srU294BeAB98HnyIoYHjRZZz4XOT/OdfraE2TrBdd4p8L4fAjnF3X7Dm3UdUnWTMkOgxJo+YQkz02hBD+L4SiVHTIuYeRnqONLpMsLzgnSSowrLPto9cnmH3Q+SlvrgG/CyHttgAB7iHTKYI3bm4Xg4FyQ5wjx5retOxifpSD5U1TVfpPI80TbkyknMnPWzZ8DkDJIK4A4Sevf61HyOlg3QMG+4gPImpOeCzm9WDF3nHVM+ie+bglGa3lq+6+Dcmzz3PekQ7rititvpIGP7fAVQaRsdbAACAPei0Komev0r/fmvgZG5rA1soloaCS9Vs2IhtR5krcOzS997BCb23F55pXNWZcKQKmmWU3M2I5xFuT81k7By8x+WDEzqbnKaxTGZeYuRjMKHJ5Cw1av9a0IyJ1MYTskKjrJH/2oV75r6pTfbtqnYs/ANGqg/fdwo1KPVf/pketqeOAYHzu/vPUg/ry7iZHe0TG0bQqM6LNkS4WbW3W3D+cUvV7gjKtZff1O3K3CixEelAln9fifNMi0DYZiPLG8+HKlzruoCSuiye4VK5GG+B59fPnBbDRGa8DbvOoSSmt9XRhk0NTbby3xrK7mxq5MpYWNVN3puKucT56G0k+sv9Y0zv751869+W5wEAwMtTJ9W1c3m1dYE6WBm4O15g9vJfiAhiDHWBWtnaXWAmS/0V4X/WcWsDO9ieCob9z6RG2hesebHhykLqIBMmftMW5wpcS8TlZSoaMjIc7m53QaNEbY8oYSXWu+ND3pmXUSKK3j3f+dwMZce0uo3JkBKkH538ph6acVkVnFfYPDAwd8TTbdsivWMPsGWQd5i3APfaoBhJI7MG/dy+r/oy7GRarZYRfGtZdURuscpEQMIV9zroPXiKukQ05FUwFbEFs4daWDsHUbpc7T98303Qv1nDyQAAYO/UGkn/XY1+t/IZNxf8M/AR2gb17EWwpdVbD4OXqPgCXtKGaiDsbdMiumbRrWIu0WzUG5lto4UQlqzr6s5p1wS27/XPaD5kTM7ohCslZI7Gl41uVBgmbkTJVec/3PPP6n49qNzp/pDx45q1WFDInjyIq/dYYu5LzOta+n9dX38zy+cMAEDhqa5l/871aStZk6zF3evYEqveqekQ/ZCKNnUUTcQ3kP4PmW1HT7JJ7OREMMaPth9zHorxvKYNPuTM00wbUWl8Rbys90WrtpbzEzRdp/Hyp+3Xn4vw0zGzTeXzJcX0yRJrNTE477bSyIzLrIl2fv4qstBdwcstKzJDPPQtwKWT+dkOa6ixRsxdQvwcBzI58Y3vO6+D53UH/+y8sv9iM2BrZHmhBx9Mf0aFiP/PBebsiwP62rZqXb38bcYu+SbGuiaZomtdhSNjSpngESU1CkDwMECMxNuLREzw8PCljUB8tl7CjVLfyJJQU+zg5r0QRMQ74WAw2s2smt3nN+uzZtBUnvBvVr3a2JBRckTfcLy2Ls08dsVRuen2huMVjnXsV9cFc58oiARNPBw4wEVcRONkfLUfrt/DxMCAcI8XKUhh0EQjslqwbnIBHY0inwf0fW6l20RMPQUAQBPuvB8Mf6zA/7+AHc1OHBHolG9o+iovzdIyMqakhlJfx6LG0CLZXK9Yx6Im+VpSFswqJasoKS3bCJFWlayXb5FjVZJkam6Sah+Hg7/+/UYXVai0KgYAwPVzb7odoI62zg5GF9mgkZ4CBzFn57W5wDii+xPm/8kDju8Mds6GVuZGF6gFzZ9RIWZO1hd5dMV6Rui/rgzdGYjsrJxNzS/SWMiZwf/aB8MZqP5i2LOcAx5i5Oj4X3rMcBrFX5cMfB5Cc2sD04t81uI5N8lf+2I+D6XFRVrFeT6Gv3ZEcRqfo5GDud1/LYf5EfavtVOeRvLvf/yXNh8/4f61+l89inN2Mre62BN9it+j/bVSrJ+xT1EIgsOF/1+NJw9skwGo338uw/+fv894KvxH0JOnpYlgQO1/A/rDye8fAU+e0iSEAZQCAX8+cv0j3MnDl1dh4KgQgTMc9PxfvF8f2fz/X9jAomjSL/B+PMAJq+3kWUbYhkz9DuvnePFH0JOnEPFhQKeRgD8divwR7OT7B7BgAcjAn44xnlY6QpjSfSMHzvICw+/hSGDgaCiAs77AAGv3ZFxODWPX9Q+QP77AcJpYchixeFTAOcJ3WL0nM3FGGL2Wf0b9Rfh+mmpqGNUjf8D/OWeHVX4yL2aBUc5PDVwsZz9NPBmMeB8a4OwxNKzuk6s0PYzur38E/XnxP00yLYxkblrg3An075WDYZRHnxX7fAZgh/nBaSQ/hc+w0k+mwrDDXIkOuED4fJpqChjVNafh/5w0/77isLJJ6IELJM2nyWaEke1/FvxzLbCsMPij58H/IWT+fY24YWp0kwH425D5NEMsMIZKzkd2Nj9cMH6IGIG/zJd/5DoZxxLAcIX+guvHaPnsd2cjJuAsge5ZV4I3p8D9HOjCWj4ZijLDWEZhBi4U6J5dd9tpDD9FrWedqnQswIWi1t/jg2Hwg8+D/2PUClv8k4GiMEzx589N8tvA4UfSkzmfKAwpDhj4LyaXP/KezNwEYHiXz8/7y0zxtB5eg+lhPwT4u2Tv90T8METibMB/J9mDreXJBEsKppZFf0X4u2TvR/6TWZI0DH8vO/B/kZr9KOBkfCMLI6D1GvB/FGCdfWEb5ADOERvBOjsZoDDBOOPgBC4SG/1IcDL+OLmJWvTn4gIukMqc/bbX8wv8Xzxb/j0cKQwcPTdw5kjl93sKOpgae/4J82927IOngf+UpvwelQYGlYMHOG+a8vtqsMJUI/yM0OcrCh2M/O0zcPwUpPzeAATGgAovcOEg5TQLDDAWPpyB5VzjHHYrTcYHXDRDOetW2v+cFOerFuz+ae08XD/GJ783xANjSJ0f+Ov45DRLzDCWGs7DZnG2BnHC+GERAP4uOTn7J/SU06h+Dk3O+gl9/Qyw55NNCSNbQxA4V17ye92wd938s+CeTzgBjHB8IeAMUcnv5VLAyDX+Pdr5RGKdEHks+vFn2BMoiEj//Jw4IA7gXwUAJOF/vvt/AQAA//9O58Q6MlcAAA=="); err != nil { panic("add binary content to resource manager failed: " + err.Error()) } } From d5d199ebefd16f8a8190e7fe9203159faa232cf4 Mon Sep 17 00:00:00 2001 From: John Guo Date: Fri, 4 Mar 2022 11:36:05 +0800 Subject: [PATCH 10/17] Swagger UI updates --- net/ghttp/ghttp_server_swagger.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/net/ghttp/ghttp_server_swagger.go b/net/ghttp/ghttp_server_swagger.go index 7786a072b..846993c9a 100644 --- a/net/ghttp/ghttp_server_swagger.go +++ b/net/ghttp/ghttp_server_swagger.go @@ -11,8 +11,10 @@ import ( ) const ( - swaggerUIDefaultURL = `https://petstore.swagger.io/v2/swagger.json` - swaggerUITemplate = ` + swaggerUIDocName = `redoc.standalone.js` + swaggerUIDocNamePlaceHolder = `{SwaggerUIDocName}` + swaggerUIDocURLPlaceHolder = `{SwaggerUIDocUrl}` + swaggerUITemplate = ` @@ -27,8 +29,8 @@ const ( - - + + ` @@ -41,11 +43,11 @@ func (s *Server) swaggerUI(r *Request) { return } if r.StaticFile != nil && r.StaticFile.File != nil && r.StaticFile.IsDir { - r.Response.Write(gstr.Replace( - swaggerUITemplate, - swaggerUIDefaultURL, - s.config.OpenApiPath, - )) + content := gstr.ReplaceByMap(swaggerUITemplate, map[string]string{ + swaggerUIDocURLPlaceHolder: s.config.OpenApiPath, + swaggerUIDocNamePlaceHolder: gstr.TrimRight(r.GetUrl(), "/") + "/" + swaggerUIDocName, + }) + r.Response.Write(content) r.ExitAll() } } From 531cc7b86489d920183e3f0811e31da3070204ae Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 7 Mar 2022 09:57:51 +0800 Subject: [PATCH 11/17] fix issue in package gproc --- net/ghttp/ghttp_server.go | 2 +- os/gproc/gproc_comm.go | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/net/ghttp/ghttp_server.go b/net/ghttp/ghttp_server.go index a731b31e6..0961cba70 100644 --- a/net/ghttp/ghttp_server.go +++ b/net/ghttp/ghttp_server.go @@ -128,7 +128,7 @@ func (s *Server) Start() error { swaggerui.Init() s.AddStaticPath(s.config.SwaggerPath, swaggerUIPackedPath) s.BindHookHandler(s.config.SwaggerPath+"/*", HookBeforeServe, s.swaggerUI) - s.Logger().Debugf( + s.Logger().Infof( ctx, `swagger ui is serving at address: %s%s/`, s.getListenAddress(), diff --git a/os/gproc/gproc_comm.go b/os/gproc/gproc_comm.go index 546142a16..b547d31d7 100644 --- a/os/gproc/gproc_comm.go +++ b/os/gproc/gproc_comm.go @@ -107,10 +107,12 @@ func getCommPidFolderPath() (folderPath string, err error) { break } } - err = gerror.Newf( - `cannot find available folder for storing pid to port mapping files in paths: %+v`, - availablePaths, - ) + if commPidFolderPath == "" { + err = gerror.Newf( + `cannot find available folder for storing pid to port mapping files in paths: %+v`, + availablePaths, + ) + } }) folderPath = commPidFolderPath return From 8a27463e44687bbfd3539a7c4acf23b39eeee6d4 Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 7 Mar 2022 17:39:41 +0800 Subject: [PATCH 12/17] ensure sequence for json.Marshal for gmap.ListMap/TreeMap --- container/gmap/gmap_list_map.go | 44 +++++++++++++++++---- container/gmap/gmap_z_example_list_test.go | 11 +++--- container/gmap/gmap_z_unit_list_map_test.go | 24 ++++++++++- container/gpool/gpool.go | 4 +- container/gtree/gtree_avltree.go | 23 ++++++++++- container/gtree/gtree_btree.go | 22 ++++++++++- container/gtree/gtree_redblacktree.go | 23 ++++++++++- 7 files changed, 129 insertions(+), 22 deletions(-) diff --git a/container/gmap/gmap_list_map.go b/container/gmap/gmap_list_map.go index eed95f1a6..9ab520629 100644 --- a/container/gmap/gmap_list_map.go +++ b/container/gmap/gmap_list_map.go @@ -7,6 +7,9 @@ package gmap import ( + "bytes" + "fmt" + "github.com/gogf/gf/v2/container/glist" "github.com/gogf/gf/v2/container/gvar" "github.com/gogf/gf/v2/internal/empty" @@ -15,6 +18,13 @@ import ( "github.com/gogf/gf/v2/util/gconv" ) +// ListMap is a map that preserves insertion-order. +// +// It is backed by a hash table to store values and doubly-linked list to store ordering. +// +// Structure is not thread safe. +// +// Reference: http://en.wikipedia.org/wiki/Associative_array type ListMap struct { mu rwmutex.RWMutex data map[interface{}]*glist.Element @@ -58,7 +68,7 @@ func (m *ListMap) IteratorAsc(f func(key interface{}, value interface{}) bool) { m.mu.RLock() defer m.mu.RUnlock() if m.list != nil { - node := (*gListMapNode)(nil) + var node *gListMapNode m.list.IteratorAsc(func(e *glist.Element) bool { node = e.Value.(*gListMapNode) return f(node.key, node.value) @@ -72,7 +82,7 @@ func (m *ListMap) IteratorDesc(f func(key interface{}, value interface{}) bool) m.mu.RLock() defer m.mu.RUnlock() if m.list != nil { - node := (*gListMapNode)(nil) + var node *gListMapNode m.list.IteratorDesc(func(e *glist.Element) bool { node = e.Value.(*gListMapNode) return f(node.key, node.value) @@ -146,8 +156,10 @@ func (m *ListMap) MapStrAny() map[string]interface{} { func (m *ListMap) FilterEmpty() { m.mu.Lock() if m.list != nil { - keys := make([]interface{}, 0) - node := (*gListMapNode)(nil) + var ( + keys = make([]interface{}, 0) + node *gListMapNode + ) m.list.IteratorAsc(func(e *glist.Element) bool { node = e.Value.(*gListMapNode) if empty.IsEmpty(node.value) { @@ -495,7 +507,7 @@ func (m *ListMap) Merge(other *ListMap) { other.mu.RLock() defer other.mu.RUnlock() } - node := (*gListMapNode)(nil) + var node *gListMapNode other.list.IteratorAsc(func(e *glist.Element) bool { node = e.Value.(*gListMapNode) if e, ok := m.data[node.key]; !ok { @@ -514,8 +526,26 @@ func (m *ListMap) String() string { } // MarshalJSON implements the interface MarshalJSON for json.Marshal. -func (m ListMap) MarshalJSON() ([]byte, error) { - return json.Marshal(gconv.Map(m.Map())) +func (m ListMap) MarshalJSON() (jsonBytes []byte, err error) { + if m.data == nil { + return []byte("null"), nil + } + buffer := bytes.NewBuffer(nil) + buffer.WriteByte('{') + m.Iterator(func(key, value interface{}) bool { + valueBytes, valueJsonErr := json.Marshal(value) + if valueJsonErr != nil { + err = valueJsonErr + return false + } + if buffer.Len() > 1 { + buffer.WriteByte(',') + } + buffer.WriteString(fmt.Sprintf(`"%v":%s`, key, valueBytes)) + return true + }) + buffer.WriteByte('}') + return buffer.Bytes(), nil } // UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal. diff --git a/container/gmap/gmap_z_example_list_test.go b/container/gmap/gmap_z_example_list_test.go index 0f6645d50..0fab8cd80 100644 --- a/container/gmap/gmap_z_example_list_test.go +++ b/container/gmap/gmap_z_example_list_test.go @@ -8,6 +8,7 @@ package gmap_test import ( "fmt" + "github.com/gogf/gf/v2/container/gmap" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/internal/json" @@ -562,12 +563,10 @@ func ExampleListMap_String() { func ExampleListMap_MarshalJSON() { var m gmap.ListMap - m.Sets(g.MapAnyAny{ - "k1": "v1", - "k2": "v2", - "k3": "v3", - "k4": "v4", - }) + m.Set("k1", "v1") + m.Set("k2", "v2") + m.Set("k3", "v3") + m.Set("k4", "v4") bytes, err := json.Marshal(&m) if err == nil { diff --git a/container/gmap/gmap_z_unit_list_map_test.go b/container/gmap/gmap_z_unit_list_map_test.go index 433dd48d0..0605e20a1 100644 --- a/container/gmap/gmap_z_unit_list_map_test.go +++ b/container/gmap/gmap_z_unit_list_map_test.go @@ -190,8 +190,9 @@ func Test_ListMap_Json(t *testing.T) { } m1 := gmap.NewListMapFrom(data) b1, err1 := json.Marshal(m1) + t.AssertNil(err1) b2, err2 := json.Marshal(gconv.Map(data)) - t.Assert(err1, err2) + t.AssertNil(err2) t.Assert(b1, b2) }) // Unmarshal @@ -226,6 +227,27 @@ func Test_ListMap_Json(t *testing.T) { }) } +func Test_ListMap_Json_Sequence(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListMap() + for i := 'z'; i >= 'a'; i-- { + m.Set(string(i), i) + } + b, err := json.Marshal(m) + t.AssertNil(err) + t.Assert(b, `{"z":122,"y":121,"x":120,"w":119,"v":118,"u":117,"t":116,"s":115,"r":114,"q":113,"p":112,"o":111,"n":110,"m":109,"l":108,"k":107,"j":106,"i":105,"h":104,"g":103,"f":102,"e":101,"d":100,"c":99,"b":98,"a":97}`) + }) + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListMap() + for i := 'a'; i <= 'z'; i++ { + m.Set(string(i), i) + } + b, err := json.Marshal(m) + t.AssertNil(err) + t.Assert(b, `{"a":97,"b":98,"c":99,"d":100,"e":101,"f":102,"g":103,"h":104,"i":105,"j":106,"k":107,"l":108,"m":109,"n":110,"o":111,"p":112,"q":113,"r":114,"s":115,"t":116,"u":117,"v":118,"w":119,"x":120,"y":121,"z":122}`) + }) +} + func Test_ListMap_Pop(t *testing.T) { gtest.C(t, func(t *gtest.T) { m := gmap.NewListMapFrom(g.MapAnyAny{ diff --git a/container/gpool/gpool.go b/container/gpool/gpool.go index 12baececf..f96986301 100644 --- a/container/gpool/gpool.go +++ b/container/gpool/gpool.go @@ -109,7 +109,7 @@ func (p *Pool) Get() (interface{}, error) { if f.expireAt == 0 || f.expireAt > gtime.TimestampMilli() { return f.value, nil } else if p.ExpireFunc != nil { - // TODO: move expire function calling asynchronously from `Get` operation. + // TODO: move expire function calling asynchronously out from `Get` operation. p.ExpireFunc(f.value) } } else { @@ -129,7 +129,7 @@ func (p *Pool) Size() int { // Close closes the pool. If `p` has ExpireFunc, // then it automatically closes all items using this function before it's closed. -// Commonly you do not need call this function manually. +// Commonly you do not need to call this function manually. func (p *Pool) Close() { p.closed.Set(true) } diff --git a/container/gtree/gtree_avltree.go b/container/gtree/gtree_avltree.go index 23c9fa0c8..e7dc8f7e0 100644 --- a/container/gtree/gtree_avltree.go +++ b/container/gtree/gtree_avltree.go @@ -7,6 +7,7 @@ package gtree import ( + "bytes" "fmt" "github.com/gogf/gf/v2/container/gvar" @@ -780,8 +781,26 @@ func output(node *AVLTreeNode, prefix string, isTail bool, str *string) { } // MarshalJSON implements the interface MarshalJSON for json.Marshal. -func (tree AVLTree) MarshalJSON() ([]byte, error) { - return json.Marshal(tree.MapStrAny()) +func (tree AVLTree) MarshalJSON() (jsonBytes []byte, err error) { + if tree.root == nil { + return []byte("null"), nil + } + buffer := bytes.NewBuffer(nil) + buffer.WriteByte('{') + tree.Iterator(func(key, value interface{}) bool { + valueBytes, valueJsonErr := json.Marshal(value) + if valueJsonErr != nil { + err = valueJsonErr + return false + } + if buffer.Len() > 1 { + buffer.WriteByte(',') + } + buffer.WriteString(fmt.Sprintf(`"%v":%s`, key, valueBytes)) + return true + }) + buffer.WriteByte('}') + return buffer.Bytes(), nil } // getComparator returns the comparator if it's previously set, diff --git a/container/gtree/gtree_btree.go b/container/gtree/gtree_btree.go index 1123a9185..2f758ab6f 100644 --- a/container/gtree/gtree_btree.go +++ b/container/gtree/gtree_btree.go @@ -944,8 +944,26 @@ func (tree *BTree) deleteChild(node *BTreeNode, index int) { } // MarshalJSON implements the interface MarshalJSON for json.Marshal. -func (tree BTree) MarshalJSON() ([]byte, error) { - return json.Marshal(tree.MapStrAny()) +func (tree BTree) MarshalJSON() (jsonBytes []byte, err error) { + if tree.root == nil { + return []byte("null"), nil + } + buffer := bytes.NewBuffer(nil) + buffer.WriteByte('{') + tree.Iterator(func(key, value interface{}) bool { + valueBytes, valueJsonErr := json.Marshal(value) + if valueJsonErr != nil { + err = valueJsonErr + return false + } + if buffer.Len() > 1 { + buffer.WriteByte(',') + } + buffer.WriteString(fmt.Sprintf(`"%v":%s`, key, valueBytes)) + return true + }) + buffer.WriteByte('}') + return buffer.Bytes(), nil } // getComparator returns the comparator if it's previously set, diff --git a/container/gtree/gtree_redblacktree.go b/container/gtree/gtree_redblacktree.go index 01d875dcc..f614ebe77 100644 --- a/container/gtree/gtree_redblacktree.go +++ b/container/gtree/gtree_redblacktree.go @@ -7,6 +7,7 @@ package gtree import ( + "bytes" "fmt" "github.com/gogf/gf/v2/container/gvar" @@ -925,8 +926,26 @@ func (tree *RedBlackTree) nodeColor(node *RedBlackTreeNode) color { } // MarshalJSON implements the interface MarshalJSON for json.Marshal. -func (tree RedBlackTree) MarshalJSON() ([]byte, error) { - return json.Marshal(gconv.Map(tree.Map())) +func (tree RedBlackTree) MarshalJSON() (jsonBytes []byte, err error) { + if tree.root == nil { + return []byte("null"), nil + } + buffer := bytes.NewBuffer(nil) + buffer.WriteByte('{') + tree.Iterator(func(key, value interface{}) bool { + valueBytes, valueJsonErr := json.Marshal(value) + if valueJsonErr != nil { + err = valueJsonErr + return false + } + if buffer.Len() > 1 { + buffer.WriteByte(',') + } + buffer.WriteString(fmt.Sprintf(`"%v":%s`, key, valueBytes)) + return true + }) + buffer.WriteByte('}') + return buffer.Bytes(), nil } // UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal. From aea9f6fe18c269c1e3674e33505caa1fbc9a4c57 Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 7 Mar 2022 19:46:05 +0800 Subject: [PATCH 13/17] keep sequence in attributes definition for oai.Schemas --- protocol/goai/goai_requestbody.go | 19 +++---- protocol/goai/goai_response.go | 19 +++---- protocol/goai/goai_shema.go | 19 ++++--- protocol/goai/goai_shemaref.go | 4 +- protocol/goai/goai_shemas.go | 64 +++++++++++++++++++++++ protocol/goai/goai_z_unit_test.go | 86 +++++++++++++++---------------- 6 files changed, 138 insertions(+), 73 deletions(-) create mode 100644 protocol/goai/goai_shemas.go diff --git a/protocol/goai/goai_requestbody.go b/protocol/goai/goai_requestbody.go index 7cc91ca86..1676623fc 100644 --- a/protocol/goai/goai_requestbody.go +++ b/protocol/goai/goai_requestbody.go @@ -47,18 +47,19 @@ func (oai *OpenApiV3) getRequestSchemaRef(in getRequestSchemaRefInput) (*SchemaR } var ( - dataFieldsPartsArray = gstr.Split(in.RequestDataField, ".") - bizRequestStructSchemaRef, bizRequestStructSchemaRefExist = oai.Components.Schemas[in.BusinessStructName] - schema, err = oai.structToSchema(in.RequestObject) + dataFieldsPartsArray = gstr.Split(in.RequestDataField, ".") + bizRequestStructSchemaRef = oai.Components.Schemas.Get(in.BusinessStructName) + schema, err = oai.structToSchema(in.RequestObject) ) if err != nil { return nil, err } - if in.RequestDataField == "" && bizRequestStructSchemaRefExist { + if in.RequestDataField == "" && bizRequestStructSchemaRef != nil { // Normal request. - for k, v := range bizRequestStructSchemaRef.Value.Properties { - schema.Properties[k] = v - } + bizRequestStructSchemaRef.Value.Properties.Iterator(func(key string, ref SchemaRef) bool { + schema.Properties.Set(key, ref) + return true + }) } else { // Common request. structFields, _ := gstructs.Fields(gstructs.FieldsInput{ @@ -76,7 +77,7 @@ func (oai *OpenApiV3) getRequestSchemaRef(in getRequestSchemaRefInput) (*SchemaR if err = oai.tagMapToSchema(structField.TagMap(), bizRequestStructSchemaRef.Value); err != nil { return nil, err } - schema.Properties[fieldName] = bizRequestStructSchemaRef + schema.Properties.Set(fieldName, *bizRequestStructSchemaRef) break } default: @@ -90,7 +91,7 @@ func (oai *OpenApiV3) getRequestSchemaRef(in getRequestSchemaRefInput) (*SchemaR if err != nil { return nil, err } - schema.Properties[fieldName] = *schemaRef + schema.Properties.Set(fieldName, *schemaRef) break } } diff --git a/protocol/goai/goai_response.go b/protocol/goai/goai_response.go index 8b47e51e1..059e4a443 100644 --- a/protocol/goai/goai_response.go +++ b/protocol/goai/goai_response.go @@ -51,18 +51,19 @@ func (oai *OpenApiV3) getResponseSchemaRef(in getResponseSchemaRefInput) (*Schem } var ( - dataFieldsPartsArray = gstr.Split(in.CommonResponseDataField, ".") - bizResponseStructSchemaRef, bizResponseStructSchemaRefExist = oai.Components.Schemas[in.BusinessStructName] - schema, err = oai.structToSchema(in.CommonResponseObject) + dataFieldsPartsArray = gstr.Split(in.CommonResponseDataField, ".") + bizResponseStructSchemaRef = oai.Components.Schemas.Get(in.BusinessStructName) + schema, err = oai.structToSchema(in.CommonResponseObject) ) if err != nil { return nil, err } - if in.CommonResponseDataField == "" && bizResponseStructSchemaRefExist { + if in.CommonResponseDataField == "" && bizResponseStructSchemaRef != nil { // Normal response. - for k, v := range bizResponseStructSchemaRef.Value.Properties { - schema.Properties[k] = v - } + bizResponseStructSchemaRef.Value.Properties.Iterator(func(key string, ref SchemaRef) bool { + schema.Properties.Set(key, ref) + return true + }) } else { // Common response. structFields, _ := gstructs.Fields(gstructs.FieldsInput{ @@ -80,7 +81,7 @@ func (oai *OpenApiV3) getResponseSchemaRef(in getResponseSchemaRefInput) (*Schem if err = oai.tagMapToSchema(structField.TagMap(), bizResponseStructSchemaRef.Value); err != nil { return nil, err } - schema.Properties[fieldName] = bizResponseStructSchemaRef + schema.Properties.Set(fieldName, *bizResponseStructSchemaRef) break } default: @@ -95,7 +96,7 @@ func (oai *OpenApiV3) getResponseSchemaRef(in getResponseSchemaRefInput) (*Schem if err != nil { return nil, err } - schema.Properties[fieldName] = *schemaRef + schema.Properties.Set(fieldName, *schemaRef) break } } diff --git a/protocol/goai/goai_shema.go b/protocol/goai/goai_shema.go index b1ebe2429..627c3e332 100644 --- a/protocol/goai/goai_shema.go +++ b/protocol/goai/goai_shema.go @@ -9,6 +9,7 @@ package goai import ( "reflect" + "github.com/gogf/gf/v2/container/gmap" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/internal/utils" "github.com/gogf/gf/v2/os/gstructs" @@ -18,8 +19,6 @@ import ( "github.com/gogf/gf/v2/util/gvalid" ) -type Schemas map[string]SchemaRef - // Schema is specified by OpenAPI/Swagger 3.0 standard. type Schema struct { OneOf SchemaRefs `json:"oneOf,omitempty" yaml:"oneOf,omitempty"` @@ -78,8 +77,8 @@ func (oai *OpenApiV3) addSchema(object ...interface{}) error { } func (oai *OpenApiV3) doAddSchemaSingle(object interface{}) error { - if oai.Components.Schemas == nil { - oai.Components.Schemas = map[string]SchemaRef{} + if oai.Components.Schemas.refs == nil { + oai.Components.Schemas.refs = gmap.NewListMap() } var ( @@ -88,21 +87,21 @@ func (oai *OpenApiV3) doAddSchemaSingle(object interface{}) error { ) // Already added. - if _, ok := oai.Components.Schemas[structTypeName]; ok { + if oai.Components.Schemas.Get(structTypeName) != nil { return nil } // Take the holder first. - oai.Components.Schemas[structTypeName] = SchemaRef{} + oai.Components.Schemas.Set(structTypeName, SchemaRef{}) schema, err := oai.structToSchema(object) if err != nil { return err } - oai.Components.Schemas[structTypeName] = SchemaRef{ + oai.Components.Schemas.Set(structTypeName, SchemaRef{ Ref: "", Value: schema, - } + }) return nil } @@ -111,7 +110,7 @@ func (oai *OpenApiV3) structToSchema(object interface{}) (*Schema, error) { var ( tagMap = gmeta.Data(object) schema = &Schema{ - Properties: map[string]SchemaRef{}, + Properties: createSchemas(), } ) if len(tagMap) > 0 { @@ -153,7 +152,7 @@ func (oai *OpenApiV3) structToSchema(object interface{}) (*Schema, error) { if err != nil { return nil, err } - schema.Properties[fieldName] = *schemaRef + schema.Properties.Set(fieldName, *schemaRef) } return schema, nil } diff --git a/protocol/goai/goai_shemaref.go b/protocol/goai/goai_shemaref.go index 1fd6a8bfa..ec6207c6e 100644 --- a/protocol/goai/goai_shemaref.go +++ b/protocol/goai/goai_shemaref.go @@ -70,7 +70,7 @@ func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap var ( structTypeName = oai.golangTypeToSchemaName(golangType) ) - if _, ok := oai.Components.Schemas[structTypeName]; !ok { + if oai.Components.Schemas.Get(structTypeName) == nil { if err := oai.addSchema(reflect.New(golangType).Interface()); err != nil { return nil, err } @@ -81,7 +81,7 @@ func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap default: // Normal struct object. var structTypeName = oai.golangTypeToSchemaName(golangType) - if _, ok := oai.Components.Schemas[structTypeName]; !ok { + if oai.Components.Schemas.Get(structTypeName) == nil { if err := oai.addSchema(reflect.New(golangType).Elem().Interface()); err != nil { return nil, err } diff --git a/protocol/goai/goai_shemas.go b/protocol/goai/goai_shemas.go new file mode 100644 index 000000000..c25b8ce00 --- /dev/null +++ b/protocol/goai/goai_shemas.go @@ -0,0 +1,64 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package goai + +import ( + "github.com/gogf/gf/v2/container/gmap" +) + +type Schemas struct { + refs *gmap.ListMap +} + +func createSchemas() Schemas { + return Schemas{ + refs: gmap.NewListMap(), + } +} + +func (s *Schemas) init() { + if s.refs == nil { + s.refs = gmap.NewListMap() + } +} + +func (s Schemas) Get(name string) *SchemaRef { + s.init() + value := s.refs.Get(name) + if value != nil { + ref := value.(SchemaRef) + return &ref + } + return nil +} + +func (s Schemas) Set(name string, ref SchemaRef) { + s.init() + s.refs.Set(name, ref) +} + +func (s Schemas) Map() map[string]SchemaRef { + s.init() + m := make(map[string]SchemaRef) + s.refs.Iterator(func(key, value interface{}) bool { + m[key.(string)] = value.(SchemaRef) + return true + }) + return m +} + +func (s Schemas) Iterator(f func(key string, ref SchemaRef) bool) { + s.init() + s.refs.Iterator(func(key, value interface{}) bool { + return f(key.(string), value.(SchemaRef)) + }) +} + +func (s Schemas) MarshalJSON() ([]byte, error) { + s.init() + return s.refs.MarshalJSON() +} diff --git a/protocol/goai/goai_z_unit_test.go b/protocol/goai/goai_z_unit_test.go index 3ae7645d9..7c734f74a 100644 --- a/protocol/goai/goai_z_unit_test.go +++ b/protocol/goai/goai_z_unit_test.go @@ -48,14 +48,14 @@ func Test_Basic(t *testing.T) { }) t.AssertNil(err) // Schema asserts. - t.Assert(len(oai.Components.Schemas), 2) - t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`].Value.Type, goai.TypeObject) - t.Assert(len(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`].Value.Properties), 7) - t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`].Value.Properties[`appId`].Value.Type, goai.TypeNumber) - t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`].Value.Properties[`resourceId`].Value.Type, goai.TypeString) + t.Assert(len(oai.Components.Schemas.Map()), 2) + t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Type, goai.TypeObject) + t.Assert(len(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Properties.Map()), 7) + t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Properties.Get(`appId`).Value.Type, goai.TypeNumber) + t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Properties.Get(`resourceId`).Value.Type, goai.TypeString) - t.Assert(len(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`].Value.Properties), 3) - t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`].Value.Properties[`Params`].Value.Type, goai.TypeArray) + t.Assert(len(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`).Value.Properties.Map()), 3) + t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`).Value.Properties.Get(`Params`).Value.Type, goai.TypeArray) }) } @@ -108,14 +108,14 @@ func TestOpenApiV3_Add(t *testing.T) { t.AssertNil(err) // fmt.Println(oai.String()) // Schema asserts. - t.Assert(len(oai.Components.Schemas), 3) - t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`].Value.Type, goai.TypeObject) - t.Assert(len(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`].Value.Properties), 7) - t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`].Value.Properties[`appId`].Value.Type, goai.TypeNumber) - t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`].Value.Properties[`resourceId`].Value.Type, goai.TypeString) + t.Assert(len(oai.Components.Schemas.Map()), 3) + t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Type, goai.TypeObject) + t.Assert(len(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Properties.Map()), 7) + t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Properties.Get(`appId`).Value.Type, goai.TypeNumber) + t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Properties.Get(`resourceId`).Value.Type, goai.TypeString) - t.Assert(len(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`].Value.Properties), 3) - t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`].Value.Properties[`Params`].Value.Type, goai.TypeArray) + t.Assert(len(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`).Value.Properties.Map()), 3) + t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`).Value.Properties.Get(`Params`).Value.Type, goai.TypeArray) // Paths. t.Assert(len(oai.Paths), 1) @@ -158,9 +158,9 @@ func TestOpenApiV3_Add_Recursive(t *testing.T) { }) t.AssertNil(err) // Schema asserts. - t.Assert(len(oai.Components.Schemas), 3) - t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CategoryTreeItem`].Value.Type, goai.TypeObject) - t.Assert(len(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CategoryTreeItem`].Value.Properties), 3) + t.Assert(len(oai.Components.Schemas.Map()), 3) + t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CategoryTreeItem`).Value.Type, goai.TypeObject) + t.Assert(len(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CategoryTreeItem`).Value.Properties.Map()), 3) }) } @@ -222,7 +222,7 @@ func TestOpenApiV3_Add_AutoDetectIn(t *testing.T) { fmt.Println(oai.String()) - t.Assert(len(oai.Components.Schemas), 2) + t.Assert(len(oai.Components.Schemas.Map()), 2) t.Assert(len(oai.Paths), 1) t.AssertNE(oai.Paths[path].Get, nil) t.Assert(len(oai.Paths[path].Get.Parameters), 3) @@ -274,9 +274,9 @@ func TestOpenApiV3_CommonRequest(t *testing.T) { }) t.AssertNil(err) // Schema asserts. - t.Assert(len(oai.Components.Schemas), 3) + t.Assert(len(oai.Components.Schemas.Map()), 3) t.Assert(len(oai.Paths), 1) - t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties), 3) + t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties.Map()), 3) }) } @@ -319,9 +319,9 @@ func TestOpenApiV3_CommonRequest_WithoutDataField_Setting(t *testing.T) { t.AssertNil(err) // Schema asserts. // fmt.Println(oai.String()) - t.Assert(len(oai.Components.Schemas), 3) + t.Assert(len(oai.Components.Schemas.Map()), 3) t.Assert(len(oai.Paths), 1) - t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties), 5) + t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties.Map()), 5) }) } @@ -359,9 +359,9 @@ func TestOpenApiV3_CommonRequest_EmptyRequest(t *testing.T) { t.AssertNil(err) // Schema asserts. // fmt.Println(oai.String()) - t.Assert(len(oai.Components.Schemas), 3) + t.Assert(len(oai.Components.Schemas.Map()), 3) t.Assert(len(oai.Paths), 1) - t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties), 3) + t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties.Map()), 3) }) } @@ -414,10 +414,10 @@ func TestOpenApiV3_CommonRequest_SubDataField(t *testing.T) { t.AssertNil(err) // Schema asserts. // fmt.Println(oai.String()) - t.Assert(len(oai.Components.Schemas), 4) + t.Assert(len(oai.Components.Schemas.Map()), 4) t.Assert(len(oai.Paths), 1) - t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties), 1) - t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties[`Request`].Value.Properties), 4) + t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties.Map()), 1) + t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties.Get(`Request`).Value.Properties.Map()), 4) }) } @@ -460,13 +460,13 @@ func TestOpenApiV3_CommonResponse(t *testing.T) { }) t.AssertNil(err) - //g.Dump(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties) + //g.Dump(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Map()) // Schema asserts. - t.Assert(len(oai.Components.Schemas), 3) + t.Assert(len(oai.Components.Schemas.Map()), 3) t.Assert(len(oai.Paths), 1) - t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties), 3) + t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Map()), 3) t.Assert( - oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties["data"].Value.Description, + oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Get("data").Value.Description, `Result data for certain request according API definition`, ) }) @@ -511,9 +511,9 @@ func TestOpenApiV3_CommonResponse_WithoutDataField_Setting(t *testing.T) { t.AssertNil(err) // Schema asserts. fmt.Println(oai.String()) - t.Assert(len(oai.Components.Schemas), 3) + t.Assert(len(oai.Components.Schemas.Map()), 3) t.Assert(len(oai.Paths), 1) - t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties), 8) + t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Map()), 8) }) } @@ -551,10 +551,10 @@ func TestOpenApiV3_CommonResponse_EmptyResponse(t *testing.T) { t.AssertNil(err) // Schema asserts. // fmt.Println(oai.String()) - t.Assert(len(oai.Components.Schemas), 3) + t.Assert(len(oai.Components.Schemas.Map()), 3) t.Assert(len(oai.Paths), 1) t.Assert(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Ref, `github.com.gogf.gf.v2.protocol.goai_test.Req`) - t.Assert(len(oai.Paths["/index"].Put.Responses["200"].Value.Content["application/json"].Schema.Value.Properties), 3) + t.Assert(len(oai.Paths["/index"].Put.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Map()), 3) }) } @@ -607,10 +607,10 @@ func TestOpenApiV3_CommonResponse_SubDataField(t *testing.T) { t.AssertNil(err) // Schema asserts. // fmt.Println(oai.String()) - t.Assert(len(oai.Components.Schemas), 4) + t.Assert(len(oai.Components.Schemas.Map()), 4) t.Assert(len(oai.Paths), 1) - t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties), 1) - t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties[`Response`].Value.Properties), 7) + t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Map()), 1) + t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Get(`Response`).Value.Properties.Map()), 7) }) } @@ -663,12 +663,12 @@ func TestOpenApiV3_ShortTags(t *testing.T) { t.AssertNil(err) // fmt.Println(oai.String()) // Schema asserts. - t.Assert(len(oai.Components.Schemas), 3) + t.Assert(len(oai.Components.Schemas.Map()), 3) t.Assert(oai.Paths[`/test1/{appId}`].Summary, `CreateResourceReq sum`) t.Assert(oai. Components. - Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`]. - Value.Properties[`resourceId`].Value.Description, `资源Id`) + Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`). + Value.Properties.Get(`resourceId`).Value.Description, `资源Id`) }) } @@ -698,7 +698,7 @@ func TestOpenApiV3_HtmlResponse(t *testing.T) { t.AssertNil(err) // fmt.Println(oai.String()) - t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.Res`].Value.Type, goai.TypeString) + t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.Res`).Value.Type, goai.TypeString) }) } @@ -745,6 +745,6 @@ func TestOpenApiV3_HtmlResponseWithCommonResponse(t *testing.T) { t.AssertNil(err) // fmt.Println(oai.String()) - t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.Res`].Value.Type, goai.TypeString) + t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.Res`).Value.Type, goai.TypeString) }) } From 6d323cc5290f09553d62233b51708e44756026cb Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 7 Mar 2022 20:49:30 +0800 Subject: [PATCH 14/17] improve package goai --- protocol/goai/goai.go | 7 +++-- protocol/goai/goai_parameter.go | 12 +++++--- protocol/goai/goai_requestbody.go | 1 - protocol/goai/goai_shema.go | 23 ++++++++++++-- protocol/goai/goai_z_unit_test.go | 51 +++++++++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 9 deletions(-) diff --git a/protocol/goai/goai.go b/protocol/goai/goai.go index 90c4bf020..235acc8ff 100644 --- a/protocol/goai/goai.go +++ b/protocol/goai/goai.go @@ -43,7 +43,6 @@ type ExternalDocs struct { } const ( - HttpMethodAll = `ALL` HttpMethodGet = `GET` HttpMethodPut = `PUT` HttpMethodPost = `POST` @@ -85,7 +84,11 @@ const ( TagNameConsumes = `consumes` TagNameType = `type` TagNameDomain = `domain` - TagNameValidate = `v` +) + +const ( + patternKeyForRequired = `required` + patternKeyForIn = `in:` ) var ( diff --git a/protocol/goai/goai_parameter.go b/protocol/goai/goai_parameter.go index 082bd60e4..d1126b908 100644 --- a/protocol/goai/goai_parameter.go +++ b/protocol/goai/goai_parameter.go @@ -9,6 +9,7 @@ package goai import ( "fmt" + "github.com/gogf/gf/v2/container/gset" "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/internal/json" @@ -81,10 +82,6 @@ func (oai *OpenApiV3) newParameterRefWithStructMethod(field gstructs.Field, path parameter.Required = true case ParameterInCookie, ParameterInHeader, ParameterInQuery: - // Check validation tag. - if validateTagValue := field.Tag(TagNameValidate); gstr.ContainsI(validateTagValue, `required`) { - parameter.Required = true - } default: return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid tag value "%s" for In`, parameter.In) @@ -96,6 +93,13 @@ func (oai *OpenApiV3) newParameterRefWithStructMethod(field gstructs.Field, path } parameter.Schema = schemaRef + // Required check. + if parameter.Schema.Value.Pattern != "" { + if gset.NewStrSetFrom(gstr.Split(parameter.Schema.Value.Pattern, "|")).Contains(patternKeyForRequired) { + parameter.Required = true + } + } + return &ParameterRef{ Ref: "", Value: parameter, diff --git a/protocol/goai/goai_requestbody.go b/protocol/goai/goai_requestbody.go index 1676623fc..d6311a0c8 100644 --- a/protocol/goai/goai_requestbody.go +++ b/protocol/goai/goai_requestbody.go @@ -97,7 +97,6 @@ func (oai *OpenApiV3) getRequestSchemaRef(in getRequestSchemaRefInput) (*SchemaR } } } - return &SchemaRef{ Value: schema, }, nil diff --git a/protocol/goai/goai_shema.go b/protocol/goai/goai_shema.go index 627c3e332..a3989502c 100644 --- a/protocol/goai/goai_shema.go +++ b/protocol/goai/goai_shema.go @@ -10,6 +10,7 @@ import ( "reflect" "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/container/gset" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/internal/utils" "github.com/gogf/gf/v2/os/gstructs" @@ -154,6 +155,16 @@ func (oai *OpenApiV3) structToSchema(object interface{}) (*Schema, error) { } schema.Properties.Set(fieldName, *schemaRef) } + + schema.Properties.Iterator(func(key string, ref SchemaRef) bool { + if ref.Value != nil && ref.Value.Pattern != "" { + validationRuleSet := gset.NewStrSetFrom(gstr.Split(ref.Value.Pattern, "|")) + if validationRuleSet.Contains(patternKeyForRequired) { + schema.Required = append(schema.Required, key) + } + } + return true + }) return schema, nil } @@ -165,8 +176,16 @@ func (oai *OpenApiV3) tagMapToSchema(tagMap map[string]string, schema *Schema) e // Validation info to OpenAPI schema pattern. for _, tag := range gvalid.GetTags() { if validationTagValue, ok := tagMap[tag]; ok { - _, validationRule, _ := gvalid.ParseTagValue(validationTagValue) - schema.Pattern = validationRule + _, validationRules, _ := gvalid.ParseTagValue(validationTagValue) + schema.Pattern = validationRules + // Enum checks. + if len(schema.Enum) == 0 { + for _, rule := range gstr.SplitAndTrim(validationRules, "|") { + if gstr.HasPrefix(rule, patternKeyForIn) { + schema.Enum = gconv.Interfaces(gstr.SplitAndTrim(rule[len(patternKeyForIn):], ",")) + } + } + } break } } diff --git a/protocol/goai/goai_z_unit_test.go b/protocol/goai/goai_z_unit_test.go index 7c734f74a..f18a77761 100644 --- a/protocol/goai/goai_z_unit_test.go +++ b/protocol/goai/goai_z_unit_test.go @@ -748,3 +748,54 @@ func TestOpenApiV3_HtmlResponseWithCommonResponse(t *testing.T) { t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.Res`).Value.Type, goai.TypeString) }) } + +func Test_Required_In_Schema(t *testing.T) { + type CommonReq struct { + AppId int64 `json:"appId" v:"required" in:"cookie" description:"应用Id"` + ResourceId string `json:"resourceId" in:"query" description:"资源Id"` + } + type SetSpecInfo struct { + StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" description:"StorageType"` + Shards int32 `description:"shards 分片数"` + Params []string `description:"默认参数(json 串-ClickHouseParams)"` + } + type CreateResourceReq struct { + CommonReq + gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default"` + Name string `description:"实例名称"` + Product string `description:"业务类型"` + Region string `v:"required|min:1" description:"区域"` + SetMap map[string]*SetSpecInfo `v:"required|min:1" description:"配置Map"` + SetSlice []SetSpecInfo `v:"required|min:1" description:"配置Slice"` + } + + gtest.C(t, func(t *gtest.T) { + var ( + err error + oai = goai.New() + req = new(CreateResourceReq) + ) + err = oai.Add(goai.AddInput{ + Object: req, + }) + t.AssertNil(err) + var ( + schemaKey1 = `github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq` + schemaKey2 = `github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo` + ) + t.Assert(oai.Components.Schemas.Map()[schemaKey1].Value.Required, g.Slice{ + "appId", + "Region", + "SetMap", + "SetSlice", + }) + t.Assert(oai.Components.Schemas.Map()[schemaKey2].Value.Required, g.Slice{ + "StorageType", + }) + t.Assert(oai.Components.Schemas.Map()[schemaKey2].Value.Properties.Map()["StorageType"].Value.Enum, g.Slice{ + "CLOUD_PREMIUM", + "CLOUD_SSD", + "CLOUD_HSSD", + }) + }) +} From 9cf6124c4c21d07182ad4314a3cc13de2d7888c8 Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 7 Mar 2022 21:09:02 +0800 Subject: [PATCH 15/17] improve package goai --- protocol/goai/goai_parameter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/goai/goai_parameter.go b/protocol/goai/goai_parameter.go index d1126b908..f3f219646 100644 --- a/protocol/goai/goai_parameter.go +++ b/protocol/goai/goai_parameter.go @@ -94,7 +94,7 @@ func (oai *OpenApiV3) newParameterRefWithStructMethod(field gstructs.Field, path parameter.Schema = schemaRef // Required check. - if parameter.Schema.Value.Pattern != "" { + if parameter.Schema != nil && parameter.Schema.Value.Pattern != "" { if gset.NewStrSetFrom(gstr.Split(parameter.Schema.Value.Pattern, "|")).Contains(patternKeyForRequired) { parameter.Required = true } From ade9ae3c0b5b8cea161d557139ab6cc1bca30fbf Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 7 Mar 2022 21:29:37 +0800 Subject: [PATCH 16/17] improve package goai --- protocol/goai/goai_parameter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/goai/goai_parameter.go b/protocol/goai/goai_parameter.go index f3f219646..faed0d41c 100644 --- a/protocol/goai/goai_parameter.go +++ b/protocol/goai/goai_parameter.go @@ -94,7 +94,7 @@ func (oai *OpenApiV3) newParameterRefWithStructMethod(field gstructs.Field, path parameter.Schema = schemaRef // Required check. - if parameter.Schema != nil && parameter.Schema.Value.Pattern != "" { + if parameter.Schema.Value != nil && parameter.Schema.Value.Pattern != "" { if gset.NewStrSetFrom(gstr.Split(parameter.Schema.Value.Pattern, "|")).Contains(patternKeyForRequired) { parameter.Required = true } From e09704a40825331dd612b49afef7e20b5ca6d234 Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 7 Mar 2022 22:10:34 +0800 Subject: [PATCH 17/17] example updates for package gmap --- container/gmap/gmap_z_example_list_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container/gmap/gmap_z_example_list_test.go b/container/gmap/gmap_z_example_list_test.go index 0fab8cd80..8e935fc52 100644 --- a/container/gmap/gmap_z_example_list_test.go +++ b/container/gmap/gmap_z_example_list_test.go @@ -196,7 +196,7 @@ func ExampleListMap_Sets() { m.Sets(addMap) fmt.Println(m) - // Output: + // May Output: // {"key1":"val1","key2":"val2","key3":"val3"} }