From b19e47783b20653f8427cbdde902c20e1208845b Mon Sep 17 00:00:00 2001 From: John Date: Fri, 30 Nov 2018 09:48:57 +0800 Subject: [PATCH] =?UTF-8?q?ghttp=E5=A2=9E=E5=8A=A0=E9=9D=99=E6=80=81?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=9B=AE=E5=BD=95=E6=98=A0=E5=B0=84=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=9B=E6=94=B9=E8=BF=9Bgspath=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=9B=E6=94=B9=E8=BF=9B?= =?UTF-8?q?gconv=E7=9A=84slice=E8=BD=AC=E6=8D=A2=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E5=A2=9E=E5=8A=A0gconv.Map=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO.MD | 5 +- g/container/garray/garray_sorted_int.go | 2 +- g/container/garray/garray_sorted_interface.go | 2 +- g/container/garray/garray_sorted_string.go | 2 +- g/encoding/gjson/gjson.go | 21 ++-- g/net/ghttp/ghttp_response.go | 18 ++- g/net/ghttp/ghttp_server.go | 31 +---- g/net/ghttp/ghttp_server_config.go | 18 ++- g/net/ghttp/ghttp_server_config_static.go | 80 ++++++++++--- g/net/ghttp/ghttp_server_handler.go | 29 ++++- g/os/gcfg/gcfg.go | 47 +++++--- g/os/gspath/gspath.go | 29 ++++- g/os/gview/gview.go | 45 ++++++-- g/util/gconv/gconv_map.go | 108 ++++++++++++++++++ g/util/gconv/gconv_slice.go | 25 +++- g/util/gconv/gconv_struct.go | 6 +- g/util/gvalid/gvalid_test.go | 2 +- geg/net/ghttp/server/{ => static}/static.go | 3 +- geg/net/ghttp/server/static/static_path.go | 14 +++ geg/net/ghttp/server/static/static_path2.go | 15 +++ geg/os/gview/basic/gview.go | 4 +- geg/os/gview/template/main.go | 6 +- geg/other/test.go | 34 +++++- geg/util/gconv/gconv_map.go | 25 ++++ geg/util/gconv/gconv_slice.go | 24 ++++ 25 files changed, 480 insertions(+), 115 deletions(-) create mode 100644 g/util/gconv/gconv_map.go rename geg/net/ghttp/server/{ => static}/static.go (68%) create mode 100644 geg/net/ghttp/server/static/static_path.go create mode 100644 geg/net/ghttp/server/static/static_path2.go create mode 100644 geg/util/gconv/gconv_map.go create mode 100644 geg/util/gconv/gconv_slice.go diff --git a/TODO.MD b/TODO.MD index 24e46eef7..cec181d1e 100644 --- a/TODO.MD +++ b/TODO.MD @@ -42,7 +42,7 @@ 1. gtcp提供简便的包发送/接收方法(SendPkg/RecvPkg)以解决常见的TCP通信粘包问题,并完善文档(参考:https://www.cnblogs.com/kex1n/p/6502002.html); 1. gfile对于文件的读写强行使用了gfpool,在某些场景下不合适,需要考虑剥离开,并为开发者提供单独的指针池文件操作特性; 1. 路由增加不区分大小写得匹配方式; -1. gform对于MySQL字段类型为datetime类型的时区问题分析; + # DONE @@ -98,4 +98,5 @@ 1. WebServer事件回调允许对同一个路由规则绑定多个事件回调; 1. gcfg/gview/ghttp等模块加上对临时文件目录的自动添加监听判断(基本是开发环境下,特别是windows环境),去掉临时文件的监听,避免临时文件过大引起的运行缓慢占用内存问题; 1. 改进gfpool在文件指针变化时的更新; -1. ghttp hook回调使用方式在注册路由比较多的时候,优先级可能使得开发者混乱,考虑方式便于管理; \ No newline at end of file +1. ghttp hook回调使用方式在注册路由比较多的时候,优先级可能使得开发者混乱,考虑方式便于管理; +1. gform对于MySQL字段类型为datetime类型的时区问题分析; \ No newline at end of file diff --git a/g/container/garray/garray_sorted_int.go b/g/container/garray/garray_sorted_int.go index 4cf8f8b34..b11a1ee1e 100644 --- a/g/container/garray/garray_sorted_int.go +++ b/g/container/garray/garray_sorted_int.go @@ -161,7 +161,7 @@ func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int) case 0 : case 1 : min = mid + 1 } - if cmp == 0 || min > max { + if cmp == 0 || min >= max { break } } diff --git a/g/container/garray/garray_sorted_interface.go b/g/container/garray/garray_sorted_interface.go index db5c0ed22..0211366ca 100644 --- a/g/container/garray/garray_sorted_interface.go +++ b/g/container/garray/garray_sorted_interface.go @@ -154,7 +154,7 @@ func (a *SortedArray) binSearch(value interface{}, lock bool)(index int, result case 0 : case 1 : min = mid + 1 } - if cmp == 0 || min > max { + if cmp == 0 || min >= max { break } } diff --git a/g/container/garray/garray_sorted_string.go b/g/container/garray/garray_sorted_string.go index 77edd80b7..6f0d4889a 100644 --- a/g/container/garray/garray_sorted_string.go +++ b/g/container/garray/garray_sorted_string.go @@ -155,7 +155,7 @@ func (a *SortedStringArray) binSearch(value string, lock bool) (index int, resul case 0 : case 1 : min = mid + 1 } - if cmp == 0 || min > max { + if cmp == 0 || min >= max { break } } diff --git a/g/encoding/gjson/gjson.go b/g/encoding/gjson/gjson.go index ebf771fd2..3b9553e02 100644 --- a/g/encoding/gjson/gjson.go +++ b/g/encoding/gjson/gjson.go @@ -48,13 +48,20 @@ func New(value interface{}, safe...bool) *Json { vc : false , } default: - // 这里效率会比较低 - b, _ := Encode(value) - v, _ := Decode(b) - j = &Json{ - p : &v, - c : byte(gDEFAULT_SPLIT_CHAR), - vc : false, + v := (interface{})(nil) + if v = gconv.Map(value); v != nil { + j = &Json { + p : &v, + c : byte(gDEFAULT_SPLIT_CHAR), + vc : false, + } + } else { + v = gconv.Interfaces(value) + j = &Json { + p : &v, + c : byte(gDEFAULT_SPLIT_CHAR), + vc : false, + } } } j.mu = rwmutex.New(safe...) diff --git a/g/net/ghttp/ghttp_response.go b/g/net/ghttp/ghttp_response.go index 2de89e99f..5a3a924e2 100644 --- a/g/net/ghttp/ghttp_response.go +++ b/g/net/ghttp/ghttp_response.go @@ -9,12 +9,12 @@ package ghttp import ( "bytes" - "gitee.com/johng/gf/g/os/gfile" - "net/http" - "gitee.com/johng/gf/g/util/gconv" - "gitee.com/johng/gf/g/encoding/gparser" - "strconv" "fmt" + "gitee.com/johng/gf/g/encoding/gparser" + "gitee.com/johng/gf/g/os/gfile" + "gitee.com/johng/gf/g/util/gconv" + "net/http" + "strconv" ) // 服务端请求返回对象。 @@ -159,9 +159,7 @@ func (r *Response) WriteStatus(status int, content...string) { // 静态文件处理 func (r *Response) ServeFile(path string) { // 首先判断是否给定的path已经是一个绝对路径 - if !gfile.Exists(path) { - path, _ = r.Server.paths.Search(path) - } + path = gfile.RealPath(path) if path == "" { r.WriteStatus(http.StatusNotFound) return @@ -172,9 +170,7 @@ func (r *Response) ServeFile(path string) { // 静态文件下载处理 func (r *Response) ServeFileDownload(path string, name...string) { // 首先判断是否给定的path已经是一个绝对路径 - if !gfile.Exists(path) { - path, _ = r.Server.paths.Search(path) - } + path = gfile.RealPath(path) if path == "" { r.WriteStatus(http.StatusNotFound) return diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 70ee22aae..a82d6b46e 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -18,7 +18,6 @@ import ( "gitee.com/johng/gf/g/os/gfile" "gitee.com/johng/gf/g/os/glog" "gitee.com/johng/gf/g/os/gproc" - "gitee.com/johng/gf/g/os/gspath" "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/util/gconv" "gitee.com/johng/gf/g/util/gregex" @@ -215,26 +214,10 @@ func (s *Server) Start() error { // 仅在开启静态文件服务的时候有效 if s.config.FileServerEnabled { - // 如果设置了静态文件目录,那么优先按照静态文件目录进行检索,其次是当前可执行文件工作目录; - // 并且如果是开发环境,默认也会添加main包的源码目录路径做为二级检索。 - if s.config.ServerRoot != "" { - if rp, err := s.paths.Set(s.config.ServerRoot); err != nil { - glog.Error("ghttp.SetServerRoot failed:", err.Error()) - return err - } else { - glog.Debug("ghttp.SetServerRoot:", rp) - } - } - // 添加当前可执行文件运行目录到搜索目录 - if gfile.SelfDir() != gfile.TempDir() { - s.paths.Add(gfile.SelfDir()) - } // (开发环境)添加main源码包到搜索目录 if p := gfile.MainPkgPath(); p != "" && gfile.Exists(p) { - s.paths.Add(p) + s.AddSearchPath(p) } - // (安全控制)不能访问当前执行文件 - s.paths.Remove(gfile.SelfPath()) } // 底层http server配置 @@ -251,18 +234,6 @@ func (s *Server) Start() error { } } - // 配置相关相对路径处理 - if s.config.HTTPSCertPath != "" && !gfile.Exists(s.config.HTTPSCertPath) { - if t, _ := s.paths.Search(s.config.HTTPSCertPath); t != "" { - s.config.HTTPSCertPath = t - } - } - if s.config.HTTPSKeyPath != "" && !gfile.Exists(s.config.HTTPSKeyPath) { - if t, _ := s.paths.Search(s.config.HTTPSKeyPath); t != "" { - s.config.HTTPSKeyPath = t - } - } - // gzip压缩文件类型 //if s.config.GzipContentTypes != nil { // for _, v := range s.config.GzipContentTypes { diff --git a/g/net/ghttp/ghttp_server_config.go b/g/net/ghttp/ghttp_server_config.go index 07b409608..dd91b75cf 100644 --- a/g/net/ghttp/ghttp_server_config.go +++ b/g/net/ghttp/ghttp_server_config.go @@ -7,6 +7,8 @@ package ghttp import ( + "fmt" + "gitee.com/johng/gf/g/os/gfile" "gitee.com/johng/gf/g/os/glog" "net/http" "strconv" @@ -49,7 +51,7 @@ type ServerConfig struct { ServerAgent string // Server Agent ServerRoot string // 服务器服务的本地目录根路径(检索优先级比StaticPaths低) SearchPaths []string // 静态文件搜索目录(包含ServerRoot,按照优先级进行排序) - StaticPaths []map[string]string // 静态文件目录映射(按照优先级进行排序) + StaticPaths []staticPathItem // 静态文件目录映射(按照优先级进行排序) FileServerEnabled bool // 是否允许静态文件服务(总开关,默认开启) // COOKIE @@ -94,7 +96,7 @@ var defaultServerConfig = ServerConfig { IndexFolder : false, ServerAgent : "gf", ServerRoot : "", - StaticPaths : make([]map[string]string, 0), + StaticPaths : make([]staticPathItem, 0), FileServerEnabled : true, CookieMaxAge : gDEFAULT_COOKIE_MAX_AGE, @@ -192,8 +194,16 @@ func (s *Server)EnableHTTPS(certFile, keyFile string) { glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR) return } - s.config.HTTPSCertPath = certFile - s.config.HTTPSKeyPath = keyFile + certFileRealPath := gfile.RealPath(certFile) + if certFileRealPath == "" { + glog.Fatal(fmt.Sprintf(`[ghttp] EnableHTTPS failed: certFile "%s" does not exist`, certFile)) + } + keyFileRealPath := gfile.RealPath(keyFile) + if keyFileRealPath == "" { + glog.Fatal(fmt.Sprintf(`[ghttp] EnableHTTPS failed: keyFile "%s" does not exist`, keyFile)) + } + s.config.HTTPSCertPath = certFileRealPath + s.config.HTTPSKeyPath = keyFileRealPath } // 设置http server参数 - ReadTimeout diff --git a/g/net/ghttp/ghttp_server_config_static.go b/g/net/ghttp/ghttp_server_config_static.go index 3e0055fd0..b00720424 100644 --- a/g/net/ghttp/ghttp_server_config_static.go +++ b/g/net/ghttp/ghttp_server_config_static.go @@ -9,11 +9,20 @@ package ghttp import ( + "fmt" + "gitee.com/johng/gf/g/container/garray" "gitee.com/johng/gf/g/os/gfile" "gitee.com/johng/gf/g/os/glog" + "gitee.com/johng/gf/g/util/gconv" "strings" ) +// 静态文件目录映射关系对象 +type staticPathItem struct { + prefix string // 映射的URI前缀 + path string // 静态文件目录绝对路径 +} + // 设置http server参数 - IndexFiles,默认展示文件,如:index.html, index.htm func (s *Server)SetIndexFiles(index []string) { if s.Status() == SERVER_STATUS_RUNNING { @@ -50,30 +59,69 @@ func (s *Server)SetServerRoot(root string) { // RealPath的作用除了校验地址正确性以外,还转换分隔符号为当前系统正确的文件分隔符号 path := gfile.RealPath(root) if path == "" { - glog.Error("invalid root path \"" + root + "\"") + glog.Fatal(fmt.Sprintf(`[ghttp] SetServerRoot failed: path "%s" does not exist`, root)) } - s.config.ServerRoot = strings.TrimRight(path, gfile.Separator) + s.config.SearchPaths = []string{strings.TrimRight(path, gfile.Separator)} } // 添加静态文件搜索目录,必须给定目录的绝对路径 -func (s *Server) AddSearchPath(path string) error { - if rp, err := s.paths.Add(path); err != nil { - glog.Error("[ghttp] AddSearchPath failed:", err.Error()) - return err - } else { - glog.Debug("[ghttp] AddSearchPath:", rp) +func (s *Server) AddSearchPath(path string) { + if s.Status() == SERVER_STATUS_RUNNING { + glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR) + return } - return nil + // RealPath的作用除了校验地址正确性以外,还转换分隔符号为当前系统正确的文件分隔符号 + realPath := gfile.RealPath(path) + if realPath == "" { + glog.Fatal(fmt.Sprintf(`[ghttp] AddSearchPath failed: path "%s" does not exist`, path)) + } + s.config.SearchPaths = append(s.config.SearchPaths, realPath) } // 添加URI与静态目录的映射 -func (s *Server) AddStaticPath(prefix string, path string) error { - if rp, err := s.paths.Add(path); err != nil { - glog.Error("[ghttp] AddSearchPath failed:", err.Error()) - return err - } else { - glog.Debug("[ghttp] AddSearchPath:", rp) +func (s *Server) AddStaticPath(prefix string, path string) { + if s.Status() == SERVER_STATUS_RUNNING { + glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR) + return + } + // RealPath的作用除了校验地址正确性以外,还转换分隔符号为当前系统正确的文件分隔符号 + realPath := gfile.RealPath(path) + if realPath == "" { + glog.Fatal(fmt.Sprintf(`[ghttp] AddStaticPath failed: path "%s" does not exist`, path)) + } + addItem := staticPathItem { + prefix : prefix, + path : realPath, + } + if len(s.config.StaticPaths) > 0 { + // 先添加item + s.config.StaticPaths = append(s.config.StaticPaths, addItem) + // 按照prefix从长到短进行排序 + array := garray.NewSortedArray(0, func(v1, v2 interface{}) int { + s1 := gconv.String(v1) + s2 := gconv.String(v2) + r := len(s2) - len(s1) + if r == 0 { + r = strings.Compare(s1, s2) + } + return r + }, false) + for _, v := range s.config.StaticPaths { + array.Add(v.prefix) + } + // 按照重新排序的顺序重新添加item + paths := make([]staticPathItem, 0) + for _, v := range array.Slice() { + for _, item := range s.config.StaticPaths { + if strings.EqualFold(gconv.String(v), item.prefix) { + paths = append(paths, item) + break + } + } + } + s.config.StaticPaths = paths + } else { + s.config.StaticPaths = []staticPathItem { addItem } } - return nil } diff --git a/g/net/ghttp/ghttp_server_handler.go b/g/net/ghttp/ghttp_server_handler.go index 2dfcd65ee..f66c6099e 100644 --- a/g/net/ghttp/ghttp_server_handler.go +++ b/g/net/ghttp/ghttp_server_handler.go @@ -10,11 +10,13 @@ package ghttp import ( "fmt" "gitee.com/johng/gf/g/encoding/ghtml" + "gitee.com/johng/gf/g/os/gspath" "gitee.com/johng/gf/g/os/gtime" "net/http" "os" "reflect" "sort" + "strings" ) // 默认HTTP Server处理入口,http包底层默认使用了gorutine异步处理请求,所以这里不再异步执行 @@ -62,7 +64,7 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) { isStaticDir := false // 优先执行静态文件检索(检测是否存在对应的静态文件,包括index files处理) if s.config.FileServerEnabled { - staticFile, isStaticDir = s.paths.Search(r.URL.Path, s.config.IndexFiles...) + staticFile, isStaticDir = s.searchStaticFile(r.URL.Path) if staticFile != "" { request.isFileRequest = true } @@ -125,6 +127,31 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) { s.callHookHandler(HOOK_AFTER_OUTPUT, request) } +// 查找静态文件的绝对路径 +func (s *Server) searchStaticFile(uri string) (filePath string, isDir bool) { + // 优先查找URI映射 + if len(s.config.StaticPaths) > 0 { + for _, item := range s.config.StaticPaths { + if len(uri) >= len(item.prefix) && strings.EqualFold(item.prefix, uri[0 : len(item.prefix)]) { + // 防止类似 /static/style 映射到 /static/style.css 的情况 + if len(uri) > len(item.prefix) && uri[len(item.prefix)] != '/' { + continue + } + return gspath.Search(item.path, uri[len(item.prefix):], s.config.IndexFiles...) + } + } + } + // 其次查找root和search path + if len(s.config.SearchPaths) > 0 { + for _, path := range s.config.SearchPaths { + if filePath, isDir = gspath.Search(path, uri, s.config.IndexFiles...); filePath != "" { + return filePath, isDir + } + } + } + return "", false +} + // 初始化控制器 func (s *Server) callServeHandler(h *handlerItem, r *Request) { defer func() { diff --git a/g/os/gcfg/gcfg.go b/g/os/gcfg/gcfg.go index f30fdc6dc..65f8eb2af 100644 --- a/g/os/gcfg/gcfg.go +++ b/g/os/gcfg/gcfg.go @@ -12,10 +12,12 @@ import ( "bytes" "errors" "fmt" + "gitee.com/johng/gf/g/container/garray" "gitee.com/johng/gf/g/container/gmap" "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/container/gvar" "gitee.com/johng/gf/g/encoding/gjson" + "gitee.com/johng/gf/g/os/gfile" "gitee.com/johng/gf/g/os/gfsnotify" "gitee.com/johng/gf/g/os/glog" "gitee.com/johng/gf/g/os/gspath" @@ -28,7 +30,7 @@ const ( // 配置管理对象 type Config struct { name *gtype.String // 默认配置文件名称 - paths *gspath.SPath // 搜索目录路径 + paths *garray.StringArray // 搜索目录路径 jsons *gmap.StringInterfaceMap // 配置文件对象 vc *gtype.Bool // 层级检索是否执行分隔符冲突检测(默认为false,检测会比较影响检索效率) } @@ -41,7 +43,7 @@ func New(path string, file...string) *Config { } c := &Config { name : gtype.NewString(name), - paths : gspath.New(), + paths : garray.NewStringArray(0, 1), jsons : gmap.NewStringInterfaceMap(), vc : gtype.NewBool(), } @@ -52,18 +54,26 @@ func New(path string, file...string) *Config { } // 判断从哪个配置文件中获取内容,返回配置文件的绝对路径 -func (c *Config) filePath(file...string) string { +func (c *Config) filePath(file...string) (path string) { name := c.name.Val() if len(file) > 0 { name = file[0] } - path, _ := c.paths.Search(name) + c.paths.RLockFunc(func(array []string) { + for _, v := range array { + if path, _ = gspath.Search(v, name); path != "" { + break + } + } + }) if path == "" { buffer := bytes.NewBuffer(nil) buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" in following paths:", name)) - for k, v := range c.paths.Paths() { - buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v)) - } + c.paths.RLockFunc(func(array []string) { + for k, v := range array { + buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v)) + } + }) glog.Error(buffer.String()) } return path @@ -71,13 +81,16 @@ func (c *Config) filePath(file...string) string { // 设置配置管理器的配置文件存放目录绝对路径 func (c *Config) SetPath(path string) error { - if rp, err := c.paths.Set(path); err != nil { - glog.Error("[gcfg] SetPath failed:", err.Error()) + realPath := gfile.RealPath(path) + if realPath == "" { + err := errors.New(fmt.Sprintf(`path "%s" does not exist`, path)) + glog.Error(fmt.Sprintf(`[gcfg] SetPath failed: %s`, err.Error())) return err - } else { - c.jsons.Clear() - glog.Debug("[gcfg] SetPath:", rp) } + c.jsons.Clear() + c.paths.Clear() + c.paths.Append(realPath) + glog.Debug("[gcfg] SetPath:", realPath) return nil } @@ -90,12 +103,14 @@ func (c *Config) SetViolenceCheck(check bool) { // 添加配置管理器的配置文件搜索路径 func (c *Config) AddPath(path string) error { - if rp, err := c.paths.Add(path); err != nil { - glog.Debug("[gcfg] AddPath failed:", err.Error()) + realPath := gfile.RealPath(path) + if realPath == "" { + err := errors.New(fmt.Sprintf(`path "%s" does not exist`, path)) + glog.Error(fmt.Sprintf(`[gcfg] AddPath failed: %s`, err.Error())) return err - } else { - glog.Debug("[gcfg] AddPath:", rp) } + c.paths.Append(realPath) + glog.Debug("[gcfg] AddPath:", realPath) return nil } diff --git a/g/os/gspath/gspath.go b/g/os/gspath/gspath.go index ee0f6f12c..48a00683a 100644 --- a/g/os/gspath/gspath.go +++ b/g/os/gspath/gspath.go @@ -33,14 +33,36 @@ type SPathCacheItem struct { isDir bool // 是否目录 } +var ( + // 单个目录路径对应的SPath对象指针,用于路径检索对象复用 + pathsMap = gmap.NewStringInterfaceMap() +) + // 创建一个搜索对象 -func New () *SPath { - return &SPath { - paths : garray.NewStringArray(0, 2), +func New(path...string) *SPath { + sp := &SPath { + paths : garray.NewStringArray(0, 1), cache : gmap.NewStringStringMap(), } + if len(path) > 0 { + sp.Add(path[0]) + } + return sp } +// 创建/获取一个单例的搜索对象, root必须为目录的绝对路径 +func Get(root string) *SPath { + return pathsMap.GetOrSetFuncLock(root, func() interface{} { + return New(root) + }).(*SPath) +} + +// 检索root目录(必须为绝对路径)下面的name文件的绝对路径,indexFiles用于指定当检索到的结果为目录时,同时检索是否存在这些indexFiles文件 +func Search(root string, name string, indexFiles...string) (filePath string, isDir bool) { + return Get(root).Search(name, indexFiles...) +} + + // 设置搜索路径,只保留当前设置项,其他搜索路径被清空 func (sp *SPath) Set(path string) (realPath string, err error) { realPath = gfile.RealPath(path) @@ -63,6 +85,7 @@ func (sp *SPath) Set(path string) (realPath string, err error) { } sp.paths.Clear() sp.cache.Clear() + sp.paths.Append(realPath) sp.updateCacheByPath(realPath) sp.addMonitorByPath(realPath) diff --git a/g/os/gview/gview.go b/g/os/gview/gview.go index d61e1dbb8..cc5682df3 100644 --- a/g/os/gview/gview.go +++ b/g/os/gview/gview.go @@ -11,6 +11,7 @@ import ( "bytes" "errors" "fmt" + "gitee.com/johng/gf/g/container/garray" "gitee.com/johng/gf/g/encoding/ghash" "gitee.com/johng/gf/g/encoding/ghtml" "gitee.com/johng/gf/g/encoding/gurl" @@ -29,7 +30,7 @@ import ( // 视图对象 type View struct { mu sync.RWMutex - paths *gspath.SPath // 模板查找目录(绝对路径) + paths *garray.StringArray // 模板查找目录(绝对路径) data map[string]interface{} // 模板变量 funcmap map[string]interface{} // FuncMap delimiters []string // 模板变量分隔符号 @@ -64,7 +65,7 @@ func ParseContent(content string, params Params) ([]byte, error) { // 生成一个视图对象 func New(path...string) *View { view := &View { - paths : gspath.New(), + paths : garray.NewStringArray(0, 1), data : make(map[string]interface{}), funcmap : make(map[string]interface{}), delimiters : make([]string, 2), @@ -96,23 +97,28 @@ func New(path...string) *View { // 设置模板目录绝对路径 func (view *View) SetPath(path string) error { - if rp, err := view.paths.Set(path); err != nil { - glog.Error("[gview] SetPath failed:", err.Error()) + realPath := gfile.RealPath(path) + if realPath == "" { + err := errors.New(fmt.Sprintf(`path "%s" does not exist`, path)) + glog.Error(fmt.Sprintf(`[gview] SetPath failed: %s`, err.Error())) return err - } else { - glog.Debug("[gview] SetPath:", rp) } + view.paths.Clear() + view.paths.Append(realPath) + glog.Debug("[gview] SetPath:", realPath) return nil } // 添加模板目录搜索路径 func (view *View) AddPath(path string) error { - if rp, err := view.paths.Add(path); err != nil { - glog.Error("[gview] AddPath failed:", err.Error()) + realPath := gfile.RealPath(path) + if realPath == "" { + err := errors.New(fmt.Sprintf(`path "%s" does not exist`, path)) + glog.Error(fmt.Sprintf(`[gview] AddPath failed: %s`, err.Error())) return err - } else { - glog.Debug("[gview] AddPath:", rp) } + view.paths.Append(realPath) + glog.Debug("[gview] AddPath:", realPath) return nil } @@ -134,9 +140,24 @@ func (view *View) Assign(key string, value interface{}) { // 解析模板,返回解析后的内容 func (view *View) Parse(file string, params Params, funcmap...map[string]interface{}) ([]byte, error) { - path, _ := view.paths.Search(file) + path := "" + view.paths.RLockFunc(func(array []string) { + for _, v := range array { + if path, _ = gspath.Search(v, file); path != "" { + break + } + } + }) if path == "" { - return nil, errors.New("tpl \"" + file + "\" not found") + buffer := bytes.NewBuffer(nil) + buffer.WriteString(fmt.Sprintf("[gview] cannot find template file \"%s\" in following paths:", file)) + view.paths.RLockFunc(func(array []string) { + for k, v := range array { + buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v)) + } + }) + glog.Error(buffer.String()) + return nil, errors.New(fmt.Sprintf(`tpl "%s" not found`, file)) } content := gfcache.GetContents(path) // 执行模板解析,互斥锁主要是用于funcmap diff --git a/g/util/gconv/gconv_map.go b/g/util/gconv/gconv_map.go new file mode 100644 index 000000000..6d5e31fab --- /dev/null +++ b/g/util/gconv/gconv_map.go @@ -0,0 +1,108 @@ +// Copyright 2018 gf Author(https://gitee.com/johng/gf). 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://gitee.com/johng/gf. + +package gconv + +import ( + "reflect" +) + +// 任意类型转换为 map[string]interface{} 类型, +// 如果给定的输入参数i不是map类型,那么转换会失败,返回nil. +func Map(i interface{}) map[string]interface{} { + if i == nil { + return nil + } + if r, ok := i.(map[string]interface{}); ok { + return r + } else { + // 仅对常见的几种map组合进行断言,最后才会使用反射 + m := make(map[string]interface{}) + switch i.(type) { + case map[interface{}]interface{}: + for k, v := range i.(map[interface{}]interface{}) { + m[String(k)] = v + } + case map[interface{}]string: + for k, v := range i.(map[interface{}]string) { + m[String(k)] = v + } + case map[interface{}]int: + for k, v := range i.(map[interface{}]int) { + m[String(k)] = v + } + case map[interface{}]uint: + for k, v := range i.(map[interface{}]uint) { + m[String(k)] = v + } + case map[interface{}]float32: + for k, v := range i.(map[interface{}]float32) { + m[String(k)] = v + } + case map[interface{}]float64: + for k, v := range i.(map[interface{}]float64) { + m[String(k)] = v + } + + case map[string]bool: + for k, v := range i.(map[string]bool) { + m[k] = v + } + case map[string]int: + for k, v := range i.(map[string]int) { + m[k] = v + } + case map[string]uint: + for k, v := range i.(map[string]uint) { + m[k] = v + } + case map[string]float32: + for k, v := range i.(map[string]float32) { + m[k] = v + } + case map[string]float64: + for k, v := range i.(map[string]float64) { + m[k] = v + } + + case map[int]interface{}: + for k, v := range i.(map[int]interface{}) { + m[String(k)] = v + } + case map[int]string: + for k, v := range i.(map[int]string) { + m[String(k)] = v + } + case map[uint]string: + for k, v := range i.(map[uint]string) { + m[String(k)] = v + } + // 不是常见类型,则使用反射 + default: + rv := reflect.ValueOf(i) + kind := rv.Kind() + // 如果是指针,那么需要转换到指针对应的数据项,以便识别真实的类型 + if kind == reflect.Ptr { + rv = rv.Elem() + kind = rv.Kind() + } + if kind == reflect.Map { + ks := rv.MapKeys() + for _, k := range ks { + m[String(k.Interface())] = rv.MapIndex(k).Interface() + } + } else if kind == reflect.Struct { + rt := rv.Type() + for i := 0; i < rv.NumField(); i++ { + m[rt.Field(i).Name] = rv.Field(i).Interface() + } + } else { + return nil + } + } + return m + } +} diff --git a/g/util/gconv/gconv_slice.go b/g/util/gconv/gconv_slice.go index 8f8a35782..2acb19884 100644 --- a/g/util/gconv/gconv_slice.go +++ b/g/util/gconv/gconv_slice.go @@ -6,7 +6,10 @@ package gconv -import "fmt" +import ( + "fmt" + "reflect" +) // 任意类型转换为[]int类型 func Ints(i interface{}) []int { @@ -296,6 +299,26 @@ func Interfaces(i interface{}) []interface{} { for _, v := range i.([]float64) { array = append(array, v) } + // 不是常见类型,则使用反射 + default: + rv := reflect.ValueOf(i) + kind := rv.Kind() + // 如果是指针,那么需要转换到指针对应的数据项,以便识别真实的类型 + if kind == reflect.Ptr { + rv = rv.Elem() + kind = rv.Kind() + } + switch kind { + case reflect.Slice: fallthrough + case reflect.Array: + for i := 0; i < rv.Len(); i++ { + array = append(array, rv.Index(i).Interface()) + } + case reflect.Struct: + for i := 0; i < rv.NumField(); i++ { + array = append(array, rv.Field(i).Interface()) + } + } } if len(array) > 0 { return array diff --git a/g/util/gconv/gconv_struct.go b/g/util/gconv/gconv_struct.go index 869be54ca..1261065f1 100644 --- a/g/util/gconv/gconv_struct.go +++ b/g/util/gconv/gconv_struct.go @@ -205,10 +205,12 @@ func bindVarToStructIfDefaultConvertionFailed(structFieldValue reflect.Value, va switch structFieldValue.Kind() { case reflect.Struct: Struct(value, structFieldValue) - case reflect.Slice: + + case reflect.Slice: fallthrough + case reflect.Array: a := reflect.Value{} v := reflect.ValueOf(value) - if v.Kind() == reflect.Slice { + if v.Kind() == reflect.Slice || v.Kind() == reflect.Array { a = reflect.MakeSlice(structFieldValue.Type(), v.Len(), v.Len()) for i := 0; i < v.Len(); i++ { n := reflect.New(structFieldValue.Type().Elem()).Elem() diff --git a/g/util/gvalid/gvalid_test.go b/g/util/gvalid/gvalid_test.go index 61b6e1cd1..42c74c014 100644 --- a/g/util/gvalid/gvalid_test.go +++ b/g/util/gvalid/gvalid_test.go @@ -5,7 +5,7 @@ // You can obtain one at https://gitee.com/johng/gf. // 单元测试 -// go test *.go -bench=".*" +// go test *.go package gvalid diff --git a/geg/net/ghttp/server/static.go b/geg/net/ghttp/server/static/static.go similarity index 68% rename from geg/net/ghttp/server/static.go rename to geg/net/ghttp/server/static/static.go index ca23ddf19..cecc0b45c 100644 --- a/geg/net/ghttp/server/static.go +++ b/geg/net/ghttp/server/static/static.go @@ -2,11 +2,12 @@ package main import "gitee.com/johng/gf/g" -// 静态文件服务器 +// 静态文件服务器基本使用 func main() { s := g.Server() s.SetIndexFolder(true) s.SetServerRoot("/Users/john/Temp") + s.AddSearchPath("/Users/john/Documents") s.SetPort(8199) s.Run() } diff --git a/geg/net/ghttp/server/static/static_path.go b/geg/net/ghttp/server/static/static_path.go new file mode 100644 index 000000000..1cb36b6e4 --- /dev/null +++ b/geg/net/ghttp/server/static/static_path.go @@ -0,0 +1,14 @@ +package main + +import "gitee.com/johng/gf/g" + +// 静态文件服务器,支持自定义静态目录映射 +func main() { + s := g.Server() + s.SetIndexFolder(true) + s.SetServerRoot("/Users/john/Temp") + s.AddSearchPath("/Users/john/Documents") + s.AddStaticPath("/my-doc", "/Users/john/Documents") + s.SetPort(8199) + s.Run() +} diff --git a/geg/net/ghttp/server/static/static_path2.go b/geg/net/ghttp/server/static/static_path2.go new file mode 100644 index 000000000..fa83f7a6c --- /dev/null +++ b/geg/net/ghttp/server/static/static_path2.go @@ -0,0 +1,15 @@ +package main + +import "gitee.com/johng/gf/g" + +// 静态文件服务器,支持自定义静态目录映射 +func main() { + s := g.Server() + s.SetIndexFolder(true) + s.SetServerRoot("/Users/john/Temp") + s.AddSearchPath("/Users/john/Documents") + s.AddStaticPath("/my-doc", "/Users/john/Documents") + s.AddStaticPath("/my-doc/test", "/Users/john/Temp") + s.SetPort(8199) + s.Run() +} diff --git a/geg/os/gview/basic/gview.go b/geg/os/gview/basic/gview.go index d4479acfb..f54590bbb 100644 --- a/geg/os/gview/basic/gview.go +++ b/geg/os/gview/basic/gview.go @@ -11,6 +11,8 @@ func main() { b, err := v.Parse("gview.tpl", map[string]interface{} { "k" : "v", }) - fmt.Println(err) + if err != nil { + panic(err) + } fmt.Println(string(b)) } \ No newline at end of file diff --git a/geg/os/gview/template/main.go b/geg/os/gview/template/main.go index 4676f9e71..d1f75f659 100644 --- a/geg/os/gview/template/main.go +++ b/geg/os/gview/template/main.go @@ -10,10 +10,12 @@ import ( func main() { v := g.View() // 设置模板目录为当前main.go所在目录下的template目录 - v.AddPath(gfile.MainPkgPath() + gfile.Separator + "template") + v.AddPath(gfile.MainPkgPath() + gfile.Separator + "template2") b, err := v.Parse("index.html", map[string]interface{} { "k" : "v", }) - fmt.Println(err) + if err != nil { + panic(err) + } fmt.Println(string(b)) } \ No newline at end of file diff --git a/geg/other/test.go b/geg/other/test.go index fc3151fcf..7a79d5cf6 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,9 +1,39 @@ package main import ( - "gitee.com/johng/gf/g" + "fmt" + "gitee.com/johng/gf/g/encoding/gparser" ) func main() { - g.Database() + + + type DemoInfo struct { + Name string + Age string + } + + + //r.Response.Write("Hello World") + l := map[interface{}][]DemoInfo{} + + el := [...]DemoInfo{ + {Name:"Bala", Age:"15"}, + {Name:"CeCe", Age:"18"}, + {Name:"ChenLo", Age:"28"}, + {Name:"Bii", Age:"22"}, + {Name:"Ann", Age:"23"}, + {Name:"Bmx", Age:"88"}, + } + + for _,v := range el{ + l[string(v.Name[0])] = append(l[string(v.Name[0])],v) + } + //fmt.Println(l) + + + + b, err := gparser.VarToJson(l) + fmt.Println(err) + fmt.Println(string(b)) } diff --git a/geg/util/gconv/gconv_map.go b/geg/util/gconv/gconv_map.go new file mode 100644 index 000000000..30b38596e --- /dev/null +++ b/geg/util/gconv/gconv_map.go @@ -0,0 +1,25 @@ +package main + +import ( + "fmt" + "gitee.com/johng/gf/g/util/gconv" +) + + +// struct转map +func main() { + type User struct { + Uid int + Name string + } + // 对象 + fmt.Println(gconv.Map(User{ + Uid : 1, + Name : "john", + })) + // 指针 + fmt.Println(gconv.Map(&User{ + Uid : 1, + Name : "john", + })) +} \ No newline at end of file diff --git a/geg/util/gconv/gconv_slice.go b/geg/util/gconv/gconv_slice.go new file mode 100644 index 000000000..11ba23f96 --- /dev/null +++ b/geg/util/gconv/gconv_slice.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "gitee.com/johng/gf/g/util/gconv" +) + +// struct转slice +func main() { + type User struct { + Uid int + Name string + } + // 对象 + fmt.Println(gconv.Interfaces(User{ + Uid : 1, + Name : "john", + })) + // 指针 + fmt.Println(gconv.Interfaces(&User{ + Uid : 1, + Name : "john", + })) +} \ No newline at end of file