go-zero 基础 -- 框架设计

框架设计
在这里插入图片描述

1、api语法介绍

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

1.2 api语法结构

syntax语法声明
import语法块
info语法块
type语法块
service语法块
隐藏通道

在以上语法结构中,各个语法块从语法上来说,按照语法块为单位,可以在.api文件中任意位置声明, 但是为了提高阅读效率,我们建议按照以上顺序进行声明,因为在将来可能会通过严格模式来控制语法块的顺序。

1.2.1 syntax语法声明

syntax是新加入的语法结构,该语法的引入可以解决:

  • 快速针对api版本定位存在问题的语法结构
  • 针对版本做语法解析
  • 防止api语法大版本升级导致前后不能向前兼容

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

语法定义

'syntax'={
    
    checkVersion(p)}STRING

一个api语法文件只能有0或者1个syntax语法声明,如果没有syntax,则默认为v1版本

// 不规范写法
syntax="v1"

// 规范写法(推荐)
syntax = "v2"

1.2.2 import语法块

这里import不像golang那样包含package声明,仅仅是一个文件路径的引入,最终解析后会把所有的声明都汇聚到一个spec.Spec中。 不能import多个相同路径,否则会解析错误。

语法定义

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

STRING:一串英文双引号包裹的字符串,如"foo.api"

// 正确语法
import "foo.api"
import "foo/bar.api"

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

1.2.3 info语法块

info语法块是一个包含了多个键值对的语法体,其作用相当于一个api服务的描述,解析器会将其映射到spec.Spec中, 以备用于翻译成其他语言(golang、java等) 时需要携带的meta元素。如果仅仅是对当前api的一个说明,而不考虑其翻译 时传递到其他语言,则使用简单的多行注释或者java风格的文档注释即可,关于注释说明请参考下文的 隐藏通道

不能使用重复的key,每个api文件只能有0或者1个info语法块

语法定义

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

VALUE:key对应的值,可以为单行的除’\r’,‘\n’,'/'后的任意字符,多行请以""包裹,不过强烈建议所有都以""包裹

// 不规范写法
info(
foo: foo value
bar:"bar value"
    desc:"long long long long
long long text"
)

// 规范写法(推荐)
info(
    foo: "foo value"
    bar: "bar value"
    desc: "long long long long long long text"
)

1.2.4 type语法块

api服务中,我们需要用到一个结构体(类)来作为请求体,响应体的载体,因此我们需要声明一些结构体来完成这件事情, type语法块由golang的type演变而来,当然也保留着一些golang type的特性,沿用golang特性有:

  • 保留了golang内置数据类型bool,int,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64,uintptr ,float32,float64,complex64,complex128,string,byte,rune,
  • 兼容golang struct风格声明
  • 保留golang关键字

正确语法示例

不规范写法

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"`
    }
)

type(
    // service handler返回[]fooBar
    fooBar struct{
    
    
        FooBar int `json:"fooBar"`
    }
)

规范写法(推荐)

type Foo{
    
    
    Id int `path:"id"`
    Foo int `json:"foo"`
}

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

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

type(
    // service handler返回FooBarList
    FooBarList{
    
    
        FooBarList []FooBar `json:"fooBarList"`
    }
)

tag定义和golang中json tag语法一样,除了json tag外,go-zero还提供了另外一些tag来实现对字段的描述, 详情见下表。

tag表

绑定参数时,以下四个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 修饰符
tag key 描述 提供方 有效范围 示例
optional 定义当前字段为可选参数 go-zero request json:"name,optional"
options 定义当前字段的枚举值,多个以竖线|隔开 go-zero request json:"gender,options=male"
default 定义当前字段默认值 go-zero request json:"gender,default=male"
range 定义当前字段数值范围 go-zero request json:"age,range=[0:120]"

tag修饰符需要在tag value后以英文逗号,隔开

1.2.5 service语法块

service语法块用于定义api服务,包含服务名称,服务metadata,中间件声明,路由,handler等。

main api和被import的api服务名称必须一致,不能出现服务名称歧义。
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:包含了一个可选语法块atServerserviceApi语法块,其遵循序列模式(编写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响应体语法定义

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 -

正确语法示例

不规范写法

@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)// 不推荐数组作为响应体
  
  @handler fooBar
  post /foo/bar (Foo) returns // 可以省略'returns'
}

规范写法(推荐)

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

1.2.6 隐藏通道

隐藏通道目前主要为空白符号、换行符号以及注释,这里我们只说注释,因为空白符号和换行符号我们目前拿来也无用。

单行注释

单行注释必须要以//开头,内容为不能包含换行符

正确语法示例

// doc
// comment

java风格文档注释

单行注释必须要以/*开头,*/结尾的任意字符。

正确语法示例

/**
 * java-style doc
 */

1.2.7 Doc&Comment

如果想获取某一个元素的doc或者comment开发人员需要怎么定义?

Doc

我们规定上一个语法块(非隐藏通道内容)的行数line+1到当前语法块第一个元素前的所有注释(单行,或者多行)均为doc, 且保留了//、/**/原始标记。

Comment

我们规定当前语法块最后一个元素所在行开始的一个注释块(当行,或者多行)为comment 且保留了///**/原始标记。

语法块DocComment的支持情况
在这里插入图片描述
以下为对应语法块解析后细带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
}

1.3 api目录介绍

.
├── etc
│   └── greet-api.yaml              // 配置文件
├── go.mod                          // mod文件
├── greet.api                       // api描述文件
├── greet.go                        // main函数入口
└── internal                        
    ├── config  
    │   └── config.go               // 配置声明type
    ├── handler                     // 路由及handler转发
    │   ├── greethandler.go
    │   └── routes.go
    ├── logic                       // 业务逻辑
    │   └── greetlogic.go
    ├── middleware                  // 中间件文件
    │   └── greetmiddleware.go
    ├── svc                         // logic所依赖的资源池
    │   └── servicecontext.go
    └── types                       // request、response的struct,根据api自动生成,不建议编辑
        └── types.go

2、rpc服务目录

2.1 proto 文件

// greet.proto

syntax = "proto3";

package stream;

option go_package = "./greet";

message StreamReq {
    
    
  string name = 1;
}

message StreamResp {
    
    
  string greet = 1;
}

service StreamGreeter {
    
    
  rpc greet(StreamReq) returns (StreamResp);
}

生成的目录结构

.
├── etc
│   └── greet.yaml
├── go.mod
├── go.sum
├── greet // [1]
│   ├── greet.pb.go
│   └── greet_grpc.pb.go
├── greet.go
├── greet.proto
├── internal
│   ├── config
│   │   └── config.go
│   ├── logic
│   │   └── greetlogic.go
│   ├── server
│   │   └── streamgreeterserver.go
│   └── svc
│       └── servicecontext.go
└── streamgreeter
    └── streamgreeter.go

[1] pb.go & _grpc.pb.go 文件所在目录并非固定,该目录由 go_opt & go-grpc_opt 与 proto文件中的 go_package 值共同决定,想要了解grpc代码生成目录逻辑请阅读 Go Generated Code

猜你喜欢

转载自blog.csdn.net/chinusyan/article/details/129674455