GF(Go Frame)是一款模块化、高性能、企业级的Go基础开发框架。完整详实的中文文档,可以减轻学习的压力。
主库:https://github.com/gogf/gf
码云:https://gitee.com/johng/gf
中文文档:https://itician.org/pages/viewpage.action?pageId=1114119
B站视频: https://www.bilibili.com/video/bv157411Z7Le
B站实战:https://www.bilibili.com/video/BV1oT4y1G7ge/
官方案例:
https://github.com/gogf/gf-demos
https://github.com/gogf/gf-home
https://github.com/CrazyRocks/goadmin 全自动一键生成的Golang admin
目录/文件 | 说明 | 描述 |
---|---|---|
app | 业务逻辑层 | 所有的业务逻辑存放目录。 |
–api | 业务接口 | 接收/解析用户输入参数的入口/接口层。 |
–dao | 数据访问 | 数据库的访问操作,仅包含最基础的数据库CURD方法 |
–model | 结构模型 | 数据结构管理模块,管理数据实体对象,以及输入与输出数据结构定义。 |
–service | 逻辑封装 | 业务逻辑封装管理,实现特定的业务逻辑实现和封装。 |
boot | 初始化包 | 用于项目初始化参数设置,往往作为main.go中第一个被import的包。 |
config | 配置管理 | 所有的配置文件存放目录。 |
docker | 镜像文件 | Docker镜像相关依赖文件,脚本文件等等。 |
document | 项目文档 | Documentation项目文档,如: 设计文档、帮助文档等等。 |
i18n | I18N国际化 | I18N国际化配置文件目录。 |
library | 公共库包 | 公共的功能封装包,往往不包含业务需求实现。 |
packed | 打包目录 | 将资源文件打包的Go文件存放在这里,boot包初始化时会自动调用。 |
public | 静态目录 | 仅有该目录下的文件才能对外提供静态服务访问。 |
router | 路由注册 | 用于路由统一的注册管理。 |
template | 模板文件 | MVC模板文件存放的目录。 |
Dockerfile | 镜像描述 | 云原生时代用于编译生成Docker镜像的描述文件。 |
go.mod | 依赖管理 | 使用Go Module包管理的依赖描述文件。 |
main.go | 入口文件 | 程序入口文件。 |
根据项目实际情况增删目录。例如,没有i18n及template需求的场景,直接删除对应目录即可。
三层架构设计与框架代码分层映射关系
新建项目
示例 https://github.com/gogf/gf-demos
项目创建推荐使用GF工具链gf init命令: https://github.com/gogf/gf-cli
安装工具: wget https://goframe.org/cli/linux_amd64/gf && chmod +x gf && ./gf install
使用中,在新建项目后,总是需要删除go.mod,然后重建。
通过gf工具建立的框架,只实现了一个hello的结构及目录搭建。
每个目录下都有一个.gitkeep文件,科普一下:作用是使Git保留一个空文件夹(我使用VSCode,可以设置为隐藏不显示,免得碍眼)
Hello World
压缩前12MB,压缩后4.6MB。
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Write("哈喽世界!")
})
s.Run()
}
静态服务器
package main
import (
"github.com/gogf/gf/frame/g"
)
func main() {
s := g.Server()
s.SetIndexFolder(true) //是否允许列出Server主目录的文件列表
s.SetServerRoot("/home/ease/down") //设置Server的主目录
s.Run()
}
多端口监听
这还是很方便的
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request){
r.Response.Writeln("go frame!")
})
s.SetPort(8100, 8200, 8300)
s.Run()
}
多实例支持
同时开启了两个端口的静态服务
package main
import (
"github.com/gogf/gf/frame/g"
)
func main() {
s1 := g.Server("s1") //单例名称参数,该参数用于标识不同的Server实例
s1.SetPort(8080)
s1.SetIndexFolder(true)
s1.SetServerRoot("/home/www/static1")
s1.Start()
s2 := g.Server("s2")
s2.SetPort(8088)
s2.SetIndexFolder(true)
s2.SetServerRoot("/home/www/static2")
s2.Start()
g.Wait()
}
域名绑定
支持多域名绑定,并且不同的域名可以绑定不同的服务
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func Hello1(r *ghttp.Request) {
r.Response.Write("127.0.0.1: Hello1!")
}
func Hello2(r *ghttp.Request) {
r.Response.Write("localhost: Hello2!")
}
func Hello3(r *ghttp.Request) {
r.Response.Write("外网客人你好!")
}
func main() {
s := g.Server()
s.Domain("127.0.0.1").BindHandler("/", Hello1)
s.Domain("localhost").BindHandler("/", Hello2)
s.Domain("wyyyh.dynv6.net").BindHandler("/", Hello3)
s.SetPort(8000)
s.Run()
}
这样也是可以的: s.Domain(“localhost1,localhost2,localhost3”).BindHandler("/", Hello2)
不支持泛域名形式
路由注册
package main
import (
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/frame/g"
)
func main() {
s := g.Server()
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(8199)
s.Run()
}
访问测试: http://127.0.0.1:8199/class3-math/john/score
package main
import (
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/frame/g"
)
func main() {
s := g.Server()
s.BindHandler("/:name", func(r *ghttp.Request){
r.Response.Writeln(r.Router.Uri)
})
s.BindHandler("/:name/update", func(r *ghttp.Request){
r.Response.Writeln(r.Router.Uri)
})
s.BindHandler("/:name/:action", func(r *ghttp.Request){
r.Response.Writeln(r.Router.Uri)
})
s.BindHandler("/:name/*any", func(r *ghttp.Request){
r.Response.Writeln(r.Router.Uri)
})
s.BindHandler("/user/list/{field}.html", func(r *ghttp.Request){
r.Response.Writeln(r.Router.Uri)
})
s.SetPort(8199)
s.Run()
}
匹配结果:
http://127.0.0.1:8199/user/list/2.html /user/list/{field}.html http://127.0.0.1:8199/user/update /:name/update http://127.0.0.1:8199/user/info /:name/:action http://127.0.0.1:8199/user /:name/*any
这里稍有疑惑的是最后一项,为什么没有匹配/:name
由于优先级的限制,路由规则/:name会被/:name/*any规则覆盖
注册规则
package main
import (
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/frame/g"
)
func main() {
s := g.Server()
// 该路由规则仅会在GET请求下有效
s.BindHandler("GET:/{table}/list/{page}.html", func(r *ghttp.Request){
r.Response.WriteJson(r.Router)
})
// 该路由规则仅会在GET请求及localhost域名下有效
s.BindHandler("GET:/order/info/{order_id}@localhost", func(r *ghttp.Request){
r.Response.WriteJson(r.Router)
})
// 该路由规则仅会在DELETE请求下有效
s.BindHandler("DELETE:/comment/{id}", func(r *ghttp.Request){
r.Response.WriteJson(r.Router)
})
s.SetPort(8199)
s.Run()
}
r.Router是当前匹配的路由规则信息
$ curl -XGET http://127.0.0.1:8199/order/list/1.html
{"Domain":"default","Method":"GET","Priority":3,"Uri":"/{table}/list/{page}.html"}
$ curl -XGET http://127.0.0.1:8199/order/info/1
Not Found
$ curl -XGET http://localhost:8199/order/info/1
{"Domain":"localhost","Method":"GET","Priority":3,"Uri":"/order/info/{order_id}"}
$ curl -XDELETE http://127.0.0.1:8199/comment/1000
{"Domain":"default","Method":"DELETE","Priority":2,"Uri":"/comment/{id}"}
$ curl -XGET http://127.0.0.1:8199/comment/1000
Not Found
动态路由规则
-
精准匹配规则 即指定匹配字符
-
命名匹配规则 例如:name,必须有值
-
模糊匹配规则 使用*any方式进行匹配,匹配内容可以为空
-
字段匹配规则 参数进行截取匹配,即匹配区间会有一部份精准匹配,而另一部份则为参数
rule: /db-{table}/{id}
/db-user/1 match
/db-user/2 match
/db/user/1 no match
/db-order/100 match
/database-order/100 no match
优先级控制
- 层级越深的规则优先级越高;
- 同一层级下,精准匹配优先级高于模糊匹配;
- 同一层级下,模糊匹配优先级:字段匹配 > 命名匹配 > 模糊匹配;
路由管理
使用BindHandler方法进行路由注册的方式叫做回调函数注册,是最简单的一种路由注册方式。
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Write("哈喽世界!")
})
s.Run()
}
以下示例将回调绑定在指定域名下,其它域名不可访问
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
d := g.Server().Domain("localhost")
d.BindHandler("/", func(r *ghttp.Request) {
r.Response.Write("Hello World!")
})
g.Server().Run()
}
包方法注册
package main
import (
"github.com/gogf/gf/container/gtype"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
var (
total = gtype.NewInt()
)
func Total(r *ghttp.Request) {
r.Response.Write("total:", total.Add(1))
}
func main() {
s := g.Server()
s.BindHandler("/total", Total)
s.SetPort(8199)
s.Run()
}
对象方法注册
package main
import (
"github.com/gogf/gf/container/gtype"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
type Controller struct {
total *gtype.Int
}
func (c *Controller) Total(r *ghttp.Request) {
r.Response.Write("total:", c.total.Add(1))
}
func main() {
s := g.Server()
c := &Controller{
total: gtype.NewInt(),
}
s.BindHandler("/total", c.Total)
s.SetPort(8199)
s.Run()
}
对象注册
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
type Controller struct{}
func (c *Controller) Index(r *ghttp.Request) {
r.Response.Write("index")
}
func (c *Controller) Show(r *ghttp.Request) {
r.Response.Write("show")
}
func main() {
s := g.Server()
c := new(Controller)
s.BindObject("/object", c)
s.SetPort(8199)
s.Run()
}
生成如下路由表
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
|---------|---------|---------|--------|---------------|--------------------------|------------|
default | default | :8199 | ALL | /object | main.(*Controller).Index |
|---------|---------|---------|--------|---------------|--------------------------|------------|
default | default | :8199 | ALL | /object/index | main.(*Controller).Index |
|---------|---------|---------|--------|---------------|--------------------------|------------|
default | default | :8199 | ALL | /object/show | main.(*Controller).Show |
|---------|---------|---------|--------|---------------|--------------------------|------------|
即,所有/object下的路由,均由c进行处理
路由内置变量
在路由规则中可以使用两个内置的变量:{.struct}和{.method},前者表示当前对象名称,后者表示当前注册的方法名。
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
type Order struct{}
func (o *Order) List(r *ghttp.Request) {
r.Response.Write("list")
}
func main() {
s := g.Server()
o := new(Order)
s.BindObject("/{.struct}-{.method}", o)
s.SetPort(8199)
s.Run()
}
产生如下路由表
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
|---------|---------|---------|--------|-------------|--------------------|------------|
default | default | :8199 | ALL | /order-list | main.(*Order).List |
|---------|---------|---------|--------|-------------|--------------------|------------|
命名风格规则
当方法名称带有多个单词(按照字符大写区分单词)时,路由控制器默认会自动使用英文连接符号-进行拼接,因此访问的时候方法名称需要带-号。
以下示例使用了几种内置的转换方法
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
type User struct{}
func (u *User) ShowList(r *ghttp.Request) {
r.Response.Write("list")
}
func main() {
u := new(User)
s1 := g.Server("DEFAULT ")
s2 := g.Server("FULLNAME")
s3 := g.Server("ALLLOWER")
s4 := g.Server("CAMEL ")
s1.SetNameToUriType(ghttp.URI_TYPE_DEFAULT) // (默认)全部转为小写,单词以'-'连接符号连接
s2.SetNameToUriType(ghttp.URI_TYPE_FULLNAME) // 不处理名称,以原有名称构建成URI
s3.SetNameToUriType(ghttp.URI_TYPE_ALLLOWER) // 仅转为小写,单词间不使用连接符号
s4.SetNameToUriType(ghttp.URI_TYPE_CAMEL) // 采用驼峰命名方式
s1.BindObject("/{.struct}/{.method}", u)
s2.BindObject("/{.struct}/{.method}", u)
s3.BindObject("/{.struct}/{.method}", u)
s4.BindObject("/{.struct}/{.method}", u)
s1.SetPort(8100)
s2.SetPort(8200)
s3.SetPort(8300)
s4.SetPort(8400)
s1.Start()
s2.Start()
s3.Start()
s4.Start()
g.Wait()
}
产生路由
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
|----------|---------|---------|--------|-----------------|-----------------------|------------|
DEFAULT | default | :8100 | ALL | /user/show-list | main.(*User).ShowList |
|----------|---------|---------|--------|-----------------|-----------------------|------------|
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
|----------|---------|---------|--------|-----------------|-----------------------|------------|
FULLNAME | default | :8200 | ALL | /User/ShowList | main.(*User).ShowList |
|----------|---------|---------|--------|-----------------|-----------------------|------------|
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
|----------|---------|---------|--------|-----------------|-----------------------|------------|
ALLLOWER | default | :8300 | ALL | /user/showlist | main.(*User).ShowList |
|----------|---------|---------|--------|-----------------|-----------------------|------------|
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
|----------|---------|---------|--------|-----------------|-----------------------|------------|
CAMEL | default | :8400 | ALL | /user/showList | main.(*User).ShowList |
|----------|---------|---------|--------|-----------------|-----------------------|------------|
对象方法注册
只注册其中几个。以下示例中,只注册/公开了Show方法,多个方法用逗号隔开。
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
type Controller struct{}
func (c *Controller) Index(r *ghttp.Request) {
r.Response.Write("index")
}
func (c *Controller) Show(r *ghttp.Request) {
r.Response.Write("show")
}
func main() {
s := g.Server()
c := new(Controller)
s.BindObject("/object", c, "Show")
s.SetPort(8199)
s.Run()
}
绑定路由方法
通过BindObjectMethod方法绑定指定的路由到指定的方法执行
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
type Controller struct{}
func (c *Controller) Index(r *ghttp.Request) {
r.Response.Write("index")
}
func (c *Controller) Show(r *ghttp.Request) {
r.Response.Write("show")
}
func main() {
s := g.Server()
c := new(Controller)
s.BindObjectMethod("/show", c, "Show")
s.SetPort(8199)
s.Run()
}
与之前的s.BindObject("/object", c, “Show”)有些差别。例如,产生的路由表不同,这里产生的是:
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
|---------|---------|---------|--------|-------|-------------------------|------------|
default | default | :8199 | ALL | /show | main.(*Controller).Show |
|---------|---------|---------|--------|-------|-------------------------|------------|
RESTful对象注册
通常用于API服务
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
type Controller struct{}
// RESTFul - GET
func (c *Controller) Get(r *ghttp.Request) {
r.Response.Write("GET")
}
// RESTFul - POST
func (c *Controller) Post(r *ghttp.Request) {
r.Response.Write("POST")
}
// RESTFul - DELETE
func (c *Controller) Delete(r *ghttp.Request) {
r.Response.Write("DELETE")
}
// 该方法无法映射,将会无法访问到
func (c *Controller) Hello(r *ghttp.Request) {
r.Response.Write("Hello")
}
func main() {
s := g.Server()
c := new(Controller)
s.BindObjectRest("/object", c)
s.SetPort(8199)
s.Run()
}
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
|---------|---------|---------|--------|---------|---------------------------|------------|
default | default | :8199 | DELETE | /object | main.(*Controller).Delete |
|---------|---------|---------|--------|---------|---------------------------|------------|
default | default | :8199 | GET | /object | main.(*Controller).Get |
|---------|---------|---------|--------|---------|---------------------------|------------|
default | default | :8199 | POST | /object | main.(*Controller).Post |
|---------|---------|---------|--------|---------|---------------------------|------------|
构造方法Init与析构方法Shut
// “构造函数"对象方法 // 对象收到请求时的初始化方法,在服务接口调用之前被回调执行。 func (c *Controller) Init(r *ghttp.Request) {}
// “析构函数"对象方法 // 当请求结束时被Server自动调用,可以用于对象执行一些收尾处理的操作。 func (c *Controller) Shut(r *ghttp.Request) {}
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
type Controller struct{}
func (c *Controller) Init(r *ghttp.Request) {
r.Response.Writeln("Init")
}
func (c *Controller) Shut(r *ghttp.Request) {
r.Response.Writeln("Shut")
}
func (c *Controller) Hello(r *ghttp.Request) {
r.Response.Writeln("Hello")
}
func main() {
s := g.Server()
c := new(Controller)
s.BindObject("/object", c)
s.SetPort(8199)
s.Run()
}
访问 http://127.0.0.1:8199/object/hello 后,输出结果为:Init、Hello、Shut,而路由表中并不会有这两个方法
分组路由
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
group := s.Group("/api")
group.ALL("/all", func(r *ghttp.Request) {
r.Response.Write("all")
})
group.GET("/get", func(r *ghttp.Request) {
r.Response.Write("get")
})
group.POST("/post", func(r *ghttp.Request) {
r.Response.Write("post")
})
s.SetPort(8199)
s.Run()
}
生成路由表
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
|---------|---------|---------|--------|-----------|-----------------|------------|
default | default | :8199 | ALL | /api/all | main.main.func1 |
|---------|---------|---------|--------|-----------|-----------------|------------|
default | default | :8199 | GET | /api/get | main.main.func2 |
|---------|---------|---------|--------|-----------|-----------------|------------|
default | default | :8199 | POST | /api/post | main.main.func3 |
|---------|---------|---------|--------|-----------|-----------------|------------|
层级注册
批量注册
s := g.Server()
// 前台系统自定义错误页面
s.BindStatusHandler(401, func(r *ghttp.Request) {
if !gstr.HasPrefix(r.URL.Path, "/admin") {
service.View.Render401(r)
}
})
s.BindStatusHandler(403, func(r *ghttp.Request) {
if !gstr.HasPrefix(r.URL.Path, "/admin") {
service.View.Render403(r)
}
})
s.BindStatusHandler(404, func(r *ghttp.Request) {
if !gstr.HasPrefix(r.URL.Path, "/admin") {
service.View.Render404(r)
}
})
s.BindStatusHandler(500, func(r *ghttp.Request) {
if !gstr.HasPrefix(r.URL.Path, "/admin") {
service.View.Render500(r)
}
})
// 前台系统路由注册
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(service.Middleware.Ctx)
group.ALLMap(g.Map{
"/": api.Index, // 首页
"/login": api.Login, // 登录
"/register": api.Register, // 注册
"/category": api.Category, // 栏目
"/topic": api.Topic, // 主题
"/topic/:id": api.Topic.Detail, // 主题 - 详情
"/ask": api.Ask, // 问答
"/ask/:id": api.Ask.Detail, // 问答 - 详情
"/article": api.Article, // 文章
"/article/:id": api.Article.Detail, // 文章 - 详情
"/reply": api.Reply, // 回复
"/search": api.Search, // 搜索
"/captcha": api.Captcha, // 验证码
"/user/:id": api.User.Index, // 用户 - 主页
})
// 权限控制路由
group.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(service.Middleware.Auth)
group.ALLMap(g.Map{
"/user": api.User, // 用户
"/content": api.Content, // 内容
"/interact": api.Interact, // 交互
"/file": api.File, // 文件
})
})
})
中间件/拦截器
中间件定义
func MiddlewareCORS(r *ghttp.Request) {
r.Response.CORSDefault()
r.Middleware.Next() //进一步执行下一个流程
}
中间件类型
前置中间件和后置中间件。前置即在路由服务函数调用之前调用,后置即在其后调用。(先处理自己还是先处理别人)
func Middleware(r *ghttp.Request) {
// 中间件处理逻辑
r.Middleware.Next()
}
func Middleware(r *ghttp.Request) {
r.Middleware.Next()
// 中间件处理逻辑
}
中间件注册
全局中间件
BindMiddleware方法是将中间件注册到指定的路由规则下,中间件参数可以给定多个。
BindMiddlewareDefault方法是将中间件注册到/*全局路由规则下。
Use方法是BindMiddlewareDefault别名。
全局中间件仅对动态请求拦截有效,无法拦截静态文件请求
分组路由中间件
绑定到当前分组路由中的所有的服务请求上
示例1,允许跨域请求
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func MiddlewareCORS(r *ghttp.Request) {
r.Response.CORSDefault()
r.Middleware.Next()
}
func main() {
s := g.Server()
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareCORS)
group.ALL("/user/list", func(r *ghttp.Request) {
r.Response.Writeln("list")
})
})
s.SetPort(8199)
s.Run()
}
示例2,请求鉴权处理
package main
import (
"net/http"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func MiddlewareAuth(r *ghttp.Request) {
token := r.Get("token")
if token == "123456" {
r.Response.Writeln("auth")
r.Middleware.Next()
} else {
r.Response.WriteStatus(http.StatusForbidden)
}
}
func MiddlewareCORS(r *ghttp.Request) {
r.Response.Writeln("cors")
r.Response.CORSDefault()
r.Middleware.Next()
}
func main() {
s := g.Server()
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareCORS, MiddlewareAuth)
group.ALL("/user/list", func(r *ghttp.Request) {
r.Response.Writeln("list")
})
})
s.SetPort(8199)
s.Run()
}
http://127.0.0.1:8199/api.v2/user/list 和 http://127.0.0.1:8199/api.v2/user/list?token=123456 对比来查看效果
示例3,鉴权例外处理
以下示例中,/admin下/login不作权限检测(尚未登陆),其余均需
package main
import (
"net/http"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func MiddlewareAuth(r *ghttp.Request) {
token := r.Get("token")
if token == "123456" {
r.Middleware.Next()
} else {
r.Response.WriteStatus(http.StatusForbidden)
}
}
func main() {
s := g.Server()
s.Group("/admin", func(group *ghttp.RouterGroup) {
group.ALL("/login", func(r *ghttp.Request) {
r.Response.Writeln("login")
})
group.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareAuth)
group.ALL("/dashboard", func(r *ghttp.Request) {
r.Response.Writeln("dashboard")
})
})
})
s.SetPort(8199)
s.Run()
}
示例4,统一的错误处理
package main
import (
"net/http"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func MiddlewareAuth(r *ghttp.Request) {
token := r.Get("token")
if token == "123456" {
r.Middleware.Next()
} else {
r.Response.WriteStatus(http.StatusForbidden)
}
}
func MiddlewareCORS(r *ghttp.Request) {
r.Response.CORSDefault()
r.Middleware.Next()
}
func MiddlewareErrorHandler(r *ghttp.Request) {
r.Middleware.Next()
if r.Response.Status >= http.StatusInternalServerError {
r.Response.ClearBuffer()
r.Response.Writeln("哎哟我去,服务器居然开小差了,请稍后再试吧!")
}
}
func main() {
s := g.Server()
s.Use(MiddlewareCORS)
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareAuth, MiddlewareErrorHandler)
group.ALL("/user/list", func(r *ghttp.Request) {
panic("db error: sql is xxxxxxx")
})
})
s.SetPort(8199)
s.Run()
}
示例5,自定义日志处理
package main
import (
"net/http"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func MiddlewareAuth(r *ghttp.Request) {
token := r.Get("token")
if token == "123456" {
r.Middleware.Next()
} else {
r.Response.WriteStatus(http.StatusForbidden)
}
}
func MiddlewareCORS(r *ghttp.Request) {
r.Response.CORSDefault()
r.Middleware.Next()
}
func MiddlewareLog(r *ghttp.Request) {
r.Middleware.Next()
errStr := ""
if err := r.GetError(); err != nil {
errStr = err.Error()
}
g.Log().Println(r.Response.Status, r.URL.Path, errStr)
}
func main() {
s := g.Server()
s.SetConfigWithMap(g.Map{
"AccessLogEnabled": false,
"ErrorLogEnabled": false,
})
s.Use(MiddlewareLog, MiddlewareCORS)
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareAuth)
group.ALL("/user/list", func(r *ghttp.Request) {
panic("啊!我出错了!")
})
})
s.SetPort(8199)
s.Run()
}
请求输入
Get*: 常用方法,简化参数获取,GetRequest*的别名。
GetQuery*: 获取GET方式传递过来的参数,包括Query String及Body参数解析。
GetForm*: 获取表单方式传递过来的参数,表单方式提交的参数Content-Type往往为application/x-www-form-urlencoded, application/form-data, multipart/form-data, multipart/mixed等等。
GetRequest*: 获取客户端提交的参数,不区分提交方式。
Get*Struct: 将指定类型的请求参数绑定到指定的struct对象上,注意给定的参数为对象指针。绝大部分场景中往往使用Parse方法将请求数据转换为请求对象,具体详见后续章节。
GetBody/GetBodyString: 获取客户端提交的原始数据,该数据是客户端写入到body中的原始数据,与HTTP Method无关,例如客户端提交JSON/XML数据格式时可以通过该方法获取原始的提交数据。
GetJson: 自动将原始请求信息解析为gjson.Json对象指针返回,gjson.Json对象具体在 gjson (数据动态编解码) 章节中介绍。
Exit*: 用于请求流程退出控制,详见本章后续说明;
参数类型
GetQueryString、GetQueryFloat32、GetQueryFloat64、float32、float64、GetQueryInt、GetQueryUint
转换,例如:GetVar(“id”).Int8()
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Writeln(r.Get("amount"))
r.Response.Writeln(r.GetInt("amount"))
r.Response.Writeln(r.GetFloat32("amount"))
})
s.SetPort(8199)
s.Run()
}
复杂参数
参数 | 变量 |
---|---|
k1=m&k2=n | map[k1:m k2:n] |
k=m&k=n | map[k:n] |
k=m&k[a]=n | error |
k[]=m&k[]=n | map[k:[m n]] |
k[a]=m&k[b]=n | map[k:map[a:m b:n]] |
k[a][]=m&k[a][]=n | map[k:map[a:[m n]]] |
k[a][a]=m&k[a][b]=n | map[k:map[a:map[a:m b:n]]] |
同名参数
同名参数提交格式形如:k=v1&k=v2 ,后续的变量值将会覆盖前面的变量值。
数组参数
数组参数提交格式形如:k[]=v1&k[]=v2
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Write(r.Get("array"))
})
s.SetPort(8199)
s.Run()
}
访问 http://127.0.0.1:8199/?array[]=john&array[]=smith 后,将会得到返回值 [“john”,“smith”]
Map参数
提交格式形如:k[a]=m&k[b]=n,并且支持多级Map,例如:k[a][a]=m&k[a][b]=n
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Write(r.Get("map"))
})
s.SetPort(8199)
s.Run()
}
访问 http://127.0.0.1:8199/?map[id]=1&map[name]=john 后,将会得到返回值 {“id”:“1”,“name”:“john”}
参数映射
struct中需要匹配的属性必须为公开属性(首字母大写)。
参数名称会自动按照 不区分大小写 且 忽略-/_/空格符号 的形式与struct属性进行匹配。
如果匹配成功,那么将键值赋值给属性,如果无法匹配,那么忽略该键值。
示例:
map键名 struct属性 是否匹配
name Name match
Email Email match
nickname NickName match
NICKNAME NickName match
Nick-Name NickName match
nick_name NickName match
nick name NickName match
NickName Nick_Name match
Nick-name Nick_Name match
nick_name Nick_Name match
nick name Nick_Name match
自定义规则
type User struct{
Id int
Name string
Pass1 string `p:"password1"`
Pass2 string `p:"password2"`
}
使用了p标签来指定该属性绑定的参数名称。password1参数将会映射到Pass1属性,password2将会映射到Pass2属性上
Parse转换
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
type RegisterReq struct {
Name string
Pass string `p:"password1"`
Pass2 string `p:"password2"`
}
type RegisterRes struct {
Code int `json:"code"`
Error string `json:"error"`
Data interface{} `json:"data"`
}
func main() {
s := g.Server()
s.BindHandler("/register", func(r *ghttp.Request) {
var req *RegisterReq
if err := r.Parse(&req); err != nil {
r.Response.WriteJsonExit(RegisterRes{
Code: 1,
Error: err.Error(),
})
}
// ...
r.Response.WriteJsonExit(RegisterRes{
Data: req,
})
})
s.SetPort(8199)
s.Run()
}
测试:
$ curl "http://127.0.0.1:8199/register?name=john&password1=123&password2=456"
{"code":0,"error":"","data":{"Name":"john","Pass":"123","Pass2":"456"}}
$ curl -d "name=john&password1=123&password2=456" -X POST "http://127.0.0.1:8199/register"
{"code":0,"error":"","data":{"Name":"john","Pass":"123","Pass2":"456"}}
请求校验
示例1,基本使用
属性绑定v标签
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
// 注册请求数据结构
type RegisterReq struct {
Name string `p:"username" v:"required|length:6,30#请输入账号|账号长度为:min到:max位"`
Pass string `p:"password1" v:"required|length:6,30#请输入密码|密码长度不够"`
Pass2 string `p:"password2" v:"required|length:6,30|same:password1#请确认密码|密码长度不够|两次密码不一致"`
}
// 注册返回数据结构
type RegisterRes struct {
Code int `json:"code"`
Error string `json:"error"`
Data interface{} `json:"data"`
}
func main() {
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.ALL("/register", func(r *ghttp.Request) {
var req *RegisterReq
if err := r.Parse(&req); err != nil {
r.Response.WriteJsonExit(RegisterRes{
Code: 1,
Error: err.Error(),
})
}
// ...
r.Response.WriteJsonExit(RegisterRes{
Data: req,
})
})
})
s.SetPort(8199)
s.Run()
}
测试: curl “http://127.0.0.1:8199/register?name=john&password1=123456&password2=123456”
示例2,校验错误处理
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/util/gvalid"
)
type RegisterReq struct {
Name string `p:"username" v:"required|length:6,30#请输入账号|账号长度为:min到:max位"`
Pass string `p:"password1" v:"required|length:6,30#请输入密码|密码长度不够"`
Pass2 string `p:"password2" v:"required|length:6,30|same:password1#请确认密码|密码长度不够|两次密码不一致"`
}
type RegisterRes struct {
Code int `json:"code"`
Error string `json:"error"`
Data interface{} `json:"data"`
}
func main() {
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.ALL("/register", func(r *ghttp.Request) {
var req *RegisterReq
if err := r.Parse(&req); err != nil {
// Validation error.
if v, ok := err.(*gvalid.Error); ok {
r.Response.WriteJsonExit(RegisterRes{
Code: 1,
Error: v.FirstString(),
})
}
// Other error.
r.Response.WriteJsonExit(RegisterRes{
Code: 1,
Error: err.Error(),
})
}
// ...
r.Response.WriteJsonExit(RegisterRes{
Data: req,
})
})
})
s.SetPort(8199)
s.Run()
}
通过err.(*gvalid.Error)断言的方式判断错误是否为校验错误
也可以使用gerror.Current获取第一条报错信息
JSON/XML
示例1,简单示例
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Writef("name: %v, pass: %v", r.Get("name"), r.Get("pass"))
})
s.SetPort(8199)
s.Run()
}
测试不同的提交方法:
Query数据格式 curl “http://127.0.0.1:8199/?name=john&pass=123”
Form表单提交 curl -d “name=john&pass=123” “http://127.0.0.1:8199/”
JSON数据格式 $ curl -d ‘{“name”:“john”,“pass”:“123”}’ “http://127.0.0.1:8199/”
XML数据格式 curl -d ‘john123’ “http://127.0.0.1:8199/”
XML数据格式 curl -d ‘john123’ “http://127.0.0.1:8199/”
示例2,对象转换及校验
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/util/gvalid"
)
type RegisterReq struct {
Name string `p:"username" v:"required|length:6,30#请输入账号|账号长度为:min到:max位"`
Pass string `p:"password1" v:"required|length:6,30#请输入密码|密码长度不够"`
Pass2 string `p:"password2" v:"required|length:6,30|same:password1#请确认密码|密码长度不够|两次密码不一致"`
}
type RegisterRes struct {
Code int `json:"code"`
Error string `json:"error"`
Data interface{} `json:"data"`
}
func main() {
s := g.Server()
s.BindHandler("/register", func(r *ghttp.Request) {
var req *RegisterReq
if err := r.Parse(&req); err != nil {
// Validation error.
if v, ok := err.(*gvalid.Error); ok {
r.Response.WriteJsonExit(RegisterRes{
Code: 1,
Error: v.FirstString(),
})
}
// Other error.
r.Response.WriteJsonExit(RegisterRes{
Code: 1,
Error: err.Error(),
})
}
// ...
r.Response.WriteJsonExit(RegisterRes{
Data: req,
})
})
s.SetPort(8199)
s.Run()
}
默认值绑定
默认值的struct tag名称为d
type ContentServiceGetListReq struct {
Type string // 内容模型
CategoryId uint `p:"cate"` // 栏目ID
Page int `d:"1" v:"min:0#分页号码错误"` // 分页号码
Size int `d:"10" v:"max:50#分页数量最大50条"` // 分页数量,最大50
Sort int // 排序类型(0:最新, 默认。1:活跃, 2:热度)
}
自定义变量
自定义变量的获取优先级是最高的,可以覆盖原有的客户端提交参数。
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
// 前置中间件1
func MiddlewareBefore1(r *ghttp.Request) {
r.SetParam("name", "GoFrame")
r.Response.Writeln("set name")
r.Middleware.Next()
}
// 前置中间件2
func MiddlewareBefore2(r *ghttp.Request) {
r.SetParam("site", "https://goframe.org")
r.Response.Writeln("set site")
r.Middleware.Next()
}
func main() {
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareBefore1, MiddlewareBefore2)
group.ALL("/", func(r *ghttp.Request) {
r.Response.Writefln(
"%s: %s",
r.GetParamVar("name").String(),
r.GetParamVar("site").String(),
)
})
})
s.SetPort(8199)
s.Run()
}
Context
推荐使用Context上下文对象来处理流程共享的上下文变量,甚至将该对象进一步传递到依赖的各个模块方法中
GetCtx方法用于获取当前的context.Context对象,作用同Context方法。
SetCtx方法用于设置自定义的context.Context上下文对象。
GetCtxVar方法用于获取上下文变量,并可给定当该变量不存在时的默认值。
SetCtxVar方法用于设置上下文变量
示例1,SetCtxVar/GetCtxVar
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
const (
TraceIdName = "trace-id"
)
func main() {
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(func(r *ghttp.Request) {
r.SetCtxVar(TraceIdName, "HBm876TFCde435Tgf")
r.Middleware.Next()
})
group.ALL("/", func(r *ghttp.Request) {
r.Response.Write(r.GetCtxVar(TraceIdName))
})
})
s.SetPort(8199)
s.Run()
}
示例2,SetCtx
SetCtx方法常用于中间件中整合一些第三方的组件,例如第三方的链路跟踪组件等等。
package main
import (
"context"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
const (
TraceIdName = "trace-id"
)
func main() {
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(func(r *ghttp.Request) {
ctx := context.WithValue(r.Context(), TraceIdName, "HBm876TFCde435Tgf")
r.SetCtx(ctx)
r.Middleware.Next()
})
group.ALL("/", func(r *ghttp.Request) {
r.Response.Write(r.Context().Value(TraceIdName))
// 也可以使用
// r.Response.Write(r.GetCtxVar(TraceIdName))
})
})
s.SetPort(8199)
s.Run()
}
Exit控制
- Exit: 仅退出当前执行的逻辑方法,不退出后续的请求流程,可用于替代return。
- ExitAll: 强行中断当前执行流程,当前执行方法的后续逻辑以及后续所有的逻辑方法将不再执行,常用于权限控制。
- ExitHook: 当路由匹配到多个HOOK方法时,默认是按照路由匹配优先级顺序执行HOOK方法。当在HOOK方法中调用ExitHook方法后,后续的HOOK方法将不会被继续执行,作用类似HOOK方法覆盖。
- 这三个退出函数仅在服务函数和HOOK事件回调函数中有效,无法控制中间件的执行流程。
Exit返回方法
原始代码
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
if r.GetInt("type") == 1 {
r.Response.Writeln("john")
}
r.Response.Writeln("smith")
})
s.SetPort(8199)
s.Run()
}
使用Exit的代码
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
if r.GetInt("type") == 1 {
r.Response.Writeln("john")
r.Exit()
}
r.Response.Writeln("smith")
})
s.SetPort(8199)
s.Run()
}
文件上传
上传文件大小受到ghttp.Server的ClientMaxBodySize 配置影响,默认支持的上传文件大小为8MB
服务端
https://github.com/gogf/gf/blob/master/.example/net/ghttp/client/upload/server.go
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
// Upload uploads files to /tmp .
func Upload(r *ghttp.Request) {
files := r.GetUploadFiles("upload-file")
names, err := files.Save("/tmp/")
if err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit("upload successfully: ", names)
}
// UploadShow shows uploading simgle file page.
func UploadShow(r *ghttp.Request) {
r.Response.Write(`
<html>
<head>
<title>GF Upload File Demo</title>
</head>
<body>
<form enctype="multipart/form-data" action="/upload" method="post">
<input type="file" name="upload-file" />
<input type="submit" value="upload" />
</form>
</body>
</html>
`)
}
// UploadShowBatch shows uploading multiple files page.
func UploadShowBatch(r *ghttp.Request) {
r.Response.Write(`
<html>
<head>
<title>GF Upload Files Demo</title>
</head>
<body>
<form enctype="multipart/form-data" action="/upload" method="post">
<input type="file" name="upload-file" />
<input type="file" name="upload-file" />
<input type="submit" value="upload" />
</form>
</body>
</html>
`)
}
func main() {
s := g.Server()
s.Group("/upload", func(group *ghttp.RouterGroup) {
group.POST("/", Upload)
group.ALL("/show", UploadShow)
group.ALL("/batch", UploadShowBatch)
})
s.SetPort(8199)
s.Run()
}
- r.GetUploadFiles方法获得上传的所有文件对象,也可以通过r.GetUploadFile获取单个上传的文件对象。
- 在r.GetUploadFiles(“upload-file”)中的参数"upload-file"为本示例中客户端上传时的表单文件域名称,开发者可以根据前后端约定在客户端中定义,以方便服务端接收表单文件域参数。
- 通过files.Save可以将上传的多个文件方便地保存到指定的目录下,并返回保存成功的文件名。如果是批量保存,只要任意一个文件保存失败,都将会立即返回错误。此外,Save方法的第二个参数支持随机自动命名上传文件。
- 通过group.POST(”/”, Upload)注册的路由仅支持POST方式访问
客户端
单文件上传
https://github.com/gogf/gf/blob/master/.example/net/ghttp/client/upload/client.go
package main
import (
"fmt"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/glog"
)
func main() {
path := "/home/john/Workspace/Go/github.com/gogf/gf/version.go"
r, e := ghttp.Post("http://127.0.0.1:8199/upload", "upload-file=@file:"+path)
if e != nil {
glog.Error(e)
} else {
fmt.Println(string(r.ReadAll()))
r.Close()
}
}
多文件上传
https://github.com/gogf/gf/blob/master/.example/net/ghttp/client/upload-batch/client.go
package main
import (
"fmt"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/glog"
)
func main() {
path1 := "/Users/john/Pictures/logo1.png"
path2 := "/Users/john/Pictures/logo2.png"
r, e := ghttp.Post(
"http://127.0.0.1:8199/upload",
fmt.Sprintf(`upload-file=@file:%s&upload-file=@file:%s`, path1, path2),
)
if e != nil {
glog.Error(e)
} else {
fmt.Println(string(r.ReadAll()))
r.Close()
}
}
自定义文件名称
s := g.Server()
s.BindHandler("/upload", func(r *ghttp.Request) {
file := r.GetUploadFile("TestFile")
if file == nil {
r.Response.Write("empty file")
return
}
file.Filename = "MyCustomFileName.txt"
fileName, err := file.Save(gfile.TempDir())
if err != nil {
r.Response.Write(err)
return
}
r.Response.Write(fileName)
})
s.SetPort(8999)
s.Run()
数据返回
- Write*方法用于数据的输出,可为任意的数据格式,内部通过断言对参数做自动分析。
- Write*Exit方法用于数据输出后退出当前服务方法,可用于替代return返回方法。
- WriteJson*/WriteXml方法用于特定数据格式的输出,这是为开发者提供的简便方法。
- WriteTpl*方法用于模板输出,解析并输出模板文件,也可以直接解析并输出给定的模板内容。
- ParseTpl*方法用于模板解析,解析模板文件或者模板内容,返回解析后的内容。
- 其他方法详见接口文档;
此外,需要提一下,Header的操作可以通过标准库的方法来实现,例如:
Response.Header().Set(“Content-Type”, “text/plain; charset=utf-8”)
缓冲控制
func (r *Response) Buffer() []byte
func (r *Response) BufferString() string
func (r *Response) BufferLength() int
func (r *Response) SetBuffer(data []byte)
func (r *Response) ClearBuffer()
func (r *Response) Output()
示例:通过后置中间件统一对返回的数据做处理,如果服务方法产生了异常,不将敏感错误信息推送到客户端,而统一设置错误提示信息。
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"net/http"
)
func MiddlewareErrorHandler(r *ghttp.Request) {
r.Middleware.Next()
if r.Response.Status >= http.StatusInternalServerError {
r.Response.ClearBuffer()
r.Response.Writeln("服务器居然开小差了,请稍后再试吧!")
}
}
func main() {
s := g.Server()
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareErrorHandler)
group.ALL("/user/list", func(r *ghttp.Request) {
panic("db error: sql is xxxxxxx")
})
})
s.SetPort(8199)
s.Run()
}
模板解析
- WriteTpl*方法用于模板输出,解析并输出模板文件,也可以直接解析并输出给定的模板内容。
- ParseTpl*方法用于模板解析,解析模板文件或者模板内容,返回解析后的内容。
内置变量
Config 访问默认的配置管理(config.toml)对象配置项。{{.Config.配置项}}
Cookie 访问当前请求的Cookie对象参数值 {{.Cookie.键名}}
Session 访问当前请求的Session对象参数值。{{.Session.键名}}
Query 访问当前Query String中的请求参数值。 {{.Query.键名}}
Form 访问当前表单请求参数值 {{.Form.键名}}
Request 访问当前请求参数值(不区分参数提交方式)。 {{.Request.键名}}
示例
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
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)
})
s.SetPort(8199)
s.Run()
}
JSON/XML
- WriteJson* 方法用于返回JSON数据格式,参数为任意类型,可以为string、map、struct等等。返回的Content-Type为application/json。
- WriteXml* 方法用于返回XML数据格式,参数为任意类型,可以为string、map、struct等等。返回的Content-Type为application/xml。
JSON
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.ALL("/json", func(r *ghttp.Request) {
r.Response.WriteJson(g.Map{
"id": 1,
"name": "john",
})
})
})
s.SetPort(8199)
s.Run()
}
JSONP
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.ALL("/jsonp", func(r *ghttp.Request) {
r.Response.WriteJsonP(g.Map{
"id": 1,
"name": "john",
})
})
})
s.SetPort(8199)
s.Run()
}
XML
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.ALL("/xml", func(r *ghttp.Request) {
r.Response.Write(`<?xml version="1.0" encoding="UTF-8"?>`)
r.Response.WriteXml(g.Map{
"id": 1,
"name": "john",
})
})
})
s.SetPort(8199)
s.Run()
}
Cookie
type Cookie
func GetCookie(r *Request) *Cookie
func (c *Cookie) Contains(key string) bool
func (c *Cookie) Flush()
func (c *Cookie) Get(key string, def ...string) string
func (c *Cookie) GetSessionId() string
func (c *Cookie) Map() map[string]string
func (c *Cookie) Remove(key string)
func (c *Cookie) RemoveCookie(key, domain, path string)
func (c *Cookie) Set(key, value string)
func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration, httpOnly ...bool)
func (c *Cookie) SetHttpCookie(httpCookie *http.Cookie)
func (c *Cookie) SetSessionId(id string)
- Cookie.GetSessionId()用于获取当前请求提交的SessionId,每个请求的SessionId都是唯一的,并且伴随整个请求流程,该值可能为空。 2.Cookie.SetSessionId(id string)用于自定义设置SessionId到Cookie中,返回给客户端(往往是浏览器)存储,随后客户端每一次请求在Cookie中可带上该SessionId。
在设置Cookie变量的时候可以给定过期时间,该时间为可选参数,默认的Cookie过期时间为一年。
默认的SessionId在Cookie中的存储名称为gfsession。
示例
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/cookie", func(r *ghttp.Request) {
datetime := r.Cookie.Get("datetime")
r.Cookie.Set("datetime", gtime.Datetime())
r.Response.Write("datetime:", datetime)
})
s.SetPort(8199)
s.Run()
}
通过SetCookie来设置Cookie键值对并将maxAge参数设置为0即可Cookie随着用户的浏览会话过期
r.Cookie.SetCookie(“MyCookieKey”, “MyCookieValue”, “”, “/”, 0)
Session
gsession模块
- gsession.Manager:管理Session对象、Storage持久化存储对象、以及过期时间控制。
- gsession.Session:单个Session会话管理对象,用于Session参数的增删查改等数据管理操作。
- gsession.Storage:这是一个接口定义,用于Session对象的持久化存储、数据写入/读取、存活更新等操作,开发者可基于该接口实现自定义的持久化存储特性。
存储实现方式
基于文件存储(默认):单节点部署方式下比较高效的持久化存储方式;
基于纯内存存储:性能最高效,但是无法持久化保存,重启即丢失;
基于Redis存储:远程Redis节点存储Session数据,支持应用多节点部署;
在默认情况下,ghttp.Server的Session存储使用了内存+文件的方式,使用StorageFile对象实现
文件存储示例:
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/gtime"
"time"
)
func main() {
s := g.Server()
s.SetConfigWithMap(g.Map{ //将Session的过期时间设置为1分钟
"SessionMaxAge": time.Minute,
})
s.Group("/", func(group *ghttp.RouterGroup) {
group.ALL("/set", func(r *ghttp.Request) {
r.Session.Set("time", gtime.Timestamp())
r.Response.Write("ok")
})
group.ALL("/get", func(r *ghttp.Request) {
r.Response.Write(r.Session.Map())
})
group.ALL("/del", func(r *ghttp.Request) {
r.Session.Clear()
r.Response.Write("ok")
})
})
s.SetPort(8199)
s.Run()
}
- 首先,访问 http://127.0.0.1:8199/set 设置一个Session变量;
- 随后,访问 http://127.0.0.1:8199/get 可以看到该Session变量已经设置并成功获取;
- 接着,我们停止程序,并重新启动,再次访问 http://127.0.0.1:8199/get ,可以看到Session变量已经从文件存储中恢复;
- 等待1分钟后,再次访问 http://127.0.0.1:8199/get 可以看到已经无法获取该Session,因为该Session已经过期;
内存存储示例:
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/gsession"
"github.com/gogf/gf/os/gtime"
"time"
)
func main() {
s := g.Server()
s.SetConfigWithMap(g.Map{
"SessionMaxAge": time.Minute,
"SessionStorage": gsession.NewStorageMemory(),
})
s.Group("/", func(group *ghttp.RouterGroup) {
group.ALL("/set", func(r *ghttp.Request) {
r.Session.Set("time", gtime.Timestamp())
r.Response.Write("ok")
})
group.ALL("/get", func(r *ghttp.Request) {
r.Response.Write(r.Session.Map())
})
group.ALL("/del", func(r *ghttp.Request) {
r.Session.Clear()
r.Response.Write("ok")
})
})
s.SetPort(8199)
s.Run()
}
- 首先,访问 http://127.0.0.1:8199/set 设置一个Session变量;
- 随后,访问 http://127.0.0.1:8199/get 可以看到该Session变量已经设置并成功获取;
- 接着,我们停止程序,并重新启动,再次访问 http://127.0.0.1:8199/get ,可以看到Session变量已经没有了;
Redis存储
与文件存储唯一不同的是,在每一次请求中如果需要对Session进行操作时,将会从Redis中拉取一次最新的Session数据(而文件存储只会在Session不存在时读取一次文件)。
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/gsession"
"github.com/gogf/gf/os/gtime"
"time"
)
func main() {
s := g.Server()
s.SetConfigWithMap(g.Map{
"SessionMaxAge": time.Minute,
"SessionStorage": gsession.NewStorageRedis(g.Redis()),
})
s.Group("/", func(group *ghttp.RouterGroup) {
group.ALL("/set", func(r *ghttp.Request) {
r.Session.Set("time", gtime.Timestamp())
r.Response.Write("ok")
})
group.ALL("/get", func(r *ghttp.Request) {
r.Response.Write(r.Session.Map())
})
group.ALL("/del", func(r *ghttp.Request) {
r.Session.Clear()
r.Response.Write("ok")
})
})
s.SetPort(8199)
s.Run()
}
- 首先,访问 http://127.0.0.1:8199/set 设置一个Session变量;
- 随后,访问 http://127.0.0.1:8199/get 可以看到该Session变量已经设置并成功获取;
- 接着,我们停止程序,并重新启动,再次访问 http://127.0.0.1:8199/get ,可以看到Session变量已经从Redis存储中恢复;如果我们手动修改Redis中的对应键值数据,页面刷新时也会读取到最新的值;
- 等待1分钟后,再次访问 http://127.0.0.1:8199/get 可以看到已经无法获取该Session,因为该Session已经过期;
服务配置
- 可以通过SetConfig及SetConfigWithMap来设置。
- 也可以使用Server对象的Set*/Enable*方法进行特定配置的设置。
- 主要注意的是,配置项在Server执行Start之后便不能再修改,防止产生并发安全问题。
SetConfigWithMap方法
不区分大小写,单词间也支持使用-/_/空格符号连接
s := g.Server()
s.SetConfigWithMap(g.Map{
"address": ":80",
"serverRoot": "/var/www/Server",
"indexFiles": g.Slice{"index.html", "main.html"},
"accessLogEnabled": true,
"errorLogEnabled": true,
"pprofEnabled": true,
"logPath": "/var/log/ServerLog",
"sessionIdName": "MySessionId",
"sessionPath": "/tmp/MySessionStoragePath",
"sessionMaxAge": 24 * time.Hour,
"dumpRouterMap": false,
})
s.Run()
多个配置项读取
[server]
address = ":80"
serverRoot = "/var/www/Server"
[server.server1]
address = ":8080"
serverRoot = "/var/www/Server1"
[server.server2]
address = ":8088"
serverRoot = "/var/www/Server2"
// 对应 server.server1 配置项
s1 := g.Server("server1")
// 对应 server.server2 配置项
s2 := g.Server("server2")
// 对应默认配置项 server
s3 := g.Server("none")
// 对应默认配置项 server
s4 := g.Server()
上传限制
- MaxHeaderBytes:请求头大小限制,请求头包括客户端提交的Cookie数据,默认设置为10KB。
- ClientMaxBodySize:客户端提交的Body大小限制,同时也影响文件上传大小,默认设置为8MB。
[server]
maxHeaderBytes = "20KB"
clientMaxBodySize = "200MB"
配置示例
所有的配置项请参考ServerConfig对象属性: https://godoc.org/github.com/gogf/gf/net/ghttp#ServerConfig
[server]
# 基本配置
address = ":80" # 本地监听地址。默认":80"
httpsAddr = ":443" # TLS/HTTPS配置,同时需要配置证书和密钥。默认关闭
httpsCertPath = "" # TLS/HTTPS证书文件本地路径,建议使用绝对路径。默认关闭
httpsKeyPath = "" # TLS/HTTPS密钥文件本地路径,建议使用绝对路径。默认关闭
readTimeout = "60s" # 请求读取超时时间,一般不需要配置。默认为60秒
writeTimeout = "0" # 数据返回写入超时时间,一般不需要配置。默认不超时(0)
idleTimeout = "60s" # 仅当Keep-Alive开启时有效,请求闲置时间。默认为60秒
maxHeaderBytes = "10240" # 请求Header大小限制(Byte)。默认为10KB
keepAlive = true # 是否开启Keep-Alive功能。默认true
serverAgent = "GF HTTP Server" # 服务端Agent信息。默认为"GF HTTP Server"
# 静态服务配置
indexFiles = ["index.html","index.htm"] # 自动首页静态文件检索。默认为["index.html", "index.htm"]
indexFolder = false # 当访问静态文件目录时,是否展示目录下的文件列表。默认关闭,那么请求将返回403
serverRoot = "/var/www" # 静态文件服务的目录根路径,配置时自动开启静态文件服务。默认关闭
searchPaths = ["/home/www","/var/lib/www"] # 提供静态文件服务时额外的文件搜索路径,当根路径找不到时则按照顺序在搜索目录查找。默认关闭
fileServerEnabled = false # 静态文件服务总开关。默认false
# Cookie配置
cookieMaxAge = "365d" # Cookie有效期。默认为365天
cookiePath = "/" # Cookie有效路径。默认为"/"表示全站所有路径下有效
cookieDomain = "" # Cookie有效域名。默认为当前配置Cookie时的域名
# Sessions配置
sessionMaxAge = "24h" # Session有效期。默认为24小时
sessionIdName = "gfsessionid" # SessionId的键名名称。默认为gfsessionid
sessionCookieOutput = true # Session特性开启时,是否将SessionId返回到Cookie中。默认true
sessionPath = "/tmp/gsessions" # Session存储的文件目录路径。默认为当前系统临时目录下的gsessions目录
# Logging配置
logPath = "" # 日志文件存储目录路径,建议使用绝对路径。默认为空,表示关闭
logStdout = true # 日志是否输出到终端。默认为true
errorStack = true # 当Server捕获到异常时是否记录堆栈信息到日志中。默认为true
errorLogEnabled = true # 是否记录异常日志信息到日志中。默认为true
errorLogPattern = "error-{Ymd}.log" # 异常错误日志文件格式。默认为"error-{Ymd}.log"
accessLogEnabled = false # 是否记录访问日志。默认为false
accessLogPattern = "access-{Ymd}.log" # 访问日志文件格式。默认为"access-{Ymd}.log"
# PProf配置
pprofEnabled = false # 是否开启PProf性能调试特性。默认为false
pprofPattern = "" # 开启PProf时有效,表示PProf特性的页面访问路径,对当前Server绑定的所有域名有效。
# 其他配置
clientMaxBodySize = 810241024 # 客户端最大Body上传限制大小,影响文件上传大小(Byte)。默认为8*1024*1024=8MB
formParsingMemory = 1048576 # 解析表单时的缓冲区大小(Byte),一般不需要配置。默认为1024*1024=1MB
nameToUriType = 0 # 路由注册中使用对象注册时的路由生成规则。默认为0
routeOverWrite = false # 当遇到重复路由注册时是否强制覆盖。默认为false,重复路由存在时将会在启动时报错退出
dumpRouterMap = true # 是否在Server启动时打印所有的路由列表。默认为true
graceful = false # 是否开启平滑重启特性,开启时将会在本地增加10000的本地TCP端口用于进程间通信。默认false
异常处理
这里使用一个全局的后置中间件来捕获异常
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
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("服务器居然开小差了,请稍后再试吧!")
}
}
func main() {
s := g.Server()
s.Use(MiddlewareErrorHandler)
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.ALL("/user/list", func(r *ghttp.Request) {
panic("db error: sql is xxxxxxx")
})
})
s.SetPort(8199)
s.Run()
}
HTTPClient
HTTP客户端
type Client
// 创建客户端
func NewClient() *Client
// 原始请求方法
func (c *Client) Get(url string, data ...interface{}) (*ClientResponse, error)
func (c *Client) Put(url string, data ...interface{}) (*ClientResponse, error)
func (c *Client) Post(url string, data ...interface{}) (*ClientResponse, error)
func (c *Client) Delete(url string, data ...interface{}) (*ClientResponse, error)
func (c *Client) Connect(url string, data ...interface{}) (*ClientResponse, error)
func (c *Client) Head(url string, data ...interface{}) (*ClientResponse, error)
func (c *Client) Options(url string, data ...interface{}) (*ClientResponse, error)
func (c *Client) Patch(url string, data ...interface{}) (*ClientResponse, error)
func (c *Client) Trace(url string, data ...interface{}) (*ClientResponse, error)
func (c *Client) DoRequest(method, url string, data ...interface{}) (*ClientResponse, error)
// 发起HTTP请求并返回二进制内容
func (c *Client) GetBytes(url string, data ...interface{}) []byte
func (c *Client) PutBytes(url string, data ...interface{}) []byte
func (c *Client) PostBytes(url string, data ...interface{}) []byte
func (c *Client) DeleteBytes(url string, data ...interface{}) []byte
func (c *Client) ConnectBytes(url string, data ...interface{}) []byte
func (c *Client) HeadBytes(url string, data ...interface{}) []byte
func (c *Client) OptionsBytes(url string, data ...interface{}) []byte
func (c *Client) PatchBytes(url string, data ...interface{}) []byte
func (c *Client) TraceBytes(url string, data ...interface{}) []byte
func (c *Client) RequestBytes(method string, url string, data ...interface{}) []byte
// 发起HTTP请求并返回字符串内容
func (c *Client) GetContent(url string, data ...interface{}) string
func (c *Client) PutContent(url string, data ...interface{}) string
func (c *Client) PostContent(url string, data ...interface{}) string
func (c *Client) DeleteContent(url string, data ...interface{}) string
func (c *Client) ConnectContent(url string, data ...interface{}) string
func (c *Client) HeadContent(url string, data ...interface{}) string
func (c *Client) OptionsContent(url string, data ...interface{}) string
func (c *Client) PatchContent(url string, data ...interface{}) string
func (c *Client) TraceContent(url string, data ...interface{}) string
func (c *Client) RequestContent(method string, url string, data ...interface{}) string
// 发起HTTP请求并返回泛型对象方便后续类型转换
func (c *Client) GetVar(url string, data ...interface{}) *gvar.Var
func (c *Client) PutVar(url string, data ...interface{}) *gvar.Var
func (c *Client) PostVar(url string, data ...interface{}) *gvar.Var
func (c *Client) DeleteVar(url string, data ...interface{}) *gvar.Var
func (c *Client) HeadVar(url string, data ...interface{}) *gvar.Var
func (c *Client) PatchVar(url string, data ...interface{}) *gvar.Var
func (c *Client) ConnectVar(url string, data ...interface{}) *gvar.Var
func (c *Client) OptionsVar(url string, data ...interface{}) *gvar.Var
func (c *Client) TraceVar(url string, data ...interface{}) *gvar.Var
func (c *Client) RequestVar(method string, url string, data ...interface{}) *gvar.Var
// 链式操作
func (c *Client) Ctx(ctx context.Context) *Client
func (c *Client) Timeout(t time.Duration) *Client
func (c *Client) Cookie(m map[string]string) *Client
func (c *Client) Header(m map[string]string) *Client
func (c *Client) HeaderRaw(headers string) *Client
func (c *Client) ContentType(contentType string) *Client
func (c *Client) ContentJson() *Client
func (c *Client) ContentXml() *Client
func (c *Client) BasicAuth(user, pass string) *Client
func (c *Client) Retry(retryCount int, retryInterval time.Duration) *Client
func (c *Client) Proxy(proxyURL string) *Client
func (c *Client) RedirectLimit(redirectLimit int) *Client
// 设置管理
func (c *Client) SetBasicAuth(user, pass string) *Client
func (c *Client) SetBrowserMode(enabled bool) *Client
func (c *Client) SetContentType(contentType string) *Client
func (c *Client) SetCookie(key, value string) *Client
func (c *Client) SetCookieMap(m map[string]string) *Client
func (c *Client) SetCtx(ctx context.Context) *Client
func (c *Client) SetHeader(key, value string) *Client
func (c *Client) SetHeaderMap(m map[string]string) *Client
func (c *Client) SetHeaderRaw(headers string) *Client
func (c *Client) SetPrefix(prefix string) *Client
func (c *Client) SetRetry(retryCount int, retryInterval int) *Client
func (c *Client) SetTimeout(t time.Duration) *Client
func (c *Client) SetProxy(proxyURL string)
HTTP返回对象
func (r *ClientResponse) GetCookie(key string) string
func (r *ClientResponse) GetCookieMap() map[string]string
func (r *ClientResponse) Raw() string
func (r *ClientResponse) RawDump()
func (r *ClientResponse) RawRequest() string
func (r *ClientResponse) RawResponse() string
func (r *ClientResponse) ReadAll() []byte
func (r *ClientResponse) ReadAllString() string
func (r *ClientResponse) Close() error
HTTPClient-链式操作
示例1,请求超时控制
g.Client().Timeout(3*time.Second).GetContent("http://user.svc/v1/user/info/1")
g.Client().Timeout(10*time.Second).PostContent("http://order.svc/v1/order/create", g.Map{
"uid" : 1,
"sku_id" : 10000,
"amount" : 19.99,
"create_time" : "2020-03-26 12:00:00",
})
示例2,自定义Cookie
g.Client().Cookie("sessionid", "MNV5432PIY76").GetContent("http://user.svc/v1/user/info/1")
示例3,自定义Header
g.Client().Header("Trace-Id", "XVF654RT98UJNMN641V06Y").GetContent("http://user.svc/v1/user/info/1")
g.Client().HeaderRaw(`
Referer: https://goframe.org/
User-Agent: MyTesyClient
`).GetContent("http://user.svc/v1/user/info")
示例4,提交Json请求
g.Client().ContentJson().PostContent("http://order.svc/v1/order/create", g.Map{
"uid" : 1,
"sku_id" : 10000,
"amount" : 19.99,
"create_time" : "2020-03-26 12:00:00",
})
示例5,提交Xml请求
g.Client().ContentXml().PostContent("http://order.svc/v1/order/create", g.Map{
"uid" : 1,
"sku_id" : 10000,
"amount" : 19.99,
"create_time" : "2020-03-26 12:00:00",
})
HTTPClient-基本使用
发送GET请求,并打印出返回值
if r, err := g.Client().Get("https://goframe.org"); err != nil {
panic(err)
} else {
defer r.Close()
fmt.Println(r.ReadAllString())
}
发送GET请求,下载远程文件
if r, err := g.Client().Get("https://goframe.org/cover.png"); err != nil {
panic(err)
} else {
defer r.Close()
gfile.PutBytes("/Users/john/Temp/cover.png", r.ReadAll())
}
发送POST请求,并打印出返回值
if r, err := g.Client().Post("http://127.0.0.1:8199/form", "name=john&age=18"); err != nil {
panic(err)
} else {
defer r.Close()
fmt.Println(r.ReadAllString())
}
发送POST请求,参数为map类型,并打印出返回值
if r, err := g.Client().Post(
"http://127.0.0.1:8199/form",
g.Map{
"submit" : "1",
"callback" : "http://127.0.0.1/callback?url=http://baidu.com",
}
)); err != nil {
panic(err)
} else {
defer r.Close()
fmt.Println(r.ReadAllString())
}
发送POST请求,参数为JSON数据,并打印出返回值
if r, err := g.Client().Post(
"http://user.svc/v1/user/create",
`{"passport":"john","password":"123456","password-confirm":"123456"}`,
); err != nil {
panic(err)
} else {
defer r.Close()
fmt.Println(r.ReadAllString())
}
发送DELETE请求,并打印出返回值
if r, err := g.Client().Delete("http://user.svc/v1/user/delete/1", "10000"); err != nil {
panic(err)
} else {
defer r.Close()
fmt.Println(r.ReadAllString())
}
Bytes及Content方法
以Bytes及Content后缀结尾的请求方法为直接获取返回内容的快捷方法,这些方法将会自动读取服务端返回内容并自动关闭请求连接
发送GET请求,返回服务端返回内容
// 返回content为[]bytes类型
content := g.Client().GetBytes("https://goframe.org")
发送POST请求,返回服务端返回内容
// 返回content为[]bytes类型
content := g.Client().PostBytes(
"http://user.svc/v1/user/create",
`{"passport":"john","password":"123456","password-confirm":"123456"}`,
)
// 返回content为string类型
content := g.Client().PostContent(
"http://user.svc/v1/user/create",
`{"passport":"john","password":"123456","password-confirm":"123456"}`,
)
*Var方法
以Var后缀结尾的请求方法直接请求并获取HTTP接口结果为泛型类型便于转换。
type User struct {
Id int
Name string
}
var user *User
// 也可以
// var user = new(User)
g.Client().GetVar(url).Scan(&user)
// Struct数组
var users []*User
// 也可以
// var users []User
g.Client().GetVar(url).Scan(&users)
HTTPClient-文件上传
上面已说过,略
HTTPClient-自定义Cookie
func (c *Client) SetCookie(key, value string) *Client
func (c *Client) SetCookieMap(m map[string]string) *Client
服务端
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request){
r.Response.Write(r.Cookie.Map())
})
s.SetPort(8199)
s.Run()
}
客户端
package main
import (
"fmt"
"github.com/gogf/gf/frame/g"
)
func main() {
c := g.Client()
c.SetCookie("name", "john")
c.SetCookie("score", "100")
if r, e := c.Get("http://127.0.0.1:8199/"); e != nil {
panic(e)
} else {
fmt.Println(r.ReadAllString())
}
}
或者
package main
import (
"fmt"
"github.com/gogf/gf/frame/g"
)
func main() {
c := g.Client()
c.SetCookieMap(g.MapStrStr{
"name": "john",
"score": "100",
})
if r, e := c.Get("http://127.0.0.1:8199/"); e != nil {
panic(e)
} else {
fmt.Println(r.ReadAllString())
}
}
HTTPClient-自定义Header
func (c *Client) SetHeader(key, value string) *Client
func (c *Client) SetHeaderMap(m map[string]string) *Client
func (c *Client) SetHeaderRaw(headers string) *Client
服务端
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Writef(
"Span-Id:%s,Trace-Id:%s",
r.Header.Get("Span-Id"),
r.Header.Get("Trace-Id"),
)
})
s.SetPort(8199)
s.Run()
}
客户端
package main
import (
"fmt"
"github.com/gogf/gf/frame/g"
)
func main() {
c := g.Client()
c.SetHeader("Span-Id", "0.0.1")
c.SetHeader("Trace-Id", "NBC56410N97LJ016FQA")
if r, e := c.Get("http://127.0.0.1:8199/"); e != nil {
panic(e)
} else {
fmt.Println(r.ReadAllString())
}
}
或
package main
import (
"fmt"
"github.com/gogf/gf/frame/g"
)
func main() {
c := g.Client()
c.SetHeaderRaw(`
Referer: https://localhost
Span-Id: 0.0.1
Trace-Id: NBC56410N97LJ016FQA
User-Agent: MyTestClient
`)
if r, e := c.Get("http://127.0.0.1:8199/"); e != nil {
panic(e)
} else {
fmt.Println(r.ReadAllString())
}
}
HTTPClient-请求信息打印
方便调试
func (r *ClientResponse) Raw() string
func (r *ClientResponse) RawDump()
func (r *ClientResponse) RawRequest() string
func (r *ClientResponse) RawResponse() string
package main
import (
"github.com/gogf/gf/frame/g"
)
func main() {
response, err := g.Client().Get("https://goframe.org")
if err != nil {
panic(err)
}
response.RawDump()
}
HTTPClient-代理Proxy设置
client := g.Client()
client.SetProxy("http://127.0.0.1:1081")
client.SetTimeout(5 * time.Second)
response, err := client.Get("https://api.ip.sb/ip")
if err != nil {
fmt.Println(err)
}
response.RawDump()
client := g.Client()
response, err := client.Proxy("http://127.0.0.1:1081").Get("https://api.ip.sb/ip")
if err != nil {
fmt.Println(err)
}
fmt.Println(response.RawResponse())
分页管理
import “github.com/gogf/gf/util/gpage”
文档:https://godoc.org/github.com/gogf/gf/util/gpage
分页管理-动态分页
通过GET参数(通过QueryString)传递分页参数,默认分页参数名称为page
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/gview"
)
func main() {
s := g.Server()
s.BindHandler("/page/demo", func(r *ghttp.Request) {
page := r.GetPage(100, 10) //总100条,每10条一页
buffer, _ := gview.ParseContent(`
<html>
<head>
<style>
a,span {padding:8px; font-size:16px;}
div{margin:5px 5px 20px 5px}
</style>
</head>
<body>
<div>{{.page1}}</div>
<div>{{.page2}}</div>
<div>{{.page3}}</div>
<div>{{.page4}}</div>
</body>
</html>
`, g.Map{
"page1": page.GetContent(1), //展现的样式
"page2": page.GetContent(2),
"page3": page.GetContent(3),
"page4": page.GetContent(4),
})
r.Response.Write(buffer)
})
s.SetPort(8199)
s.Run()
}
分页管理-静态分页
静态分页是指页面的分页参数使用的是路由传参.路由定义中需要给定一个page名称的路由参数,可以使用模糊匹配路由*page,也可以使用命名匹配路由:page,也可以使用字段匹配路由{page}。
示例1,使用模糊匹配路由
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/gview"
)
func main() {
s := g.Server()
s.BindHandler("/page/static/*page", func(r *ghttp.Request) {
page := r.GetPage(100, 10)
buffer, _ := gview.ParseContent(`
<html>
<head>
<style>
a,span {padding:8px; font-size:16px;}
div{margin:5px 5px 20px 5px}
</style>
</head>
<body>
<div>{{.page1}}</div>
<div>{{.page2}}</div>
<div>{{.page3}}</div>
<div>{{.page4}}</div>
</body>
</html>
`, g.Map{
"page1": page.GetContent(1),
"page2": page.GetContent(2),
"page3": page.GetContent(3),
"page4": page.GetContent(4),
})
r.Response.Write(buffer)
})
s.SetPort(8199)
s.Run()
}
示例2,使用字段匹配路由
使用了{page}字段匹配规则,用于获取当前的分页页码信息
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/gview"
)
func main() {
s := g.Server()
s.BindHandler("/:obj/*action/{page}.html", func(r *ghttp.Request) {
page := r.GetPage(100, 10)
buffer, _ := gview.ParseContent(`
<html>
<head>
<style>
a,span {padding:8px; font-size:16px;}
div{margin:5px 5px 20px 5px}
</style>
</head>
<body>
<div>{{.page1}}</div>
<div>{{.page2}}</div>
<div>{{.page3}}</div>
<div>{{.page4}}</div>
</body>
</html>
`, g.Map{
"page1": page.GetContent(1),
"page2": page.GetContent(2),
"page3": page.GetContent(3),
"page4": page.GetContent(4),
})
r.Response.Write(buffer)
})
s.SetPort(8199)
s.Run()
}
分页管理-Ajax分页
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/gview"
)
func main() {
s := g.Server()
s.BindHandler("/page/ajax", func(r *ghttp.Request) {
page := r.GetPage(100, 10)
page.AjaxActionName = "DoAjax"
buffer, _ := gview.ParseContent(`
<html>
<head>
<style>
a,span {padding:8px; font-size:16px;}
div{margin:5px 5px 20px 5px}
</style>
<script src="https://cdn.bootcss.com/jquery/2.0.3/jquery.min.js"></script>
<script>
function DoAjax(url) {
$.get(url, function(data,status) {
$("body").html(data);
});
}
</script>
</head>
<body>
<div>{{.page1}}</div>
<div>{{.page2}}</div>
<div>{{.page3}}</div>
<div>{{.page4}}</div>
</body>
</html>
`, g.Map{
"page1": page.GetContent(1),
"page2": page.GetContent(2),
"page3": page.GetContent(3),
"page4": page.GetContent(4),
})
r.Response.Write(buffer)
})
s.SetPort(8199)
s.Run()
}
分页管理-URL模板
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/gview"
)
func main() {
s := g.Server()
s.BindHandler("/page/template/{page}.html", func(r *ghttp.Request) {
page := r.GetPage(100, 10)
page.UrlTemplate = "/order/list/{.page}.html"
buffer, _ := gview.ParseContent(`
<html>
<head>
<style>
a,span {padding:8px; font-size:16px;}
div{margin:5px 5px 20px 5px}
</style>
</head>
<body>
<div>{{.page1}}</div>
<div>{{.page2}}</div>
<div>{{.page3}}</div>
<div>{{.page4}}</div>
</body>
</html>
`, g.Map{
"page1": page.GetContent(1),
"page2": page.GetContent(2),
"page3": page.GetContent(3),
"page4": page.GetContent(4),
})
r.Response.Write(buffer)
})
s.SetPort(8199)
s.Run()
}
分页管理-自定义分页
自定义标签替换
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/gview"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gpage"
)
// wrapContent wraps each of the page tag with html li and ul.
func wrapContent(page *gpage.Page) string {
content := page.GetContent(4)
content = gstr.ReplaceByMap(content, map[string]string{
"<span": "<li><span",
"/span>": "/span></li>",
"<a": "<li><a",
"/a>": "/a></li>",
})
return "<ul>" + content + "</ul>"
}
func main() {
s := g.Server()
s.BindHandler("/page/custom1/*page", func(r *ghttp.Request) {
page := r.GetPage(100, 10)
content := wrapContent(page)
buffer, _ := gview.ParseContent(`
<html>
<head>
<style>
a,span {padding:8px; font-size:16px;}
div{margin:5px 5px 20px 5px}
</style>
</head>
<body>
<div>{{.page}}</div>
</body>
</html>
`, g.Map{
"page": content,
})
r.Response.Write(buffer)
})
s.SetPort(10000)
s.Run()
}
定义分页标签名称
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/gview"
"github.com/gogf/gf/util/gpage"
)
// pageContent customizes the page tag name.
func pageContent(page *gpage.Page) string {
page.NextPageTag = "NextPage"
page.PrevPageTag = "PrevPage"
page.FirstPageTag = "HomePage"
page.LastPageTag = "LastPage"
pageStr := page.FirstPage()
pageStr += page.PrevPage()
pageStr += page.PageBar()
pageStr += page.NextPage()
pageStr += page.LastPage()
return pageStr
}
func main() {
s := g.Server()
s.BindHandler("/page/custom2/*page", func(r *ghttp.Request) {
page := r.GetPage(100, 10)
buffer, _ := gview.ParseContent(`
<html>
<head>
<style>
a,span {padding:8px; font-size:16px;}
div{margin:5px 5px 20px 5px}
</style>
</head>
<body>
<div>{{.page}}</div>
</body>
</html>
`, g.Map{
"page": pageContent(page),
})
r.Response.Write(buffer)
})
s.SetPort(10000)
s.Run()
}
高级特性
静态文件服务
默认情况下,gf Server关闭了静态文件服务的功能,如果开发者配置了静态文件目录,那么静态文件服务将会自动开启。
/ 设置http server参数 - ServerRoot
func (s *Server) SetServerRoot(root string)
// 添加静态文件搜索目录,必须给定目录的绝对路径
func (s *Server) AddSearchPath(path string)
// 设置http server参数 - IndexFiles,默认展示文件,如:index.html, index.htm
func (s *Server) SetIndexFiles(index []string)
// 是否允许展示访问目录的文件列表
func (s *Server) SetIndexFolder(enabled bool)
// 添加URI与静态目录的映射
func (s *Server) AddStaticPath(prefix string, path string)
// 静态文件服务总开关:是否开启/关闭静态文件服务
func (s *Server) SetFileServerEnabled(enabled bool)
// 设置URI重写规则
func (s *Server) SetRewrite(uri string, rewrite string)
// 设置URI重写规则(批量)
func (s *Server) SetRewriteMap(rewrites map[string]string)
示例1, 基本使用
package main
import "github.com/gogf/gf/frame/g"
// 静态文件服务器基本使用
func main() {
s := g.Server()
s.SetIndexFolder(true)
s.SetServerRoot("/Users/john/Temp")
s.AddSearchPath("/Users/john/Documents")
s.SetPort(8199)
s.Run()
}
示例2,静态目录映射
package main
import "github.com/gogf/gf/frame/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()
}
示例3,静态目录映射,优先级控制
package main
import "github.com/gogf/gf/frame/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()
}
示例4,URI重写
package main
import "github.com/gogf/gf/frame/g"
func main() {
s := g.Server()
s.SetServerRoot("/Users/john/Temp")
s.SetRewrite("/test.html", "/test1.html")
s.SetRewriteMap(g.MapStrStr{
"/my-test1" : "/test1.html",
"/my-test2" : "/test2.html",
})
s.SetPort(8199)
s.Run()
}
- 当我们访问 /test.html ,其实最终被重写到了 test1.html,返回的是该文件内容;
- 当我们访问 /my-test1 ,其实最终被重写到了 test1.html,返回的是该文件内容;
- 当我们访问 /my-test2 ,其实最终被重写到了 test2.html,返回的是该文件内容;
服务日志管理
日志配置
Logger *glog.Logger // Logger for server.
LogPath string // Directory for storing logging files.
LogStdout bool // Printing logging content to stdout.
ErrorStack bool // Logging stack information when error.
ErrorLogEnabled bool // Enable error logging files.
ErrorLogPattern string // Error log file pattern like: error-{Ymd}.log
AccessLogEnabled bool // Enable access logging files.
AccessLogPattern string // Error log file pattern like: access-{Ymd}.log
- 默认情况下,日志不会输出到文件中,而是直接打印到终端。默认情况下的access日志终端输出是关闭的,仅有error日志默认开启
- 所有的选项均可通过Server.Set方法设置,大部分选项可以通过Server.Get方法获取。
- LogPath属性用于设置日志目录,只有在设置了日志目录的情况下才会输出日志到日志文件中。
- ErrorLogPattern及AccessLogPattern用于配置日志文件名称格式,默认为error-{Ymd}.log及access-{Ymd}.log,例如:error-20191212.log, access-20191212.log。
配置文件
[server]
LogPath = "/var/log/gf-demos/server"
LogStdout = false
ErrorStack = true
ErrorLogEnabled = true
ErrorLogPattern = "error.{Ymd}.log"
AccessLogEnabled = true
AccessLogPattern = "access.{Ymd}.log"
HTTPS & TLS
示例代码
package main
import (
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := ghttp.GetServer()
s.BindHandler("/", func(r *ghttp.Request){
r.Response.Writeln("来自于HTTPS的:哈喽世界!")
})
s.EnableHTTPS("/home/john/https/server.crt", "/home/john/https/server.key")
s.SetPort(8199) //也可以通过s.SetHTTPSPort(8199)
s.Run()
}
同时支持HTTPS及HTTP访问
package main
import (
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := ghttp.GetServer()
s.BindHandler("/", func(r *ghttp.Request){
r.Response.Writeln("您可以同时通过HTTP和HTTPS方式看到该内容!")
})
s.EnableHTTPS("/home/john/https/server.crt", "/home/john/https/server.key")
s.SetHTTPSPort(443)
s.SetPort(80)
s.Run()
}
平滑重启
目前平滑重启特性需要在本地打开一个10000端口的tcp监听服务。
默认情况下平滑重启特性是关闭的,可以通过配置选项打开
func (s *Server) Restart(newExeFilePath...string) error //重启服务
Restart的参数可指定自定义重启的可执行文件路径(newExeFilePath),不传递时默认为原可执行文件路径。
func (s *Server) Shutdown() error //关闭服务
func (s *Server) EnableAdmin(pattern ...string) //将管理页面注册到指定的路由规则上,默认地址是/debug/admin
示例1:基本使用
package main
import (
"time"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/os/gproc"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request){
r.Response.Writeln("哈喽!")
})
s.BindHandler("/pid", func(r *ghttp.Request){
r.Response.Writeln(gproc.Pid())
})
s.BindHandler("/sleep", func(r *ghttp.Request){
r.Response.Writeln(gproc.Pid())
time.Sleep(10*time.Second)
r.Response.Writeln(gproc.Pid())
})
s.EnableAdmin()
s.SetPort(8199)
s.Run()
}
访问 http://127.0.0.1:8199/debug/admin ,这是s.EnableAdmin后默认注册的一个WebServer管理页面
示例2:HTTPS支持
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request){
r.Response.Writeln("哈罗!")
})
s.EnableHTTPS("/home/john/temp/server.crt", "/home/john/temp/server.key")
s.EnableAdmin()
s.SetPort(8200)
s.Run()
}
- 访问 https://127.0.0.1:8200/debug/admin/restart 平滑重启HTTPS服务;
- 访问 https://127.0.0.1:8200/debug/admin/shutdown 平滑关闭WebServer服务;
示例3:多服务及多端口
package main
import (
"github.com/gogf/gf/frame/g"
)
func main() {
s1 := g.Server("s1")
s1.EnableAdmin()
s1.SetPort(8100, 8200)
s1.Start()
s2 := g.Server("s2")
s2.EnableAdmin()
s2.SetPort(8300, 8400)
s2.Start()
g.Wait()
}
命令行管理
kill -SIGUSR1 进程ID //重启服务
kill -SIGTERM 进程ID //关闭服务
CORS跨域处理
CORS配置
限制Origin来源
// 允许跨域请求中间件
func Middleware(r *ghttp.Request) {
corsOptions := r.Response.DefaultCORSOptions()
corsOptions.AllowDomain = []string{"goframe.org", "johng.cn"}
r.Response.CORS(corsOptions)
r.Middleware.Next()
}
OPTIONS请求
有的客户端,部分浏览器在发送AJAX请求之前会首先发送OPTIONS预请求检测后续请求是否允许发送。
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func MiddlewareCORS(r *ghttp.Request) {
r.Response.CORSDefault() //通过调用CORSDefault方法使用默认的跨域设置允许跨域请求。
r.Middleware.Next()
}
func Order(r *ghttp.Request) {
r.Response.Write("GET")
}
func main() {
s := g.Server()
s.Group("/api.v1", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareCORS)
group.GET("/order", Order)
})
s.SetPort(8199)
s.Run()
}
授权跨域Origin
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func MiddlewareCORS(r *ghttp.Request) {
corsOptions := r.Response.DefaultCORSOptions()
corsOptions.AllowDomain = []string{"goframe.org", "baidu.com"}
r.Response.CORS(corsOptions)
r.Middleware.Next()
}
func Order(r *ghttp.Request) {
r.Response.Write("GET")
}
func main() {
s := g.Server()
s.Group("/api.v1", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareCORS)
group.GET("/order", Order)
})
s.SetPort(8199)
s.Run()
}
自定义检测授权
以下示例中,仅允许来自goframe.org域名的跨域请求,而来自其他域名的请求将会失败并返回403
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func MiddlewareCORS(r *ghttp.Request) {
corsOptions := r.Response.DefaultCORSOptions()
corsOptions.AllowDomain = []string{"goframe.org"}
if !r.Response.CORSAllowedOrigin(corsOptions) {
r.Response.WriteStatus(http.StatusForbidden)
return
}
r.Response.CORS(corsOptions)
r.Middleware.Next()
}
func Order(r *ghttp.Request) {
r.Response.Write("GET")
}
func main() {
s := g.Server()
s.Group("/api.v1", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareCORS)
group.GET("/order", Order)
})
s.SetPort(8199)
s.Run()
}
CSRF防御设置
通过中间件完成
import “github.com/gogf/csrf”
s := g.Server()
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.Middleware(csrf.NewWithCfg(csrf.Config{
Cookie: &http.Cookie{
Name: "_csrf",// token name in cookie
},
ExpireTime: time.Hour * 24,
TokenLength: 32,
TokenRequestKey: "X-Token",// use this key to read token in request param
}))
group.ALL("/csrf", func(r *ghttp.Request) {
r.Response.Writeln(r.Method + ": " + r.RequestURI)
})
})
前端对接
package main
import (
"net/http"
"time"
"github.com/gogf/csrf"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
// default cfg
func main() {
s := g.Server()
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.Middleware(csrf.New())
group.ALL("/csrf", func(r *ghttp.Request) {
r.Response.Writeln(r.Method + ": " + r.RequestURI)
})
})
s.SetPort(8199)
s.Run()
}
HOOK事件回调
ghttp.Server提供了事件回调注册功能,类似于其他框架的中间件功能,相比较于中间件,事件回调的特性更加简单。
func (s *Server) BindHookHandler(pattern string, hook string, handler HandlerFunc) error
func (s *Server) BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) error
支持的Hook事件列表:
ghttp.HOOK_BEFORE_SERVE 在进入/初始化服务对象之前,该事件是最常用的事件,特别是针对于权限控制、跨域请求等处理。
ghttp.HOOK_AFTER_SERVE 在完成服务执行流程之后。
ghttp.HOOK_BEFORE_OUTPUT 向客户端输出返回内容之前。
ghttp.HOOK_AFTER_OUTPUT 向客户端输出返回内容之后。
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/os/glog"
"github.com/gogf/gf/net/ghttp"
)
func main() {
// 基本事件回调使用
p := "/:name/info/{uid}"
s := g.Server()
s.BindHookHandlerByMap(p, map[string]ghttp.HandlerFunc{
ghttp.HOOK_BEFORE_SERVE : func(r *ghttp.Request){ glog.Println(ghttp.HOOK_BEFORE_SERVE) },
ghttp.HOOK_AFTER_SERVE : func(r *ghttp.Request){ glog.Println(ghttp.HOOK_AFTER_SERVE) },
ghttp.HOOK_BEFORE_OUTPUT : func(r *ghttp.Request){ glog.Println(ghttp.HOOK_BEFORE_OUTPUT) },
ghttp.HOOK_AFTER_OUTPUT : func(r *ghttp.Request){ glog.Println(ghttp.HOOK_AFTER_OUTPUT) },
})
s.BindHandler(p, func(r *ghttp.Request) {
r.Response.Write("用户:", r.Get("name"), ", uid:", r.Get("uid"))
})
s.SetPort(8199)
s.Run()
}
相同事件注册
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
// 优先调用的HOOK
func beforeServeHook1(r *ghttp.Request) {
r.SetParam("name", "GoFrame")
r.Response.Writeln("set name")
}
// 随后调用的HOOK
func beforeServeHook2(r *ghttp.Request) {
r.SetParam("site", "https://goframe.org")
r.Response.Writeln("set site")
}
// 允许对同一个路由同一个事件注册多个回调函数,按照注册顺序进行优先级调用。
// 为便于在路由表中对比查看优先级,这里讲HOOK回调函数单独定义为了两个函数。
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Writeln(r.Get("name"))
r.Response.Writeln(r.Get("site"))
})
s.BindHookHandler("/", ghttp.HOOK_BEFORE_SERVE, beforeServeHook1)
s.BindHookHandler("/", ghttp.HOOK_BEFORE_SERVE, beforeServeHook2)
s.SetPort(8199)
s.Run()
}
改变业务逻辑
package main
import (
"fmt"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
// 多事件回调示例,事件1
pattern1 := "/:name/info"
s.BindHookHandlerByMap(pattern1, map[string]ghttp.HandlerFunc{
ghttp.HOOK_BEFORE_SERVE: func(r *ghttp.Request) {
r.SetParam("uid", 1000)
},
})
s.BindHandler(pattern1, func(r *ghttp.Request) {
r.Response.Write("用户:", r.Get("name"), ", uid:", r.Get("uid"))
})
// 多事件回调示例,事件2
pattern2 := "/{object}/list/{page}.java"
s.BindHookHandlerByMap(pattern2, map[string]ghttp.HandlerFunc{
ghttp.HOOK_BEFORE_OUTPUT: func(r *ghttp.Request) {
r.Response.SetBuffer([]byte(
fmt.Sprintf("通过事件修改输出内容, object:%s, page:%s", r.Get("object"), r.GetRouterString("page"))),
)
},
})
s.BindHandler(pattern2, func(r *ghttp.Request) {
r.Response.Write(r.Router.Uri)
})
s.SetPort(8199)
s.Run()
}
允许跨域请求
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func Order(r *ghttp.Request) {
r.Response.Write("GET")
}
func main() {
s := g.Server()
s.Group("/api.v1", func(group *ghttp.RouterGroup) {
group.Hook("/*any", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) {
r.Response.CORSDefault()
})
group.GET("/order", Order)
})
s.SetPort(8199)
s.Run()
}
WebSocket服务
HTML5客户端
<!DOCTYPE html>
<html>
<head>
<title>gf websocket echo server</title>
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
</head>
<body>
<div class="container">
<div class="list-group" id="divShow"></div>
<div>
<div><input class="form-control" id="txtContent" autofocus rows="6" placeholder="请输入发送内容"></div>
<div><button class="btn btn-default" id="btnSend" style="margin-top:15px">发 送</button></div>
</div>
</div>
</body>
</html>
<script type="application/javascript">
// 显示提示信息
function showInfo(content) {
$("<div class=\"list-group-item list-group-item-info\">" + content + "</div>").appendTo("#divShow")
}
// 显示警告信息
function showWaring(content) {
$("<div class=\"list-group-item list-group-item-warning\">" + content + "</div>").appendTo("#divShow")
}
// 显示成功信息
function showSuccess(content) {
$("<div class=\"list-group-item list-group-item-success\">" + content + "</div>").appendTo("#divShow")
}
// 显示错误信息
function showError(content) {
$("<div class=\"list-group-item list-group-item-danger\">" + content + "</div>").appendTo("#divShow")
}
$(function () {
var url = "ws://127.0.0.1:8199/ws";
var ws = new WebSocket(url);
try {
// ws连接成功
ws.onopen = function () {
showInfo("WebSocket Server [" + url +"] 连接成功!");
};
// ws连接关闭
ws.onclose = function () {
if (ws) {
ws.close();
ws = null;
}
showError("WebSocket Server [" + url +"] 连接关闭!");
};
// ws连接错误
ws.onerror = function () {
if (ws) {
ws.close();
ws = null;
}
showError("WebSocket Server [" + url +"] 连接关闭!");
};
// ws数据返回处理
ws.onmessage = function (result) {
showWaring(" > " + result.data);
};
} catch (e) {
alert(e.message);
}
// 按钮点击发送数据
$("#btnSend").on("click", function () {
if (ws == null) {
showError("WebSocket Server [" + url +"] 连接失败,请F5刷新页面!");
return;
}
var content = $.trim($("#txtContent").val()).replace("/[\n]/g", "");
if (content.length <= 0) {
alert("请输入发送内容!");
return;
}
$("#txtContent").val("")
showSuccess(content);
ws.send(content);
});
// 回车按钮触发发送点击事件
$("#txtContent").on("keydown", function (event) {
if (event.keyCode == 13) {
$("#btnSend").trigger("click");
}
});
})
</script>
WebSocket服务端
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/os/glog"
)
func main() {
s := g.Server()
s.BindHandler("/ws", func(r *ghttp.Request) {
ws, err := r.WebSocket()
if err != nil {
glog.Error(err)
r.Exit()
}
for {
msgType, msg, err := ws.ReadMessage()
if err != nil {
return
}
if err = ws.WriteMessage(msgType, msg); err != nil {
return
}
}
})
s.SetServerRoot(gfile.MainPkgPath())
s.SetPort(8199)
s.Run()
}
自定义状态码处理
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request){
r.Response.Writeln("halo 世界!")
})
s.BindStatusHandler(404, func(r *ghttp.Request){
r.Response.Writeln("This is customized 404 page")
})
s.SetPort(8199)
s.Run()
}
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/status/:status", func(r *ghttp.Request) {
r.Response.Write("woops, status ", r.Get("status"), " found")
})
s.BindStatusHandler(404, func(r *ghttp.Request){
r.Response.RedirectTo("/status/404")
})
s.SetPort(8199)
s.Run()
}
网络服务开发
TCP组件
import “github.com/gogf/gf/net/gtcp”
简单的echo服务器来演示TCPServer的使用
package main
import (
"fmt"
"github.com/gogf/gf/net/gtcp"
)
func main() {
gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) {
defer conn.Close()
for {
data, err := conn.Recv(-1) //Recv方法会通过阻塞方式接收数据
if len(data) > 0 {
if err := conn.Send(append([]byte("> "), data...)); err != nil {
fmt.Println(err)
}
}
if err != nil {
break
}
}
}).Run()
}
TCP组件-连接对象
import “github.com/gogf/gf/net/gtcp”
示例1,简单使用
package main
import (
"fmt"
"time"
"github.com/gogf/gf/net/gtcp"
"github.com/gogf/gf/os/glog"
"github.com/gogf/gf/util/gconv"
)
func main() {
// Server
go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) {
defer conn.Close()
for {
data, err := conn.Recv(-1)
if len(data) > 0 {
fmt.Println(string(data))
}
if err != nil {
break
}
}
}).Run()
time.Sleep(time.Second)
// Client
conn, err := gtcp.NewConn("127.0.0.1:8999")
if err != nil {
panic(err)
}
for i := 0; i < 10000; i++ {
if err := conn.Send([]byte(gconv.String(i))); err != nil {
glog.Error(err)
}
time.Sleep(time.Second)
}
}
示例2,回显服务
package main
import (
"fmt"
"time"
"github.com/gogf/gf/net/gtcp"
"github.com/gogf/gf/os/glog"
"github.com/gogf/gf/os/gtime"
)
func main() {
// Server
go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) {
defer conn.Close()
for {
data, err := conn.Recv(-1)
if len(data) > 0 {
if err := conn.Send(append([]byte("> "), data...)); err != nil {
fmt.Println(err)
}
}
if err != nil {
break
}
}
}).Run()
time.Sleep(time.Second)
// Client
for {
if conn, err := gtcp.NewConn("127.0.0.1:8999"); err == nil {
if b, err := conn.SendRecv([]byte(gtime.Datetime()), -1); err == nil {
fmt.Println(string(b), conn.LocalAddr(), conn.RemoteAddr())
} else {
fmt.Println(err)
}
conn.Close()
} else {
glog.Error(err)
}
time.Sleep(time.Second)
}
}
示例3,HTTP客户端
package main
import (
"fmt"
"bytes"
"github.com/gogf/gf/net/gtcp"
"github.com/gogf/gf/util/gconv"
)
func main() {
conn, err := gtcp.NewConn("www.baidu.com:80")
if err != nil {
panic(err)
}
defer conn.Close()
if err := conn.Send([]byte("GET / HTTP/1.1\n\n")); err != nil {
panic(err)
}
header := make([]byte, 0)
content := make([]byte, 0)
contentLength := 0
for {
data, err := conn.RecvLine()
// header读取,解析文本长度
if len(data) > 0 {
array := bytes.Split(data, []byte(": "))
// 获得页面内容长度
if contentLength == 0 && len(array) == 2 && bytes.EqualFold([]byte("Content-Length"), array[0]) {
contentLength = gconv.Int(array[1])
}
header = append(header, data...)
header = append(header, '\n')
}
// header读取完毕,读取文本内容
if contentLength > 0 && len(data) == 0 {
content, _ = conn.Recv(contentLength)
break
}
if err != nil {
fmt.Errorf("ERROR: %s\n", err.Error())
break
}
}
if len(header) > 0 {
fmt.Println(string(header))
}
if len(content) > 0 {
fmt.Println(string(content))
}
}
核心组件
调试模式
- 命令行启动参数 - gf.debug=true
- 指定的环境变量 - GF_DEBUG=true
- 程序启动boot包中使用g.SetDebug方法手动打开/关闭
配置管理
GF的配置管理由gcfg模块实现,gcfg模块是并发安全的,仅提供配置文件读取功能,不提供数据写入/修改功能,支持的数据文件格式包括: JSON、XML、YAML/YML、TOML、INI。当配置文件在外部被修改后,配置管理器能够即时地刷新配置文件的缓存内容。
import “github.com/gogf/gf/os/gcfg”
默认读取的配置文件为config.toml
// 设置默认配置文件,默认的 config.toml 将会被覆盖
g.Cfg().SetFileName("config.json")
// 后续读取时将会读取到 config.json 配置文件内容,
g.Cfg().Get("database")
默认文件修改
通过单例模式
g.Cfg().SetFileName(“config.prod.toml”)
通过命令行启动参数
./main –gf.gcfg.file=config.prod.toml
通过环境变量(常用在容器中)
启动时修改环境变量: GF_GCFG_FILE=config.prod.toml; ./main
使用genv模块来修改环境变量:genv.Set(“GF_GCFG_FILE”, “config.prod.toml”)
配置读取
cfg包最大的特点是支持按层级获取配置数据,层级访问默认通过英文”.“号指定
// /home/www/templates/
g.Cfg().Get("viewpath")
// 127.0.0.1:6379,1
g.Cfg().Get("redis.cache")
// test2
g.Cfg().Get("database.default.1.name")
配置修改
func (c *Config) Set(pattern string, value interface{}) error
配置管理-TOML格式
跳转到这里学习
资源管理
指可以将任意文件/目录打包为Golang源码文件,并且编译到可执行文件中,随着可执行文件发布。
import “github.com/gogf/gf/os/gres”
资源管理-工具打包
gf pack -h
将项目的config,public,template三个目录的文件打包到Go文件,打包命令为:
gf pack config,public,template packed/data.go -n packed
使用打包的Go文件
1) 在boot包中优先引入packed资源包
import (
_ "my-app/packed"
// 其他包
)
2) 在main包中优先引入boot包
import (
_ "my-app/boot"
// 其他包
)
打印资源管理文件列表
以通过gres.Dump()方法打印出当前资源管理器中所有的文件列表
资源管理-方法操作
自定义打包示例
项目根目录下的public和config目录打包为data.bin二进制文件,并通过gaes加密算法对生成的二进制内容进行加密。
package main
import (
"github.com/gogf/gf/crypto/gaes"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/os/gres"
)
var (
CryptoKey = []byte("x76cgqt36i9c863bzmotuf8626dxiwu0")
)
func main() {
binContent, err := gres.Pack("public,config")
if err != nil {
panic(err)
}
binContent, err = gaes.Encrypt(binContent, CryptoKey)
if err != nil {
panic(err)
}
if err := gfile.PutBytes("data.bin", binContent); err != nil {
panic(err)
}
}
自定义解包示例
package main
import (
"github.com/gogf/gf/crypto/gaes"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/os/gres"
)
var (
CryptoKey = []byte("x76cgqt36i9c863bzmotuf8626dxiwu0")
)
func main() {
binContent := gfile.GetBytes("data.bin")
binContent, err := gaes.Decrypt(binContent, CryptoKey)
if err != nil {
panic(err)
}
if err := gres.Add(binContent); err != nil {
panic(err)
}
gres.Dump()
}
资源管理-使用示例
资源文件源码:https://github.com/gogf/gf/tree/master/os/gres/testdata/example/files
资源文件打包:https://github.com/gogf/gf/tree/master/os/gres/testdata/example/boot
日志组件
import “github.com/gogf/gf/os/glog”
日志组件-配置管理
配置文件
[logger]
path = "/var/log/" # 日志文件路径。默认为空,表示关闭,仅输出到终端
file = "{Y-m-d}.log" # 日志文件格式。默认为"{Y-m-d}.log"
prefix = "" # 日志内容输出前缀。默认为空 level = "all" # 日志输出级别
ctxKeys = [] # Context上下文变量名称,自动打印Context的变量到日志中。默认为空
headerPrint = true # 是否打印日志的头信息。默认true
stdoutPrint = true # 日志是否同时输出到终端。默认true
rotateSize = 0 # 按照日志文件大小对文件进行滚动切分。默认为0,表示关闭滚动切分特性
rotateExpire = 0 # 按照日志文件时间间隔对文件滚动切分。默认为0,表示关闭滚动切分特性
rotateBackupLimit = 0 # 按照切分的文件数量清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除
rotateBackupExpire = 0 # 按照切分的文件有效期清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除
rotateBackupCompress = 0 # 滚动切分文件的压缩比(0-9)。默认为0,表示不压缩
rotateCheckInterval = "1h" # 滚动切分的时间检测间隔,一般不需要设置。默认为1小时
默认配置
[logger]
path = "/var/log"
level = "all" //DEBU < INFO < NOTI < WARN < ERRO < CRIT,也支持ALL, DEV, PROD常见部署模式配置名称
stdout = false
多个配置项
[logger]
path = "/var/log"
level = "all"
stdout = false
[logger.logger1]
path = "/var/log/logger1"
level = "dev"
stdout = false
[logger.logger2]
path = "/var/log/logger2"
level = "prod"
stdout = true
可以通过单例对象名称获取对应配置的Logger单例对象:
// 对应 logger.logger1 配置项
l1 := g.Log("logger1")
// 对应 logger.logger2 配置项
l2 := g.Log("logger2")
// 对应默认配置项 logger
l3 := g.Log("none")
// 对应默认配置项 logger
l4 := g.Log()
日志组件-异步输出
package main
import (
"github.com/gogf/gf/os/glog"
"time"
)
func main() {
glog.SetAsync(true)
for i := 0; i < 10; i++ {
glog.Async().Print("async log", i)
}
time.Sleep(time.Second) //避免日志未输出
}
Async链式操作
package main
import (
"github.com/gogf/gf/os/glog"
"time"
)
func main() {
for i := 0; i < 10; i++ {
glog.Async().Print("async log", i)
}
time.Sleep(time.Second)
}
日志组件-Json格式
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/os/glog"
)
func main() {
glog.Debug(g.Map{"uid" : 100, "name" : "john"})
type User struct {
Uid int `json:"uid"`
Name string `json:"name"`
}
glog.Debug(User{100, "john"})
}
类型转换
import “github.com/gogf/gf/util/gconv”
package main
import (
"fmt"
"github.com/gogf/gf/util/gconv"
)
func main() {
i := 123.456
fmt.Printf("%10s %v\n", "Int:", gconv.Int(i))
fmt.Printf("%10s %v\n", "Int8:", gconv.Int8(i))
fmt.Printf("%10s %v\n", "Int16:", gconv.Int16(i))
fmt.Printf("%10s %v\n", "Int32:", gconv.Int32(i))
fmt.Printf("%10s %v\n", "Int64:", gconv.Int64(i))
fmt.Printf("%10s %v\n", "Uint:", gconv.Uint(i))
fmt.Printf("%10s %v\n", "Uint8:", gconv.Uint8(i))
fmt.Printf("%10s %v\n", "Uint16:", gconv.Uint16(i))
fmt.Printf("%10s %v\n", "Uint32:", gconv.Uint32(i))
fmt.Printf("%10s %v\n", "Uint64:", gconv.Uint64(i))
fmt.Printf("%10s %v\n", "Float32:", gconv.Float32(i))
fmt.Printf("%10s %v\n", "Float64:", gconv.Float64(i))
fmt.Printf("%10s %v\n", "Bool:", gconv.Bool(i))
fmt.Printf("%10s %v\n", "String:", gconv.String(i))
fmt.Printf("%10s %v\n", "Bytes:", gconv.Bytes(i))
fmt.Printf("%10s %v\n", "Strings:", gconv.Strings(i))
fmt.Printf("%10s %v\n", "Ints:", gconv.Ints(i))
fmt.Printf("%10s %v\n", "Floats:", gconv.Floats(i))
fmt.Printf("%10s %v\n", "Interfaces:", gconv.Interfaces(i))
}
Map转换
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gconv"
)
func main() {
type User struct {
Uid int `c:"uid"`
Name string `c:"name"`
}
// 对象
g.Dump(gconv.Map(User{
Uid: 1,
Name: "john",
}))
// 对象指针
g.Dump(gconv.Map(&User{
Uid: 1,
Name: "john",
}))
// 任意map类型
g.Dump(gconv.Map(map[int]int{
100: 10000,
}))
}
属性标签
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gconv"
)
func main() {
type User struct {
Uid int
Name string `c:"-"`
NickName string `c:"nickname, omitempty"`
Pass1 string `c:"password1"`
Pass2 string `c:"password2"`
}
user := User{
Uid: 100,
Name: "john",
Pass1: "123",
Pass2: "456",
}
g.Dump(gconv.Map(user))
}
自定义标签
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gconv"
)
func main() {
type User struct {
Id int `c:"uid"`
Name string `my-tag:"nick-name" c:"name"`
}
user := &User{
Id: 1,
Name: "john",
}
g.Dump(gconv.Map(user, "my-tag"))
}
Struct转换
自动创建对象
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gconv"
)
func main() {
type User struct {
Uid int
Name string
}
params := g.Map{
"uid": 1,
"name": "john",
}
var user *User
if err := gconv.Struct(params, &user); err != nil {
panic(err)
}
g.Dump(user)
}
数据校验
数据校验-校验规则
内置了40种常用的校验规则
import “github.com/gogf/gf/util/gvalid”
required 格式:required 说明:必需参数
required-if 格式:required-if:field,value,... 说明:必需参数(当任意所给定字段值与所给值相等时,即:当field字段的值为value时,当前验证字段为必须参数)
required-unless 格式:required-unless:field,value,... 说明:必需参数(当所给定字段值与所给值都不相等时,即:当field字段的值不为value时,当前验证字段为必须参数)
required-with 格式:required-with:field1,field2,... 说明:必需参数(当所给定任意字段值不为空时)
required-with-all 格式:required-with-all:field1,field2,... 说明:必须参数(当所给定所有字段值都不为空时)
required-without 格式:required-without:field1,field2,... 说明:必需参数(当所给定任意字段值为空时)
required-without-all 格式:required-without-all:field1,field2,...说明:必须参数(当所给定所有字段值都为空时)
date 格式:date 说明:参数为常用日期类型,格式:2006-01-02, 20060102, 2006.01.02
date-format 格式:date-format:format 说明:判断日期是否为指定的日期格式,format为Go日期格式(可以包含时间)
email 格式:email 说明:EMAIL邮箱地址
phone 格式:phone 说明:手机号
telephone 格式:telephone 说明:国内座机电话号码,"XXXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"、"XXXXXXXX"
passport 格式:passport 说明:通用帐号规则(字母开头,只能包含字母、数字和下划线,长度在6~18之间)
password 格式:password 说明:通用密码(任意可见字符,长度在6~18之间)
password2 格式:password2 说明:中等强度密码(在弱密码的基础上,必须包含大小写字母和数字)
password3 格式:password3 说明:强等强度密码(在弱密码的基础上,必须包含大小写字母、数字和特殊字符)
postcode 格式:postcode 说明:中国邮政编码
resident-id 格式:resident-id 说明:公民身份证号码
bank-card 格式:bank-card 说明:银行号验证
qq 格式:qq 说明:腾讯QQ号码
ip 格式:ip 说明:IPv4/IPv6地址
ipv4 格式:ipv4 说明:IPv4地址
ipv6 格式:ipv6 说明:IPv6地址
mac 格式:mac 说明:MAC地址
url 格式:url 说明:URL
domain 格式:domain 说明:域名
length 格式:length:min,max 说明:参数长度为min到max(长度参数为整形),注意中文一个汉字占3字节
min-length 格式:min-length:min 说明:参数长度最小为min(长度参数为整形),注意中文一个汉字占3字节
max-length 格式:max-length:max 说明:参数长度最大为max(长度参数为整形),注意中文一个汉字占3字节
between 格式:between:min,max 说明:参数大小为min到max(支持整形和浮点类型参数)
min 格式:min:min 说明:参数最小为min(支持整形和浮点类型参数)
max 格式:max:max 说明:参数最大为max(支持整形和浮点类型参数)
json 格式:json 说明:判断数据格式为JSON
integer 格式:integer 说明:整数
float 格式:float 说明:浮点数
boolean 格式:boolean 说明:布尔值(1,true,on,yes:true | 0,false,off,no,"":false)
same 格式:same:field 说明:参数值必需与field参数的值相同
different 格式:different:field 说明:参数值不能与field参数的值相同
in 格式:in:value1,value2,... 说明:参数值应该在value1,value2,...中(字符串匹配)
not-in 格式:not-in:value1,value2,... 说明:参数值不应该在value1,value2,...中(字符串匹配)
regex 格式:regex:pattern 说明:参数值应当满足正则匹配规则pattern
数据校验-单数据校验
示例1,校验数据长度,使用默认的错误提示
rule := "length:6,16"
if e := gvalid.Check("123456", rule, nil); e != nil {
fmt.Println(e.String())
}
if e := gvalid.Check("12345", rule, nil); e != nil {
fmt.Println(e.String())
}
// 输出: 字段长度为6到16个字符
示例2,校验数据类型及大小,并且使用自定义的错误提示
rule := "integer|between:6,16"
msgs := "请输入一个整数|参数大小不对啊老铁"
if e := gvalid.Check(5.66, rule, msgs); e != nil {
fmt.Println(e.Map())
}
// 输出: map[integer:请输入一个整数 between:参数大小不对啊老铁]
rule := "url|min-length:11"
msgs := map[string]string{
"url" : "请输入正确的URL地址",
"min-length" : "地址长度至少为:min位"
}
if e := gvalid.Check("https://goframeorg", rule, msgs); e != nil {
fmt.Println(e.Map())
}
// 输出: map[url:请输入正确的URL地址]
示例3,使用自定义正则校验数据格式,使用默认错误提示
// 参数长度至少为6个数字或者6个字母,但是总长度不能超过16个字符
rule := `regex:\d{6,}|\D{6,}|max-length:16`
if e := gvalid.Check("123456", rule, nil); e != nil {
fmt.Println(e.Map())
}
if e := gvalid.Check("abcde6", rule, nil); e != nil {
fmt.Println(e.Map())
}
// 输出: map[regex:字段值不合法]
数据校验-多数据校验
示例1,默认错误提示
params := map[string]interface{} {
"passport" : "",
"password" : "123456",
"password2" : "1234567",
}
rules := map[string]string {
"passport" : "required|length:6,16",
"password" : "required|length:6,16|same:password2",
"password2" : "required|length:6,16",
}
if e := gvalid.CheckMap(params, rules); e != nil {
fmt.Println(e.Maps())
}
// 输出: map[passport:map[required:字段不能为空 length:字段长度为6到16个字符] password:map[same:字段值不合法]]
示例2,自定义错误提示
params := map[string]interface{} {
"passport" : "",
"password" : "123456",
"password2" : "1234567",
}
rules := map[string]string {
"passport" : "required|length:6,16",
"password" : "required|length:6,16|same:password2",
"password2" : "required|length:6,16",
}
msgs := map[string]interface{} {
"passport" : "账号不能为空|账号长度应当在:min到:max之间",
"password" : map[string]string {
"required" : "密码不能为空",
"same" : "两次密码输入不相等",
},
}
if e := gvalid.CheckMap(params, rules, msgs); e != nil {
fmt.Println(e.String())
}
// 输出:账号不能为空; 账号长度应当在6到16之间; 两次密码输入不相等
缓存管理
import “github.com/gogf/gf/os/gcache”
缓存管理-内存缓存
示例1,基本使用
package main
import (
"fmt"
"github.com/gogf/gf/os/gcache"
)
func main() {
// 创建一个缓存对象,
// 当然也可以便捷地直接使用gcache包方法
c := gcache.New()
// 设置缓存,不过期
c.Set("k1", "v1", 0)
// 获取缓存
v, _ := c.Get("k1")
fmt.Println(v)
// 获取缓存大小
n, _ := c.Size()
fmt.Println(n)
// 缓存中是否存在指定键名
b, _ := c.Contains("k1")
fmt.Println(b)
// 删除并返回被删除的键值
fmt.Println(c.Remove("k1"))
// 关闭缓存对象,让GC回收资源
c.Close()
}
示例2,缓存控制
package main
import (
"fmt"
"github.com/gogf/gf/os/gcache"
"time"
)
func main() {
// 当键名不存在时写入,设置过期时间1000毫秒
gcache.SetIfNotExist("k1", "v1", 1000*time.Millisecond)
// 打印当前的键名列表
keys, _ := gcache.Keys()
fmt.Println(keys)
// 打印当前的键值列表
values, _ := gcache.Values()
fmt.Println(values)
// 获取指定键值,如果不存在时写入,并返回键值
v, _ := gcache.GetOrSet("k2", "v2", 0)
fmt.Println(v)
// 打印当前的键值对
data1, _ := gcache.Data()
fmt.Println(data1)
// 等待1秒,以便k1:v1自动过期
time.Sleep(time.Second)
// 再次打印当前的键值对,发现k1:v1已经过期,只剩下k2:v2
data2, _ := gcache.Data()
fmt.Println(data2)
}
示例3,GetOrSetFunc*
// 根据关键字进行markdown文档搜索,返回文档path列表
func SearchMdByKey(key string) ([]string, error) {
v, err := gcache.GetOrSetFunc("doc_search_result_"+key, func() (interface{}, error) {
// 当该key的检索缓存不存在时,执行检索
array := garray.NewStrArray()
docPath := g.Cfg().GetString("doc.path")
paths, err := gcache.GetOrSetFunc("doc_files_recursive", func() (interface{}, error) {
// 当目录列表不存在时,执行检索
return gfile.ScanDir(docPath, "*.md", true)
}, 0)
if err != nil {
return nil, err
}
// 遍历markdown文件列表,执行字符串搜索
for _, path := range gconv.Strings(paths) {
content := gfile.GetContents(path)
if len(content) > 0 {
if strings.Index(content, key) != -1 {
index := gstr.Replace(path, ".md", "")
index = gstr.Replace(index, docPath, "")
array.Append(index)
}
}
}
return array.Slice(), nil
}, 0)
if err != nil {
return nil, err
}
return gconv.Strings(v), nil
}
示例4,LRU缓存淘汰控制
package main
import (
"github.com/gogf/gf/os/gcache"
"time"
"fmt"
)
func main() {
// 设置LRU淘汰数量
c := gcache.New(2)
// 添加10个元素,不过期
for i := 0; i < 10; i++ {
c.Set(i, i, 0)
}
n, _ := c.Size()
fmt.Println(n)
keys, _ := c.Keys()
fmt.Println(keys)
// 读取键名1,保证该键名是优先保留
v, _ := c.Get(1)
fmt.Println(v)
// 等待一定时间后(默认1秒检查一次),
// 元素会被按照从旧到新的顺序进行淘汰
time.Sleep(2*time.Second)
n, _ = c.Size()
fmt.Println(n)
keys, _ = c.Keys()
fmt.Println(keys)
}
模板引擎
示例1,解析模板文件
index.tpl
id:{{.id}}, name:{{.name}}
main.go
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/template", func(r *ghttp.Request) {
r.Response.WriteTpl("index.tpl", g.Map{
"id": 123,
"name": "john",
})
})
s.SetPort(8199)
s.Run()
}
示例2,解析模板内容
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/template", func(r *ghttp.Request){
tplContent := `id:{{.id}}, name:{{.name}}`
r.Response.WriteTplContent(tplContent, g.Map{
"id" : 123,
"name" : "john",
})
})
s.SetPort(8199)
s.Run()
}
示例3,自定义模板分隔符
// main.go
package main
import (
"fmt"
"github.com/gogf/gf/frame/g"
)
func main() {
v := g.View()
v.SetDelimiters("${", "}") //这里定义新的
b, err := v.Parse("gview_delimiters.tpl", map[string]interface{} {
"k" : "v",
})
fmt.Println(err)
fmt.Println(string(b))
}
修改模板目录
- 单例模式获取全局View对象,通过SetPath方法手动修改
- 修改命令行启动参数 - gf.gview.path
- 修改指定的环境变量 - GF_GVIEW_PATH
模板引擎-模板配置
[viewer]
Paths = ["/var/www/template"] # 模板文件搜索目录路径,建议使用绝对路径。默认为当前程序工作路径
DefaultFile = "index.html" # 默认解析的模板引擎文件。默认为"index.html"
Delimiters = ["${", "}"] # 模板引擎变量分隔符号。默认为 ["{{", "}}"]
AutoEncode = false # 是否默认对变量内容进行XSS编码。默认为false
[viewer.Data] # 自定义的全局Key-Value键值对,将在模板解析中可被直接使用到
Key1 = "Value1"
Key2 = "Value2"
随后可以使用g.View()获取默认的单例对象时自动获取并设置该配置。
多个配置项
[viewer]
paths = ["template", "/var/www/template"]
defaultFile = "index.html"
delimiters = ["${", "}"]
[viewer.data]
name = "gf"
version = "1.10.0"
[viewer.view1]
defaultFile = "layout.html"
delimiters = ["${", "}"]
[viewer.view2]
defaultFile = "main.html"
delimiters = ["#{", "}"]
我们可以通过单例对象名称获取对应配置的View单例对象:
// 对应 viewer.view1 配置项
v1 := g.View("view1")
// 对应 viewer.view2 配置项
v2 := g.View("view2")
// 对应默认配置项 viewer
v3 := g.View("none")
// 对应默认配置项 viewer
v4 := g.View()
模板引擎-模板标签
使用 . 来访问当前对象的值(模板局部变量)。
使用 $ 来引用当前模板根级的上下文(模板全局变量)。
使用 $var 来访问特定的模板变量。
模板中支持的 go 语言符号
{{"string"}} // 一般 string
{{`raw string`}} // 原始 string
{{'c'}} // byte
{{print nil}} // nil 也被支持
模板中的 pipeline
{{. | FuncA | FuncB | FuncC}}
if … else … end
range … end
with … end
define 自定义模板内容块(给一段模板内容定义一个模板名称),可用于模块定义和模板嵌套
template
include 在模板中可以使用include标签载入其他模板
与template标签的区别是:include仅支持文件路径,不支持模板名称;而tempalte标签仅支持模板名称,不支持文件路径。
模板引擎-模板函数
and and会逐一判断每个参数,将返回第一个为空的参数,否则就返回最后一个非空参数
{{and .X .Y .Z}}
call call可以调用函数,并传入参数
{{call .Field.Func .Arg1 .Arg2}}
index index支持map, slice, array, string,读取指定类型对应下标的值。
len 返回对应类型的长度
not 返回输入参数的否定值
or
printf
println
urlquery 字符串网址化
eq / ne / lt / le / gt / ge
text 将value变量值去掉HTML标签,仅显示文字内容(并且去掉script标签)
{{.value | text}}
htmlencode/encode/html 将value变量值进行html转义
htmldecode/decode 将value变量值进行html反转义
urlencode/url 将url变量值进行url转义
urldecode 将url变量值进行url反转义
date 将timestamp时间戳变量进行时间日期格式化
{{.timestamp | date .format}}
{{date .format .timestamp}}
{{date .format}}
compare 将str1和str2进行字符串比较
replace 将str中的search替换为replace
substr 将str从start索引位置(索引从0开始)进行字符串截取length,支持中文
strlimit 将str字符串截取length长度,支持中文
concat 拼接字符串
hidestr 将str字符串按照percent百分比从字符串中间向两边隐藏字符
{{"热爱GF热爱生活" | hidestr 20 "*"}}
highlight 将str字符串中的关键字key按照定义的颜色color进行前置颜色高亮
toupper/tolower 将str字符串进行大小写转换。
nl2br 将str字符串中的\n/\r替换为html中的标签
dump 格式化打印变量,功能类似于g.Dump方法
map 将模板变量转换为map[string]interface{}类型,常用于range…end遍历。
maps 将模板变量转换为[]map[string]interface{}类型,常用于range…end遍历。
json 将模板变量转换为JSON字符串,常用于将模板变量内容嵌入到Javacript代码中。
模板函数-自定义函数
package main
import (
"fmt"
"github.com/gogf/gf/frame/g"
)
// 用于测试的带参数的内置函数
func funcHello(name string) string {
return fmt.Sprintf(`Hello %s`, name)
}
func main() {
// 绑定全局的模板函数
g.View().BindFunc("hello", funcHello)
// 普通方式传参
parsed1, err := g.View().ParseContent(`{{hello "GoFrame"}}`, nil)
if err != nil {
panic(err)
}
fmt.Println(string(parsed1))
// 通过管道传参
parsed2, err := g.View().ParseContent(`{{"GoFrame" | hello}}`, nil)
if err != nil {
panic(err)
}
fmt.Println(string(parsed2))
}
模板引擎-模板变量
变量对象
我们可以在模板中使用自定义的对象,并可在模板中访问对象的属性及调用其方法。
package main
import (
"github.com/gogf/gf/frame/g"
)
type T struct {
Name string
}
func (t *T) Hello(name string) string {
return "Hello " + name
}
func (t *T) Test() string {
return "This is test"
}
func main() {
t := &T{"John"}
v := g.View()
content := `{{.t.Hello "there"}}, my name's {{.t.Name}}. {{.t.Test}}.`
if r, err := v.ParseContent(content, g.Map{"t" : t}); err != nil {
g.Dump(err)
} else {
g.Dump(r)
}
}
模板引擎-模板布局
gview模板引擎支持两种layout模板布局方式:
define + template方式
include模板嵌入方式
define + template方式
layout.html
<!DOCTYPE html>
<html>
<head>
<title>GoFrame Layout</title>
{{template "header" .}}
</head>
<body>
<div class="container">
{{template "container" .}}
</div>
<div class="footer">
{{template "footer" .}}
</div>
</body>
</html>
header.html
{{define "header"}}
<h1>{{.header}}</h1>
{{end}}
container.html
{{define "container"}}
<h1>{{.container}}</h1>
{{end}}
footer.html
{{define "footer"}}
<h1>{{.footer}}</h1>
{{end}}
include模板嵌入方式
layout.html
{{include "header.html" .}}
{{include .mainTpl .}}
{{include "footer.html" .}}
header.html
<h1>HEADER</h1>
footer.html
<h1>FOOTER</h1>
main1.html
<h1>MAIN1</h1>
main2.html
<h1>MAIN2</h1>
模板引擎-XSS处理
配置文件
[viewer] autoencode = true
模板引擎-I18N变量
package main
import (
"fmt"
"github.com/gogf/gf/frame/g"
)
func main() {
content := `{{.name}} says "a{#hello}{#world}!"`
result1, _ := g.View().ParseContent(content, g.Map{
"name": "john",
"I18nLanguage": "zh-CN",
})
fmt.Println(result1)
result2, _ := g.View().ParseContent(content, g.Map{
"name": "john",
"I18nLanguage": "ja",
})
fmt.Println(result2)
}