本文开始记录开始时间为2023-09,如果官方文档有更新导致本文中链接失效或与官方文档不相同的地方或错漏,以官方文档为准
准备工作
前置条件
已安装Go语言开发环境,已配置好GOROOT、GOPATH环境变量
熟悉Go语言基本语法与使用
GoFrame文档:https://goframe.org/
学习过程以官方文档为主,本文内容均摘自官方文档,
本阶段只介绍Web开发部分,微服务部分以后有机会新开
安装框架工具
https://github.com/gogf/gf/releases
下载对应的包安装。推荐安装到GOROOT的bin目录中
用以下命令查看是否安装成功
gf -v
项目初始化
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn
# 如果已经设置过可以不要上面这两行
gf init gf_demo -u # 如果之前已经创建过项目,并且不需要创建最新版本则省略-u
常用代理地址:
https://goproxy.cn
https://goproxy.io
https://mirrors.aliyun.com/goproxy/
项目启动
进入项目中main.go文件所在的目录运行如下命令
gf run main.go
启动成功后,在浏览器中输入http://127.0.0.1:8000/hello
查看结果
框架设计
关于框架设计的内容,有点过于抽象,内容也是偏理论的,初学就来纠结这部分基本上也难以理解,所以这部分的其他内容可以放到以后再来研究。不过也需要了解一点基础知识,比如MVC
与3-Tier Architecture
,这部分内容详见文档代码分层设计,不需要完全理解,知道个大概也就可以了。
项目目录结构
/ ├── api 请求接口输入/输出数据结构定义 ├── hack 项目开发工具、脚本 ├── internal 业务逻辑存放目录,核心代码 │ ├── cmd 入口指令与其他命令工具目录 │ ├── consts 常量定义目录 │ ├── controller 控制器目录,接收/解析用户请求 │ ├── dao 数据访问对象目录,用于和底层数据库交互 │ ├── logic 核心业务逻辑代码目录 │ ├── model 数据结构管理模块,管理数据实体对象,以及输入与输出数据结构定义 │ | ├── do 数据操作中业务模型与实例模型转换,由工具维护,不能手动修改 │ │ └── entity 数据模型是模型与数据集合的一对一关系,由工具维护,不用手动修改。 │ └── service 业务接口定义层。具体的接口实现在logic中进行注入。 ├── manifest 包含程序编译、部署、运行、配置的文件 ├── resource 静态资源文件 ├── utility ├── go.mod └── main.go 程序入口文件
有关项目目录更多详细介绍以及请求分层流转见文档工程目录设计
路由
路由注册
函数注册
相关方法:
func (s *Server) BindHandler(pattern string, handler interface{})
其中handler
的定义方式有如下两种:
func(request *ghttp.Request)
func(ctx context.Context, BizRequest)(BizResponse, error)
匿名函数与普通函数注册 internal/cmd/cmd.go
package cmd
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gcmd"
)
func handler(req *ghttp.Request) {
req.Response.Writeln("<h1>Hello World From handler</h1>")
}
var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
s := g.Server()
// 直接用匿名函数进行路由注册
s.BindHandler("/hello", func(req *ghttp.Request) {
req.Response.Writeln("<h1>Hello World!</h1>")
})
// 或者使用提前定义好的函数来进行注册
s.BindHandler("/world", handler)
s.Run()
return nil
},
}
)
注册成功后在浏览器输入http://127.0.0.1:8000/hello或者http://127.0.0.1:8000/world即可访问对应的路由
指定HTTP请求方法 上述方法注册路由默认支持所有HTTP请求方法,如果需要指定请求方法,可用以下写法:
// 该路由只支持GET请求
s.BindHandler("GET:/hello", func(req *ghttp.Request) {
req.Response.Writeln("<h1>Hello World! GET</h1>")
})
// 该路由只支持POST请求
s.BindHandler("POST:/hello", func(req *ghttp.Request) {
req.Response.Writeln("<h1>Hello World! POST</h1>")
})
对于同一路由可以定义不同的请求方法实现不同功能。
几个最常用HTTP方法
方法 描述 GET 用于获取数据,不会修改服务端资源数据 POST 将资源数据提交到服务端,常用于在服务端创建新数据 PUT 将资源数据提交到服务端,常用于修改已存在的资源数据 DELETE 用于删除服务端资源数据
对象方法注册
还可以用对象当中的方法来注册路由。
选定义一个名为user
控制器
internal/controller/user/user.go
package user
import "github.com/gogf/gf/v2/net/ghttp"
type Controller struct{}
func New() *Controller {
return &Controller{}
}
func (c *Controller) AddUser(r *ghttp.Request) {
r.Response.Writeln("添加用户")
}
func (c *Controller) UpdateUser(r *ghttp.Request) {
r.Response.Writeln("更新用户")
}
func (c *Controller) DeleteUser(r *ghttp.Request) {
r.Response.Writeln("删除用户")
}
func (c *Controller) ListUser(r *ghttp.Request) {
r.Response.Writeln("用户列表")
}
func (c *Controller) GetUser(r *ghttp.Request) {
r.Response.Writeln("查询一个用户")
}
func (c *Controller) Post(r *ghttp.Request) {
r.Response.Writeln("添加用户")
}
func (c *Controller) Put(r *ghttp.Request) {
r.Response.Writeln("更新用户")
}
func (c *Controller) Delete(r *ghttp.Request) {
r.Response.Writeln("删除用户")
}
func (c *Controller) Get(r *ghttp.Request) {
r.Response.Writeln("查询一个用户")
}
internal/cmd/cmd.go
package cmd
import (
"context"
// 引入控制器user包
"gf_demo/internal/controller/user"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcmd"
)
var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
s := g.Server()
// 定义对象
usercontroller := user.New()
// 将对象方法绑定到路由
s.BindHandler("/adduser", usercontroller.AddUser)
s.Run()
return nil
},
}
)
对象注册
对象里的方法可以批量注册
相关方法
func (s *Server) BindObject(pattern string, object interface{}, method ...string)
func (s *Server) BindObjectMethod(pattern string, object interface{}, method string)
func (s *Server) BindObjectRest(pattern string, object interface{})
绑定全部公共方法
internal/cmd/cmd.go
package cmd
import (
"context"
"starting/internal/controller/user"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcmd"
)
var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
s := g.Server()
usercontroller := user.New()
// 绑定user控制器中所有公共方法
s.BindObject("/user", usercontroller)
s.Run()
return nil
},
}
)
绑定指定方法
usercontroller := user.New()
// 绑定user控制器中多个方法
s.BindObject("/user", usercontroller, "AddUser,UpdateUser")
// 绑定单个方法
s.BindObjectMethod("/deluser", usercontroller, "DeleteUser")
以RESTFul方绑定对象方法
usercontroller := user.New()
s.BindObjectRest("/user", usercontroller)
分组注册
可以为不同路由设置一个相同的前缀,即分组路由,分组路由有以下两种写法
s := g.Server()
usercontroller := user.New()
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")
})
})
s.Run()
s := g.Server()
usercontroller := user.New()
group := s.Group("/user")
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Bind(
usercontroller, // 绑定到控制器对象
)
// 可以用GET POST PUT等定义路由
group.GET("/get", func(r *ghttp.Request) {
r.Response.Writeln("/user/get")
})
s.Run()
规范路由
GoFrame中提供了规范化的路由注册方式,注册方法如下
func Handler(ctx context.Context, req *Request) (res *Response, err error)
其中Request
与Response
为自定义的结构体。
通过如下方式指定请求方法与路径
type HelloReq struct {
g.Meta `path:"/hello" method:"get"`
}
请求输入
普通请求输入
基础代码如下:路由用group绑定到控制器后,在控制器中写如下方法,以下代码均在此修改:
func (c *Controller) Params(request *ghttp.Request) {
m := request.GetQueryMap()
request.Response.WriteJson(m)
}
query参数获取
query参数是指以?a=1&b=2
的形式写在url中的参数,通常由GET方法传递。
单个参数值
m := request.GetQuery("name")
GetQuery可以指定参数名称,获取对应的参数值,如果值不存在,则返回nil
还可以指定默认值,当对应参数值不存在时,返回指定的默认值
m := request.GetQuery("name", "孙行者")
返回的是一个gvar.Var
类型,可以根据需要进行类型转换,常用类型转换方法如下
func (v *Var) Bytes() []byte
func (v *Var) String() string
func (v *Var) Bool() bool
func (v *Var) Int() int
func (v *Var) Int8() int8
func (v *Var) Int16() int16
func (v *Var) Int32() int32
func (v *Var) Int64() int64
func (v *Var) Uint() uint
func (v *Var) Uint8() uint8
func (v *Var) Uint16() uint16
func (v *Var) Uint32() uint32
func (v *Var) Uint64() uint64
func (v *Var) Float32() float32
func (v *Var) Float64() float64
func (v *Var) Time(format ...string) time.Time
func (v *Var) Duration() time.Duration
func (v *Var) GTime(format ...string)
批量获取Query参数
GoFrame中提供了GetQueryMap
,GetQueryMapStrStr
,GetQueryMapStrVar
三个方法用于批量获取Query参数,三个方法使用方式一致,只是返回类型不同。
- 获取全部Query参数
m := request.GetQueryMap()
- 指定需要获取的参数名称与默认值
m := request.GetQueryMap(map[string]interface{}{"name": "者行孙", "age": 600})
将Query参数转化为自定义结构体
可以自定义结构体,将请求参数直接转化为对应的结构体:
type user struct {
Name string
Age int
}
var u *user
err := request.ParseQuery(&u)
if err != nil {
request.Response.WritelnExit("转换出借")
}
如上,结构体中成员为Name
、Age
,参数为name
和age
则参成功转换,如果结构体成员变量名与参数名不一致则无法转换,此时需要为成员变量指定其对应的参数,可以用json:
/param:
/p:
这些方式来指定。如下
type user struct {
UserName string `json:"name"`
UserAge int `p:"age"`
}
表单参数获取(POST参数获取)
表单参数获取是指获取application/x-www-form-urlencoded
、application/form-data
、multipart/form-data
等数据,也可以用来获取以json格式提交的数据,简单理解即为可以获取POST方法提交的数据。
单个参数
m := request.GetForm("name")
GetForm
用于指定参数名称,获取对应参数值,如果对应参数不存在,返回nil
也可以指定默认值,当指定参数不存在时,返回默认值
m := request.GetForm("name", "烧包谷")
返回的是一个gvar.Var
类型,可以根据需要进行类型转换
批量获取请求数据
可以用GetFormMap
、GetFormMapStrStr
、GetFormMapStrVar
批量获取请求数据,三个方法使用方式一样,只是返回的Map类型不同。
m := request.GetFormMap()
可以指定需要获取的参数以及默认值
m := request.GetFormMap(map[string]interface{}{"name": "大洋芋"})
将请求数据转化为自定义结构体
和Query参数一样,也可以将请求参数直接转为自定义结构体。如果结构体成员名称与参数名称不一致,也可以用json:
、param:
、p:
这些tag来指定对应的参数名称
type user struct {
UserName string `json:"name"`
UserAge int `p:"age"`
}
var u *user
err := request.ParseForm(&u)
if err != nil {
request.Response.WritelnExit("转换出借")
}
动态路由参数获取
动态路由需要对现有代码进行一点改动,需要先在api
包中定义请求与返回数据格式,对指定的路由进行动态注册:
api
package api
import (
"github.com/gogf/gf/v2/frame/g"
)
type Res struct {
g.Meta `mime:"text/html"`
}
type ParamReq struct {
g.Meta `path:"/params/:name" method:"all"`
}
再将控制器的的方法利用api
数据结构进行修改:
Controller
func (c *Controller) Params(ctx context.Context, req *api.ParamReq) (res *api.Res, err error) {
request := g.RequestFromCtx(ctx)
u := request.GetRouter("name") //取出动态路由 :name 名称参数
request.Response.WriteJson(g.Map{"data": u})
return
}
获取单个参数 [由路由 :name 变量名传递]
u := request.GetRouter("name")
返回gvar.Var
类型,可以按需要进行类型转换。也可以指定默认值。
批量获取参数
u := request.GetRouterMap()
返回值为map[string]string
。如果没有设置动态路由,则返回nil
所有请求参数获取
GoFrame中还提供了一些方法获取所有请求参数,用法与上面两种类似,只是不区分请求方法。如果GET和POST提供的参数名称相同,则POST参数优先。
获取单个参数
data := request.GetRequest("name")
// 简写
data := request.Get("name")
返回gvar.Var
类型,可以提供默认值。
批量获取请求参数
data := request.GetRequestMap()
// 可以指定需要获取的参数名及默认值
data := request.GetRequestMap(g.Map{"name": ""})
// 还有以下几种
data := request.GetRequestMapStrStr()
data := request.GetRequestMapStrVar()
将请求参数转为自定义结构体
request.Parse(&u) // u为自定义结构体指针
Api请求输入
在api
中定义请求与响应数据结构,可以直接将需要接收的参数定义为请求结构体的成员,请求时会自动转为对应结构体。
例如,将前面的api
请求部分改为
type ParamReq struct {
g.Meta `path:"/params" method:"post"`
UserName string `p:"name" d:"林冲"`
UserAge int `p:"age" d:"110"`
}
其中p:
或param:
用于指定该成员对应的请求参数名,d:
或default:
用于指定默认值。如果Query与Body中有相同名称的参数,则以Body中的参数优先。
响应输出
在控制器中新建如下方法,用来测试响应输出。以下所有代码均在此处修改。
func (c *Controller) Resp(req *ghttp.Request) {
// 以下代码在此写
}
文本数据返回
GoFrame中通过以下方法返回文本数据到客户端:
func (r *Response) Write(content ...interface{})
func (r *Response) WriteExit(content ...interface{})
func (r *Response) Writef(format string, params ...interface{})
func (r *Response) WritefExit(format string, params