入门16_goframe框架学习

WEB服务开发:https://goframe.org/pages/viewpage.action?pageId=1114405

开始使用

先来一个Hello World:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)

func main() {
s := g.Server() //单例模式,默认监听80

// Set*方法来设置Server的属性
s.SetIndexFolder(true) //是否允许列出Server主目录的文件列表(默认为false)。
s.SetServerRoot("/home/www/") //SetServerRoot用来设置Server的主目录(类似nginx中的root配置,默认为空)。(只有设置了主目录,才支持对应主目录下的静态文件的访问。)

//动态路由
s.BindHandler("/{class}-{course}/:name/*act", func(r *ghttp.Request) {
r.Response.Writef(
"%v %v %v %v",
r.Get("class"),
r.Get("course"),
r.Get("name"),
r.Get("act"),
)
})

//多端口
s.SetPort(8100, 8200, 8300)

//域名绑定
s.Domain("127.0.0.1").BindHandler("/", Hello1)
s.Domain("localhost").BindHandler("/", Hello2)

s.Run()
}

其他:平滑重启,HTTPS支持

路由注册

路由规则

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
s := g.Server()
// 三种模糊匹配路由规则,:name、*any、{field}分别表示命名匹配规则、模糊匹配规则及字段匹配规则
s.BindHandler("/{class}-{course}/:name/*act", func(r *ghttp.Request){
r.Response.Writeln(r.Get("class"))
r.Response.Writeln(r.Get("course"))
r.Response.Writeln(r.Get("name"))
r.Response.Writeln(r.Get("act"))
})
s.SetPort(8199)
s.Run()
}

BindHandler的原型:

1
2
func (s *Server) BindHandler(pattern string, handler HandlerFunc) error  
[HTTPMethod:]路由规则[@域名]

其中HTTPMethod(支持的Method:GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE)和@域名为非必需参数

1
2
3
4
// 该路由规则仅会在GET请求及localhost域名下有效
s.BindHandler("GET:/order/info/{order_id}@localhost", func(r *ghttp.Request){
r.Response.WriteJson(r.Router)
})

优先级控制
优先级控制按照深度优先策略,最主要的几点因素:

1
2
3
层级越深的规则优先级越高;  
同一层级下,精准匹配优先级高于模糊匹配;
同一层级下,模糊匹配优先级:字段匹配 > 命名匹配 > 模糊匹配;

路由注册

注册方式使用难度安全系数执行性能内存消耗
回调函数注册
执行对象注册
控制器方式注册

比较指标说明:

  1. 使用难度:主要指对于执行流程以及数据管理维护的复杂度;
  2. 安全系数:主要指在异步多协程下的数据安全管理维护;
  3. 执行性能:执行性能,相对比较的结果;
  4. 内存消耗:内存消耗,相对比较的结果;

注册方法

1
2
3
4
5
func (s *Server) BindHandler(pattern string, handler HandlerFunc) error

func (s *Server) BindObject(pattern string, obj interface{}, methods ...string) error
func (s *Server) BindObjectMethod(pattern string, obj interface{}, method string) error
func (s *Server) BindObjectRest(pattern string, obj interface{}) error

域名路由注册方法(略)

注册方式
函数注册(略)
对象注册

1
2
3
4
5
6
7
8
9
10
11
12

func main() {
u := new(User)
s1 := g.Server("xxx")
s1.SetNameToUriType(ghttp.URI_TYPE_DEFAULT)//命名风格规则
s1.BindObject("/{.struct}/{.method}", u)//路由内置变量

s1.SetPort(8100)
s1.Start()

g.Wait()
}

对象方法注册
假如控制器中有若干公开方法,但是我只想注册其中几个,其余的方法我不想对外公开
我们可以通过BindObject传递第三个非必需参数替换实现,参数支持传入多个方法名称,多个名称以英文,号分隔(方法名称参数区分大小写)。

1
s.BindObject("/object", c, "Show")

绑定路由方法
我们可以通过BindObjectMethod方法绑定指定的路由到指定的方法执行(方法名称参数区分大小写)。
BindObjectMethod和BindObject的区别: BindObjectMethod将对象中的指定方法与指定路由规则进行绑定,第三个method参数只能指定一个方法名称; BindObject注册时,所有的路由都是对象方法名称按照规则生成的,第三个methods参数可以指定多个注册的方法名称。

