go-zero学习 第二章 进阶之API

重要提示

  1. 因官网重新改版,本文是基于官网最新版本的文档并整合旧文档重新进行全面总结、归纳。
  2. 本文主要对官网 指南 进行提炼总结,未涉及部分将在后续章节陆续补充完善。
  3. API部分主要是对外提供服务,go-zero可以通过编写一个 api 文件,生成一个完整的api服务。

相关命令

  1. api生成api服务命令
go-zero-api\api> goctl api go -api ./doc/ucenter.api -dir ./code

2 API 语法

参考1:语句

api文件中包含以下语句,按从上到下的顺序:

  1. syntax 语句
  2. info 语句
  3. import 语句
  4. 结构体语句(HTTP 服务的请求/响应体语句)
  5. @server 语句
  6. @doc 语句
  7. @handler 语句
  8. HTTP 路由语句
  9. HTTP 服务声明语句
  10. 注释语句

完整的 api 语法示例

syntax = "v1"

info (
    title:   "api 文件完整示例写法"
    desc:    "演示如何编写 api 文件"
    author:  "keson.an"
    date:    "2022 年 12 月 26 日"
    version: "v1"
)

type UpdateReq {
    Arg1 string `json:"arg1"`
}

type ListItem {
    Value1 string `json:"value1"`
}

type LoginReq {
    Username string `json:"username"`
    Password string `json:"password"`
}

type LoginResp {
    Name string `json:"name"`
}

type FormExampleReq {
    Name string `form:"name"`
}

type PathExampleReq {
    // path 标签修饰的 id 必须与请求路由中的片段对应,如
    // id 在 service 语法块的请求路径上一定会有 :id 对应,见下文。
    ID string `path:"id"`
}

type PathExampleResp {
    Name string `json:"name"`
}

@server (
    jwt:        Auth // 对当前 Foo 语法块下的所有路由,开启 jwt 认证,不需要则请删除此行
    prefix:     /v1 // 对当前 Foo 语法块下的所有路由,新增 /v1 路由前缀,不需要则请删除此行
    group:      g1 // 对当前 Foo 语法块下的所有路由,路由归并到 g1 目录下,不需要则请删除此行
    timeout:    3s // 对当前 Foo 语法块下的所有路由进行超时配置,不需要则请删除此行
    middleware: AuthInterceptor // 对当前 Foo 语法块下的所有路由添加中间件,不需要则请删除此行
    maxBytes:   1024 // 对当前 Foo 语法块下的所有路由添加请求体大小控制,单位为 byte,goctl 版本 >= 1.5.0 才支持
)
service Foo {
    // 定义没有请求体和响应体的接口,如 ping
    @handler ping
    get /ping

    // 定义只有请求体的接口,如更新信息
    @handler update
    post /update (UpdateReq)

    // 定义只有响应体的结构,如获取全部信息列表
    @handler list
    get /list returns ([]ListItem)

    // 定义有结构体和响应体的接口,如登录
    @handler login
    post /login (LoginReq) returns (LoginResp)

    // 定义表单请求
    @handler formExample
    post /form/example (FormExampleReq)

    // 定义 path 参数
    @handler pathExample
    get /path/example/:id (PathExampleReq) returns (PathExampleResp)
}

其他示例:https://go-zero.dev/docs/tasks/dsl/api#快速入门


1 syntax 语句
syntax 语句用于标记 api 语言的版本,不同的版本可能语法结构有所不同,随着版本的提升会做不断的优化,当前版本为 v1

示例:

syntax = "v1"

2 info 语句
info 语句是 api 语言的 meta 信息,其仅用于对当前 api 文件进行描述,暂不参与代码生成,其和注释还是有一些区别,注释一般是依附某个 syntax 语句存在,而 info 语句是用于描述整个 api 信息的,当然,不排除在将来会参与到代码生成里面来。

示例:

info(
	title : "XXX系统"
	author: "哈哈哈"
	email: "123456@qq.com"
	version: "1.0.0.0"
)

