「学习笔记」Golang – GoFrame框架
文章目录
GoFrame 是一款模块化、高性能的 Go 语言开发框架。
准备工作
- 前置条件:已安装Go语言开发环境,已配置好GOROOT、GOPATH环境变量;GoFrame文档:https://goframe.org/。
- 安装框架工具:GoFrame 框架提供了功能强大的 gf 命令行开发辅助工具,工具地址:https://github.com/gogf/gf/releases。 下载对应的包安装。推荐安装到GOROOT的bin目录中,详见官方文档-工具安装-install
gf -v ## 查看是否安装成功
项目初始化
配置代理,在cmd中输入
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn
## 常用代理地址:`https://goproxy.cn`,`https://goproxy.io`,`https://mirrors.aliyun.com/goproxy/`
创建项目(-u:可选项,把框架初始化到最新版本)
gf init gf_demo -u
项目启动
进入项目中main.go文件所在的目录运行如下命令
gf run main.go
启动成功后,在浏览器中输入http://127.0.0.1:8000/hello查看结果。
目录结构
/
├── api 接口定义:请求接口输入/输出数据结构定义
├── hack 项目开发工具、脚本
├── internal 业务逻辑存放目录,核心代码
│ ├── cmd 入口指令与其他命令工具目录
│ ├── consts 常量定义目录
│ ├── controller 接口实现:控制器目录,接收/解析用户请求
│ ├── service 业务接口:具体的接口实现在logic中进行注入。
│ ├── logic 业务封装:核心业务逻辑代码目录
│ ├── model 数据结构管理模块,管理数据实体对象,以及输入与输出数据结构定义
│ | ├── do 数据操作中业务模型与实例模型转换,由工具维护,不能手动修改
│ │ └── entity 数据模型是模型与数据集合的一对一关系,由工具维护,不用手动修改。
│ └── dao 数据访问对象目录,用于和底层数据库交互
├── manifest 包含程序编译、部署、运行、配置的文件
├── resource 静态资源文件
├── utility
├── go.mod
└── main.go 程序入口文件
更多详细介绍见文档工程目录设计
WEB服务开发
WebServer 模块是其中比较核心的模块,由 ghttp 模块实现。实现了丰富完善的相关组件,例如: Router、 Cookie、 Session、路由注册、配置管理、模板引擎、缓存控制等等。
## 创建并运行一个 WebServer
package main
import (
"github.com/gogf/gf/v2/frame/g"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request){ //路由注册
r.Response.Writeln("go frame!")
})
s.SetPort(8080)
s.Run()
}
更多详细介绍见文档WEB服务开发
路由注册
// 只支持GET请求
s.BindHandler("GET:/hello", func(req *ghttp.Request) {
req.Response.Writeln("<h1>Hello World! GET</h1>")
})
// 将对象方法绑定到路由
s.BindHandler("/adduser", usercontroller.AddUser)
// 绑定user控制器中所有公共方法
s.BindObject("/user", usercontroller)
// 绑定user控制器中多个方法
s.BindObject("/user", usercontroller, "AddUser,UpdateUser")
// 绑定单个方法
s.BindObjectMethod("/deluser", usercontroller, "DeleteUser")
// 以RESTFul方绑定对象方法
s.BindObjectRest("/user", usercontroller)
// 分组注册
s.Group("/user", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Bind(
usercontroller, // 绑定到控制器对象
)
// 可以用 GET POST PUT 等定义路由
group.GET("/get", func(r *ghttp.Request) {
r.Response.Writeln("/user/get")
})
})
规范路由
GoFrame中提供了规范化的路由注册方式,注册方法如下:
func Handler(ctx context.Context, req *Request) (res *Response, err error)
其中Request与Response为自定义的结构体。
示例代码:
package main
import (
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
// 指定请求方法与路径
type HelloReq struct {
g.Meta `path:"/hello" method:"get"`
Name string `v:"required" dc:"Your name"`
}
// 指定返回内容
type HelloRes struct {
Reply string `dc:"Reply content"`
}
// 控制器实现
type HelloController struct{}
func (HelloController) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) {
res = &HelloRes{
Reply: fmt.Sprintf(`Hi %s`, req.Name),
}
return
}
func main() {
s := g.Server()
s.Use(ghttp.MiddlewareHandlerResponse)
s.Group("/", func(group *ghttp.RouterGroup) {
group.Bind(
new(HelloController),
)
})
s.Run()
}
更多详细介绍见文档规范路由
参数获取
query参数获取
query参数是指以?a=1&b=2的形式写在url中的参数,通常由GET方法传递。
m := request.GetQuery("name")//单个参数值
m := request.GetQuery("name", "孙行者") // 对应参数值不存在时,返回指定的默认值
m := request.GetQueryMap()//获取全部Query参数
m := request.GetQueryMap(map[string]interface{}{"name": "者行孙", "age": 600})//指定需要获取的参数名称与默认值
// 将请求参数直接转化为对应的结构体
var u *user
err := request.ParseQuery(&u)
GoFrame中提供了GetQueryMap,GetQueryMapStrStr,GetQueryMapStrVar三个方法用于批量获取Query参数,三个方法使用方式一致,只是返回类型不同。
表单参数获取(POST参数获取)
表单参数获取是指获取application/x-www-form-urlencoded、application/form-data、multipart/form-data等数据,也可以用来获取以json格式提交的数据,简单理解即为可以获取POST方法提交的数据。
m := request.GetForm("name") //单个参数
m := request.GetForm("name", "烧包谷")//指定默认值
m := request.GetFormMap()//批量获取请求数据
m := request.GetFormMap(map[string]interface{}{"name": "大洋芋"})//指定需要获取的参数以及默认值
//将请求数据转化为自定义结构体
var u *user
err := request.ParseForm(&u)
可以用GetFormMap、GetFormMapStrStr、GetFormMapStrVar批量获取请求数据,三个方法使用方式一样,只是返回的Map类型不同。
动态路由参数获取
动态路由需要对现有代码进行一点改动,需要先在api包中对指定的路由进行动态注册:
type ParamsRes struct {
g.Meta `mime:"text/html"`
}
type ParamsReq struct {
g.Meta `path:"/params/:name" method:"all"`
}
再将控制器的方法利用api数据结构进行修改:
func (c *Controller) Params(ctx context.Context, req *api.ParamsReq) (res *api.ParamsRes, err error) {
request := g.RequestFromCtx(ctx)
u := request.GetRouter("name") //获取路由参数,返回string;批量获取 request.GetRouterMap(),返回值为`map[string]string`
request.Response.WriteJson(g.Map{"data": u})
return
}
所有请求参数获取
GoFrame中还提供了一些方法获取所有请求参数,用法与上面类似,只是不区分请求方法。按照参数优先级进行覆盖。
data := request.GetRequest("name") //获取单个参数
data := request.Get("name") // GetRequest简写,获取单个参数
data := request.GetRequestMap() //批量获取请求数据
data := request.GetRequestMap(g.Map{"name": ""})// 可以指定需要获取的参数名及默认值
request.Parse(&u) // 将请求参数转为自定义结构体,u为自定义结构体指针
请求参数优先级
- Get 及 GetRequset 方法:
Custom > Form > Body > Query > Router,也就是说自定义参数的优先级最高,其次是 Form 表单参数,以此类推。 - GetQuery 方法:
Query > Body,也就是说 query 参数将会覆盖 Body 中提交的同名参数。例如,Query 和 Body 中都提交了同名参数 id,参数值分别为 1 和 2,那么 Get(“id”) 将会返回 2,而 GetQuery(“id”) 将会返回 1。 - GetForm 方法:无优先级,仅用于获取 Form 表单参数。
请求参数总结
Get/GetRequset:常用方法,获取客户端提交的所有参数,按照参数优先级进行覆盖,不区分提交方式。GetQuery:获取 GET 方式传递过来的参数,包括 Query String 及 Body 参数解析。GetForm:获取表单方式传递过来的参数,表单方式提交的参数 Content-Type 往往为 application/x-www-form-urlencoded, application/form-data, multipart/form-data, multipart/mixed 等等。GetBody/GetBodyString:获取客户端提交的原始数据,该数据是客户端写入到 body 中的原始数据,与 HTTP Method 无关,例如客户端提交 JSON/XML 数据格式时可以通过该方法获取原始的提交数据。GetJson:自动将原始请求信息解析为 gjson.Json 对象指针返回。详见通用编解码-gjson。Get*Struct:将指定提交类型的所有请求参数绑定到指定的 struct 对象上,注意给定的参数为对象指针。绝大部分场景中往往使用 Parse 方法将请求数据转换为请求对象,详见请求输入-默认值绑定。
请求输入
推荐将输入和输出定义为 struct 结构体对象,以便于结构化的参数输入输出维护。 GoFrame 框架支持非常便捷的对象转换,支持将客户端提交的参数如 Query参数、表单参数、内容参数、 JSON/XML 等参数非常便捷地转换为指定的 struct 结构体,并且支持提交参数与 struct 属性的映射关系维护。
type ParamReq struct {
g.Meta `path:"/params" method:"post"`
UserName string `p:"username" v:"required|length:4,30#请输入账号|账号长度为:{min}到:{max}位"`
PassWord string `p:"password" v:"required|length:6,30#请输入密码|密码长度不够"`
UserAge int `p:"age" d:"18"`
}
其中p:或param:用于指定该成员对应的请求参数名,d:或default:用于指定默认值。,v:或valid:用于设置该属性的校验规格。
struct的tag(标签)
| Tag(简写) | 全称 | 描述 |
|---|---|---|
| v | valid | 数据校验标签。 |
| p | param | 自定义请求参数匹配。 |
| d | default | 请求参数默认值绑定。 |
| orm | orm | ORM标签,用于指定表名、关联关系。 |
| dc | description | 通用结构体属性描述,ORM和接口都用到。属于框架默认的属性描述标签。 |
数据返回
HTTP Server 的数据返回通过 ghttp.Response 对象实现, ghttp.Response 对象实现了标准库的 http.ResponseWriter 接口。数据输出使用 Write* 相关方法实现,并且数据输出采用了 Buffer 机制,因此数据的处理效率比较高。任何时候可以通过 OutputBuffer 方法输出缓冲区数据到客户端,并清空缓冲区数据。
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()
}
统一返回结构
{
"code":0, //自定义编码,用来表示请求成功与失败
"msg":"请求成功", //提示信息,如果请求出错则为错误信息
"data":{} //请求返回数据,请求出错一般为null
}
GoFrame为前后端分离的API开发提供了很好的支持,只需要借助api模块就可以方便完成类似的返回结构,不需要自行定义。
- 在
api中定义请求与响应数据结构:
type ApiReq struct {
g.Meta `path:"/api" method:"all"`
}
type ApiRes struct {
UserName string `json:"name"`
UserAge int `json:"age"`
List g.Array `json:"list"`
}
- 在控制器中定义对应的方法:
func (c *Controller) Api(ctx context.Context, req *api.ApiReq) (res *api.ApiRes, err error) {
res = &api.ApiRes{ //实例化返回数据并返回
UserName: "张三",
UserAge: 120,
List: g.Array{1, 2, 3, 4},
}
return
}
- 用上述方法返回数据,会自动返回如下格式JSON数据:
{
"code":0,
"message":"",
"data":{
"name":"张三",
"age":120,
"list":[1,2,3,4]
}
}
以上数据格式是通过中间件ghttp.MiddlewareHandlerResponse实现的,实际应用当中可以仿照这一中间件自行定义中间件来确定需要的数据返回格式。
数据库ORM
GoFrame 框架的 ORM 功能由 gdb 模块实现,用于常用关系型数据库的 ORM 操作。 默认并且推荐的配置文件数据格式为 yaml
简化配置通过配置项 link 指定,格式如下:类型:账号:密码@协议(地址)/数据库名称?特性配置
配置示例:
database:
default:
link: "mysql:root:123456@tcp(127.0.0.1:3306)/test_db"
user:
link: "mysql:root:123456@tcp(127.0.0.1:3306)/user_db"
ORM原生操作
原生操作允许你直接使用SQL语句进行数据库操作,提供了更大的灵活性和控制力。这对于复杂的查询或特定的优化非常有用。
- ORM 对象
db := g.DB() // 获取默认配置的数据库对象(配置名称为"default")
db := g.DB("user") // 获取配置分组名称为"user"的数据库对象
// 使用原生单例管理方法获取数据库对象单例
db, err := gdb.Instance()
db, err := gdb.Instance("user")
数据库引擎底层采用了链接池设计,当链接不再使用时会自动关闭。注意,参数域支持并建议使用预处理模式(使用 ? 占位符)进行输入,避免 SQL 注入风险。
- 数据操作
// 数据写入
r, err := db.Insert(ctx, "user", gdb.Map {
"name": "john",
})
// 数据查询(列表)
list, err := db.GetAll(ctx, "select * from user limit 2")
list, err := db.GetAll(ctx, "select * from user where age > ? and name like ?", g.Slice{18, "%john%"})
list, err := db.GetAll(ctx, "select * from user where status=?", g.Slice{1})
// 数据查询(单条)
one, err := db.GetOne(ctx, "select * from user where uid=?", 1)
// 数据保存
r, err := db.Save(ctx, "user", gdb.Map {
"uid" : 1,
"name" : "john",
})
// 批量操作, 其中 batch 参数用于指定批量操作中分批写入条数数量(默认是 10)。
_, err := db.Insert(ctx, "user", gdb.List {
{"name": "john_1"},
{"name": "john_2"},
{"name": "john_3"},
{"name": "john_4"},
}, 10)
// 数据更新/删除, db.Update/db.Delete 同理
r, err := db.Update(ctx, "user", gdb.Map {"name": "john"}, "uid=?", 10000) // UPDATE `user` SET `name`='john' WHERE `uid`=10000
r, err := db.Update(ctx, "user", "name='john'", "uid=10000") // UPDATE `user` SET `name`='john' WHERE `uid`=10000
r, err := db.Update(ctx, "user", "name=?", "uid=?", "john", 10000) // UPDATE `user` SET `name`='john' WHERE `uid`=10000
ORM链式操作
链式操作是一种更简洁灵活、更直观的方式来构建SQL查询。是 GoFrame 框架官方推荐的数据库操作方式。链式操作可以通过数据库对象的 db.Model 方法或者事务对象的 tx.Model 方法,基于指定的数据表返回一个链式操作对象 *Model。
- 模型创建
g.Model("user")
// 或者
g.DB().Model("user")
gdb 是非链式安全的,也就是说链式操作的每一个方法都将对当前操作的 Model 属性进行修改,因此该 Model 对象 不可以重复使用。
- 实现链式安全
// Clone 方法
user := g.Model("user")// 定义一个用户模型单例
m := user.Clone()// 克隆一个新的用户模型
// Safe 方法
user := g.Model("user").Safe() // 通过 Safe 方法设置当前模型为 链式安全 的对象,后续的每一个链式操作都将返回一个新的 Model 对象,该 Model 对象可重复使用。
- 数据操作
// 数据写入
g.Model("user").Insert(g.Map{"name": "john"}) // INSERT INTO `user`(`name`) VALUES('john')
g.Model("user").Replace(g.Map{"uid": 10000, "name": "john"})// REPLACE INTO `user`(`uid`,`name`) VALUES(10000,'john')
g.Model("user").Save(g.Map{"uid": 10001, "name": "john"})// INSERT INTO `user`(`uid`,`name`) VALUES(10001,'john') ON DUPLICATE KEY UPDATE `uid`=VALUES(`uid`),`name`=VALUES(`name`)
// 批量保存
g.Model("user").Data(g.List{ // INSERT INTO `user`(`uid`,`name`) VALUES(10000,'john_1'),(10001,'john_2'),(10002,'john_3') ON DUPLICATE KEY UPDATE `uid`=VALUES(`uid`),`name`=VALUES(`name`)
{"uid":10000, "name": "john_1"},
{"uid":10001, "name": "john_2"},
{"uid":10002, "name": "john_3"},
}).Save()
// 更新与删除
g.Model("user").Update(g.Map{"name" : "john guo"}, "name", "john")// UPDATE `user` SET `name`='john guo' WHERE name='john'
g.Model("user").Where("uid", 10).Delete()// DELETE FROM `user` WHERE `uid`=10
g.Model("user").Delete("uid", 10)// DELETE FROM `user` WHERE `uid`=10
// 使用Fields方法查询指定字段,不使用默认*
g.Model("user").Fields("uid,name").Where("uid > ?", 1).Limit(0, 10).All() // SELECT uid,name FROM user WHERE uid>1 LIMIT 0,10
g.Model("user").Where("uid", 1).Where("name", "john").One()// SELECT * FROM user WHERE (uid=1) AND (name='john') LIMIT 1
g.Model("user").Where("uid=?", 1).Where("name=?", "john").One()
g.Model("user").Where("uid=?", 1).WhereOr("name=?", "john").One()// SELECT * FROM user WHERE (uid=1) OR (name='john') LIMIT 1
- 数据查询比较常用的几个方法:
- All 用于查询并返回多条记录的列表/数组。
- One 用于查询并返回单条记录。
- Array 用于查询指定字段列的数据,返回数组。
- Value 用于查询并返回一个字段值,往往需要结合 Fields 方法使用。
- Count 用于查询并返回记录数。
// SELECT * FROM `user` WHERE `score`>60
Model("user").Where("score>?", 60).All()
// SELECT * FROM `user` WHERE `score`>60 LIMIT 1
Model("user").Where("score>?", 60).One()
// SELECT `name` FROM `user` WHERE `score`>60
Model("user").Fields("name").Where("score>?", 60).Array()
// SELECT `name` FROM `user` WHERE `uid`=1 LIMIT 1
Model("user").Fields("name").Where("uid", 1).Value()
// SELECT COUNT(1) FROM `user` WHERE `status` IN(1,2,3)
Model("user").Where("status", g.Slice{1,2,3}).Count()
相关系列文章
- 「学习笔记」Golang -- GoFrame + Casbin 实现企业级 RBAC 权限管理
- 「学习笔记」Golang -- GoFrame框架
- 「学习笔记」Golang -- Go协程 与 通道
- 「学习笔记」Golang -- 读写数据
- 「学习笔记」Golang -- 动态类型与反射
- 「学习笔记」Golang -- 控制语句与错误处理
- 「学习笔记」Golang -- 内置数据类型
- 「学习笔记」Golang -- 基础入门