go-zero api語法介紹

2022-04-25 17:47 更新

api示例 

/**
 * api語法示例及語法說明
 */

// api語法版本
syntax = "v1"

// import literal
import "foo.api"

// import group
import (
    "bar.api"
    "foo/bar.api"
)
info(
    author: "songmeizi"
    date:   "2020-01-08"
    desc:   "api語法示例及語法說明"
)

// type literal

type Foo{
    Foo int `json:"foo"`
}

// type group

type(
    Bar{
        Bar int `json:"bar"`
    }
)

// service block
@server(
    jwt:   Auth
    group: foo
)
service foo-api{
    @doc "foo"
    @handler foo
    post /foo (Foo) returns (Bar)
}

api語法結構

  • syntax語法聲明
  • import語法塊
  • info語法塊
  • type語法塊
  • service語法塊
  • 隱藏通道

在以上語法結構中,各個語法塊從語法上來說,按照語法塊為單位,可以在.api文件中任意位置聲明, 但是為了提高閱讀效率,我們建議按照以上順序進行聲明,因為在將來可能會通過嚴格模式來控制語法塊的順序。

syntax語法聲明

?syntax?是新加入的語法結構,該語法的引入可以解決:

  • 快速針對api版本定位存在問題的語法結構
  • 針對版本做語法解析
  • 防止api語法大版本升級導致前后不能向前兼容

注意

被import的api必須要和main api的syntax版本一致。

語法定義

'syntax'={checkVersion(p)}STRING

語法說明

  • ?syntax?:固定token,標志一個syntax語法結構的開始
  • ?checkVersion?:自定義go方法,檢測STRING是否為一個合法的版本號,目前檢測邏輯為,STRING必須是滿足(?m)"v[1-9][0-9]*"正則。
  • ?STRING?:一串英文雙引號包裹的字符串,如"v1"

一個api語法文件只能有0或者1個syntax語法聲明,如果沒有syntax,則默認為v1版本

正確語法示例

  • eg1:不規(guī)范寫法
syntax="v1"
  • eg2:規(guī)范寫法(推薦)
syntax = "v2"

錯誤語法示例

  • eg1:
syntax = "v0"
  • eg2:
syntax = v1
  • eg3:
syntax = "V1"

import語法塊

隨著業(yè)務規(guī)模增大,api中定義的結構體和服務越來越多,所有的語法描述均為一個api文件,這是多么糟糕的一個問題, 其會大大增加了閱讀難度和維護難度,import語法塊可以幫助我們解決這個問題,通過拆分api文件, 不同的api文件按照一定規(guī)則聲明,可以降低閱讀難度和維護難度。

注意

這里import不像golang那樣包含package聲明,僅僅是一個文件路徑的引入,最終解析后會把所有的聲明都匯聚到一個spec.Spec中。 不能import多個相同路徑,否則會解析錯誤。

語法定義

'import' {checkImportValue(p)}STRING  
|'import' '(' ({checkImportValue(p)}STRING)+ ')'

語法說明

  • ?import?:固定token,標志一個import語法的開始
  • ?checkImportValue?:自定義go方法,檢測STRING是否為一個合法的文件路徑,目前檢測邏輯為,STRING必須是滿足(?m)"(/?[a-zA-Z0-9_#-])+\.api"正則。
  • ?STRING?:一串英文雙引號包裹的字符串,如"foo.api"

正確語法示例

  • eg:
import "foo.api"
import "foo/bar.api"

import(
    "bar.api"
    "foo/bar/foo.api"
)

錯誤語法示例

  • eg:
import foo.api
import "foo.txt"
import (
    bar.api
    bar.api
)

info語法塊

info語法塊是一個包含了多個鍵值對的語法體,其作用相當于一個api服務的描述,解析器會將其映射到spec.Spec中, 以備用于翻譯成其他語言(golang、java等) 時需要攜帶的meta元素。如果僅僅是對當前api的一個說明,而不考慮其翻譯 時傳遞到其他語言,則使用簡單的多行注釋或者java風格的文檔注釋即可,關于注釋說明請參考下文的 隱藏通道。

