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、指導意見或者但願歸入的特性。