RESTful对象注册
构造方法Init与析构方法Shut
对象中的Init和Shut是两个在HTTP请求流程中被Server自动调用的特殊方法(类似构造函数和析构函数的作用)。

分组路由
层级注册

中间件/拦截器

前置中间件
后置中间件

请求输入

提交方式

GF框架下,有以下几种提交类型:
Router: 路由参数,来源于路由规则匹配。
Query: URL中的Query String参数解析,如:http://127.0.0.1/index?id=1&name=john 中的id=1&name=john。
Form: 表单提交参数,最常见的提交方式,提交的Content-Type往往为:application/x-www-form-urlencoded、multipart/form-data、multipart/mixed。
Body: 原始提交内容,从Body中获取并解析得到的参数,JSON/XML请求往往使用这种方式提交。
Custom: 自定义参数,往往在服务端的中间件、服务函数中通过SetParam/GetParam方法管理。

参数类型

获取的参数方法可以对指定键名的数据进行自动类型转换,例如:http://127.0.0.1:8199/?amount=19.66,通过GetQueryString将会返回19.66的字符串类型,GetQueryFloat32/GetQueryFloat64将会分别返回float32和float64类型的数值19.66
如果有更多参数类型转换的需求,可以使用Get*Var参数获取方法,获得*gvar.Var变量再进行相应类型转换。例如,假如我们要获取一个int8类型的参数,我们可以这样GetVar(“id”).Int8()。

参数优先级

Get*及GetRequset*方法:Router < Query < Body < Form < Custom,也就是说自定义参数的优先级最高,其次是Form表单参数,再次是Body提交参数,以此类推。例如,Query和Form中都提交了同样名称的参数id,参数值分别为1和2,那么Get(“id”)/GetForm(“id”)将会返回2,而GetQuery(“id”)将会返回1。
GetQuery*方法:Query > Body,也就是说query string的参数将会覆盖Body中提交的同名参数。例如,Query和Body中都提交了同样名称的参数id,参数值分别为1和2,那么Get(“id”)将会返回2,而GetQuery(“id”)将会返回1。
GetForm*方法:由于该类型的方法仅用于获取Form表单参数,因此没什么优先级的差别。

数据返回

Write*方法用于数据的输出,可为任意的数据格式,内部通过断言对参数做自动分析。
Write*Exit方法用于数据输出后退出当前服务方法,可用于替代return返回方法。
WriteJson*/WriteXml方法用于特定数据格式的输出,这是为开发者提供的简便方法。
WriteTpl*方法用于模板输出,解析并输出模板文件,也可以直接解析并输出给定的模板内容。
ParseTpl*方法用于模板解析,解析模板文件或者模板内容,返回解析后的内容。

缓冲控制

Response输出采用了缓冲控制,输出的内容预先写入到一块缓冲区,等待服务方法执行完毕后才真正地输出到客户端。该特性在提高执行效率同时为输出内容的控制提供了更高的灵活性。
举个例子:
我们通过后置中间件统一对返回的数据做处理,如果服务方法产生了异常,那么不能将敏感错误信息推送到客户端,而统一设置错误提示信息。

1
2
3
4
5
6
7
8

func MiddlewareErrorHandler(r *ghttp.Request) {
r.Middleware.Next()
if r.Response.Status >= http.StatusInternalServerError {
r.Response.ClearBuffer()
r.Response.Writeln("服务器居然开小差了,请稍后再试吧!")
}
}

模板解析

Response支持模板文件/内容解析输出,或者模板文件/内容解析返回。与直接使用模板对象解析模板功能不同的是,Response的解析支持一些请求相关的内置变量。模板解析包含以下方法:
WriteTpl*方法用于模板输出,解析并输出模板文件,也可以直接解析并输出给定的模板内容。
ParseTpl*方法用于模板解析,解析模板文件或者模板内容,返回解析后的内容。

1
2
3
4
5
6
s.BindHandler("/", func(r *ghttp.Request){
r.Cookie.Set("theme", "default")
r.Session.Set("name", "john")
content :=`Config:{{.Config.redis.cache}}, Cookie:{{.Cookie.theme}}, Session:{{.Session.name}}, Query:{{.Query.name}}`
r.Response.WriteTplContent(content, nil)
})

