线上答题系统,微服务架构的小小实践,项目代码
单体应用在改造成微服务架构后,模块与模块间的交互会变得复杂,功能增多,接口也会增多,因此十分有必要维护一份系统的API接口说明。答题系统从上到下分为view、controller、service、model四层,controller和前端view之间使用REST API交互,因此使用目前比较流行的swagger进行接口维护。controller和service之间是RPC接口,使用的是protocol buffers协议,可以使用protoc-gen-doc工具直接生成rpc接口说明的html页面。
技术选型
前端view层:planeui+jquery
controller层(页面逻辑处理):beego
service层:go-micro
model层:beego的orm
REST API展示:swagger
RPC API展示:protoc-gen-doc
Beego支持自动化生成swagger API文档,只需要按语法规则在routers/router.go中配置好路由 ,并在对应的cotroller方法写好路由注释。以用户的CURD为例:
在routers/router.go最顶部注释
// @APIVersion 1.0.0
// @Title mobile API
// @Description mobile has every tool to get any job done, so codename for the new mobile APIs.
// @Contact [email protected]
package routers
目前自动化文档只支持如下的写法的解析,其他写法函数不会自动解析,即 namespace+Include 的写法,而且只支持二级解析,一级版本号,二级分别表示应用模块。因此/v1/users的请求对应UserManageController。
func init() {
ns :=
beego.NewNamespace("/v1",
beego.NSNamespace("/users",
beego.NSInclude(
&controllers.UserManageController{},
),
),
)
beego.AddNamespace(ns)
}
在REST API对应的方法前进行路由注释,即可让请求匹配该方法。如新增用户,当以POST请求访问/v1/users时,就会执行UserManageController.AddUser方法。删除用户,则是DELETE方法访问/v1/users,最终执行DeleteUserById,删除的用户id可从request payload中获取。获取用户是GET方法访问,修改用户信息是PUT方法访问,最终分别执行GetUser和ChangeUser方法。
package controllers
type UserManageController struct {
beego.Controller
}
// @Title 新增用户
// @Description 新增用户
// @Param user_name formData string false "用户名"
// @Param login_name formData string false "用户登陆名"
// @Param user_phone_number formData string false "用户手机号码"
// @Param user_job_number formData string false "用户工号"
// @Param user_gender formData int false "用户性别"
// @Success 200 {string} success
// @Failure 400 no enough input
// @Failure 500 server wrong
// @router / [post]
func (this *UserManageController) AddUser() {
userName := this.GetString("user_name”) //获取传入参数
//……略……
}
// @Title 删除用户
// @Description 删除用户
// @Param user_name body int64 false "用户id"
// @Success 200 {string} success
// @Failure 400 user doesn't exit
// @Failure 500 server's wrong
// @router / [delete]
func (this *UserManageController) DeleteUserById() {
type deleteInput struct {
Delete_id string `json:"delete_id"`
}
inparam := deleteInput{}
err := json.Unmarshal(this.Ctx.Input.RequestBody, &inparam)
//……略……
}
// @Title 修改用户信息
// @Description 修改用户信息
// @Param user_name formData string false "用户名"
// @Param login_name formData string false "用户登陆名"
// @Param user_phone_number formData string false "用户手机号码"
// @Param user_job_number formData string false "用户工号"
// @Param user_gender formData int false "用户性别"
// @Success 200 {string} success
// @Failure 400 user doesn't exit
// @Failure 500 server's wrong
// @router / [put]
func (this *UserManageController) ChangeUser() {
changeId, _ := this.GetInt64("change_id") //获取传入参数
//……略……
}
// @Title 获取用户列表
// @Description 获取用户列表
// @Success 200 {object} model.Users
// @Param offset query string true "页码"
// @Param limit query string true "一页展示数量"
// @router /all [get]
func (this *UserManageController) UserManage() {
offset, _ := this.GetInt32("offset")
limit, _ := this.GetInt32("limit”)
//……略……
}
一些注释解释如下,目前的一个缺陷是@Success不能支持复杂的json返回格式,返回的object必须先定义好,比较麻烦。
注释标记 | 含义 |
---|---|
@Title | API 所表达的含义,是一个文本,空格之后的内容全部解析为 title |
@Description | API详细的描述,是一个文本,空格之后的内容全部解析为Description |
@Param | 参数,表示需要传递到服务器端的参数,有五列参数,使用空格或者 tab 分割。含义分别是: 1.参数名 2.参数类型,可以有的值是 formData、query、path、body、header,formData 表示是 post 请求的数据,query 表示带在 url 之后的参数,path 表示请求路径上得参数,例如上面例子里面的 key,body 表示是一个 raw 数据请求,header 表示带在 header 信息中得参数。 3.参数类型 4.是否必须 5.注释 |
@Success | 成功返回给客户端的信息,三个参数,第一个是 status code。第二个参数是返回的类型,必须使用 {} 包含,第三个是返回的对象或者字符串信息,如果是 {object} 类型,那么 bee 工具在生成 docs 的时候会扫描对应的对象,这里填写的是想对你项目的目录名和对象,例如 models.ZDTProduct.ProductList 就表示 /models/ZDTProduct 目录下的 ProductList 对象。三个参数必须通过空格分隔。 |
@Failure | 失败返回的信息,包含两个参数,使用空格分隔,第一个表示 status code,第二个表示错误信息 |
@router | 路由信息,包含两个参数,使用空格分隔,第一个是请求的路由地址,支持正则和自定义路由,和之前的路由规则一样,第二个参数是支持的请求方法,放在 [] 之中,如果有多个方法,那么使用 , 分隔。 |
必须在route的namespace下面设置swagger路由,不然无法访问到swagger页面。
beego.SetStaticPath("/swagger", "swagger")
命令:bee run -gendoc=true -downdoc=true
访问地址http://localhost:8081/swagger/#!
最终可以看到文件目录中多了swagger文件夹,其中swagger.json、swagger.yaml就是beego对外提供REST API的描述,同时也自动生成系统的API的swagger网页。
go-micro中的RPC接口使用的是Protocol Buffers协议,目前也有现成的工具可以直接根据定义的protoc文件生成html或json。那就是protoc-gen-doc,这是protocol buffers的文档生成插件,支持生成HTML、DocBook、Markdown、JSON四种类型的文档。
go get -u github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc
protoc-gen-doc支持的语法很简单,目前只支持/*和//这种方式编写简单的接口说明,最终生成出来的效果不一样。可以使用/*对proto文件或定义的message进行说明,//对字段,服务方法,枚举值和扩展等说明。若某个注释不想显示,只需要加上@exclude标签即可。
syntax = "proto3";
/**
*用户模块,对外提供用户的查询管理接口
*/
service UserManage {
//根据用户id返回用户信息
rpc GetUserById(GetUserByIdReq) returns (UserMesssage) {}
}
message UserMesssage {
int64 id = 1;//用户id
string login_name = 2;//登录名
string pwd = 3;//密码
string name = 4;//用户名
string phone_number = 5;//手机号码
string job_number = 6;//工号
int32 permission = 7;//权限
int32 gender = 8;//性别
bool deleted = 9;//是否删除
}
message GetUserByIdReq {
int64 userId = 1;//用户id
}
进入protoc定义文件所在目录,执行下列命令即可生成service_proto.html,包括我们系统各个服务模块定义的rpc接口的参数、作用说明。
protoc --doc_out=html,service_proto.html:./ ./*/*.proto
—doc_out可设置为html、json、markdown、docbook,最终会生成不一样的格式。目前html页面比较简单,可生成json后自己做优化展示。