31 KiB
api 语法 概述 api 是 go-zero 自研的领域特性语言(下文称 api 语言 或 api 描述语言),旨在实现人性化的基础描述语言,作为生成 HTTP 服务最基本的描述语言。
api 领域特性语言包含语法版本,info 块,结构体声明,服务描述等几大块语法组成,其中结构体和 Golang 结构体 语法几乎一样,只是移出了 struct 关键字。
快速入门 本次仅以 demo 形式快速介绍 api 文件的写法,更详细的写法示例可参考 《API 定义完整示例》,详细 api 语法规范可参考 《API 规范》。
示例 1. 编写最简单的 ping 路由服务 syntax = "v1"
// 定义 HTTP 服务 service foo { get /ping } 示例 2. 编写一个登录接口 api 文件 syntax = "v1"
type (
// 定义登录接口的请求体
LoginReq {
Username string json:"username"
Password string json:"password"
}
// 定义登录接口的响应体
LoginResp {
Id int64 json:"id"
Name string json:"name"
Token string json:"token"
ExpireAt string json:"expireAt"
}
)
// 定义 HTTP 服务 // 微服务名称为 user,生成的代码目录和配置文件将和 user 值相关 service user { // 定义 http.HandleFunc 转换的 go 文件名称及方法 @handler Login // 定义接口 // 请求方法为 post // 路由为 /user/login // 请求体为 LoginReq // 响应体为 LoginResp,响应体必须有 returns 关键字修饰 post /user/login (LoginReq) returns (LoginResp) }
示例 3. 编写简单的用户服务 api 文件 syntax = "v1"
type (
// 定义登录接口的 json 请求体
LoginReq {
Username string json:"username"
Password string json:"password"
}
// 定义登录接口的 json 响应体
LoginResp {
Id int64 json:"id"
Name string json:"name"
Token string json:"token"
ExpireAt string json:"expireAt"
}
)
type (
// 定义获取用户信息的 json 请求体
GetUserInfoReq {
Id int64 json:"id"
}
// 定义获取用户信息的 json 响应体
GetUserInfoResp {
Id int64 json:"id"
Name string json:"name"
Desc string json:"desc"
}
// 定义更新用户信息的 json 请求体
UpdateUserInfoReq {
Id int64 json:"id"
Name string json:"name"
Desc string json:"desc"
}
)
// 定义 HTTP 服务 // @server 语法块主要用于控制对 HTTP 服务生成时 meta 信息,目前支持功能有: // 1. 路由分组 // 2. 中间件声明 // 3. 路由前缀 // 4. 超时配置 // 5. jwt 鉴权开关 // 所有声明仅对当前 service 中的路由有效 @server ( // 代表当前 service 代码块下的路由生成代码时都会被放到 login 目录下 group: login // 定义路由前缀为 "/v1" prefix: /v1 ) // 微服务名称为 user,生成的代码目录和配置文件将和 user 值相关 service user { // 定义 http.HandleFunc 转换的 go 文件名称及方法,每个接口都会跟一个 handler @handler login // 定义接口 // 请求方法为 post // 路由为 /user/login // 请求体为 LoginReq // 响应体为 LoginResp,响应体必须有 returns 关键字修饰 post /user/login (LoginReq) returns (LoginResp) }
// @server 语法块主要用于控制对 HTTP 服务生成时 meta 信息,目前支持功能有: // 1. 路由分组 // 2. 中间件声明 // 3. 路由前缀 // 4. 超时配置 // 5. jwt 鉴权开关 // 所有声明仅对当前 service 中的路由有效 @server ( // 代表当前 service 代码块下的所有路由均需要 jwt 鉴权 // goctl 生成代码时会将当前 service 代码块下的接口 // 信息添加上 jwt 相关代码,Auth 值为 jwt 密钥,过期 // 等信息配置的 golang 结构体名称 jwt: Auth // 代表当前 service 代码块下的路由生成代码时都会被放到 user 目录下 group: user // 定义路由前缀为 "/v1" prefix: /v1 ) // 注意,定义多个 service 代码块时,服务名称必须一致,因此这里的服务名称必须 // 和上文的 service 名称一样,为 user 服务。 service user { // 定义 http.HandleFunc 转换的 go 文件名称及方法,每个接口都会跟一个 handler @handler getUserInfo // 定义接口 // 请求方法为 post // 路由为 /user/info // 请求体为 GetUserInfoReq // 响应体为 GetUserInfoResp,响应体必须有 returns 关键字修饰 post /user/info (GetUserInfoReq) returns (GetUserInfoResp)
// 定义 http.HandleFunc 转换的 go 文件名称及方法,每个接口都会跟一个 handler
@handler updateUserInfo
// 定义接口
// 请求方法为 post
// 路由为 /user/info/update
// 请求体为 UpdateUserInfoReq
// 由于不需要响应体,因此可以忽略不写
post /user/info/update (UpdateUserInfoReq)
}
示例 4. 编写带有中间件的 api 服务 syntax = "v1"
type GetUserInfoReq {
Id int64 json:"id"
}
type GetUserInfoResp {
Id int64 json:"id"
Name string json:"name"
Desc string json:"desc"
}
// @server 语法块主要用于控制对 HTTP 服务生成时 meta 信息,目前支持功能有: // 1. 路由分组 // 2. 中间件声明 // 3. 路由前缀 // 4. 超时配置 // 5. jwt 鉴权开关 // 所有声明仅对当前 service 中的路由有效 @server ( // 定义一个鉴权控制的中间件,多个中间件以英文逗号,分割,如 Middleware1,Middleware2,中间件按声明顺序执行 middleware: AuthInterceptor ) // 定义一个名称为 user 的服务 service user { // 定义 http.HandleFunc 转换的 go 文件名称及方法,每个接口都会跟一个 handler @handler getUserInfo // 定义接口 // 请求方法为 post // 路由为 /user/info // 请求体为 GetUserInfoReq // 响应体为 GetUserInfoResp,响应体必须有 returns 关键字修饰 post /user/info (GetUserInfoReq) returns (GetUserInfoResp) }
示例 5. 编写带有超时配置的 api 服务 syntax = "v1"
type GetUserInfoReq {
Id int64 json:"id"
}
type GetUserInfoResp {
Id int64 json:"id"
Name string json:"name"
Desc string json:"desc"
}
// @server 语法块主要用于控制对 HTTP 服务生成时 meta 信息,目前支持功能有: // 1. 路由分组 // 2. 中间件声明 // 3. 路由前缀 // 4. 超时配置 // 5. jwt 鉴权开关 // 所有声明仅对当前 service 中的路由有效 @server ( // 定义一个超时时长为 3 秒的超时配置,这里可填写为 time.Duration 的字符串形式,详情可参考 // https://pkg.go.dev/time#Duration.String timeout: 3s ) // 定义一个名称为 user 的服务 service user { // 定义 http.HandleFunc 转换的 go 文件名称及方法,每个接口都会跟一个 handler @handler getUserInfo // 定义接口 // 请求方法为 post // 路由为 /user/info // 请求体为 GetUserInfoReq // 响应体为 GetUserInfoResp,响应体必须有 returns 关键字修饰 post /user/info (GetUserInfoReq) returns (GetUserInfoResp) }
示例 6. 结构体引用 syntax = "v1"
type Base {
Code int json:"code"
Msg string json:"msg"
}
type UserInfo {
Id int64 json:"id"
Name string json:"name"
Desc string json:"desc"
}
type GetUserInfoReq {
Id int64 json:"id"
}
type GetUserInfoResp {
// api 支持匿名结构体嵌套,也支持结构体引用
Base
Data UserInfo json:"data"
// 匿名结构体
Nested {
Foo string json:"foo"
} json:"nested"
}
// 定义一个名称为 user 的服务 service user { // 定义 http.HandleFunc 转换的 go 文件名称及方法,每个接口都会跟一个 handler @handler getUserInfo // 定义接口 // 请求方法为 post // 路由为 /user/info // 请求体为 GetUserInfoReq // 响应体为 GetUserInfoResp,响应体必须有 returns 关键字修饰 post /user/info (GetUserInfoReq) returns (GetUserInfoResp) }
示例 7. 控制最大请求体控制的 api 服务 syntax = "v1"
type GetUserInfoReq {
Id int64 json:"id"
}
type GetUserInfoResp {
Id int64 json:"id"
Name string json:"name"
Desc string json:"desc"
}
// @server 语法块主要用于控制对 HTTP 服务生成时 meta 信息,目前支持功能有: // 1. 路由分组 // 2. 中间件声明 // 3. 路由前缀 // 4. 超时配置 // 5. jwt 鉴权开关 // 所有声明仅对当前 service 中的路由有效 @server ( // 定义一个请求体限制在 1MB 以内的请求,goctl >= 1.5.0 版本支持 maxBytes: 1048576 ) // 定义一个名称为 user 的服务 service user { // 定义 http.HandleFunc 转换的 go 文件名称及方法,每个接口都会跟一个 handler @handler getUserInfo // 定义接口 // 请求方法为 post // 路由为 /user/info // 请求体为 GetUserInfoReq // 响应体为 GetUserInfoResp,响应体必须有 returns 关键字修饰 post /user/info (GetUserInfoReq) returns (GetUserInfoResp) } API 定义完整示例 api 示例 下文仅展示 api 文件的完整写法和对应语法块的功能说明,如需查看 api 规范定义,可参考 《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: 1048576 // 对当前 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)
}
API 规范 概述 api 是 go-zero 自研的领域特性语言(下文称 api 语言 或 api 描述语言),旨在实现人性化的基础描述语言,作为生成 HTTP 服务最基本的描述语言。
api 领域特性语言包含语法版本,info 块,结构体声明,服务描述等几大块语法组成,其中结构体和 Golang 结构体 语法几乎一样,只是移除了 struct 关键字。
目标 学习成本低 可读性强 扩展自由 HTTP 服务一键生成 编写一个 api 文件,生成多种语言代码服务 语法标记符号 api 语法是使用 扩展巴科斯范式(EBNF) 来描述的,在扩展巴科斯范式中指定
Syntax = { Production } . Production = production_name "=" [ Expression ] "." . Expression = Term { "|" Term } . Term = Factor { Factor } . Factor = production_name | token [ "…" token ] | Group | Option | Repetition . Group = "(" Expression ")" . Option = "[" Expression "]" . Repetition = "{" Expression "}" . Production 由 Term 和如下操作符组成,如下操作符优先级递增:
| alternation () grouping [] option (0 or 1 times) {} repetition (0 to n times) 形式 a...b 表示从 a 到 b 的一组字符作为替代,如 0...9 代表 0 到 9 的有效数值。
. 表示 ENBF 的终结符号。
注意 产生式的名称如果为小写,则代表终结 token,驼峰式的产生式名称则为非终结符 token,如:
// 终结 token number = "0"..."9" . lower_letter = "a"..."z" .
// 非终结 token DataType = TypeLit | TypeGroup . TypeLit = TypeAlias | TypeStruct . 源代码表示 源代码表示是用来描述 api 语法的最基础元素。
字符 newline = /_ 代表换行符, Unicode 值为 U+000A _/ . unicode*char = /* 除换行符 newline 外的其他 Unicode 字符 _/ . unicode_letter = /_ 字母 a...z|A...Z Unicode _/ . unicode_digit = /_ 数值 0...9 Unicode _/ . 字母和数字 下划线字符 _ (U+005F) 被视为小写字母。
letter = "A"..."Z" | "a"..."z" | "_" . decimal_digit = "0" … "9" . 抽象语法树 抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。比如,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现;而类似于 if-condition-then 这样的条件跳转语句,可以使用带有三个分支的节点来表示。
抽象语法树是代码的树形表示。它们是编译器工作方式的基本组成部分。当编译器转换一些代码时,基本上有以下步骤:
词法分析(Lexical Analysis) 语法分析(Syntax Analysis) 代码生成(Code Generation) task-grpc-demo-grpcui AST 分析过程 词法分析 词法分析(Lexical Analysis)是计算机科学中将字符序列转换为记号(token)序列的过程。进行词法分析的程序或者函数叫作词法分析器(lexical analyzer,简称 lexer),也叫扫描器(scanner)。词法分析器一般以函数的形式存在,供语法分析器调用。
在 api 语言中,词法分析是将字符转换为词法元素序列的过程,其中词法元素包括 注释 和 Token。
词法元素 注释 在 api 领域特性语言中有 2 种格式:
单行注释以 // 开始,行尾结束。
// 这是一个单行注释示例 多行注释(文档注释)以 /_ 开始,以第一个 _/ 结束。
/这是在同意行内的文档注释/ /_ 这是在多行的文档注释 _/ Token Token 是组成节点的最基本元素,由 标识符(identifier)、关键字(keyword)、运算符(operator)、标点符号(punctuation)、字面量(literal)组成,空白符(White space)一般由空格(U+0020)、水平制表符(U+0009)、回车符(U+000D)和 换行符(U+000A)组成,在 api 语言中,Token 不包含 运算符(operator)。
Token 的 Golang 结构体定义为:
type Token struct { Type Type Text string Position Position }
type Position struct { Filename string Line int Column int } 如 api 语句 syntax="v1",其词法化后的为:
文本 类型 syntax 标识符 = 操作符 "v1" 字符串 ID 标识符 ID 标识符一般是结构体,变量,类型等的名称实体,ID 标识符一般有 1 到 n 个字母和数字组成,且开头必须为字母(记住上文提到的 _ 也被当做小写字母看待),其 EBNF 表示法为:
identifier = letter { letter | unicode_digit } . ID 标识符示例:
a _a1 GoZero 有些 ID 标识符是预先定义的,api 沿用了 Golang 预定义 ID 标识符 。
预定义类型: any bool byte comparable complex64 complex128 error float32 float64 int int8 int16 int32 int64 rune string uint uint8 uint16 uint32 uint64 uintptr
预定义常量: true false iota
零值: nil
预定义函数: append cap close complex copy delete imag len make new panic print println real recover 关键字 关键字是一些特殊的 ID 标识符,是系统保留字,api 的关键字沿用了 Golang 关键字,结构体中不得使用 Golang 关键字作为标识符。
Golang 关键字
break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var 标点符号 标点符号可以用于对 Token、表达式做分割、分组,以下是 api 语言中的标点符号:
- , ( )
- . / ; { } = : , ; ... 字符串 字符串字面量是由一组字符序列组成的常量。在 api 中沿用了 Golang 的字符串,有 2 种形式: 原始字符串(raw string)和普通符串(双引号字符串)。
原始字符串的字符序列在两个反引号之间,除反引号外,任何字符都可以出现,如 foo
;
普通字符串的字符序列在两个双引号之间,除双引号外,任何字符都可以出现,如 "foo"。
注意 在 api 语言中,双引号字符串不支持 " 来实现字符串转义。
string_lit = raw_string_lit | interpreted_string_lit .
raw_string_lit = "" { unicode_char | newline } "
" .
interpreted_string_lit = "
{ unicode_value | byte_value } "
.
字符串示例:
// 原始字符串
``
foo
bar
json:"baz"
// 普通字符串 "" "foo" "bar" 语法分析 语法分析(Syntax Analysis)又叫语法解析,这个过程是将词法元素转换为树的过程,而语法树一般由节点(Node)、表达式(Expression)、语句(Statement)组成,语法解析的过程除了词汇转换成树外,还需要完成语义分析。
节点 节点(Node)是 Token 的变体,是一个接口类型,是组成表达式、语句的基本元素,其在 Golang 中的结构体定义为:
// Node represents a node in the AST. type Node interface { // Pos returns the position of the first character belonging to the node. Pos() token.Position // End returns the position of the first character immediately after the node. End() token.Position // Format returns the node's text after format. Format(...string) string // HasHeadCommentGroup returns true if the node has head comment group. HasHeadCommentGroup() bool // HasLeadingCommentGroup returns true if the node has leading comment group. HasLeadingCommentGroup() bool // CommentGroup returns the node's head comment group and leading comment group. CommentGroup() (head, leading CommentGroup) } 表达式 表达式(Expression)是组成语句的基本元素,可以理解为一个句子中的 “短语”,在 api 语言中包含的表达式如下:
数据类型表达式 结构体中的 field 表达式 key-value 表达式 服务声明表达式 HTTP 请求/响应体表达式 HTTP 路由表达式 在 api 中 Golang 的结构体定义为:
// Expr represents an expression in the AST. type Expr interface { Node exprNode() } 语句 语句(Statement)是组成抽象语法树的基本元素,抽象语法树可以理解成一篇文章,而语句是组成文章的多条句子,在 api 语言中包含语句如下:
@doc 语句 @handler 语句 @server 语句 HTTP 服务的请求/响应体语句 注释语句 import 语句 info 语句 HTTP 路由语句 HTTP 服务声明语句 syntax 语句 结构体语句 在 api 中 Golang 的结构体定义为:
// Stmt represents a statement in the AST. type Stmt interface { Node stmtNode() } 代码生成 我们一旦有了抽象语法树,就可以通过它来打印或者生成不同的代码了,在 api 抽象语法树行成后,可以支持:
打印 AST api 语言格式化 Golang HTTP 服务生成 Typescript 代码生成 Dart 代码生成 Kotlin 代码生成 除此之外,还可以根据 AST 进行扩展,比如插件:
goctl-go-compact goctl-swagger goctl-php api 语法标记 api = SyntaxStmt | InfoStmt | { ImportStmt } | { TypeStmt } | { ServiceStmt } . syntax 语句 syntax 语句用于标记 api 语言的版本,不同的版本可能语法结构有所不同,随着版本的提升会做不断的优化,当前版本为 v1。
syntax 的 EBNF 表示为:
SyntaxStmt = "syntax" "=" "v1" . syntax 语法写法示例:
syntax = "v1" info 语句 info 语句是 api 语言的 meta 信息,其仅用于对当前 api 文件进行描述,暂不参与代码生成,其和注释还是有一些区别,注释一般是依附某个 syntax 语句存在,而 info 语句是用于描述整个 api 信息的,当然,不排除在将来会参与到代码生成里面来,info 语句的 EBNF 表示为:
InfoStmt = "info" "(" { InfoKeyValueExpr } ")" . InfoKeyValueExpr = InfoKeyLit [ interpreted_string_lit ] . InfoKeyLit = identifier ":" . info 语句写法示例:
// 不包含 key-value 的 info 块 info ()
// 包含 key-value 的 info 块 info ( foo: "bar" bar: ) import 语句 import 语句是在 api 中引入其他 api 文件的语法块,其支持相对/绝对路径,不支持 package 的设计,其 EBNF 表示为:
ImportStmt = ImportLiteralStmt | ImportGroupStmt . ImportLiteralStmt = "import" interpreted_string_lit . ImportGroupStmt = "import" "(" { interpreted_string_lit } ")" . import 语句写法示例:
// 单行 import import "foo" import "/path/to/file"
// import 组 import () import ( "bar" "relative/to/file" ) 数据类型 api 中的数据类型基本沿用了 Golang 的数据类型,用于对 rest 服务的请求/响应体结构的描述,其 EBNF 表示为:
TypeStmt = TypeLiteralStmt | TypeGroupStmt . TypeLiteralStmt = "type" TypeExpr . TypeGroupStmt = "type" "(" { TypeExpr } ")" . TypeExpr = identifier [ "=" ] DataType . DataType = AnyDataType | ArrayDataType | BaseDataType | InterfaceDataType | MapDataType | PointerDataType | SliceDataType | StructDataType . AnyDataType = "any" . ArrayDataType = "[" { decimal_digit } "]" DataType . BaseDataType = "bool" | "uint8" | "uint16" | "uint32" | "uint64" | "int8" | "int16" | "int32" | "int64" | "float32" | "float64" | "complex64" | "complex128" | "string" | "int" | "uint" | "uintptr" | "byte" | "rune" | "any" | .
InterfaceDataType = "interface{}" . MapDataType = "map" "[" DataType "]" DataType . PointerDataType = "*" DataType . SliceDataType = "[" "]" DataType . StructDataType = "{" { ElemExpr } "}" . ElemExpr = [ ElemNameExpr ] DataType [ Tag ]. ElemNameExpr = identifier { "," identifier } . Tag = raw_string_lit . 数据类型写法示例:
// 空结构体 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"
Array [3]int json:"array"
// 结构体内嵌 goctl 1.6.8 版本支持
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"
}
)
注意 不支持 package 设计,如 time.Time。 service 语句 service 语句是对 HTTP 服务的直观描述,包含请求 handler,请求方法,请求路由,请求体,响应体,jwt 开关,中间件声明等定义。
其 EBNF 表示为:
ServiceStmt = [ AtServerStmt ] "service" ServiceNameExpr "(" { ServiceItemStmt } ")" . ServiceNameExpr = identifier [ "-api" ] . @server 语句 @server 语句是对一个服务语句的 meta 信息描述,其对应特性包含但不限于:
jwt 开关 中间件 路由分组 路由前缀 @server 的 EBNF 表示为:
AtServerStmt = "@server" "(" { AtServerKVExpr } ")" .
AtServerKVExpr = AtServerKeyLit [ AtServerValueLit ] .
AtServerKeyLit = identifier ":" .
AtServerValueLit = PathLit | identifier { "," identifier } .
PathLit = "
{ "/" { identifier | "-" identifier} } "
.
@server 写法示例:
// 空内容 @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
) 服务条目 服务条目(ServiceItemStmt)是对单个 HTTP 请求的描述,包括 @doc 语句,handler 语句,路由语句信息,其 EBNF 表示为:
ServiceItemStmt = [ AtDocStmt ] AtHandlerStmt RouteStmt . @doc 语句 @doc 语句是对单个路由的 meta 信息描述,一般为 key-value 值,可以传递给 goctl 及其插件来进行扩展生成,其 EBNF 表示为:
AtDocStmt = AtDocLiteralStmt | AtDocGroupStmt . AtDocLiteralStmt = "@doc" interpreted_string_lit . AtDocGroupStmt = "@doc" "(" { AtDocKVExpr } ")" . AtDocKVExpr = AtServerKeyLit interpreted_string_lit . AtServerKeyLit = identifier ":" . @doc 写法示例:
// 有内容的 @doc 组 @doc ( foo: "bar" bar: "baz" ) @handler 语句 @handler 语句是对单个路由的 handler 信息控制,主要用于生成 golang http.HandleFunc 的实现转换方法,其 EBNF 表示为:
AtHandlerStmt = "@handler" identifier . @handler 写法示例:
@handler foo 路由语句 路由语句是对单此 HTTP 请求的具体描述,包括请求方法,请求路径,请求体,响应体信息,其 EBNF 表示为:
RouteStmt = Method PathExpr [ BodyStmt ] [ "returns" ] [ BodyStmt ]. Method = "get" | "head" | "post" | "put" | "patch" | "delete" | "connect" | "options" | "trace" . PathExpr = "/" identifier { ( "-" identifier ) | ( ":" identifier) } . BodyStmt = "(" identifier ")" . 路由语句写法示例:
// 没有请求体和响应体的写法 get /ping
// 只有请求体的写法 get /foo (foo)
// 只有响应体的写法 post /foo returns (foo)
// 有请求体和响应体的写法 post /foo (foo) returns (bar) service 写法示例
// 带 @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)
}
参数规则 概述 在 go-zero 中,我们通过 api 语言来声明 HTTP 服务,然后通过 goctl 生成 HTTP 服务代码,在之前我们系统性的介绍了 API 规范。
在 go-zero 中已经内置了一些参数校验的规则,接下来我们来看一下 go-zero 中的参数接收/校验规则吧。
参数接收规则 在 api 描述语言中,我们可以通过在 tag 中来声明参数接收规则,目前 go-zero 支持的参数接收规则如下:
接收规则 说明 生效范围 接收 tag 示例 请求示例 json json 序列化 请求体&响应体 json:"foo" {"key":"vulue"} path 路由参数 请求体 path:"id" /foo/:id form post 请求的表单(支持 content-type 为 form-data 和 x-www-form-urlencoded) 参数请求接收标识,get 请求的 query 参数接收标识 请求体 form:"name" GET /search?key=vulue header http 请求体接收标识 请求体 header:"Content-Length" origin: https://go-zero.dev 温馨提示 go-zero 中不支持多 tag 来接收参数,即一个字段只能有一个 tag,如下写法可能会导致参数接收不到:
type Foo {
Name string json:"name" form:"name"
}
参数校验规则
在 api 描述语言中,我们可以通过在 tag 中来声明参数接收规则,除此之外,还支持参数的校验,参数校验的规则仅对 请求体 有效,参数校验的规则写在 tag value 中,目前 go-zero 支持的参数校验规则如下:
接收规则 说明 示例
optional 当前字段是可选参数,允许为零值(zero value) json:"foo,optional"
options 当前参数仅可接收的枚举值 json:"gender,options=foo|bar"
default 当前参数默认值 json:"gender,default=male"
range 当前参数数值有效范围,仅对数值有效,写法规则详情见下文温馨提示 json:"age,range=[0:120]"
range 表达式值规则
左开右闭区间:(min:max],表示大于 min 小于等于 max,当 min 缺省时,min 代表数值 0,当 max 缺省时,max 代表无穷大,min 和 max 不能同时缺省
左闭右开区间:[min:max),表示大于等于 min 小于 max,当 max 缺省时,max 代表数值 0,当 min 缺省时,min 代表无穷大,min 和 max 不能同时缺省
闭区间:[min:max],表示大于等于 min 小于等于 max,当 min 缺省时,min 代表数值 0,当 max 缺省时,max 代表无穷大,min 和 max 不能同时缺省
开区间:(min:max),表示大于 min 小于 max,当 min 缺省时,min 代表数值 0,当 max 缺省时,max 代表无穷大,min 和 max 不能同时缺省
API Import 概述 在 go-zero 中,我们通过 api 语言来声明 HTTP 服务,然后通过 goctl 生成 HTTP 服务代码,在之前我们系统性的介绍了 API 规范。
在 HTTP 服务开发中,我们都是通过 api 描述语言来描述 HTTP 服务,随着业务量的增加,api 文件可能会越来越大,又或者我们有一些公共结构体,如果我们都写在同一个 api 文件中,那么 api 文件将变成非常巨大,不易阅读和维护,我们可以通过 api import 来引入其他 api 文件解决这类问题。
api 文件引入 假设我们 HTTP 服务的响应格式统一为如下 json 格式:
{ "code": 0, "msg": "success", "data": {} } 通过如上 json 可以看出,我们的响应格式中有 code、msg、data 三个字段,其中 code 和 msg 是固定的,data 是可变的,我们可以将其中 2 个字段 code,msg 抽象出来,定义为一个公共的结构体,然后在其他 api 文件中引入这个结构体。
示例,假设我们有一个用户服务来查询用户信息和修改用户信息,我们可以将 code 和 msg 抽象在 base.api 中,然后 user.api 中复用和定义具体的响应结构体即可。
base.api user.api syntax = "v1"
type Base {
Code int json:"code"
Msg string json:"msg"
}
温馨提示
在 api 描述语言中,没有 package 的概念,所以在引入其他 api 文件时,需要使用相对路径,如上面示例中的 import "base.api",如果是在同一个目录下,亦可以使用 import "./base.api"。 import 支持相对路径和绝对路径。
在 api 描述语言中,我们规定将所有 service 语法块声明的 HTTP 服务信息都放在 main api 文件中,抽象结构体放在其他 api 文件中,然后在 main api 文件中引入其他 api 文件,这样可以让 main api 文件更加简洁,易于维护,而被引入的 api 文件中不允许出现 service 语法块,否则会报错。
特别注意:api 引入不支持循环引入!!!