下面解释单体服务的helloworld程序
在go-zero中,只需要写好
.api文件就可以自动由goctl生成代码,程序员只需要写相关的逻辑即可,下面就是使用goctl api new helloworld进行生成代码文件,在使用goctl之前,需要对goctl进行安装从其中安装go-zero官网
PS D:\awesomeProject\test-go-zero> goctl api new helloworld
Done.
PS D:\awesomeProject\test-go-zero> tree /F .\helloworld\
D:\AWESOMEPROJECT\TEST-GO-ZERO\HELLOWORLD
│ helloworld.api
│ helloworld.go
├───etc
│ helloworld-api.yaml
└───internal
├───config
│ config.go
├───handler
│ helloworldhandler.go
│ routes.go
├───logic
│ helloworldlogic.go
├───svc
│ servicecontext.go
└───types
types.go上面是生成的代码结构:
/etc: 下面存放的是配置文件/internal: 下面存放的是具体的代码文件
config: 静态配置文件对应的结构体声明目录handler:handler 目录,可选,一般 http 服务会有这一层做路由管理,handler为固定后缀logic:业务目录,所有业务编码文件都存放在这个目录下面,logic为固定后缀svc:依赖注入目录,所有 logic 层需要用到的依赖都要在这里进行显式注入types:结构体存放目录
配置文件
配置文件在etc目录下的
helloworld-api.yaml,为什么是这个文件呢?在启动文件中可以找到答案govar configFile = flag.String("f", "etc/client-api.yaml", "the config file")所以说可以根据自己的需要进行指定.使用
-f,缺省值为etc/client-api.yaml,再看其中的内容yamlName: client-api Host: 0.0.0.0 Port: 8080为什么需要配置这些内容呢,这可以在
internal/conf中找到答案gotype Config struct { rest.RestConf } // 下面是rest.RestConf的内容 RestConf struct { service.ServiceConf Host string `json:",default=0.0.0.0"` 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"` // There are default values for all the items in Middlewares. Middlewares MiddlewaresConf // TraceIgnorePaths is paths blacklist for trace middleware. TraceIgnorePaths []string `json:",optional"` }从其中可以看出来,配置文件之所以可以配置这些东西,是因为其中有
RestConf,这些都是它的属性。需要注意的是,这个是通过匿名结构体的方式放入的,所以在配置yaml的时候,可以不用加前缀,直接写RestConf中出现的内容,但是如果出现以下形式:gotype Config struct { RestConf rest.RestConf }那么在书写yaml的时候,需要加上前缀,就像这样:
yamlRestConf: Name: client-api Host: 0.0.0.0 Port: 8080相应的地方也需要修改,所以说,建议直接使用原来生成的代码,不做修改
helloworld.api
代码几乎都是根据这个文件进行生成的
# 这里的path指定的name,是和下面service对应的:name对应的,会将请求的这部分数据存到Request的Name属性上
type Request {
Name string `path:"name,options=you|me"`
}
type Response {
Message string `json:"message"`
}
service helloworld-api {
@handler HelloworldHandler
get /from/:name(Request) returns (Response)
}上面是api的全部内容,下面就是它生成的东西,会一一列举出来
type Request {
Name string `path:"name,options=you|me"`
}
type Response {
Message string `json:"message"`
}
// 以上的内容是api文件内容,而下面就是生成的内容
/////////////////////////////////////////////////////////////////////////
// 以下代码生成在/internal/types目录下
// Code generated by goctl. DO NOT EDIT.
package types
type Request struct {
Name string `path:"name,options=you|me"`
}
type Response struct {
Message string `json:"message"`
}可以看到在api文件中定义的数据类型,在types中出现,具体数据类型的,在官网对api类型的说明中可以找到
service helloworld-api {
@handler HelloworldHandler
get /from/:name(Request) returns (Response)
}在说这个内容之前,需要看几个代码
func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
// 以上内容是对配置文件的加载
server := rest.MustNewServer(c.RestConf)
defer server.Stop()
// 以上是生成一个http server,并且在最后关闭
ctx := svc.NewServiceContext(c)
handler.RegisterHandlers(server, ctx)
// 以上使用config中的内容生成context,这个context和golang中的context不同,需要区分开,这里的context里面存储着一些client,比如说在使用redis时,需要使用client,可以保存在其中,然后再logics里面进行调用
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
server.Start()
}这里就引出了第二个文件
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodGet,
Path: "/from/:name",
Handler: HelloworldHandler(serverCtx),
},
},
)
}
/// helloworldhandler.go
func HelloworldHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.Request
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := logic.NewHelloworldLogic(r.Context(), svcCtx)
resp, err := l.Helloworld(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}路由的注册在这里,这里就可以看到api文件的部分内容了,api文件中的Get就对应着这里的方法,还有路径,以及Handler方法,也是由@handler指定的,可以在目录结构中看到router.go和helloworldhandler.go是在同一个文件夹下,之后所有的handler都将会在该文件夹下生成文件,而这里的并不是写逻辑的地方,真正写逻辑的地方在logic包下,这里的handler只是对参数进行解析,以及对结果进行返回的一些处理
package logic
type HelloworldLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewHelloworldLogic(ctx context.Context, svcCtx *svc.ServiceContext) *HelloworldLogic {
return &HelloworldLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *HelloworldLogic) Helloworld(req *types.Request) (resp *types.Response, err error) {
resp = new(types.Response)
resp.Message = req.Name
return
}真正写逻辑的地方在logic包下的,这里已经指定了返回值使用的变量,所以只需要对这个变量进行赋值,不需要声明,return也不需要进行指明返回的变量
添加新的内容
在go-zero中添加方法也是简单的,只需要修改api文件,然后重新生成代码即可
type Request {
Name string `path:"name,options=you|me"`
}
type Response {
Message string `json:"message"`
}
type GetUserRequest {
Id int `path:"id"`
Name string `path:"name"`
Gender string `path:"gender,options=m|w"`
}
type GetUserResponse {
Id int `path:"id"`
Name string `path:"name"`
Gender string `path:"gender"`
}
service helloworld-api {
@handler HelloworldHandler
get /from/:name (Request) returns (Response)
@handler GetUserHandler
get /:id/:name/:gender (GetUserRequest) returns (GetUserResponse)
}在其中添加了GetUserHandler相关的内容,定义了这个方法的请求体的数据结构,以及返回体的数据结构,这些都可以在代码的结构可以看出来
PS D:\awesomeProject\test-go-zero\helloworld> tree /F
D:.
│ helloworld.api
│ helloworld.go
├───etc
│ helloworld-api.yaml
└───internal
├───config
│ config.go
├───handler
│ getuserhandler.go
│ helloworldhandler.go
│ routes.go
├───logic
│ getuserlogic.go
│ helloworldlogic.go
├───svc
│ servicecontext.go
└───types
types.go相比于之前的文件,新添加了
getuserlogic.go和getuserhandler.go,可以看看router.go与之前的区别
// Code generated by goctl. DO NOT EDIT.
package handler
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodGet,
Path: "/from/:name",
Handler: HelloworldHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/:id/:name/:gender",
Handler: GetUserHandler(serverCtx),
},
},
)
}就是添加了这个请求相关的内容,接下来看看
getuserhandler.go的内容
func GetUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GetUserRequest
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := logic.NewGetUserLogic(r.Context(), svcCtx)
resp, err := l.GetUser(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}相比于之前的HellowWorld相关的代码,没有什么区别,只有在request和response的类型的区别,还有执行的logic的相关代码的区别,下面看看logic相关代码
package logic
import (
"context"
"test-go-zero/helloworld/internal/svc"
"test-go-zero/helloworld/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetUserLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserLogic {
return &GetUserLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetUserLogic) GetUser(req *types.GetUserRequest) (resp *types.GetUserResponse, err error) {
resp = new(types.GetUserResponse)
resp.Id = req.Id
resp.Name = req.Name
resp.Gender = req.Gender
return
}在这里可以看出,其实这部分的代码也是差不多的,差别在于对请求的处理的相关代码
总结
在写好api文件之后,几乎是不需要对其他的东西进行操作的,非常的便捷,只需要关注业务逻辑即可