JSON/XML

WriteJson* 方法用于返回JSON数据格式,参数为任意类型,可以为string、map、struct等等。返回的Content-Type为application/json。
WriteXml* 方法用于返回XML数据格式,参数为任意类型,可以为string、map、struct等等。返回的Content-Type为application/xml。

1
2
3
4
5
6
group.ALL("/json", func(r *ghttp.Request) {
r.Response.WriteJson(g.Map{
"id": 1,
"name": "john",
})
})
1
2
3
4
5
s.BindHandler("/cookie", func(r *ghttp.Request) {
datetime := r.Cookie.Get("datetime")
r.Cookie.Set("datetime", gtime.Datetime())
r.Response.Write("datetime:", datetime)
})

Session

任何时候都可以通过ghttp.Request获取Session对象,因为Cookie和Session都是和请求会话相关,因此都属于Request的成员对象,并对外公开。GF框架的Session默认过期时间是24小时。
SessionId默认通过Cookie来传递,并且也支持客户端通过Header传递SessionId,SessionId的识别名称可以通过ghttp.Server的SetSessionIdName进行修改。
Session的操作是支持并发安全的,这也是框架在对Session的设计上不采用直接以map的形式操作数据的原因。在HTTP请求流程中,我们可以通过ghttp.Request对象来获取Session对象,并执行相应的数据操作。
ghttp.Server中的SessionId使用的是客户端的 RemoteAddr + Header 请求信息通过guid模块来生成的,保证随机及唯一性:https://github.com/gogf/ gf/blob/master/net/ghttp/ghttp_request.go

Session-文件存储
Session-内存存储
Session-Redis存储

1
2
3
4
s.SetConfigWithMap(g.Map{
"SessionMaxAge": time.Minute,
"SessionStorage": gsession.NewStorageMemory(),内存// 空,文件,"SessionStorage": gsession.NewStorageRedis(g.Redis()), redis
})

服务配置(略)

异常处理

我们这里使用一个全局的后置中间件来捕获异常,当异常产生后,捕获并写入到指定的日志文件中,返回固定友好的提示信息,避免敏感的报错信息暴露给调用端。

1
2
3
4
5
6
7
8
9
10
11

func MiddlewareErrorHandler(r *ghttp.Request) {
r.Middleware.Next()
if err := r.GetError(); err != nil {
// 记录到自定义错误日志文件
g.Log("exception").Error(err)
//返回固定的友好信息
r.Response.ClearBuffer()
r.Response.Writeln("服务器居然开小差了,请稍后再试吧!")
}
}

HTTPClient(略)

gf-jwt说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 重新定义GfJWTMiddleware
authMiddleware, err := jwt.New(&jwt.GfJWTMiddleware{
Realm: "test zone", // 用于展示中间件的名称
Key: []byte("secret key"), // 密钥
Timeout: time.Minute * 5, // token过期时间
MaxRefresh: time.Minute * 5,
IdentityKey: "id", // 身份验证的key值
TokenLookup: "header: Authorization, query: token, cookie: jwt", // token检索模式,用于提取token-> Authorization
TokenHeadName: "Bearer", // token在请求头时的名称,默认值为Bearer
// 客户端在header中传入Authorization 对一个值是Bearer + 空格 + token
TimeFunc: time.Now, // 测试或服务器在其他时区可设置该属性
Authenticator: Authenticator, // 根据登录信息对用户进行身份验证的回调函数
LoginResponse: LoginResponse, // 完成登录后返回的信息,用户可自定义返回数据,默认返回
RefreshResponse: auth.RefreshResponse, // 刷新token后返回的信息,用户可自定义返回数据,默认返回
Unauthorized: auth.Unauthorized, // 处理不进行授权的逻辑
IdentityHandler: auth.IdentityHandler, // 解析并设置用户身份信息,使得r.get('JWT_PAYLOAD")可取的
PayloadFunc: auth.PayloadFunc, // 登录期间的回调的函数
})

整体调用链如下

有了上图就大致清楚,我们希望校验token时,做某些操作(比如查询刷新菜单权限等),就是在IdentityHandler中修改了。
用户名密码登录时,记录登录日志,在Authenticator里更合适。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×