3 import 语句
import 语句是在当前 api 中引入其他 api 文件的语法块,其支持相对/绝对路径,不支持 package 的设计,如 time.Time

示例:

// 单行 import
import "foo"
import "/path/to/file"

// import 组
import ()
import (
    "bar"
    "relative/to/file"
)

4 结构体语句(HTTP 服务的请求/响应体语句)
api 中的数据类型基本沿用了 Golang 的数据类型,用于对 rest 服务的请求/响应体结构的描述。

示例1:

// 别名类型 [1]
type Int int
type Integer = int

// 空结构体
type Foo {}

// 单个结构体
type Bar {
    Foo int               `json:"foo"`
    Bar bool              `json:"bar"`
    Baz []string          `json:"baz"`
    Qux map[string]string `json:"qux"`
}

type Baz {
    Bar    `json:"baz"`
    // 结构体内嵌 [2]
    Qux {
        Foo string `json:"foo"`
        Bar bool   `json:"bar"`
    } `json:"baz"`
}

// 空结构体组
type ()

// 结构体组
type (
    Int int
    Integer = int
    Bar {
        Foo int               `json:"foo"`
        Bar bool              `json:"bar"`
        Baz []string          `json:"baz"`
        Qux map[string]string `json:"qux"`
    }
)

示例2(实际使用):

type (
	/* 1 公用类 */
	BaseModel {
		Id   int64  `json:"id"`   // id
		Name string `json:"name,optional"` // 名称
		Data string `json:"data,optional"` // 数据:string
	}

	/* 2 公共返回 */
	BaseModelResp {
		Code    string       `json:"code"`    // 状态码
		Message string       `json:"message"` // 提示信息
		Data    []*BaseModel `json:"data,optional"`
	}
	
	/* 3 分类标签接口的返回数据 */
	GetClassLabelResp {
		Code           string       `json:"code"`           // 状态码
		Message        string       `json:"message"`        // 提示信息
		ClassLabelList []*BaseModel `json:"classLabelList"` // 分类标签集合:id-名称
	}
)

注意

  1. 虽然语法上支持别名,但是在语义分析时会对别名进行拦截,这或在将来进行放开。
  2. 虽然语法上支持结构体内嵌,但是在语义分析时会对别名进行拦截,这或在将来进行放开。

除此之外

  1. 目前 api 语法中虽然支持了数组的语法,但是在语义分析时会对数组进行拦截,目前建议用切片替代,这或在将来放开。
  2. 不支持 package 设计,如 time.Time

5 @server 语句
@server 语句是对一个服务语句的 meta 信息描述,其对应特性包含但不限于:

  • jwt 开关
  • 中间件
  • 路由分组
  • 路由前缀

示例1:

// 空内容
@server()

// 有内容
@server (
    // jwt 声明
    // 如果 key 固定为 “jwt:”,则代表开启 jwt 鉴权声明
    // value 则为配置文件的结构体名称
    jwt: Auth

    // 路由前缀
    // 如果 key 固定为 “prefix:”
    // 则代表路由前缀声明,value 则为具体的路由前缀值,字符串中没让必须以 / 开头
    prefix: /v1

    // 路由分组
    // 如果 key 固定为 “group:”,则代表路由分组声明
    // value 则为具体分组名称,在 goctl生成代码后会根据此值进行文件夹分组
    group: Foo

    // 中间件
    // 如果 key 固定为 middleware:”,则代表中间件声明
    // value 则为具体中间件函数名称,在 goctl生成代码后会根据此值进生成对应的中间件函数
    middleware: AuthInterceptor

    // 超时控制
    // 如果 key 固定为  timeout:”,则代表超时配置
    // value 则为具体中duration,在 goctl生成代码后会根据此值进生成对应的超时配置
    timeout: 3s

    // 其他 key-value,除上述几个内置 key 外,其他 key-value
    // 也可以在作为 annotation 信息传递给 goctl 及其插件,但就
    // 目前来看,goctl 并未使用。
    foo: bar
)