注意
不能使用重復的key,每個api文件只能有0或者1個info語法塊

語法定義

'info' '(' (ID {checkKeyValue(p)}VALUE)+ ')'

語法說明

  • ?info?:固定token,標志一個info語法塊的開始
  • ?checkKeyValue?:自定義go方法,檢測VALUE是否為一個合法值。
  • ?VALUE?:key對應的值,可以為單行的除'\r','\n','/'后的任意字符,多行請以""包裹,不過強烈建議所有都以""包裹

正確語法示例

  • eg1:不規(guī)范寫法
info(
foo: foo value
bar:"bar value"
    desc:"long long long long
long long text"
)
  • eg2:規(guī)范寫法(推薦)
info(
    foo: "foo value"
    bar: "bar value"
    desc: "long long long long long long text"
)

錯誤語法示例

  • eg1:沒有key-value內(nèi)容
info()
  • eg2:不包含冒號
info(
    foo value
)
  • eg3:key-value沒有換行
info(foo:"value")
  • eg4:沒有key
info(
    : "value"
)
  • eg5:非法的key
info(
    12: "value"
)
  • eg6:移除舊版本多行語法
info(
    foo: >
    some text
    <
)

type語法塊

在api服務中,我們需要用到一個結構體(類)來作為請求體,響應體的載體,因此我們需要聲明一些結構體來完成這件事情, type語法塊由golang的type演變而來,當然也保留著一些golang type的特性,沿用golang特性有:

  • 保留了golang內(nèi)置數(shù)據(jù)類型?bool?,?int?,?int8?,?int16?,?int32?,?int64?,?uint?,?uint8?,?uint16?,?uint32?,?uint64?,?uintptr ?,?float32?,?float64?,?complex64?,?complex128?,?string?,?byte?,?rune?,
  • 兼容golang struct風格聲明
  • 保留golang關鍵字
注意
  • 不支持alias
  • 不支持time.Time數(shù)據(jù)類型
  • 結構體名稱、字段名稱、不能為golang關鍵字

語法定義

由于其和golang相似,因此不做詳細說明,具體語法定義請在 ApiParser.g4 中查看typeSpec定義。

語法說明

參考golang寫法

正確語法示例

  • eg1:不規(guī)范寫法
type Foo struct{
    Id int `path:"id"` // ①
    Foo int `json:"foo"`
}

type Bar struct{
    // 非導出型字段
    bar int `form:"bar"`
}

type(
    // 非導出型結構體
    fooBar struct{
        FooBar int `json:"fooBar"`
    }
)
  • eg2:規(guī)范寫法(推薦)
type Foo{
    Id int `path:"id"`
    Foo int `json:"foo"`
}

type Bar{
    Bar int `form:"bar"`
}

type(
    FooBar{
        FooBar int `json:"fooBar"`
    }
)

錯誤語法示例

  • eg
type Gender int // 不支持

// 非struct token
type Foo structure{ 
  CreateTime time.Time // 不支持time.Time,且沒有聲明 tag
}

// golang關鍵字 var
type var{} 

type Foo{
  // golang關鍵字 interface
  Foo interface  // 沒有聲明 tag
}


type Foo{
  foo int 
  // map key必須要golang內(nèi)置數(shù)據(jù)類型,且沒有聲明 tag
  m map[Bar]string
}

 tag定義和golang中json tag語法一樣,除了json tag外,go-zero還提供了另外一些tag來實現(xiàn)對字段的描述, 詳情見下表。

  • tag表

綁定參數(shù)時,以下四個tag只能選擇其中一個

tag key 描述 提供方 有效范圍 示例
json json序列化tag golang request、response json:"fooo"
path 路由path,如/foo/:id go-zero request path:"id"
form 標志請求體是一個form(POST方法時)或者一個query(GET方法時/search?name=keyword) go-zero request form:"name"
header HTTP header,如 Name: value go-zero request header:"name"
  • tag修飾符

常見參數(shù)校驗描述

