Skip to content

下面解释单体服务的helloworld程序

在go-zero中,只需要写好.api文件就可以自动由goctl生成代码,程序员只需要写相关的逻辑即可,下面就是使用goctl api new helloworld进行生成代码文件,在使用goctl之前,需要对goctl进行安装

从其中安装go-zero官网

powershell
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,为什么是这个文件呢?在启动文件中可以找到答案

go
var configFile = flag.String("f", "etc/client-api.yaml", "the config file")

所以说可以根据自己的需要进行指定.使用-f,缺省值为etc/client-api.yaml,再看其中的内容

yaml
Name: client-api
Host: 0.0.0.0
Port: 8080

为什么需要配置这些内容呢,这可以在internal/conf中找到答案

go
type 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中出现的内容,但是如果出现以下形式:

go
type Config struct {
	RestConf rest.RestConf
}

那么在书写yaml的时候,需要加上前缀,就像这样:

yaml
RestConf:
    Name: client-api
    Host: 0.0.0.0
    Port: 8080

相应的地方也需要修改,所以说,建议直接使用原来生成的代码,不做修改

helloworld.api

代码几乎都是根据这个文件进行生成的

apl
# 这里的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的全部内容,下面就是它生成的东西,会一一列举出来

go
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类型的说明中可以找到

go
service helloworld-api {
	@handler HelloworldHandler
	get /from/:name(Request) returns (Response)
}

在说这个内容之前,需要看几个代码

go

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()
}

这里就引出了第二个文件

golang
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只是对参数进行解析,以及对结果进行返回的一些处理

go
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文件,然后重新生成代码即可

powershell
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相关的内容,定义了这个方法的请求体的数据结构,以及返回体的数据结构,这些都可以在代码的结构可以看出来

powershell
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.gogetuserhandler.go,可以看看router.go与之前的区别

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的内容

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相关代码

go
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文件之后,几乎是不需要对其他的东西进行操作的,非常的便捷,只需要关注业务逻辑即可