由于项目的需要,自己开始了解GoFrame这个框架,网上没有视频学习,所以开始看官网学习,在学习的时候,感觉GoFrame真的是一个不错的框架,整理自己的笔记。大部分和github上的一样,但是本文进行整合,方便大家的学习。
Go(又称 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种跨平台(Mac OS、Windows、Linux 等)静态强类型、编译型语言。由Ken Thompson(肯·汤普森)联合创立,Unix操作系统的发明人之一(排在第一号)。
golang基础教程-快速入门go语言
github:https://github.com/goflyfox/gostudy
gitee:https://gitee.com/flyfox/gostudy
GF(Go Frame)是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设以及开发工具链,提供了常用的基础开发模块,如:缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等。并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、Middleware、服务注册、模板引擎等等,支持热重启、热更新、域名绑定、TLS/HTTPS、Rewrite等特性。
目录结构及基本介绍:
GF
├── container -- 基础类型:数组,通道,列表,map,队列,环,set,树,类型处理和转换
│ ├── garray
│ ├── gchan
│ ├── glist
│ ├── gmap
│ ├── gpool
│ ├── gqueue
│ ├── gring
│ ├── gset
│ ├── gtree
│ ├── gtype
│ └── gvar
├── crypto -- 加密和解密:常用的md5,aes,3des
│ ├── gaes
│ ├── gcrc32
│ ├── gdes
│ ├── gmd5
│ └── gsha1
├── database -- 数据库:关系型数据库(mysql,postgre,oracle)和redis
│ ├── gdb
│ └── gredis
├── debug -- 调试
│ └── gdebug
├── DONATOR.MD
├── encoding --编解码:常用的base64和json
│ ├── gbase64
│ ├── gbinary
│ ├── gcharset
│ ├── gcompress
│ ├── ghash
│ ├── ghtml
│ ├── gini
│ ├── gjson
│ ├── gparser
│ ├── gtoml
│ ├── gurl
│ ├── gxml
│ └── gyaml
├── errors -- 错误处理
│ └── gerror
├── frame -- 核心框架:web,mvc
│ ├── g
│ ├── gins
│ └── gmvc
├── go.mod
├── i18n -- 国际化
│ └── gi18n
├── internal 系统:空处理,锁,结构体
│ ├── cmdenv
│ ├── empty
│ ├── fileinfo
│ ├── intlog
│ ├── mutex
│ ├── rwmutex
│ ├── structs
│ └── utils
├── LICENSE
├── net -- 网络:http,tpc,udp
│ ├── ghttp
│ ├── gipv4
│ ├── gipv6
│ ├── gsmtp
│ ├── gtcp
│ └── gudp
├── os -- 系统:定时任务,命令行交互,日志,文件处理,缓存,session,时间
│ ├── gbuild
│ ├── gcache
│ ├── gcfg
│ ├── gcmd
│ ├── gcron
│ ├── genv
│ ├── gfcache
│ ├── gfile
│ ├── gfpool
│ ├── gfsnotify
│ ├── glog
│ ├── gmlock
│ ├── gmutex
│ ├── gproc
│ ├── gres
│ ├── grpool
│ ├── gsession
│ ├── gspath
│ ├── gtime
│ ├── gtimer
│ └── gview
├── README.MD
├── README_ZH.MD
├── RELEASE.1.MD
├── RELEASE.2.MD
├── test -- 单元测试
│ └── gtest
├── text -- 文本处理:正则,字符串处理
│ ├── gregex
│ └── gstr
├── TODO.MD
├── util -- 常用工具:类型转换,随机数,uuid,校验
│ ├── gconv
│ ├── gmode
│ ├── gpage
│ ├── grand
│ ├── gutil
│ ├── guuid
│ └── gvalid
└── version.go
这里仅以windows为例:(苹果用户直接在 ~/.bash_profile
或者 ~/.zshrc
(看自己电脑情况而定)中配置即可)
# 打开cmd设置
set GOPATH=D:\Project\GOPATH
set GOROOT=D:\Project\GO
set PATH=%PATH%;%GOROOT%\bin
当然应该将这些环境变量配置到系统环境变量中
go version
即可展示安装golang版本# go version
go version go1.14 windows/amd64
https://goproxy.io
https://goproxy.cn
https://mirrors.aliyun.com/goproxy/
大家想详细了解可以看我的另一篇博客:https://blog.csdn.net/weixin_51261234/article/details/123555904?spm=1001.2014.3001.5501,此中有代理的详细设置和介绍
go.mod
是Go项目的依赖描述文件:
module hello
go 1.14
require github.com/gogf/gf v1.11.7
配置完编译成功后,生成go.sum
依赖分析结果,里面会有当前所有的依赖详细信息;
通过go.mod引用goframe,构建下载,打印版本号;项目文件如下:
module hello
go 1.14
require github.com/gogf/gf v1.11.7
package main
import (
"fmt"
"github.com/gogf/gf"
)
func main() {
fmt.Println("hello world!")
// 打印GF版本
fmt.Println(gf.VERSION)
}
让我们来运行第一个web程序
module hello
go 1.14
require github.com/gogf/gf v1.11.7
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("Welcome GoFrame!")
})
s.BindHandler("/hello", func(r *ghttp.Request){
r.Response.Writeln("Hello World!")
})
s.SetPort(80)
s.Run()
}
运行然后打开浏览器,访问:http://127.0.0.1/和http://127.0.0.1/hello查看效果
GF
框架提供了非常强大的WebServer
,由ghttp
模块实现。实现了丰富完善的相关组件,例如:Router、Cookie、Session、路由注册、配置管理、模板引擎、缓存控制等等,支持热重启、热更新、多域名、多端口、多实例、HTTPS、Rewrite等等特性。
这里主要介绍基本项目启动和配置参数
web:.
│ go.mod -- go module
│ go.sum
│ main.go -- 启动文件
│
├─config
│ config.toml --配置文件
│
├─gflogs
│ 2020-03-19.log -- gf系统日志
│ access-20200319.log -- 访问日志
│ error-20200319.log -- 异常日志
│
├─logs
│ 2020-03-19.log -- 业务日志
│
└─public
hello.html -- 静态文件
index.html -- 静态入口文件
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/glog"
)
func main() {
s := g.Server()
// 测试日志
s.BindHandler("/welcome", func(r *ghttp.Request) {
glog.Info("你来了!")
glog.Error("你异常啦!")
r.Response.Write("哈喽世界!")
})
// 异常处理
s.BindHandler("/panic", func(r *ghttp.Request) {
glog.Panic("123")
})
// post请求
s.BindHandler("POST:/hello", func(r *ghttp.Request) {
r.Response.Writeln("Hello World!")
})
s.Run()
}
GF
框架的核心组件均实现了便捷的文件配置管理方式,包括Server
、日志组件、数据库ORM、模板引擎等等,非常强大便捷。
[server]
# 端口号
Address = ":8199"
# 静态目录
ServerRoot = "public"
# 入口文件
IndexFiles = ["index.html", "main.html"]
# 系统访问日志
AccessLogEnabled = true
# 系统异常日志panic
ErrorLogEnabled = true
# 系统日志目录,启动,访问,异常
LogPath = "gflogs"
[logger]
# 标准日志目录
path = "logs"
# 日志级别
level = "all"
gf
框架自建了非常强大的路由功能,提供了比任何同类框架更加出色的路由特性,支持流行的命名匹配规则、模糊匹配规则及字段匹配规则,并提供了优秀的优先级管理机制。
该方法是路由注册的最基础方法,其中的pattern
为路由注册规则字符串,在其他路由注册方法中也会使用到,参数格式如下:
[HTTPMethod:]路由规则[@域名]
其中HTTPMethod
(支持的Method:GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE
)和@域名
为非必需参数,一般来说直接给定路由规则参数即可,BindHandler
会自动绑定所有的请求方式,如果给定HTTPMethod
,那么路由规则仅会在该请求方式下有效。@域名
可以指定生效的域名名称,那么该路由规则仅会在该域名下生效。
BindHandler
是最原生的路由注册方法,在大部分场景中,我们通常使用 分组路由 方式来管理路由
示例:
// hello方法,post调用
s.BindHandler("POST:/hello", func(r *ghttp.Request) {
r.Response.Writeln("url" + r.Router.Uri)
})
回调函数注册方式是最简单且最灵活的的路由注册方式,注册的服务可以是一个实例化对象的方法地址,也可以是一个包方法地址。服务需要的数据可以通过模块内部变量形式
或者对象内部变量形式
进行管理,开发者可根据实际情况进行灵活控制。
我们可以直接通过BindHandler
方法完成回调函数的注册,在框架的开发手册中很多地方都使用了回调函数注册的方式来做演示,因为这种注册方式比较简单。
示例:
// 方法注册
s.BindHandler("/total", Total)
执行对象注册是在注册时便给定一个实例化的对象,以后每一个请求都交给该对象(同一对象)处理,该对象常驻内存不释放。服务端进程在启动时便需要初始化这些执行对象,并且这些对象需要自行负责对自身数据的并发安全维护(往往对象的成员变量应当是并发安全的,每个请求执行完毕后该对象不会销毁,其成员变量也不会释放)。
// 对象注册
c := new(Controller)
s.BindObject("POST:/object", c)
GF
框架支持分组路由的注册方式,可以给分组路由指定一个prefix
前缀(也可以直接给定/
前缀,表示注册在根路由下),在该分组下的所有路由注册都将注册在该路由前缀下。分组路由注册方式也是推荐的路由注册方式。
示例:
// 分组注册及中间件
group := s.Group("/api")
group.ALL("/all", func(r *ghttp.Request) {
r.Response.Writeln("all")
})
GF
提供了优雅的中间件请求控制方式,该方式也是主流的WebServer
提供的请求流程控制方式,基于中间件设计可以为WebServer
提供更灵活强大的插件机制。经典的中间件洋葱模型:
事例:
// 分组注册及中间件
group := s.Group("/api")
group.Middleware(MiddlewareTest)
group.ALL("/all", func(r *ghttp.Request) {
r.Response.Writeln("all")
})
请求输入依靠 ghttp.Request
对象实现,ghttp.Request
继承了底层的http.Request
对象。ghttp.Request
包含一个与当前请求对应的返回输出对象Response
,用于数据的返回处理。
可以看到Request
对象的参数获取方法非常丰富,可以分为以下几类:
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
对象指针返回。Exit*
: 用于请求流程退出控制;ghttp.Response
对象实现了标准库的http.ResponseWriter
接口。数据输出使用Write*
相关方法实现,并且数据输出采用了Buffer
机制,因此数据的处理效率比较高。任何时候可以通过OutputBuffer
方法输出缓冲区数据到客户端,并清空缓冲区数据。
简要说明:
Write*
方法用于数据的输出,可为任意的数据格式,内部通过断言对参数做自动分析。Write*Exit
方法用于数据输出后退出当前服务方法,可用于替代return
返回方法。WriteJson*
/WriteXml
方法用于特定数据格式的输出,这是为开发者提供的简便方法。WriteTpl*
方法用于模板输出,解析并输出模板文件,也可以直接解析并输出给定的模板内容。ParseTpl*
方法用于模板解析,解析模板文件或者模板内容,返回解析后的内容。package main
import (
"github.com/gogf/gf/container/gtype"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server()
// 常规注册
// hello方法,post调用
s.BindHandler("POST:/hello", func(r *ghttp.Request) {
r.Response.Writeln("url" + r.Router.Uri)
})
// 所有方法,url包含name参数
s.BindHandler("/:name", func(r *ghttp.Request) {
// 获取URL name参数
r.Response.Writeln("name:" + r.GetString("name"))
r.Response.Writeln("url" + r.Router.Uri)
})
// 所有方法,url包含name参数
s.BindHandler("/:name/update", func(r *ghttp.Request) {
r.Response.Writeln("name:" + r.GetString("name"))
r.Response.Writeln("url" + r.Router.Uri)
})
// 所有方法,url包含name和action参数
s.BindHandler("/:name/:action", func(r *ghttp.Request) {
r.Response.Writeln("name:" + r.GetString("name"))
r.Response.Writeln("action:" + r.GetString("action"))
r.Response.Writeln("url" + r.Router.Uri)
})
// 所有方法,url包含field属性
s.BindHandler("/user/list/{field}.html", func(r *ghttp.Request) {
// 获取URL field属性
r.Response.Writeln("field:" + r.GetString("field"))
r.Response.Writeln("url" + r.Router.Uri)
})
// 方法注册
s.BindHandler("/total", Total)
// 对象注册
c := new(Controller)
s.BindObject("POST:/object", c)
// 分组注册及中间件
group := s.Group("/api")
group.Middleware(MiddlewareTest)
group.ALL("/all", func(r *ghttp.Request) {
r.Response.Writeln("all")
})
group.GET("/get", func(r *ghttp.Request) {
r.Response.Writeln("get")
})
group.POST("/post", func(r *ghttp.Request) {
r.Response.Writeln("post")
})
// request and response
s.BindHandler("POST:/test", func(r *ghttp.Request) {
r.Response.WriteJson(g.Map{
"name":r.GetString("name"),
"age":r.GetInt("age"),
"sex":r.Header.Get("sex"),
})
})
s.SetPort(8199)
s.Run()
}
var (
total = gtype.NewInt()
)
func Total(r *ghttp.Request) {
r.Response.Write("total:", total.Add(1))
}
// 对象注册
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 MiddlewareTest(r *ghttp.Request) {
// 前置逻辑
r.Response.Writeln("###start")
r.Middleware.Next()
// 后置逻辑
r.Response.Writeln("###end")
}
访问结果:
### 常规注册
POST http://localhost:8199/hello
###
GET http://localhost:8199/abc
###
GET http://localhost:8199/a/add
###
GET http://localhost:8199/a/update
###
GET http://localhost:8199/user/list/11.html
### 方法注册
GET http://localhost:8199/total
### 对象注册,默认访问index
POST http://localhost:8199/object/
### 对象注册,直接访问Index
POST http://localhost:8199/object/index
### 对象注册,访问show方法
POST http://localhost:8199/object/show
### 分组,默认访问index
PUT http://localhost:8199/api/all
### 对象注册,直接访问Index
GET http://localhost:8199/api/get
### 对象注册,访问show方法
POST http://localhost:8199/api/post
### request and response
POST http://localhost:8199/test
sex:man
name=liubang&age=18
###
超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。
请求:
响应:
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("/", func(r *ghttp.Request) {
r.Response.Writeln("Welcome GoFrame!")
})
// GET带参数
group.GET("/hello", func(r *ghttp.Request) {
r.Response.Writeln("Hello World!")
r.Response.Writeln("name:", r.GetString("name"))
})
// POST KV
group.POST("/test", func(r *ghttp.Request) {
r.Response.Writeln("func:test")
r.Response.Writeln("name:", r.GetString("name"))
r.Response.Writeln("age:", r.GetInt("age"))
})
// POST JSON
group.POST("/test2", func(r *ghttp.Request) {
r.Response.Writeln("func:test2")
r.Response.Writeln("passport:", r.GetString("passport"))
r.Response.Writeln("password:", r.GetString("password"))
})
// POST Header
group.POST("/test3", func(r *ghttp.Request) {
r.Response.Writeln("func:test3")
r.Response.Writeln("Cookie:", r.Header.Get("Cookie"))
})
// POST Header
group.POST("/test4", func(r *ghttp.Request) {
r.Response.Writeln("func:test4")
h := r.Header
r.Response.Writeln("accept-encoding:", h.Get("accept-encoding"))
r.Response.Writeln("accept-language:", h.Get("accept-language"))
r.Response.Writeln("referer:", h.Get("referer"))
r.Response.Writeln("cookie:", h.Get("cookie"))
r.Response.Writeln(r.Cookie.Map())
})
s.SetPort(80)
s.Run()
}
单元测试源码文件可以由多个测试用例组成,每个测试用例函数需要以Test
为前缀,例如:
func TestXXX( t *testing.T )
go test
指令来执行,没有也不需要 main() 作为函数入口。所有在以_test
结尾的源码内以Test
开头的函数会自动被执行。package test
import (
"fmt"
"github.com/gogf/gf/net/ghttp"
"testing"
)
var path = "http://127.0.0.1/api"
// GET请求
func TestGet(t *testing.T) {
if response, err := ghttp.Get(path); err != nil {
panic(err)
} else {
defer response.Close()
t.Log(response.ReadAllString())
}
if response, err := ghttp.Post(path); err != nil {
panic(err)
} else {
defer response.Close()
t.Log(response.ReadAllString())
}
}
// GET请求带参数
func TestHello(t *testing.T) {
if response, err := ghttp.Get(path + "/hello?name=whoami"); err != nil {
panic(err)
} else {
defer response.Close()
t.Log(response.ReadAllString())
}
}
// POST请求
func TestPost(t *testing.T) {
if response, err := ghttp.Post(path+"/test", "name=john&age=18"); err != nil {
panic(err)
} else {
defer response.Close()
t.Log(response.ReadAllString())
}
}
// POST JSON
func TestPostJson(t *testing.T) {
if response, err := ghttp.Post(path+"/test2",
`{"passport":"john","password":"123456"}`); err != nil {
panic(err)
} else {
defer response.Close()
t.Log(response.ReadAllString())
}
}
// POST Header头
func TestPostHeader(t *testing.T) {
c := ghttp.NewClient()
c.SetHeader("Cookie", "name=john; score=100")
if r, e := c.Post(path + "/test3"); e != nil {
panic(e)
} else {
fmt.Println(r.ReadAllString())
}
}
// POST Header头
func TestPostHeader2(t *testing.T) {
c := ghttp.NewClient()
c.SetHeaderRaw(`
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9,en;q=0.8
referer: https://idonottell.you
cookie: name=john; score=100
user-agent: my test http client
`)
if r, e := c.Post(path + "/test4"); e != nil {
panic(e)
} else {
fmt.Println(r.ReadAllString())
}
}
GF
的配置管理由gcfg
模块实现,gcfg
模块是并发安全的,仅提供配置文件读取功能,不提供数据写入/修改功能,支持的数据文件格式包括: JSON
、XML
、YAML/YML
、TOML
、INI
,项目中开发者可以灵活地选择自己熟悉的配置文件格式来进行配置管理。
默认读取执行文件所在目录及其下的config
目录,默认读取的配置文件为config.toml
;toml
类型文件也是默认的、推荐的配置文件格式,如果想要自定义文件格式,可以通过SetFileName
方法修改默认读取的配置文件名称(如:config.json
, cfg.yaml
, cfg.xml
, cfg.ini
等等)。
注:TOML大小写敏感,必须是UTF-8
编码;
配置管理器使用了缓存机制,当配置文件第一次被读取后会被缓存到内存中,下一次读取时将会直接从缓存中获取,以提高性能。同时,配置管理器提供了对配置文件的自动检测更新机制,当配置文件在外部被修改后,配置管理器能够即时地刷新配置文件的缓存内容。
配置管理器的自动检测更新机制是gf
框架特有的一大特色。
D:.
│ config_test.go -- 测试文件
│ go.mod
│ go.sum
│ main.go -- web自动更新配置演示
│
├─config
│ config.toml -- 标准配置文件
│
└─configTest -- 定制目录和配置文件
config1.toml
config2.toml
# 模板引擎目录
viewpath = "/home/www/templates/"
# MySQL数据库配置
[database]
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = "123456"
name = "test1"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = "123456"
name = "test2"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
# Redis数据库配置
[redis]
disk = "127.0.0.1:6379,0"
cache = "127.0.0.1:6379,1"
study = "hello study"
study1 = "hello study1"
config2 = "111"
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("配置",g.Config().GetString("name"))
r.Response.Writeln("Welcome GoFrame!")
})
s.SetPort(80)
s.Run()
}
package main
import (
"fmt"
"github.com/gogf/gf/frame/g"
"testing"
)
// 基本配置使用
func TestConfig(t *testing.T) {
// 默认当前路径或者config路径,默认文件config.toml
// /home/www/templates/
fmt.Println(g.Config().Get("viewpath"))
fmt.Println(g.Cfg().Get("viewpath"))
// 127.0.0.1:6379,1
c := g.Cfg()
// 分组方式
fmt.Println(c.Get("redis.cache"))
// 数组方式:test2
fmt.Println(c.Get("database.default.1.name"))
}
// 设置路径
func TestConfig2(t *testing.T) {
// 设置加载文件,默认name为default
// 设置路径
g.Cfg().SetPath("configTest")
// 设置加载文件
g.Cfg().SetFileName("config1.toml")
// 打印测试
fmt.Println(g.Cfg().Get("viewpath"))
fmt.Println(g.Cfg().Get("study"))
fmt.Println(g.Cfg().Get("study1"))
fmt.Println(g.Cfg().Get("config2"))
// 新的name就是新的实例
g.Cfg("name").SetPath("configTest")
g.Cfg("name").SetFileName("config2.toml")
fmt.Println(g.Cfg("name").Get("viewpath"))
fmt.Println(g.Cfg("name").Get("study"))
fmt.Println(g.Cfg("name").Get("study1"))
fmt.Println(g.Cfg("name").Get("config2"))
}
module gf_config
go 1.14
require github.com/gogf/gf v1.11.7
glog
是通用的高性能日志管理模块,实现了强大易用的日志管理功能,是gf
开发框架的核心模块之一。
重要的几点说明:
glog
采用了无锁设计,性能高效;glog
支持文件输出、日志级别、日志分类、调试管理、调用跟踪、链式操作等等丰富特性;glog.New
方法创建glog.Logger
对象用于自定义日志打印,也可以并推荐使用glog
默认提供的包方法来打印日志;glog.Set*
设置方法都将会全局生效;时间 [级别] 内容 换行
,其中时间
精确到毫秒级别,级别
为可选输出,内容
为调用端的参数输入,换行
为可选输出(部分方法自动为日志内容添加换行符号),日志内容示例:2018-10-10 12:00:01.568 [ERRO] 产生错误
;Print*/Debug*/Info*
方法输出日志内容到标准输出(stdout
),为防止日志的错乱,Notice*/Warning*/Error*/Critical*/Panic*/Fatal*
方法也是将日志内容输出到标准输出(stdout
);Panic*
方法在输出日志信息后会引发panic
错误方法,Fatal*
方法在输出日志信息之后会停止进程运行,并返回进程状态码值为1
(正常程序退出状态码为0
);GF v1.10
版本开始,日志组件支持单例模式,使用g.Log(单例名称)
获取不同的单例日志管理对象。提供单例对象的目的在于针对不同业务场景可以使用不同配置的日志管理对象。日志级别用于管理日志的输出,我们可以通过设定特定的日志级别来关闭/开启特定的日志内容。通过SetLevel
方法可以设置日志级别,glog
支持以下几种日志级别常量设定:
LEVEL_ALL
LEVEL_DEBU
LEVEL_INFO
LEVEL_NOTI
LEVEL_WARN
LEVEL_ERRO
LEVEL_CRIT
我们可以通过位操作
组合使用这几种级别,例如其中LEVEL_ALL
等价于LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT
。例如我们可以通过LEVEL_ALL & ^LEVEL_DEBU & ^LEVEL_INFO & ^LEVEL_NOTI
来过滤掉LEVEL_DEBU/LEVEL_INFO/LEVEL_NOTI
日志内容。
日志组件支持配置文件,当使用g.Log(单例名称)
获取Logger
单例对象时,将会自动通过默认的配置管理对象获取对应的Logger
配置。默认情况下会读取logger.单例名称
配置项,当该配置项不存在时,将会读取logger
配置项。
[logger]
# 日志目录
path = "logs"
# all LEVEL_ALL = LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT
# dev LEVEL_DEV = LEVEL_ALL
# pro LEVEL_PROD = LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT
level = "all"
# 是否打印到控制台
stdout = true
[logger.logger1]
path = "logger1"
level = "dev"
stdout = true
[logger.logger2]
path = "logger2"
level = "prod"
stdout = false
D:.
│ go.mod
│ go.sum
│ main.go
│
└─config
config.toml
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/os/glog"
)
func main() {
// 对应默认配置项 logger,默认default
g.Log().Debug("[default]Debug")
g.Log().Info("[default]info")
g.Log().Warning("[default]Warning")
g.Log().Error("[default]Error")
// 对应 logger.logger1 配置项
g.Log("logger1").Debug("[logger1]Debug")
g.Log("logger1").Info("[logger1]info")
g.Log("logger1").Warning("[logger1]Warning")
g.Log("logger1").Error("[logger1]Error")
// 对应 logger.logger2 配置项
g.Log("logger2").Debug("[logger2]Debug")
g.Log("logger2").Info("[logger2]info")
g.Log("logger2").Warning("[logger2]Warning")
g.Log("logger2").Error("[logger2]Error")
// 日志级别设置,过滤掉Info日志信息
l := glog.New()
l.Info("info1")
l.SetLevel(glog.LEVEL_ALL ^ glog.LEVEL_INFO)
l.Info("info2")
// 支持哪些级别
// LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT
// 异常
g.Log().Panic("this is panic!")
g.Log().Info("............")
}
[logger]
# 日志目录
path = "logs"
# all LEVEL_ALL = LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT
# dev LEVEL_DEV = LEVEL_ALL
# pro LEVEL_PROD = LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT
level = "all"
# 是否打印到控制台
stdout = true
[logger.logger1]
path = "logger1"
level = "dev"
stdout = true
[logger.logger2]
path = "logger2"
level = "prod"
stdout = false
gf
框架的ORM
功能由gdb
模块实现,用于常用关系型数据库的ORM
操作。其最大的特色在于同时支持map
和struct
两种方式操作数据库。gdb
默认情况下使用的是map
数据类型作为基础的数据表记录载体,开发者无需预先定义数据表记录struct
便可直接对数据表记录执行各种操作。这样的设计赋予了开发者更高的灵活度和简便性。
支持的数据库类型:Mysql,SQLite,PostgreSQL,SQLServer,Oracle
如果我们使用g
对象管理模块中的g.DB("数据库分组名称")
方法获取数据库操作对象,数据库对象将会自动读取config.toml
配置文件中的相应配置项(通过配置管理模块),并自动初始化该数据库操作的单例对象。
[database]
[[database.default]]
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
[[database.user]]
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/user"
注意每一项分组配置均可以是多个节点,支持负载均衡权重策略。如果不使用多节点负载均衡特性,仅使用配置分组特性,也可以简化为如下格式:
[database]
[database.default]
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
[database.user]
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/user"
如果仅仅是单数据库节点,不使用配置分组特性,那么也可以简化为如下格式:
[database]
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
不同数据类型对应的linkinfo
如下:
数据库类型 | Linkinfo配置 | 更多参数 |
---|---|---|
mysql | mysql: 账号:密码@tcp(地址:端口)/数据库名称 |
mysql |
pgsql | pgsql: user=账号 password=密码 host=地址 port=端口 dbname=数据库名称 |
pq |
mssql | mssql: user id=账号;password=密码;server=地址;port=端口;database=数据库名称;encrypt=disable |
go-mssqldb |
sqlite | sqlite: 文件绝对路径 (如: /var/lib/db.sqlite3 ) |
go-sqlite3 |
oracle | oracle: 账号/密码@地址:端口/数据库名称 |
go-oci8 |
gdb
支持日志输出,内部使用的是glog.Logger
对象实现日志管理,并且可以通过配置文件对日志对象进行配置。默认情况下gdb
关闭了DEBUG
日志输出,如果需要打开DEBUG
信息需要将数据库的debug
参数设置为true
。以下是为一个配置文件示例:
[database]
[database.logger]
Path = "/var/log/gf-app/sql"
Level = "all"
Stdout = true
[database.primary]
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/user_center"
debug = true
其中database.logger
即为gdb
的日志配置,当该配置不存在时,将会使用日志组件的默认配置
为便于数据表记录的操作,ORM定义了5种基本的数据类型:
type Map map[string]interface{} // 数据记录
type List []Map // 数据记录列表
type Value *gvar.Var // 返回数据表记录值
type Record map[string]Value // 返回数据表记录键值对
type Result []Record // 返回数据表记录列表
Map
与List
用于ORM操作过程中的输入参数类型(与全局类型g.Map
和g.List
一致,在项目开发中常用g.Map
和g.List
替换);Value/Record/Result
用于ORM操作的结果数据类型;这三个链式操作方法用于数据的写入,并且支持自动的单条或者批量的数据写入,三者区别如下:
Insert
使用INSERT INTO
语句进行数据库写入,如果写入的数据中存在主键或者唯一索引时,返回失败,否则写入一条新数据;
Replace
使用REPLACE INTO
语句进行数据库写入,如果写入的数据中存在主键或者唯一索引时,会删除原有的记录,必定会写入一条新记录;
Save
使用INSERT INTO
语句进行数据库写入,如果写入的数据中存在主键或者唯一索引时,更新原有数据,否则写入一条新数据;
在部分数据库类型中,并不支持
Replace/Save
方法
Update
更新方法Update
用于数据的更新,往往需要结合Data
及Where
方法共同使用。Data
方法用于指定需要更新的数据,Where
方法用于指定更新的条件范围。同时,Update
方法也支持直接给定数据和条件参数。
Delete
删除方法Delete
方法用于数据的删除。
Where/And/Or
查询条件这三个方法用于传递查询条件参数,支持的参数为任意的string/map/slice/struct/*struct
类型。
Where
条件参数推荐使用字符串的参数传递方式(并使用?
占位符预处理),因为map
/struct
类型作为查询参数无法保证顺序性,且在部分情况下(数据库有时会帮助你自动进行查询索引优化),数据库的索引和你传递的查询条件顺序有一定关系。
当使用多个Where
方法连接查询条件时,作用同And
。 此外,当存在多个查询条件时,gdb
会默认将多个条件分别使用()
符号进行包含,这种设计可以非常友好地支持查询条件分组。
All/One/Value/Count
数据查询这四个方法是数据查询比较常用的方法:
All
用于查询并返回多条记录的列表/数组。One
用于查询并返回单条记录。Value
用于查询并返回一个字段值,往往需要结合Fields
方法使用。Count
用于查询并返回记录数。此外,也可以看得到这四个方法定义中也支持条件参数的直接输入,参数类型与Where
方法一致。但需要注意,其中Value
方法的参数中至少应该输入字段参数。
CREATE TABLE `user` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`site` varchar(255) DEFAULT NULL,
PRIMARY KEY (`uid`)
) ENGINE=InnoDB AUTO_INCREMENT=10000 ;
package test
import (
"fmt"
"github.com/gogf/gf/frame/g"
"testing"
)
// Insert
func TestInsert(t *testing.T) {
// INSERT INTO `user`(`name`) VALUES('john')
_, err := g.DB().Table("user").Data(g.Map{"uid": 10000, "name": "john"}).Insert()
if err != nil {
panic(err)
}
}
// Update
func TestUpdate(t *testing.T) {
// UPDATE `user` SET `name`='john guo' WHERE name='john'
_, err := g.DB().Table("user").Data("name", "john guo").
Where("name", "john").Update()
if err != nil {
panic(err)
}
}
// Delete
func TestDelete(t *testing.T) {
// DELETE FROM `user` WHERE uid=10
_, err := g.DB().Table("user").Where("uid", 10000).Delete()
if err != nil {
panic(err)
}
}
// Select Where
func TestWhere(t *testing.T) {
// INSERT INTO `user`(`name`) VALUES('john')
g.DB().Table("user").Data(g.Map{"uid": 10001, "name": "john"}).Insert()
g.DB().Table("user").Data(g.Map{"uid": 10002, "name": "john2"}).Insert()
// 数量
count, err := g.DB().Table("user").Where("uid", 10001).Count()
if err != nil {
panic(err)
}
fmt.Println("count:", count)
// 获取单个值
v, err := g.DB().Table("user").Where("uid", 10001).Fields("name").Value()
if err != nil {
panic(err)
}
fmt.Println("name:", v.String())
// 查询对象
r, err := g.DB().Table("user").Where("uid", 10002).One()
if err != nil {
panic(err)
}
fmt.Println("name:", r.Map()["name"])
// 查询对象
//l, err := g.DB().Table("user").As("t").Where("t.uid > ?", 10000).All()
// 也可以简写为 select * from user as t where t.uid > 10000
l, err := g.DB().Table("user").As("t").All("t.uid > ?", 10000)
if err != nil {
panic(err)
}
for index, value := range l {
fmt.Println(index, value["uid"], value["name"])
}
g.DB().Table("user").Where("uid", 10001).Delete()
g.DB().Table("user").Where("uid", 10002).Delete()
}
gredis
模块实现,底层采用了链接池设计。Redis是当前比较热门的NOSQL系统之一,它是一个开源的使用ANSI c语言编写的key-value存储系统(区别于MySQL的二维表格的形式存储。)。性能出色:Redis读取的速度是110000次/s,写的速度是81000次/s。
String: 字符串、Hash: 散列、List: 列表、Set: 集合、Sorted Set: 有序集合
PUB/SUB:发布订阅;
在5.0支持了全新数据类型:Streams
缓存,登录验证码,消息队列,过滤器,分布式锁,限流等
绝大部分情况下推荐使用g.Redis
单例方式来操作redis。因此同样推荐使用配置文件来管理Redis配置,在config.toml
中的配置示例如下:
# Redis数据库配置
[redis]
default = "127.0.0.1:6379,0"
cache = "127.0.0.1:6379,1,123456?idleTimeout=600"
其中,Redis的配置格式为:host:port[,db,pass?maxIdle=x&maxActive=x&idleTimeout=x&maxConnLifetime=x]
各配置项说明如下:
配置项名称 | 是否必须 | 默认值 | 说明 |
---|---|---|---|
host | 是 | - | 地址 |
port | 是 | - | 端口 |
db | 否 | 0 | 数据库 |
pass | 否 | - | 授权密码 |
maxIdle | 否 | 0 | 允许限制的连接数(0表示不闲置) |
maxActive | 否 | 0 | 最大连接数量限制(0表示不限制) |
idleTimeout | 否 | 60 | 连接最大空闲时间(单位秒,不允许设置为0) |
maxConnLifetime | 否 | 60 | 连接最长存活时间(单位秒,不允许设置为0) |
其中的default
和cache
分别表示配置分组名称,我们在程序中可以通过该名称获取对应配置的redis对象。不传递分组名称时,默认使用redis.default
配置分组项)来获取对应配置的redis客户端单例对象。
可以看到通过客户端方法Do/Receive
获取的数据都是二进制形式[]byte
的,需要开发者手动进行数据转换。
当然,gredis
模块也提供了DoVar/ReceiveVar
方法,用以获取可供方便转换的gvar.Var
通用变量结果。
通过gvar.Var
的强大转换功能可以转换为任意的数据类型,如基本数据类型Int
,String
,Strings
,或者结构体Struct
等等。
D:.
│ go.mod
│ go.sum
│ main.go
│
└─config
config.toml
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gconv"
)
func main() {
// redis字符串操作
g.Redis().Do("SET", "k", "v")
v, _ := g.Redis().Do("GET", "k")
g.Log().Info(gconv.String(v))
// 获取cache链接
v2, _ := g.Redis("cache").Do("GET", "k")
g.Log().Info(gconv.String(v2))
// DoVar转换
v3, _ := g.Redis().DoVar("GET", "k")
g.Log().Info(v3.String())
// setex
g.Redis().Do("SETEX", "keyEx", 2000, "v4")
v4, _ := g.Redis().DoVar("GET", "keyEx")
g.Log().Info(v4.String())
// list
g.Redis().Do("RPUSH", "keyList", "v5")
v5, _ := g.Redis().DoVar("LPOP", "keyList")
g.Log().Info(v5.String())
// hash
g.Redis().Do("HSET", "keyHash", "v1", "v6")
v6, _ := g.Redis().DoVar("HGET", "keyHash", "v1")
g.Log().Info(v6.String())
// set
g.Redis().Do("SADD", "keySet", "v7")
v7, _ := g.Redis().DoVar("SPOP", "keySet")
g.Log().Info(v7.String())
// sort set
g.Redis().Do("ZADD", "keySortSet", 1, "v8")
v8, _ := g.Redis().DoVar("ZREM", "keySortSet", "v8")
g.Log().Info(v8.Int())
}
# Redis数据库配置
[redis]
default = "127.0.0.1:6379,0"
cache = "127.0.0.1:6379,1,123456?idleTimeout=600"
p := fmt.Println
p("Contains: ", gstr.Contains("test", "es"))
p("Count: ", gstr.Count("test", "t"))
p("HasPrefix: ", gstr.HasPrefix("test", "te"))
p("HasSuffix: ", gstr.HasSuffix("test", "st"))
p("Join: ", gstr.Join([]string{"a", "b"}, "-"))
p("Repeat: ", gstr.Repeat("a", 5))
p("Replace: ", gstr.Replace("foo", "o", "0", -1))
p("Replace: ", gstr.Replace("foo", "o", "0", 1))
p("Split: ", gstr.Split("a-b-c-d-e", "-"))
p("ToLower: ", gstr.ToLower("TEST"))
p("ToUpper: ", gstr.ToUpper("test"))
p("Trim: ", gstr.Trim(" test "))
g.Map实现type Map = map[string]interface{}
支持并发安全开关选项的map
容器,最常用的数据结构。
该模块包含多个数据结构的map
容器:HashMap
、TreeMap
和ListMap
。
类型 | 数据结构 | 平均复杂度 | 支持排序 | 有序遍历 | 说明 |
---|---|---|---|---|---|
HashMap |
哈希表 | O(1) | 否 | 否 | 高性能读写操作,内存占用较高,随机遍历 |
ListMap |
哈希表+双向链表 | O(2) | 否 | 是 | 支持按照写入顺序遍历,内存占用较高 |
TreeMap |
红黑树 | O(log N) | 是 | 是 | 内存占用紧凑,支持键名排序及有序遍历 |
此外,
gmap
模块支持多种以哈希表为基础数据结构的常见类型map
定义:IntIntMap
、IntStrMap
、IntAnyMap
、StrIntMap
、StrStrMap
、StrAnyMap
。
使用场景:
任何map
/哈希表/关联数组使用场景,尤其是并发安全场景中。
// 常规map方法
p := fmt.Println
// 初始化
m2 := g.Map{"a": 1, "b": 2}
p(m2)
// 设置
m2["c"] = 25
p(m2)
// 获取
p(m2["b"])
// 删除
delete(m2, "c")
// 遍历
for k, v := range m2 {
p(k, v)
}
p("###########################")
// 创建一个默认的gmap对象,
// 默认情况下该gmap对象不支持并发安全特性,
// 初始化时可以给定true参数开启并发安全特性。
m := gmap.New()
// 设置键值对
for i := 0; i < 10; i++ {
m.Set(i, i)
}
// 查询大小
fmt.Println(m.Size())
// 批量设置键值对(不同的数据类型对象参数不同)
m.Sets(map[interface{}]interface{}{
10: 10,
11: 11,
})
fmt.Println(m.Size())
// 查询是否存在
fmt.Println(m.Contains(1))
// 查询键值
fmt.Println(m.Get(1))
// 删除数据项
m.Remove(9)
fmt.Println(m.Size())
// 批量删除
m.Removes([]interface{}{10, 11})
fmt.Println(m.Size())
// 当前键名列表(随机排序)
fmt.Println(m.Keys())
// 当前键值列表(随机排序)
fmt.Println(m.Values())
// 查询键名,当键值不存在时,写入给定的默认值
fmt.Println(m.GetOrSet(100, 100))
// 删除键值对,并返回对应的键值
fmt.Println(m.Remove(100))
// 遍历map
m.Iterator(func(k interface{}, v interface{}) bool {
fmt.Printf("%v:%v ", k, v)
return true
})
// 清空map
m.Clear()
// 判断map是否为空
fmt.Println(m.IsEmpty())
gjson
模块实现了强大的JSON
编码/解析,支持数据层级检索、动态创建修改Json
对象,并支持常见数据格式的解析和转换等特点。 // 创建json
jsonContent := `{"name":"john", "score":"100"}`
j := gjson.New(jsonContent)
fmt.Println(j.Get("name"))
fmt.Println(j.Get("score"))
// 创建json
j2 := gjson.New(nil)
j2.Set("name", "John")
j2.Set("score", 99.5)
fmt.Printf(
"Name: %s, Score: %v\n",
j2.GetString("name"),
j2.GetFloat32("score"),
)
fmt.Println(j2.MustToJsonString())
// struct转json
type Me struct {
Name string `json:"name"`
Score int `json:"score"`
}
me := Me{
Name: "john",
Score: 100,
}
j3 := gjson.New(me)
fmt.Println(j3.Get("name"))
fmt.Println(j3.Get("score"))
// 转换回Struct
Me2 := new(Me)
if err := j.ToStruct(Me2); err != nil {
panic(err)
}
fmt.Printf(`%+v`, Me2)
fmt.Println()
// 格式转换
fmt.Println("JSON:")
fmt.Println(j3.MustToJsonString())
fmt.Println("======================")
fmt.Println("XML:")
fmt.Println(j3.MustToXmlString("document"))
fmt.Println("======================")
fmt.Println("YAML:")
fmt.Println(j3.MustToYamlString())
fmt.Println("======================")
fmt.Println("TOML:")
fmt.Println(j3.MustToTomlString())
p := fmt.Println
// md5加密
p(gmd5.MustEncrypt("123456"))
gf
框架提供了非常强大的类型转换包gconv
,可以实现将任何数据类型转换为指定的数据类型,对常用基本数据类型之间的无缝转换,同时也支持任意类型到struct
对象的转换。由于gconv
模块内部大量使用了断言而非反射(仅struct
转换使用到了反射),因此执行的效率非常高。
项目中我们经常会遇到大量struct
的使用,以及各种数据类型到struct
的转换/赋值(特别是json
/xml
/各种协议编码转换的时候)。为提高编码及项目维护效率,gconv
模块为各位开发者带来了极大的福利,为数据解析提供了更高的灵活度。
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))
fmt.Println("##############")
// struct和map转换
type User struct {
Uid int `c:"uid"`
Name string `c:"name"`
}
// 对象
m := gconv.Map(User{
Uid : 1,
Name : "john",
})
fmt.Println(m)
fmt.Println("##############")
user := (*User)(nil)
err := gconv.Struct(m, &user)
if err != nil {
panic(err)
}
g.Dump(user)