tag key 描述 提供方 有效范圍 示例
optional 定義當前字段為可選參數(shù) go-zero request json:"name,optional"
options 定義當前字段的枚舉值,多個以豎線|隔開 go-zero request json:"gender,options=male"
default 定義當前字段默認值 go-zero request json:"gender,default=male"
range 定義當前字段數(shù)值范圍 go-zero request json:"age,range=[0:120]"
Tip
tag修飾符需要在tag value后以英文逗號,隔開

service語法塊

service語法塊用于定義api服務,包含服務名稱,服務metadata,中間件聲明,路由,handler等。

注意
  • main api和被import的api服務名稱必須一致,不能出現(xiàn)服務名稱歧義。
  • handler名稱不能重復
  • 路由(請求方法+請求path)名稱不能重復
  • 請求體必須聲明為普通(非指針)struct,響應體做了一些向前兼容處理,詳請見下文說明

語法定義

serviceSpec:    atServer? serviceApi;
atServer:       '@server' lp='(' kvLit+ rp=')';
serviceApi:     {match(p,"service")}serviceToken=ID serviceName lbrace='{' serviceRoute* rbrace='}';
serviceRoute:   atDoc? (atServer|atHandler) route;
atDoc:          '@doc' lp='('? ((kvLit+)|STRING) rp=')'?;
atHandler:      '@handler' ID;
route:          {checkHttpMethod(p)}httpMethod=ID path request=body? returnToken=ID? response=replybody?;
body:           lp='(' (ID)? rp=')';
replybody:      lp='(' dataType? rp=')';
// kv
kvLit:          key=ID {checkKeyValue(p)}value=LINE_VALUE;

serviceName:    (ID '-'?)+;
path:           (('/' (ID ('-' ID)*))|('/:' (ID ('-' ID)?)))+;

語法說明

  • ?serviceSpec?:包含了一個可選語法塊atServer和serviceApi語法塊,其遵循序列模式(編寫service必須要按照順序,否則會解析出錯)
  • ?atServer?: 可選語法塊,定義key-value結構的server metadata,'@server' 表示這一個server語法塊的開始,其可以用于描述serviceApi或者route語法塊,其用于描述不同語法塊時有一些特殊關鍵key 需要值得注意,見 atServer關鍵key描述說明。
  • ?serviceApi?:包含了1到多個serviceRoute語法塊
  • ?serviceRoute?:按照序列模式包含了atDoc,handler和route
  • ?atDoc?:可選語法塊,一個路由的key-value描述,其在解析后會傳遞到spec.Spec結構體,如果不關心傳遞到spec.Spec, 推薦用單行注釋替代。
  • ?handler?:是對路由的handler層描述,可以通過atServer指定handler key來指定handler名稱, 也可以直接用atHandler語法塊來定義handler名稱
  • ?atHandler?:'@handler' 固定token,后接一個遵循正則[_a-zA-Z][a-zA-Z_-]*)的值,用于聲明一個handler名稱
  • ?route?:路由,有httpMethod、path、可選request、可選response組成,httpMethod是必須是小寫。
  • ?body?:api請求體語法定義,必須要由()包裹的可選的ID值
  • ?replyBody?:api響應體語法定義,必須由()包裹的struct、array(向前兼容處理,后續(xù)可能會廢棄,強烈推薦以struct包裹,不要直接用array作為響應體)
  • ?kvLit?: 同info key-value
  • ?serviceName?: 可以有多個'-'join的ID值
  • ?path?:api請求路徑,必須以'/'或者'/:'開頭,切不能以'/'結尾,中間可包含ID或者多個以'-'join的ID字符串

atServer關鍵key描述說明

  • 修飾service時
key 描述 示例
jwt 聲明當前service下所有路由需要jwt鑒權,且會自動生成包含jwt邏輯的代碼 jwt: Auth
group 聲明當前service或者路由文件分組 group: login
middleware 聲明當前service需要開啟中間件 middleware: AuthMiddleware
prefix 添加路由分組 prefix: api
  • 修飾route時
key 描述 示例
handler 聲明一個handler -

正確語法示例

  • eg1:不規(guī)范寫法
