diff --git a/README.MD b/README.MD index b8f5d5ec7..5b8924bc6 100644 --- a/README.MD +++ b/README.MD @@ -1,16 +1,16 @@ Go Frame - 轻量级的Go语言开发框架。 -# 安装 +## 安装 ``` go get -u gitee.com/johng/gf ```` -# 使用 +## 使用 ```go import "gitee.com/johng/gf/g/xxx" ``` -# 说明 +## 说明 . ├── g 【框架目录】 │ ├── container 【常用数据结构容器】 @@ -60,4 +60,8 @@ import "gitee.com/johng/gf/g/xxx" │ ├── geg 【框架示例】 ├── vendor 【第三方包】 - └── version.go 【版本信息】 \ No newline at end of file + └── version.go 【版本信息】 + +## 配置 + 键名 键值类型 配置说明 + Server名称.gf.mvc.view.path string 模板目录绝对路径 diff --git a/g/frame/gconfig/gconfig.go b/g/frame/gconfig/gconfig.go new file mode 100644 index 000000000..3e350582b --- /dev/null +++ b/g/frame/gconfig/gconfig.go @@ -0,0 +1,48 @@ +// 全局配置管理对象 +package gconfig + +import ( + "gitee.com/johng/gf/g/container/gmap" + "gitee.com/johng/gf/g/encoding/gjson" +) + +// 配置对象 +var config = gmap.NewStringInterfaceMap() + +// 获取配置 +func Get(k string) interface{} { + return config.Get(k) +} + +func GetInt(k string) int { + if v := config.Get(k); v != nil { + if r, ok := v.(int); ok { + return r + } + } + return 0 +} + +func GetString(k string) string { + if v := config.Get(k); v != nil { + if r, ok := v.(string); ok { + return r + } + } + return "" +} + +// 适用于json文件配置,在设置的时候通过gjson进行解析后再保存 +func GetJson(k string) *gjson.Json { + if v := config.Get(k); v != nil { + if r, ok := v.(*gjson.Json); ok { + return r + } + } + return nil +} + +// 设置配置 +func Set(k string, v interface{}) { + config.Set(k, v) +} \ No newline at end of file diff --git a/g/frame/gmvc/controller.go b/g/frame/gmvc/controller.go index bc7759371..d12883b50 100644 --- a/g/frame/gmvc/controller.go +++ b/g/frame/gmvc/controller.go @@ -1,8 +1,46 @@ +// MVC控制器基类 package gmvc -import "gitee.com/johng/gf/g/net/ghttp" +import ( + "gitee.com/johng/gf/g/net/ghttp" + "gitee.com/johng/gf/g/net/gsession" +) + +const ( + gDEFAULT_SESSION_ID_NAME = "gfsessionid" +) // 控制器基类 type Controller struct { - ghttp.ControllerBase + Server *ghttp.Server // Web Server对象 + Request *ghttp.ClientRequest // 请求数据对象 + Response *ghttp.ServerResponse // 返回数据对象 + Cookie *ghttp.Cookie // COOKIE操作对象 + Session *gsession.Session // SESSION操作对象 + View *View // 视图对象 } + +// 控制器初始化 +func (c *Controller) Init(s *ghttp.Server, r *ghttp.ClientRequest, w *ghttp.ServerResponse) { + c.Server = s + c.Request = r + c.Response = w + c.Cookie = ghttp.NewCookie(c.Request, c.Response) + c.View = NewView(c) + if r := c.Cookie.Get(gDEFAULT_SESSION_ID_NAME); r != "" { + c.Session = gsession.Get(r) + } else { + c.Session = gsession.Get(gsession.Id()) + } +} + +// 控制器结束请求 +func (c *Controller) Shut() { + if c.Cookie.Get(gDEFAULT_SESSION_ID_NAME) == "" { + c.Cookie.Set(gDEFAULT_SESSION_ID_NAME, c.Session.Id()) + } + c.Cookie.Output() + c.Response.Output() +} + + diff --git a/g/frame/gmvc/view.go b/g/frame/gmvc/view.go index 831035b75..2f9ceb910 100644 --- a/g/frame/gmvc/view.go +++ b/g/frame/gmvc/view.go @@ -1,115 +1,78 @@ package gmvc import ( - "gitee.com/johng/gf/g/container/gmap" - "html/template" - "gitee.com/johng/gf/g/os/gfile" "sync" - "strings" - "bytes" - "errors" + "html/template" + "gitee.com/johng/gf/g/os/gview" + "gitee.com/johng/gf/g/frame/gconfig" ) -// 视图对象 +// 视图对象(一个请求一个视图对象,用完即销毁) type View struct { - mu sync.RWMutex - path string // 模板目录(绝对路径) - tpls *gmap.StringInterfaceMap // 已解析的模板对象指针,防止重复解析 - suffix string // 模板文件名后缀 + mu sync.RWMutex // 并发互斥锁 + ctl *Controller // 所属控制器 + view *gview.View // 底层视图对象 + data map[string]interface{} // 视图数据 } -// 模板对象 -type Template struct { - mu sync.RWMutex // 并发互斥锁 - path string // 模板文件(绝对路径) - data map[string]interface{} // 全局的模板变量 - content string // 模板内容(解析之后保存到内存中) - template *template.Template // 底层模板对象 - lasterror error // 最近一次错误 -} - -// 生成一个视图对象 -func NewView(path string) *View { +// 创建一个MVC请求中使用的视图对象 +func NewView(c *Controller) *View { + path := c.Server.GetName() + ".gf.mvc.view.path" return &View{ - path : path, - tpls : gmap.NewStringInterfaceMap(), - suffix : "tpl", + ctl : c, + view : gview.GetView(gconfig.GetString(path)), + data : make(map[string]interface{}), } } -// 设置模板文件后缀名 -func (view *View) SetSuffix(suffix string) { - view.mu.Lock() - defer view.mu.Unlock() - view.suffix = suffix -} - -// 获取模板文件后缀名 -func (view *View) GetSuffix() string { - view.mu.Lock() - defer view.mu.Unlock() - return view.suffix -} - -// 根据文件名称生成一个模板对象,或者获取一个现有的模板对象 -func (view *View) Template(file string) (*Template, error) { - path := strings.TrimRight(view.path, gfile.Separator) + gfile.Separator + file + "." + view.GetSuffix() - if t := view.tpls.Get(path); t != nil { - return t.(*Template), nil - } - if !gfile.Exists(path) { - return nil, errors.New("template '" + path + "' does not exist") - } - if !gfile.IsReadable(path) { - return nil, errors.New("template '" + path + "' is not readable") - } - t := &Template{ - path : path, - data : make(map[string]interface{}), - content : gfile.GetContents(path), - template : template.New(path), - } - view.tpls.Set(path, t) - return t, nil -} - -// 绑定自定义函数,该函数是全局有效,即调用之后每个线程都会生效,因此有并发安全控制 -func (t *Template) BindFunc(name string, function interface{}) { - t.mu.Lock() - defer t.mu.Unlock() - t.template.Funcs(template.FuncMap{name : function}) -} - // 批量绑定模板变量,即调用之后每个线程都会生效,因此有并发安全控制 -func (t *Template) Assigns(data map[string]interface{}) { - t.mu.Lock() - defer t.mu.Unlock() +func (view *View) Assigns(data map[string]interface{}) { + view.mu.Lock() + defer view.mu.Unlock() for k, v := range data { - t.data[k] = v + view.data[k] = v } } // 绑定模板变量,即调用之后每个线程都会生效,因此有并发安全控制 -func (t *Template) Assign(k string, v interface{}) { - t.mu.Lock() - defer t.mu.Unlock() - t.data[k] = v +func (view *View) Assign(key string, value interface{}) { + view.mu.Lock() + defer view.mu.Unlock() + view.data[key] = value } -// 返回解析后的模板内容,可以额外指定模板变量,如果没有可以传入nil -func (t *Template) Parse(data map[string]interface{}) (string, error) { - buffer := bytes.NewBuffer(nil) - if tpl, err := t.template.Parse(t.content); err != nil { - return "", err - } else { - m := t.data - for k, v := range data { - m[k] = v - } - if err := tpl.Execute(buffer, m); err != nil { - return "", err - } +// 解析指定模板 +func (view *View) Display(file string) error { + // 查询模板 + tpl, err := view.view.Template(file) + if err != nil { + view.ctl.Response.WriteString("Tpl Parsing Error: " + err.Error()) + return err } - return buffer.String(), nil + // 绑定函数 + tpl.BindFunc("include", view.funcInclude) + // 执行解析 + view.mu.RLock() + defer view.mu.RUnlock() + content, err := tpl.Parse(view.data) + if err != nil { + view.ctl.Response.WriteString("Tpl Parsing Error: " + err.Error()) + return err + } else { + view.ctl.Response.WriteString(content) + } + return nil } +// 模板内置方法:include +func (view *View) funcInclude(file string) template.HTML { + tpl, err := view.view.Template(file) + if err != nil { + return template.HTML(err.Error()) + } + content, err := tpl.Parse(view.data) + if err != nil { + return template.HTML(err.Error()) + } + return template.HTML(content) +} diff --git a/g/net/ghttp/http_controller.go b/g/net/ghttp/http_controller.go index cde033d58..18b8ead68 100644 --- a/g/net/ghttp/http_controller.go +++ b/g/net/ghttp/http_controller.go @@ -3,6 +3,6 @@ package ghttp // 控制器接口 type Controller interface { - Init(*ClientRequest, *ServerResponse) + Init(*Server, *ClientRequest, *ServerResponse) Shut() } diff --git a/g/net/ghttp/http_controller_base.go b/g/net/ghttp/http_controller_base.go deleted file mode 100644 index 12e4d4431..000000000 --- a/g/net/ghttp/http_controller_base.go +++ /dev/null @@ -1,40 +0,0 @@ -package ghttp - -import ( - "gitee.com/johng/gf/g/net/gsession" -) - -const ( - gDEFAULT_SESSION_ID_NAME = "gfsessionid" -) - -// 控制器基类 -type ControllerBase struct { - Request *ClientRequest - Response *ServerResponse - Cookie *Cookie - Session *gsession.Session -} - -// 控制器初始化 -func (c *ControllerBase) Init(r *ClientRequest, w *ServerResponse) { - c.Request = r - c.Response = w - c.Cookie = NewCookie(c.Request, c.Response) - if r := c.Cookie.Get(gDEFAULT_SESSION_ID_NAME); r != "" { - c.Session = gsession.Get(r) - } else { - c.Session = gsession.Get(gsession.Id()) - } -} - -// 控制器结束请求 -func (c *ControllerBase) Shut() { - if c.Cookie.Get(gDEFAULT_SESSION_ID_NAME) == "" { - c.Cookie.Set(gDEFAULT_SESSION_ID_NAME, c.Session.Id()) - } - c.Cookie.Output() - c.Response.Output() -} - - diff --git a/g/net/ghttp/http_server.go b/g/net/ghttp/http_server.go index 44332e2db..52a861728 100644 --- a/g/net/ghttp/http_server.go +++ b/g/net/ghttp/http_server.go @@ -39,7 +39,7 @@ type HandlerFunc struct { } // Server表,用以存储和检索名称与Server对象之间的关联关系 -var serverMapping *gmap.StringInterfaceMap = gmap.NewStringInterfaceMap() +var serverMapping = gmap.NewStringInterfaceMap() // 创建一个默认配置的HTTP Server(默认监听端口是80) func GetServer(name string) (*Server) { @@ -81,6 +81,11 @@ func (s *Server) Run() error { return nil } +// 获取 +func (s *Server) GetName() string { + return s.name +} + // http server setting设置 // 注意使用该方法进行http server配置时,需要配置所有的配置项,否则没有配置的属性将会默认变量为空 func (s *Server)SetConfig(c ServerConfig) error { diff --git a/g/net/ghttp/http_server_domain.go b/g/net/ghttp/http_server_domain.go index a521a2a1e..d32e847be 100644 --- a/g/net/ghttp/http_server_domain.go +++ b/g/net/ghttp/http_server_domain.go @@ -12,7 +12,7 @@ type Domain struct { } // 域名对象表,用以存储和检索域名(支持多域名)与域名对象之间的关联关系 -var domains *gmap.StringInterfaceMap = gmap.NewStringInterfaceMap() +var domains = gmap.NewStringInterfaceMap() // 生成一个域名对象 func (s *Server) Domain(domain string) *Domain { diff --git a/g/net/ghttp/http_server_handler.go b/g/net/ghttp/http_server_handler.go index bfc44826b..d64c9c840 100644 --- a/g/net/ghttp/http_server_handler.go +++ b/g/net/ghttp/http_server_handler.go @@ -21,7 +21,7 @@ func (s *Server)defaultHttpHandle(w http.ResponseWriter, r *http.Request) { // 执行处理HTTP请求 func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) { request := &ClientRequest{} - response := &ServerResponse {server : s} + response := &ServerResponse{} request.Request = *r response.ResponseWriter = w if h := s.getHandler(gDEFAULT_DOMAIN, r.Method, r.URL.Path); h != nil { @@ -38,7 +38,7 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) { // 初始化控制器 func (s *Server)initController(h *HandlerFunc, r *ClientRequest, w *ServerResponse) { c := reflect.New(h.ctype) - c.MethodByName("Init").Call([]reflect.Value{reflect.ValueOf(r), reflect.ValueOf(w)}) + c.MethodByName("Init").Call([]reflect.Value{reflect.ValueOf(s), reflect.ValueOf(r), reflect.ValueOf(w)}) c.MethodByName(h.fname).Call(nil) c.MethodByName("Shut").Call(nil) } diff --git a/g/net/ghttp/http_server_response.go b/g/net/ghttp/http_server_response.go index fb55c5183..fd6f9a7cd 100644 --- a/g/net/ghttp/http_server_response.go +++ b/g/net/ghttp/http_server_response.go @@ -10,7 +10,6 @@ import ( type ServerResponse struct { http.ResponseWriter bufmu sync.RWMutex // 缓冲区互斥锁 - server *Server // 所属Server对象 buffer []byte // 每个请求的返回数据缓冲区 } diff --git a/g/os/gview/gview.go b/g/os/gview/gview.go new file mode 100644 index 000000000..221efa883 --- /dev/null +++ b/g/os/gview/gview.go @@ -0,0 +1,130 @@ +package gview + +import ( + "gitee.com/johng/gf/g/container/gmap" + "html/template" + "gitee.com/johng/gf/g/os/gfile" + "sync" + "strings" + "bytes" + "errors" +) + +// 视图对象 +type View struct { + mu sync.RWMutex + path string // 模板目录(绝对路径) + tpls *gmap.StringInterfaceMap // 已解析的模板对象指针,防止重复解析 + suffix string // 模板文件名后缀 +} + +// 模板对象 +type Template struct { + mu sync.RWMutex // 并发互斥锁 + path string // 模板文件(绝对路径) + data map[string]interface{} // 全局的模板变量 + content string // 模板文件内容(解析之后保存到内存中) + funcmap map[string]interface{} // FuncMap +} + +// 视图表 +var viewMap = gmap.NewStringInterfaceMap() + +// 获取或者创建一个视图对象 +func GetView(path string) *View { + if r := viewMap.Get(path); r != nil { + return r.(*View) + } + v := New(path) + viewMap.Set(path, v) + return v +} + +// 生成一个视图对象 +func New(path string) *View { + return &View{ + path : path, + tpls : gmap.NewStringInterfaceMap(), + suffix : "tpl", + } +} + +// 设置模板文件后缀名 +//func (view *View) SetSuffix(suffix string) { +// view.mu.Lock() +// defer view.mu.Unlock() +// view.suffix = suffix +//} + +// 获取模板文件后缀名 +func (view *View) GetSuffix() string { + view.mu.RLock() + defer view.mu.RUnlock() + return view.suffix +} + +// 根据文件名称生成一个模板对象,或者获取一个现有的模板对象 +func (view *View) Template(file string) (*Template, error) { + path := strings.TrimRight(view.path, gfile.Separator) + gfile.Separator + file + "." + view.GetSuffix() + if t := view.tpls.Get(path); t != nil { + return t.(*Template), nil + } + if !gfile.Exists(path) { + return nil, errors.New("template '" + path + "' does not exist") + } + if !gfile.IsReadable(path) { + return nil, errors.New("template '" + path + "' is not readable") + } + t := &Template{ + path : path, + data : make(map[string]interface{}), + content : gfile.GetContents(path), + funcmap : make(map[string]interface{}), + } + view.tpls.Set(path, t) + return t, nil +} + +// 绑定自定义函数,该函数是全局有效,即调用之后每个线程都会生效,因此有并发安全控制 +func (t *Template) BindFunc(name string, function interface{}) { + t.mu.Lock() + defer t.mu.Unlock() + t.funcmap[name] = function +} + +// 批量绑定模板变量,即调用之后每个线程都会生效,因此有并发安全控制 +func (t *Template) Assigns(data map[string]interface{}) { + t.mu.Lock() + defer t.mu.Unlock() + for k, v := range data { + t.data[k] = v + } +} + +// 绑定模板变量,即调用之后每个线程都会生效,因此有并发安全控制 +func (t *Template) Assign(k string, v interface{}) { + t.mu.Lock() + defer t.mu.Unlock() + t.data[k] = v +} + +// 返回解析后的模板内容,可以额外指定模板变量,如果没有可以传入nil +// 函数内部的底层template必须每次调用都新生成一个,防止错误:html/template: cannot Parse after Execute +func (t *Template) Parse(data map[string]interface{}) (string, error) { + t.mu.RLock() + defer t.mu.RUnlock() + buffer := bytes.NewBuffer(nil) + if tpl, err := template.New(t.path).Funcs(t.funcmap).Parse(t.content); err != nil { + return "", err + } else { + m := t.data + for k, v := range data { + m[k] = v + } + if err := tpl.Execute(buffer, m); err != nil { + return "", err + } + } + return buffer.String(), nil +} + diff --git a/geg/frame/mvc/controller/user/user.go b/geg/frame/mvc/controller/user/user.go index 0dd83d71a..235eec881 100644 --- a/geg/frame/mvc/controller/user/user.go +++ b/geg/frame/mvc/controller/user/user.go @@ -3,6 +3,7 @@ package user import ( "gitee.com/johng/gf/g/net/ghttp" "gitee.com/johng/gf/g/frame/gmvc" + "fmt" ) // 定义业务相关的控制器对象 @@ -22,17 +23,11 @@ func init() { // 定义操作逻辑 func (c *ControllerUser) Info() { - //c.Session.Set("name", "john") - c.Response.WriteString("session:" + c.Session.GetString("name")) - c.Response.Write([]byte("user information page")) - //t, err := template.New("test").Funcs(template.FuncMap{"add": Add}).Parse(gfile.GetContents("/home/john/Workspace/Go/GOPATH/src/gitee.com/johng/gf/geg/frame/mvc/view/user/info.tpl")) - //if err != nil { - // fmt.Println(err) - //} - // - //t.Execute(w.ResponseWriter, map[string]string{ - // "name" : "john", - //}) + //c.Response.WriteString("user information page") + c.View.Assign("name", "john") + if err := c.View.Display("user/index"); err != nil { + fmt.Println(err) + } } diff --git a/geg/frame/mvc/main.go b/geg/frame/mvc/main.go index 2a9519edd..8a29f210b 100644 --- a/geg/frame/mvc/main.go +++ b/geg/frame/mvc/main.go @@ -4,9 +4,11 @@ import ( "gitee.com/johng/gf/g/net/ghttp" _ "gitee.com/johng/gf/geg/frame/mvc/controller/user" + "gitee.com/johng/gf/g/frame/gconfig" ) func main() { + gconfig.Set("johng.gf.mvc.view.path", "/home/john/Workspace/Go/GOPATH/src/gitee.com/johng/gf/geg/frame/mvc/view") ghttp.GetServer("johng").SetAddr(":8199") ghttp.GetServer("johng").Run() } diff --git a/geg/frame/mvc/view/user/footer.tpl b/geg/frame/mvc/view/user/footer.tpl index a79ba001f..c3e0c6f02 100644 --- a/geg/frame/mvc/view/user/footer.tpl +++ b/geg/frame/mvc/view/user/footer.tpl @@ -1 +1,2 @@ -
age:{{.}}
-add:{{add 1 2}}
+tpl vals: {{.}}
+ {{include "user/footer"}}