示例2(实际使用):

@server(
	jwt: Auth
	middleware: CheckUrl
	group: app/appSystem
	prefix: /app/appSystem
)

6 @doc 语句
@doc 语句是对单个路由的 meta 信息描述,一般为 key-value 值,可以传递给 goctl 及其插件来进行扩展生成。

示例1:

// 单行 @doc
@doc "foo"

// 空 @doc 组
@doc ()

// 有内容的 @doc 组
@doc (
    foo: "bar"
    bar: "baz"
)

示例2(实际使用):

    @doc(
        summary: "XXX列表"
    )

7 @handler 语句
@handler 语句是对单个路由的 handler 信息控制,主要用于生成 golang http.HandleFunc 的实现转换方法。

示例:

@handler foo

@handler GetClassLabelHandler

注意:不论定义的handler是否有handler后缀,goctl根据api实际生成的代码文件均会以xxxhandler为后缀,所以在api中定义handler时加不加handler后缀没有意义。


8 HTTP 路由语句
路由语句是对此 HTTP 请求的具体描述,包括请求方法,请求路径,请求体,响应体信息。

示例:

// 没有请求体和响应体的写法
get /ping

// 只有请求体的写法
get /foo (foo)

// 只有响应体的写法
post /foo returns (foo)

// 有请求体和响应体的写法
post /foo (foo) returns (bar)

9 HTTP 服务声明语句
示例:

// 带 @server 的写法
@server (
    prefix: /v1
    group: Login
)
service user {
    @doc "登录"
    @handler login
    post /user/login (LoginReq) returns (LoginResp)

    @handler getUserInfo
    get /user/info/:id (GetUserInfoReq) returns (GetUserInfoResp)
}
@server (
    prefix: /v1
    middleware: AuthInterceptor
)
service user {
    @doc "登录"
    @handler login
    post /user/login (LoginReq) returns (LoginResp)

    @handler getUserInfo
    get /user/info/:id (GetUserInfoReq) returns (GetUserInfoResp)
}


// 不带 @server 的写法
service user {
    @doc "登录"
    @handler login
    post /user/login (LoginReq) returns (LoginResp)

    @handler getUserInfo
    get /user/info/:id (GetUserInfoReq) returns (GetUserInfoResp)
}

10 注释语句
api 领域特性语言中有 2 种格式:

  • 单行注释以 // 开始,行尾结束。
  • 多行注释(文档注释)以 /* 开始,以第一个 */ 结束。

3 API服务代码实战

参考1:请求参数

前面讲解了api文件的语法,这里在使用时具体讲解一些细节,主要有以下内容:

  1. path参数
  2. form参数
  3. json参数
  4. header参数
  5. 参数修饰符
  6. 文件上传/下载
  7. 请求体大小限制
  8. api分组
  9. 中间件
  10. 统一返回信息
  11. 鉴权【需要结合rpc服务,下一节再讲解】

1、2、3、4、5、6、7都是针对请求参数,1、2、3、4可以划归为一组,在绑定参数时,只能选择1、2、3、4其中一个。

请求参数:

标签类型描述提供方使用范围示例
path路由path,如/foo/:idgo-zerorequestpath:"id"
form标志请求体是一个form(POST方法时)或者一个query(GET方法时/search?name=keyword)go-zerorequestform:"name"
jsonjson序列化taggolangrequest、responsejson:"fooo"
headerHTTP header,如 Name: valuego-zerorequestheader:"name"

参数修饰符:

标签类型描述提供方使用范围示例
optional定义当前字段为可选参数go-zerorequestjson:"name,optional"
options定义当前字段的枚举值,多个以,隔开go-zerorequestjson:"gender,options=male,emale"
default定义当前字段默认值go-zerorequestjson:"gender,default=male"
range定义当前字段数值范围go-zerorequestjson:"age,range=[0:120]"

3.1 请求参数、文件上传/预览、分组示例