@server(
  jwt: Auth
  group: foo
  middleware: AuthMiddleware
  prefix api
)
service foo-api{
  @doc(
    summary: foo
  )
  @server(
    handler: foo
  )
  // 非導出型body
  post /foo/:id (foo) returns (bar)

  @doc "bar"
  @handler bar
  post /bar returns ([]int)// 不推薦數(shù)組作為響應體

  @handler fooBar
  post /foo/bar (Foo) returns // 可以省略'returns'
}
  • eg2:規(guī)范寫法(推薦)
@server(
  jwt: Auth
  group: foo
  middleware: AuthMiddleware
  prefix: api
)
service foo-api{
  @doc "foo"
  @handler foo
  post /foo/:id (Foo) returns (Bar)
}

service foo-api{
  @handler ping
  get /ping

  @doc "foo"
  @handler bar
  post /bar/:id (Foo)
}

錯誤語法示例

// 不支持空的server語法塊
@server(
)
// 不支持空的service語法塊
service foo-api{
}

service foo-api{
  @doc kkkk // 簡版doc必須用英文雙引號引起來
  @handler foo
  post /foo

  @handler foo // 重復的handler
  post /bar

  @handler fooBar
  post /bar // 重復的路由

  // @handler和@doc順序錯誤
  @handler someHandler
  @doc "some doc"
  post /some/path

  // handler缺失
  post /some/path/:id

  @handler reqTest
  post /foo/req (*Foo) // 不支持除普通結構體外的其他數(shù)據(jù)類型作為請求體

  @handler replyTest
  post /foo/reply returns (*Foo) // 不支持除普通結構體、數(shù)組(向前兼容,后續(xù)考慮廢棄)外的其他數(shù)據(jù)類型作為響應體
}

隱藏通道

隱藏通道目前主要為空白符號、換行符號以及注釋,這里我們只說注釋,因為空白符號和換行符號我們目前拿來也無用。

單行注釋

語法定義
'//' ~[\r\n]*
語法說明 

由語法定義可知道,單行注釋必須要以//開頭,內(nèi)容為不能包含換行符

正確語法示例
// doc
// comment
錯誤語法示例
// break
line comments

java風格文檔注釋

語法定義
'/*' .*? '*/'
語法說明

由語法定義可知道,單行注釋必須要以/*開頭,*/結尾的任意字符。

正確語法示例
/**
 * java-style doc
 */
錯誤語法示例
/*
 * java-style doc */
 */

Doc&Comment

如果想獲取某一個元素的doc或者comment開發(fā)人員需要怎么定義?

Doc

我們規(guī)定上一個語法塊(非隱藏通道內(nèi)容)的行數(shù)line+1到當前語法塊第一個元素前的所有注釋(單行,或者多行)均為doc, 且保留了//、/*、*/原始標記。

Comment

我們規(guī)定當前語法塊最后一個元素所在行開始的一個注釋塊(當行,或者多行)為comment 且保留了//、/*、*/原始標記。 語法塊Doc和Comment的支持情況

語法塊 parent語法塊 Doc Comment
syntaxLit api ? ?
kvLit infoSpec ? ?
importLit importSpec ? ?
typeLit api ? ?
typeLit typeBlock ? ?
field typeLit ? ?
key-value atServer ? ?
atHandler serviceRoute ? ?
route serviceRoute ? ?

以下為對應語法塊解析后細帶doc和comment的寫法

// syntaxLit doc
syntax = "v1" // syntaxLit commnet

info(
  // kvLit doc
  author: songmeizi // kvLit comment
)

// typeLit doc
type Foo {}

type(
  // typeLit doc
  Bar{}

  FooBar{
    // filed doc
    Name int // filed comment
  }
)

@server(
  /**
   * kvLit doc
   * 開啟jwt鑒權
   */
  jwt: Auth /**kvLit comment*/
)
service foo-api{
  // atHandler doc
  @handler foo //atHandler comment

  /*
   * route doc
   * post請求
   * path為 /foo
   * 請求體:Foo
   * 響應體:Foo
   */
  post /foo (Foo) returns (Foo) // route comment
}


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號