mirror of
https://gitee.com/johng/gf
synced 2026-07-06 13:42:46 +08:00
ghttp增加静态文件目录映射功能;改进gspath目录管理功能;改进gconv的slice转换功能,并增加gconv.Map方法
This commit is contained in:
5
TODO.MD
5
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回调使用方式在注册路由比较多的时候,优先级可能使得开发者混乱,考虑方式便于管理;
|
||||
1. ghttp hook回调使用方式在注册路由比较多的时候,优先级可能使得开发者混乱,考虑方式便于管理;
|
||||
1. gform对于MySQL字段类型为datetime类型的时区问题分析;
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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...)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
108
g/util/gconv/gconv_map.go
Normal file
108
g/util/gconv/gconv_map.go
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// 单元测试
|
||||
// go test *.go -bench=".*"
|
||||
// go test *.go
|
||||
|
||||
package gvalid
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
14
geg/net/ghttp/server/static/static_path.go
Normal file
14
geg/net/ghttp/server/static/static_path.go
Normal file
@ -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()
|
||||
}
|
||||
15
geg/net/ghttp/server/static/static_path2.go
Normal file
15
geg/net/ghttp/server/static/static_path2.go
Normal file
@ -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()
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
|
||||
25
geg/util/gconv/gconv_map.go
Normal file
25
geg/util/gconv/gconv_map.go
Normal file
@ -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",
|
||||
}))
|
||||
}
|
||||
24
geg/util/gconv/gconv_slice.go
Normal file
24
geg/util/gconv/gconv_slice.go
Normal file
@ -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",
|
||||
}))
|
||||
}
|
||||
Reference in New Issue
Block a user