代码:请求参数、文件上传/预览、分组示例 代码示例
1 api文件:

syntax = "v1"

info(
	title : "go-zero-api"
	desc: "ucenter"
	author: "ximuqi"
	email: "xxx"
	version: "0.0.1"
)

type (
	/* 1 公用类(json) */
	BaseModelJson {
		Id   int64  `json:"id"`            // id
		Name string `json:"name,optional"` // 名称
		Data string `json:"data,optional"` // 数据
	}

	/* 2 公用类(form) */
	BaseModelForm {
		Id   int64  `form:"id"`            // id
		Name string `form:"name,optional"` // 名称
		Data string `form:"data,optional"` // 数据
	}

	/* 3 公用类 Path */
	PathReq {
		Id   int64  `path:"id"`   // id
		Name string `path:"name"` // 名称
	}

	/* 4 上传文件 */
	FileUploadReq {
		Id       int64   `form:"id"`                // 父级-id
		Type     int64   `form:"type,optional"`     // 类型 1:证书文件;2:私钥文件
		FileList []*byte `form:"fileList,optional"` // 文件列表
	}

	/* 5 下载/预览文件 */
	FileShowReq {
		Id      int64  `form:"id"`               // 文件-id
		FileUrl string `form:"fileUrl,optional"` // 文件地址
	}

	/* 6 用户账号密码登录 model */
	UserModel {
		Account  string `json:"account"`                      // 账号
		Password string `json:"password"`                     // 密码
		Gender   int64  `json:"gender,options=1:2:3"`         // 性别 1:未设置;2:男性;3:女性
		Current  int64  `json:"current,optional,default=1"`   // 当前页码
		PageSize int64  `json:"pageSize,optional,default=20"` // 每页数量
	}

	/* 7 用户登录返回 model */
	UserLoginResp {
		Id           int64  `json:"id"`           // 用户id
		Account      string `json:"account"`      // 账号
		Username     string `json:"username"`     // 登录账号
		Gender       int64  `json:"gender"`       // 性别 1:未设置;2:男性;3:女性
		Avatar       string `json:"avatar"`       // 头像
		AccessToken  string `json:"token"`        // token
		AccessExpire int64  `json:"accessExpire"` // 过期时间戳
		RefreshAfter int64  `json:"refreshAfter"` //
	}
)

@server(
	middleware: Check
	group: ucenter
	prefix: /ucenter
)
service uCenter {
	@doc(
		summary: "1 json参数"
	)
	@handler jsonParam
	post /jsonParam (BaseModelJson) returns (BaseModelJson)

	@doc(
		summary: "2 表单参数(post)"
	)
	@handler formParamPost
	post /formParamPost (BaseModelForm) returns (BaseModelJson)

	@doc(
		summary: "3 表单参数(Get)"
	)
	@handler formParamGet
	get /formParamGet (BaseModelForm) returns (BaseModelJson)

	@doc(
		summary: "4 path参数"
	)
	@handler pathParam
	get /pathParam/:id/:name (PathReq) returns (BaseModelJson)

	@doc(
		summary: "5 文件上传"
	)
	@handler uploadFile
	post /uploadFile (FileUploadReq) returns (BaseModelJson)

	@doc(
		summary: "6 文件下载"
	)
	@handler downloadFile
	get /downloadFile (FileShowReq)

	@doc(
		summary: "7 文件预览"
	)
	@handler previewFile
	get /previewFile (FileShowReq)
}

@server(
	group: group2
	prefix: /group2
)
service uCenter {
	@doc(
		summary: "8 登录"
	)
	@handler login
	post /login (UserModel) returns (UserLoginResp)
}

2 api生成api服务命令

go-zero-api\api> goctl api go -api ./doc/ucenter.api -dir ./code

3 代码结构

#查看 目录文件结构
tree /f
#查看 目录结构
tree

目录结构:

└─api
    ├─code
    │  ├─etc
    │  └─internal
    │      ├─config
    │      ├─handler
    │      │  ├─group2
    │      │  └─ucenter
    │      ├─logic
    │      │  ├─group2
    │      │  └─ucenter
    │      ├─middleware
    │      ├─svc
    │      └─types
    └─doc

目录结构说明:
etc:静态配置文件目录
conf:静态配置文件对应的结构体声明目录
handler:handler 为固定后缀,其下的两个分组一般不需要修改,只有在文件上传下载时需要特殊处理。
logic:业务目录,所有业务编码文件都存放在这个目录下面,logic 为固定后缀。
middleware:中间件。
svc:依赖注入目录,所有 logic 层需要用到的依赖都要在这里进行显式注入。
types:结构体存放目录。

通过目录结构可以看到,在handlerlogic目录下都有ucentergroup2文件夹,这是分组的结果,我们的逻辑代码主要在logic下。

4 请求测试
根据.api文件api模块代码生成时默认的端口是 8888,查看及修改是在etc目录下的yaml文件,具体修改详见代码。

1 json参数请求测试
logic/ucenter目录下jsonparamlogic.go编写代码逻辑。

请求地址:http://localhost:8888/ucenter/jsonParam
请求方式:POST
请求格式:JSON
请求数据:{"id":1,"name":"测试"}
返回结果:{"id":1,"name":"测试","data":""}

2 form参数请求测试【POST】
logic/ucenter目录下formparampostlogic.go编写代码逻辑。

请求地址:http://localhost:8888/ucenter/formParamPost
请求方式:POST
请求格式:FORM
请求数据:id=2,name="hello"
返回结果:{"id":2,"name":"hello","data":""}

3 form参数请求测试【GET】
logic/ucenter目录下formparamgetlogic.go编写代码逻辑。

请求地址:http://localhost:8888/ucenter/formParamGet?id=3&name=hello&data=测试
请求方式:GET
请求格式:URL参数
请求数据:id=3&name=hello&data=测试"
返回结果:{"id":3,"name":"hello","data":"测试"}

4 path参数请求测试
logic/ucenter目录下pathparamlogic.go编写代码逻辑。

请求地址:http://localhost:8888/ucenter/pathParam/4/hello
请求方式:GET
请求格式:URL参数
请求数据:id=4&name=hello"
返回结果:{"id":4,"name":"hello"}

5 文件上传请求测试
logic/ucenter目录下uploadfilelogic.go编写代码逻辑。

注意:go-zero对上传文件有大小限制,默认最大为1MB,可在etc目录下的yaml文件中修改大小MaxBytes,并设置上传文件的存储路径。

Name: uCenter
Host: 0.0.0.0
Port: 8888

#web请求到此api服务的超时时间
Timeout: 10000
# 将请求体最大允许字节数从1MB改为1000MB
MaxBytes: 1048576000

#文件
UploadFile:
  MaxFileNum: 1000
  MaxFileSize: 1048576000  # 1000MB
  SavePath: projects/go-zero-api/uploadFiles/

#日志配置
Log:
  Mode: file
  Path: log/go-zero-api
  Level: error
  Compress: true
  KeepDays: 180

请求地址:http://localhost:8888/ucenter/downloadFile
请求方式:GET
请求格式:FORM
请求数据:id=1&name=1_aab7803750b6412e98a9c63196efc6e8.png
返回结果:{"id":5,"name":"","data":"操作成功"}

6 文件下载
文件下载是把数据写到响应流中,所以需要同时修改downloadfilehandler.godownloadfilelogic.go,详见代码。

7 文件预览
文件预览也是把数据写到响应流中,所以需要同时修改previewfilehandler.gopreviewfilelogic.go,预览针对的是多媒体文件,如音视频、照片等,比下载少了在响应流中的流配置以及文件名称,详见代码。


3.2 中间件、统一返回信息 代码示例

代码:中间件、统一返回信息 代码示例

全局中间件的讲解

  • 中间件
  • 统一返回信息

