目录
一. go-zero 基础解释
- 教程文档地址:
二. 搭建一个基础的go-zero服务
- new–>Project–>Go modules (Environment 下输入代理地址"GOPROXY=https://goproxy.cn,direct")
注意新版本的Goland开发工具默认的go选项,就是以前的"Go modules"直接创建项目就可以了,并且GOPATH 的项目重命名为 Go (GOPATH)
- 如果创建的Project没有基于go mod,执行"go mod init 项目名称" 在当前目录中初始化和创建一个新的go.mod文件
- 执行go get命令下载go-zero
go get -u github.com/zeromicro/go-zero
1. 安装 goctl
-
goctl的优点
-
执行命令
# Go 1.15 及之前版本
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro/go-zero/tools/goctl@latest
# Go 1.16 及以后版本
GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest
- 执行"goctl -v" 查看版本校验是否安装成功
- goctl使用说明
- goland安装Goctl插件
2. 使用goctl命令创建一个go-zero服务
- 切换到上面创建的Project根目录,执行Goctl创建命令
//"core"是一个包名,可以自定义
goctl api new core
- 执行完毕后生成以下文件
api目录结构介绍
├── etc
│ └── core-api.yaml // 配置文件
├── go.mod // mod文件
├── greet.api // api描述文件
├── core.go // main函数入口
└── internal
├── config
│ └── config.go // 配置声明type
├── handler // 路由及handler转发
│ ├── greethandler.go
│ └── routes.go
├── logic // 业务逻辑
│ └── greetlogic.go
├── middleware // 中间件文件
│ └── coremiddleware.go
├── svc // logic所依赖的资源池
│ └── servicecontext.go
└── types // request、response的struct,根据api自动生成,不建议编辑
└── types.go
main函数介绍
- 其中"core.go"文件是main函数入口
package main
import (
"flag"
"fmt"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/rest"
"go_cloud_demo/core/internal/config"
"go_cloud_demo/core/internal/handler"
"go_cloud_demo/core/internal/svc"
)
// 1.命令行参数,设置项目运行读取配置文件地址
//手动调用main方法启动服务时路径修改为"core/etc/core-api.yaml"
var configFile = flag.String("f", "etc/core-api.yaml", "the config file")
func main() {
//1.解析命令行参数
flag.Parse()
//读取配置文件信息到config.Config结构体上
var c config.Config
conf.MustLoad(*configFile, &c)
//2.创建server
server := rest.MustNewServer(c.RestConf)
defer server.Stop()
//3.创建ServiceContext
ctx := svc.NewServiceContext(c)
//4.注册Handler逻辑
handler.RegisterHandlers(server, ctx)
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
//5.启动服务
server.Start()
}
- 可以通过命令或直接运行main方法启动服务(注意启通过命令行启动时要切换到goctl创建的core文件夹下,执行main方法启动时要修改读取配置文件的路径否则可能会报找不到文件)
go run core.go -f etc/core-api.yaml
- 查看etc/core-api.yaml配置文件中设置的ip端口号,发起访问
介绍etc下的配置文件与intenal/config下的Config结构体
- 查看core.go文件,该文件中定义了main方法, 在启动服务时会基于命令行的方式读取etc文件夹下的core-api.yaml配置文件,将读取到的配置信息解析设置到intenal/config下的Config结构体上, 然后通过该结构体传递数据,设置创建Server
//当前这个Config是自动生成的
//后续如果有其它读取配置文件信息的需求,
//根据需求可以给这个结构体增加相关的属性字段
type Config struct {
rest.RestConf
}
RestConf struct {
service.ServiceConf
Host string `json:",default=0.0.0.0"` //ip
Port int //端口号
CertFile string `json:",optional"`
KeyFile string `json:",optional"`
Verbose bool `json:",optional"`
MaxConns int `json:",default=10000"` //最大连接数
MaxBytes int64 `json:",default=1048576"`
// milliseconds
Timeout int64 `json:",default=3000"` //超时时间
CpuThreshold int64 `json:",default=900,range=[0:1000]"`
Signature SignatureConf `json:",optional"`
}
SignatureConf struct {
Strict bool `json:",default=false"`
Expiry time.Duration `json:",default=1h"`
PrivateKeys []PrivateKeyConf
}
- 查看提供的配置文件core-api.yaml
Name: core-api
Host: 0.0.0.0
Port: 8888
- 这个Config是基于goctl命令创建服务时默认生成的,后续根据需求可以给这个Config增加其它配置属性,例如要增加连接mysql的配置
Name: core-api
Host: 0.0.0.0
Port: 8888
mySqlConfig:
dataBase: demo
Url: localhost
userName: root
password: root
- 改写Config
type Config struct {
rest.RestConf
MySqlConfig MySqlConfig `json:"mySqlConfig"`
}
type MySqlConfig struct {
DataBase string `json:"dataBase"`
Url string `json:"url"`
UserName string `json:"userName"`
Password string `json:"password"`
}
//此时如果执行下方代码读取yaml配置文件时,
//就会吧mysql相关的配置设置到Config的响应属性上
var c Config
conf.MustLoad("文件路径/文件名.yaml", &c)
介绍rest.MustNewServer(c.RestConf) 创建Server
- 在上一步读取到了配置参数,获取配置参数,传递给rest下的MustNewServer()方法用来创建Server, 在MustNewServer()函数中会调用NewServer()
- 在NewServer()中会创建一个Server结构体对象,在创建这个Server结构体对象时会:
- 调用 newEngine©创建 engine
- 调用 NewRouter() 初始化 patRouter
- 最终返回创建的Server
func MustNewServer(c RestConf, opts ...RunOption) *Server {
//调用NewServer()
server, err := NewServer(c, opts...)
if err != nil {
log.Fatal(err)
}
return server
}
func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
if err := c.SetUp(); err != nil {
return nil, err
}
server := &Server{
ngin: newEngine(c),
router: router.NewRouter(),
}
opts = append([]RunOption{WithNotFoundHandler(nil)}, opts...)
for _, opt := range opts {
opt(server)
}
return server, nil
}
1. engine
func newEngine(c RestConf) *engine {
svr := &engine{
conf: c,
}
if c.CpuThreshold > 0 {
svr.shedder = load.NewAdaptiveShedder(load.WithCpuThreshold(c.CpuThreshold))
svr.priorityShedder = load.NewAdaptiveShedder(load.WithCpuThreshold(
(c.CpuThreshold + topCpuUsage) >> 1))
}
return svr
}
2. patRouter
// NewRouter returns a httpx.Router.
func NewRouter() httpx.Router {
return &patRouter{
trees: make(map[string]*search.Tree),
}
}
3. svc.NewServiceContext©创建ServiceContext
- 读取配置文件后将配置数据解析到Config结果体上,查看这一步骤执行的svc下的NewServiceContext()源码,内部只是将Config结果转换成了ServiceContext结构,供后续使用
- 查看ServiceContext ,内部持有一个Config,后续可以根据需求向该结构体添加需要的属性字段
type ServiceContext struct {
Config config.Config
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
//后续可以根据需求添加需要的属性字段
//例如添加mysql连接句柄属性,创建ServiceContext时拿到config配置信息,
//获取myql相关配置,调用初始化mysql连接方法获取连接设置到该属性上
//......
}
}
handler.RegisterHandlers(server, ctx) 注册业务Handler
- 内部涉及到的相关流程如下:
- 首先不需要创建业务结构体, 业务结构体上实现业务方法
- 针对指定业务,获取业务结构体,业务结构等相关信息封装为Handler
- 拿到Handler后,设置请求路径,请求方法,封装Route
- 调用server.AddRoutes()将路由注册到server中
- 查看生成的示例
1. 编写业务结构体,实现业务方法示例
- 业务逻辑需要编写在internal/logic/xxx.go下,查看当前生成的core_logic.go文件:
- 内部提供了一个CoreLogic结构体,可以将这个结构体看为业务结构体,默认情况下有一个对外的业务接口就会有一个这样的业务结构体
- 这个CoreLogic业务结构体上实现了一个Core(req *types.Request) (resp *types.Response, err error)方法,这个方法就是对应的业务方法
- 针对这个业务结构体提供了一个NewCoreLogic()初始化函数,在执行业务时首先要调用该函数进行初始化
package logic
import (
"context"
"github.com/zeromicro/go-zero/core/logx"
"go_cloud_demo/core/internal/svc"
"go_cloud_demo/core/internal/types"
)
// 1.业务结构体
type CoreLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// 2.业务结构体初始化方法
func NewCoreLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CoreLogic {
return &CoreLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 3.业务方法
func (l *CoreLogic) Core(req *types.Request) (resp *types.Response, err error) {
if len(req.Name) <= 0 {
return nil, types.NewError("未接收到请求参数")
}
return &types.Response{
Message: "接收到请求参数:" + req.Name,
}, nil
}
2. 编写将业务module并封装为Handler的函数示例
- 上面编写了业务结构体,并在这个业务结构体上实现了业务方法Core(),针对这个业务,提供了初始化方法NewCoreLogic()
- 在internal/handler下针对每一个业务都会生成一个xxx_handler.go文件,该文件中编写将业务接口封装为Handler的函数,例如当前的CoreHandler()
package handler
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"go_cloud_demo/core/internal/logic"
"go_cloud_demo/core/internal/svc"
"go_cloud_demo/core/internal/types"
)
func CoreHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
//1.解析请求参数
var req types.Request
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
//2.创建CoreLogic结构体(基于modules)
l := logic.NewCoreLogic(r.Context(), svcCtx)
//3.执行CoreLogic上实现的业务方法并拿到返回结果
resp, err := l.Core(&req)
//4.响应
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}
3. 将Handler封装为Route调用server.AddRoutes()注册到Server示例
- 上方针对业务结构体,业务方法core(),提供了NewCoreLogic()初始化函数,并且在xxx_handler.go文件中生成了将这个业务封装为Handler的函数CoreHandler()
- 查看生成的"/handler/routes.go"文件,会调用XXXHandler()函数,针对这个业务Handler设置对应的接口路径,请求方法,封装为Route路由,并且调用server.AddRoutes()将这个路由注册到server中
package handler
import (
"net/http"
"go_cloud_demo/core/internal/svc"
"github.com/zeromicro/go-zero/rest"
)
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
// 使用use方法添加一个自定义的中间件(这里是手动加的只是做个示例)
server.Use(func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 在请求处理前执行一些逻辑
fmt.Println("before request")
// 调用下一个处理函数
next(w, r)
// 在请求处理后执行一些逻辑
fmt.Println("after request")
}
})
//2.调用server.AddRoutes()将Route注册到Server
server.AddRoutes(
//1.将Handler封装为Route
[]rest.Route{
{
//请求方式
Method: http.MethodGet,
//接口路径
Path: "/from/:name",
//业务handler函数
Handler: CoreHandler(serverCtx),
},
},
)
}
- main函数中调用这个方法,启动服务
- 查看etc/core-api.yaml配置文件中设置的ip端口号,发起访问即可
- rest下常用功能函数简介
ToMiddleware():用于将一个接收和返回http.Handler的函数转换为一个Middleware类型的函数,Middleware类型的函数是一个接收和返回http.HandlerFunc的函数,可以用于实现拦截器或者中间件的逻辑
WithMiddlewares():用于为一组路由添加一组中间件,中间件会在路由处理前后执行一些逻辑,比如验证、转换、缓存等
WithChain():用于为一个Server添加一个拦截器链,拦截器链可以在请求处理前后执行一些逻辑,比如日志、监控、限流等
WithCors():用于为一个Server添加一个默认的跨域资源共享(CORS)处理器,可以指定允许的源域名列表,如果为空,则允许所有源
WithCustomCors():用于为一个Server添加一个自定义的跨域资源共享(CORS)处理器,可以指定自定义的响应头处理函数和不允许的请求方法处理函数
WithPrefix():用于为一组路由添加一个公共的前缀,比如/api/v1
WithPriority():用于为一组路由设置优先级标志,如果为true,则这组路由会使用优先级限流器进行限流,否则使用普通限流器
WithRouter():用于为一个Server指定一个自定义的路由器,路由器是用于匹配和分发请求的对象,默认使用patRouter
WithJwt():用于为一组路由开启jwt验证功能,并指定密钥
WithJwtTransition():用于为一组路由开启jwt验证功能,并指定当前密钥和上一个密钥,用于平滑过渡密钥变更
WithMaxBytes():用于为一组路由设置最大请求体大小限制,如果请求体超过这个大小,则返回错误
WithNotFoundHandler():用于为一个Server指定一个自定义的未找到匹配路由处理器,如果为空,则使用默认的处理器
WithNotAllowedHandler():用于为一个Server指定一个自定义的不允许的请求方法处理器,如果为空,则使用默认的处理器
WithSignature():用于为一组路由开启签名验证功能,并指定签名配置信息
WithTimeout():用于为一组路由设置超时时间限制,如果请求处理超过这个时间,则返回超时错误
WithTLSConfig():用于为一个Server指定TLS加密通信配置信息,比如证书、密钥等
WithUnauthorizedCallback():用于为一个Server指定一个自定义的未授权回调函数,用于处理未授权的请求,比如返回401状态码或者重定向到登录页面
WithUnsignedCallback():用于为一个Server指定一个自定义的未签名回调函数,用于处理未签名的请求,比如返回403状态码或者提示用户签名
- Server上的use方法与rest下的WithMiddlewares()注册中间件的区别: use方法是用于为一个Server添加一个中间件,这个中间件会应用于所有的路由,use方法每次只能添加一个中间件,而WithMiddlewares或WithMiddleware()方法可以为指定一组路由添加中间件
基于.api文件使用goctl命令构建服务或生成代码示例
- 上方的示例代码都是通过"goctl api new 文件夹名称" 命令生成的,生成的文件中存在一个".api"结尾的文件,基于这个文件,可以执行goctl命令生成对应的handler,logic业务类,
- 例如当前要生成一个用户查询接口,在".api"文件中编写生成模板
- 定义接口需要的入参,反参
- 定义将业务接口封装为Handler的函数名
- 指定接口请求方式
- 指定接口url以及入参反参类型
//1.接口需要的入参
type UserQuery {
//options是入参校验
Name string `path:"name,options=you|me"`
}
//2.接口需要的反参
type UserData {
Message string `json:"message"`
}
//3."xxx-api"中定义接口
service core-api {
//指定将当前业务接口封装为Handler的函数名
@handler UserQueryHandler
//指定接口请求方式 请求路径 入参类型 反参类型
post /user/query(UserQuery) returns (UserData)
//后续如果需要增加其它接口依次向下写即可
//@handler UserQueryHandler
//post /user/query(UserQuery) returns (UserData)
}
- 切换到".api"文件所在目录,执行生成命令
//通过"core.api"文件生成
//goctl api go 表示生成go语言的服务
//api *.api 指定api文件
//dir ./ 指定生成的路径
goctl api go -api core.api -dir . -style go_zero
- 此时查看项目目录中多了几个文件
- logic文件夹下生成了编写业务结构体及方法(但是方法内部是空的需要自己编写)
- handler文件夹下生成了将该业务接口封装为Handler的方法
- 并且在handler文件夹中的routes.go文件中将该Handler封装为Router,注册到了Server中
- types文件夹中的types下生成了接口执行需要的入参反参结构体