swagger-decorator:注解方式为 Koa2 应用自动生成 Swagger 文档 从属于笔者的服务端应用程序开发与系统架构,记述了如何在以 Koa2 与 koa-router 开发服务端应用时,经过自定义 swagger-decorator 库来实现类 Spring-Boot 中注解方式动态生成 Swagger 标准的接口文档。javascript
目前我司服务端应用程序框架主要采用了 Java Spring 与 Node.js,而由于今年有不少的调研阶段的产品线 Demo 发布,持续部署、接口文档以及线上质量监控这三个问题愈发突出。本文则主要针对接口文档的实时发布进行一些探讨;在先后端分离的今天,即便是由单人纵向负责某个业务流,也须要将先后端交互的接口规范清晰地定义而且发布,以保证项目的透明性与可维护性。理想的开发流程中,应当在产品设计阶段肯定好关键字段命名、数据库表设计以及接口文档;不过实际操做中每每由于业务的多变性以及人手的缺失,使得接口的定义并不能老是实时地在项目成员之间达成一致。若是要让开发人员在更改接口的同时花费额外精力维护一份开发文档,可能对于我司这样的小公司而言存在着很大的代价与风险。软件开发中存在着所谓 Single Source of Truth 的原则,咱们也须要尽可能避免文档与实际实现的不一致形成的团队内矛盾以及无用的付出。综上所述,咱们但愿可以在编写后台代码、添加注释的同时,可以自动地生成接口文档;笔者比较熟悉 Spring 中以注解方式添加 Swagger 文档的模式,不过 Java 库的抽象程度通常较高,用起来也不怎么顺手。笔者在编写我司 node-server-boilerplate 根据本身的想法设计了 swagger-decorator。此外,项目中使用 Flow 进行静态类型检测,而且遵循我司内部的 JavaScript 编程样式指南。java
咱们可使用 npm 或者 yarn 安装 swagger-decorator,须要注意的是,由于使用了注解,所以建议是配置 Webpack 与 Babel,不熟悉的同窗能够直接参考 node-server-boilerplate :node
$ yarn add swagger-decorator # 依赖于 Babel 的 transform-decorators-legacy 转换插件来使用 Decorator $ yarn add transform-decorators-legacy -D
安装完毕以后,咱们须要对项目中使用的路由进行封装。目前笔者只是针对 koa-router 中的路由对象进行封装,将来如有必要能够针对其余框架的路由解决方案进行封装。咱们首先须要作的就是在路由定义以前使用 wrappingKoaRouter
函数修饰 router 对象:数据库
import { wrappingKoaRouter } from "swagger-decorator"; ... const Router = require("koa-router"); const router = new Router(); wrappingKoaRouter(router, "localhost:8080", "/api", { title: "Node Server Boilerplate", version: "0.0.1", description: "Koa2, koa-router,Webpack" }); //定义默认的根路由 router.get("/", async function(ctx, next) { ctx.body = { msg: "Node Server Boilerplate" }; }); //定义用户处理路由 router.scan(UserController);
该函数的参数说明以下,对于 info
的结构参考这里:npm
/** * Description 将 router 对象的方法进行封装 * @param router 路由对象 * @param host API 域名 * @param basePath API 基本路径 * @param info 其余的 Swagger 基本信息 */ export function wrappingKoaRouter( router: Object, host: string = "localhost", basePath: string = "", info: Object = {} ) {}
值得一提的是,在封装 router
时,笔者自定义了 scan
方法,其可以根据自动遍历目标类中的自定义方法,有点相似于 Java 中的 ComponentScan
:编程
/** * Description 扫描某个类中的全部静态方法,按照其注解将其添加到 * @param staticClass */ router.scan = function(staticClass: Function) { let methods = Object.getOwnPropertyNames(staticClass); // 移除前三个属性 constructor、name methods.shift(); methods.shift(); methods.shift(); for (let method of methods) { router.all(staticClass[method]); } };
准备工做完成以后,咱们便可以开始定义具体的接口控制器;笔者不喜欢过多的封装,所以这里选用了类的静态方法来定义具体的接口函数,整个 Controller 也只是朴素函数。下面笔者列举了常见的获取所有用户列表、根据用户编号获取用户详情、建立新用户这几个接口的文档注释方式:json
import { apiDescription, apiRequestMapping, apiResponse, bodyParameter, pathParameter, queryParameter } from "swagger-decorator"; import User from "../entity/User"; /** * Description 用户相关控制器 */ export default class UserController { @apiRequestMapping("get", "/users") @apiDescription("get all users list") @apiResponse(200, "get users successfully", [User]) static async getUsers(ctx, next): [User] { ... } @apiRequestMapping("get", "/user/:id") @apiDescription("get user object by id, only access self or friends") @pathParameter({ name: "id", description: "user id", type: "integer" }) @queryParameter({ name: "tags", description: "user tags, for filtering users", required: false, type: "array", items: ["string"] }) @apiResponse(200, "get user successfully", User) static async getUserByID(ctx, next): User { ... } @apiRequestMapping("post", "/user") @apiDescription("create new user") @bodyParameter({ name: "user", description: "the new user object, must include user name", required: true, schema: User }) @apiResponse(200, "create new user successfully", { status_code: "200" }) static async postUser(): number { ... } }
在对接口注解的时候,咱们须要用实体类指明返回值或者请求体中包含的参数信息,所以咱们也须要使用 swagger-decorator 提供的 entityProperty
注解来为实体类添加描述。值得一提的是,这里咱们支持直接将 Object 做为描述对象的返回值,算是避免了 Java 中的一大痛点。后端
// @flow import { entityProperty } from "swagger-decorator"; /** * Description 用户实体类 */ export default class User { // 编号 @entityProperty({ type: "integer", description: "user id, auto-generated", required: false }) id: string = 0; // 姓名 @entityProperty({ type: "string", description: "user name, 3~12 characters", required: true }) name: string = "name"; // 朋友列表 friends: [number] = [1]; // 属性 properties: { address: string } = { address: "address" }; }
对于没有添加注解的属性,swagger-decorator 会自动根据其默认值来推测类型。而后咱们就能够正常地启动应用,swagger-decorator 已经自动地为 router
对象添加了两个路由,其中 /swagger
指向了 Swagger UI:api
而 /swagger/api.json
指向了 Swagger 生成的 JSON 文档:架构
欢迎有兴趣的朋友提出 ISSUE、指导意见或者但愿归入的特性。