中间件:
3.1 请求参数、文件上传/预览、分组示例 中的api文件中已经涉及了【局部】中间件,但未深入讲解。实际上api服务的中间件分为局部中间件全局中间件,在中间件中可以拦截请求/响应并作处理。

全局中间件是加在启动类上的,全局中间件、局部中间件都是一样的,只是位置不一样罢了:

	// 全局中间件:用法一
	server.Use(func(next http.HandlerFunc) http.HandlerFunc {
		return func(w http.ResponseWriter, r *http.Request) {
			logx.Info("全局中间件")
			next(w, r)
		}
	})

	// 全局中间件:用法二
	server.Use(middleware.NewCheckUrlMiddleware().Handle)

在中间件中处理数据时,不论是操作Redis、数据库,还是其他RPC,需要什么,在创建中间件时就New对应的环境即可。
全局中间件、局部中间件的使用顺序:
全局中间件→局部中间件1→局部中间件2→全局中间件

统一返回信息:
参考:HTTP 扩展

  1. 响应信息的统一封装处理涉及两部分,分别是错误信息的封装、响应信息的封装。
├─errorx
│      baseerror.go
│      errormsg.go
│
├─response
      response.go
  1. 使用示例
    改造pathparamlogic.go逻辑的返回结果改为使用封装的错误信息,pathparamhandler.go使用统一封装的响应。

3.3 修改统一返回信息的代码生成模板

参考:模板定制化

  1. 下载生成代码的模板
    查看参考文档
  2. 修改 /.goctl/${goctl版本号}/api/handler.tpl文件

原始模板:

package {{.PkgName}}

import (
	"net/http"

	"github.com/zeromicro/go-zero/rest/httpx"
	{{.ImportPackages}}
)

func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		{{if .HasRequest}}var req types.{{.RequestType}}
		if err := httpx.Parse(r, &req); err != nil {
			httpx.ErrorCtx(r.Context(), w, err)
			return
		}

		{{end}}l := {{.LogicName}}.New{{.LogicType}}(r.Context(), svcCtx)
		{{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}})
		if err != nil {
			httpx.ErrorCtx(r.Context(), w, err)
		} else {
			{{if .HasResp}}httpx.OkJsonCtx(r.Context(), w, resp){{else}}httpx.Ok(w){{end}}
		}
	}
}

修改后的模板:

package {{.PkgName}}

import (
	"net/http"

	"github.com/zeromicro/go-zero/rest/httpx"
	{{.ImportPackages}}
)

func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		{{if .HasRequest}}var req types.{{.RequestType}}
		if err := httpx.Parse(r, &req); err != nil {
			httpx.ErrorCtx(r.Context(), w, err)
			return
		}

		{{end}}l := {{.LogicName}}.New{{.LogicType}}(r.Context(), svcCtx)
		{{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}})
/*
		if err != nil {
			httpx.ErrorCtx(r.Context(), w, err)
		} else {
			{{if .HasResp}}httpx.OkJsonCtx(r.Context(), w, resp){{else}}httpx.Ok(w){{end}}
		}
*/
		response.Response(r.Context(), w, resp, err)
	}
}

修改后生成的xxxhandler.go代码:

package ucenter

import (
	"go-zero-api/common/response"
	"net/http"

	"github.com/zeromicro/go-zero/rest/httpx"
	"go-zero-api/api/code/internal/logic/ucenter"
	"go-zero-api/api/code/internal/svc"
	"go-zero-api/api/code/internal/types"
)

func FormParamGetHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var req types.BaseModelForm
		if err := httpx.Parse(r, &req); err != nil {
			httpx.ErrorCtx(r.Context(), w, err)
			return
		}

		l := ucenter.NewFormParamGetLogic(r.Context(), svcCtx)
		resp, err := l.FormParamGet(&req)
		/*
			if err != nil {
				httpx.ErrorCtx(r.Context(), w, err)
			} else {
				httpx.OkJsonCtx(r.Context(), w, resp)
			}
		*/
		response.Response(r.Context(), w, resp, err)
	}
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值