四、Sails项目的Api文档——集成Swagger解决方案

Api的迷思

我们都知道写代码要写注释是常识。而写Api程序注释是不够的,因为使用你Api的人看不到源代码。为了让使用你Api的人可以调用,我们还必须为每一个Api写调用说明文档,甚至还必须给出使用示例。这为写Api的程序员多了不少的工作量,并且许多程序员写代码会比较认真,写文档会因为被认为是不那么重要的工作比较马虎,这也导致文档质量参差不齐。另外Api是会变的,每一次变动还需要重新修改使用说明文档,这也会照成错漏,导致文档和实际情况不一致。文档一大堆,时间一长也不知道文档都放哪里了。一个项目半年之后出现找不到说明文档的情况非常正常。如果项目后面有人事调动,新人还需要和旧人进行文档方面的交接。。。文档是许多程序员的痛,在行业内一直都没有很好的解决,直到出现了Swagger。

Swagger

Swagger概述

Swagger是一套基于OpenAPI规范构建的开源工具,它是一种规范,你只需要按照它的规范去定义接口及接口相关的信息。再通过Swagger衍生出来的一系列项目和工具,就可以做到生成各种格式的接口文档,以及在线接口调试页面等等。在开发新版本或者迭代版本的时候,只需要更新Swagger描述文件,就可以再次自动生成接口文档,做到调用端代码、服务端代码以及接口文档的一致性。
为了能更感性直观的理解Swagger,请大家前往https://editor.swagger.io/
在这里插入图片描述
归纳一下,大约有以下几点

  • 一定格式的Json来描述你的Api
  • 自动根据你的Json生成html页面(SwaggerUI)
  • 在生成的SwaggerUI里面有分组功能,有概述,描述,参数,数据示例,数据结构,试一试(Try it out)功能

如果你是前端程序员,看到这样的文档是不会也会感觉心里更有底了。( ̄▽ ̄)~*

在Sails中集成Swagger

使用Swagger看起来很美好,但是如果Json还是需要自己来写,那就是另外一种痛苦了。有许多聪明的懒人也已经注意到这个,各种各样的可以自动生成Swagger Json的工具应运而生,并且其中还有专门为Sails设计的开源库。Let’s go!

安装

npm install sails-hook-swagger-generator --save

安装完这个库之后,还有一个动作一定不能忽略,就是要在根目录下创建一个swagger文件夹,因为该库默认生成的Json是放在这个文件夹的,如果没有这个文件夹,生成的时候就会产生错误。如果已经做好了准备,启动我们的系统吧,运行:

node app.js 

Boom! 在swagger里面多出了一个名为swagger.json的文件,如果把swagger.json的内容copy到https://editor.swagger.io/ 网站上左边的编辑器里面,替代原来的内容。我们将会在右边看到我们自己的Api文档。(大家可以动手试试)

Swagger 生成设置

虽然我们可以看到生成的api文档了,但是怎么生成的我们还是需要进行配置的。在config文件夹里面添加swaggergenerator.js,然后在config/swaggergenerator.js写类似如下代码:

module.exports['swagger-generator'] = {
    disabled: false,//如果设置为true,就表示不生成,这个选项是我们开发完成投入生成的时候需要的
    swaggerJsonPath: './swagger/swagger.json',//这是生成json文件的位置,后面我可以进行修改
    swagger: {
        openapi: '3.0.0',
        info: {
            title: 'Api文档',
            description: 'AdminSystem 后端Api说明及测试用例',
            termsOfService: 'https://github.com/orgs/PassionOrganization/teams/codeteam',
            contact: {name: 'JimmyTsai', url: 'https://github.com/JimmyTsai75', email: 'zzcaism@163.com'},
            license: false,//有不需要显示的选项,可以设置为false
            version: '1.0.0'
        },
        servers: [
            { url: 'http://localhost:1898/' }
        ],
        externalDocs: {description:'点击查看更详细说明',url: "https://passionorganization.gitbook.io/sailsjs-cong-ling-kai-shi/"},
    },
    defaults: {
        responses: {
            '200': { description: 'The requested resource' },
            '404': { description: 'Resource not found' },
            '500': { description: 'Internal server error' }
        }
    },    
    includeRoute: function(routeInfo) { return true; },//这个是过滤函数,可以根据需要设置
    updateBlueprintActionTemplates: false,//可以通过这个函数设置sails蓝图模版
    postProcess: function(specifications) {  }//可以在函数中拦截输出,并修改输出内容
};

上面的这段代码是对Swagger generator进行配置,具体用请看注释。
在这里插入图片描述

生成的内容

根据配置生成的内容是swagger.json,这是个约定格式文档,大家可以打开生成的内容看看,这个地方还需要解释一下的有三个地方:

  • tags:标签是对api进行分组的一种可折叠的html页面组件,我们可以设置要生成的api放在哪里标签从而实现分组
  • components:这个里面包含两个项目,schemas和parameters 分别对应关联的查询参数和数据模型model,还有一个安全项后面的Authorization会详述。
  • paths:这个就是api路径,就是我们写在config/routes.js里面的路由路径,展开之后可以看到里面的具体设置,包括指定标签,查询参数,响应说明等

以上三个主要组件分别对应页面上的内容如下图所示:
在这里插入图片描述

这些看起来有点复杂,大致了解一下就可以,后面的具体操作可以让我们逐渐的理解更多细节,多做几个就完全明白了。

SwaggerUI

到目前为止,我们已经可以自动产生api的描述文档swagger.json,并且如果我们把内容粘贴到https://editor.swagger.io/ 这个网站上面,我们还可以看到展示效果,但是这依然不够,我们需要在我们自己的后端上面就能够展示。那我们就需要把整个https://editor.swagger.io/ 网站搬到我们的sails后端系统上,幸运的是SwaggerUI是开源的,我们可以直接下载下来

  • 下载SwaggerUI :这个开源库的网址如下:https://github.com/swagger-api/swagger-ui 我们不打算修改他,只需要download它的zip文档就够了
    在这里插入图片描述

  • Copy:下载并解压之后,源代码根目录下面有一个dist文件夹,我们只需要把这个文件夹复制到我们sails项目根目录下assets里面就可以了。现在我们已经拥有了SwaggerUI,在浏览器中输入:http://localhost:1898/dist/#/,我们可以看到如下:
    在这里插入图片描述

  • 快好了,我们还差一步,因为现在SwaggerUI不知道我们的swagger.json文档在哪里。打开assets/dist/swagger-initializer.js
    修改SwaggerUIBundle.url指向网站根目录…/swagger.json,代码如下:

//assets/dist/swagger-initializer.js
window.onload = function() {  
  window.ui = SwaggerUIBundle({
    url: "../swagger.json",//**修改这个地方为上一级目录的swagger.json**
    dom_id: '#swagger-ui',
    deepLinking: true,
    presets: [
      SwaggerUIBundle.presets.apis,
      SwaggerUIStandalonePreset
    ],
    plugins: [
      SwaggerUIBundle.plugins.DownloadUrl
    ],
    layout: "StandaloneLayout"
  });

  //</editor-fold>
};
  • 修改后,我们还需要把我们生成的swagger.json复制到assets目录下面,操作后我们的文件目录大约是这样的:
 |-- assets
    |   |-- dist
    |   |-- favicon.ico
    |   |-- dependencies
    |   |   |-- .gitkeep
    |   |   |-- sails.io.js
    |   |-- images
    |   |-- swagger.json  //这个就是我们复制过来的
    .......

记得保存一下assets/dist/swagger-initializer.js,刷新浏览器应该就看到了

在这里插入图片描述

Assets和.tmp

  • 为什么拷贝dist文件夹到assets目录下,在浏览器的http://localhost:1898/dist/下面就可以看到swaggerUI这个网页?
    那是因为sails约定assets文件夹为网站静态资源文件夹,sails启动的时候会运行服务程序,并且把assets文件夹里面的内容copy到.tmp/public,sails服务以.tmp/public文件夹为web服务的根目录,所以我们复制过去的swaggerUI可以运行起来。并且我们如果打开.tmp文件夹,我们可以看到,我们复制到assets文件夹里面的swagger.json会自动复制到.tmp/public里面。
  • 我们可以做得更好一点,既然我们生成的swagger.json要复制到assets文件夹里面,我们不应该每次都手动操作,我们完全可以修改swagger.json的生成路径,让它直接生成在assets文件夹里面就好了。
module.exports['swagger-generator'] = {
  disabled: false,
  swaggerJsonPath: 'assets/swagger.json',//这个地方直接改成我们希望的目的地
  ....

有时候我们修改swagger的生成规则(config/swaggergenerator.js)之后,保存,重启服务并且刷新浏览器,swaggerUI里面的内容没有及时变成我们要的,这个时候可以到.tmp/public 里面把swagger.json删除掉再重试

再重复一下,现代软件工程有一个非常好的理念叫“约定高于配置”(convention over configuration),在软件工程里面这是一种非常简约的设计思想,sails许多地方都体现了这点。

这种复制是通过grunt这个库来实现的,如果需要修改,可以通过tasks/config/copy.js来实现。

另外dist文件夹名称和我们的功能不是很搭,建议修改文件夹名称为apiDoc

Blueprint 蓝图

如果按照上面的操作成功了,我们应该是可以看到许多我们的Api说明的,但是很奇怪的是,多出来许多我们没有写的路由,如下图:
在这里插入图片描述
我的config/routes.js里面是这样的:里面没有’GET /user’,‘PATCH /user/{id}’ 等等

POST /api/login/account': { action: 'users/check' },
  'GET /api/currentUser': { action: 'users/curUser' },
  'GET /api/notices': { action: 'users/notices' },
  'POST /api/login/outLogin': { action: 'users/logout' },
  'GET /api/crudDemo': { action: 'Amis/crudDemo' },
  'post /api/crudnew': { action: 'Amis/crudNew' },
  'delete /api/crudDelete': { action: 'Amis/crudDelete' },
....

但是swagger generator 帮我们找出来了,这是怎么回事?

Blueprint是什么

在sails里面只要创建了model,blueprint 实际上已经帮你实现了表的增删改查了,根本不需要写任何增删改查的代码,也不需要在config/routes.js里面添加任意的控制器,这些代码已经做好。我们以我们做好的user数据模型为例,我们在config/routes.js里面添加的路由都是以api开头的,但是如果我们打开postman软件,输入http://localhost:1898/user,我们会发现,Wow,竟然是可以查询到所有用户数据的:
在这里插入图片描述
Sails 旨在减少代码量以及启动和运行功能性应用程序所需的时间。 蓝图是 Sails根据您的应用程序设计快速生成 API路由和操作的方式。默认情况下,它会自动完成Find,Create,Update,Destroy,Add几个操作。

Blueprint 配置

Blueprint API是一个很sweet的设计,但是我不是很建议使用,有几个原因

  • 采用影子路由的方式,不熟悉sails之blueprint的维护人员在config/routes.js里面看不到,会比较困惑。
  • 多个影子路由默认采用Get,这是很容易给CSRF(Cross-site request forgery) 提供方便(当然可以通过一些安全手段解决但是没有必要留这个口)
  • 有一些查询是需要权限控制和身份认证的,如果要使用blueprint,我们依然需要对它进行设置,那还不如我们自己写
  • 不少数据库的CRUD是需要定制的,这样我们还是需要对blueprint进行覆盖

所以,如果我们要设计一个安全性要求不高的,我们可以直接采用blueprint或是混合开发,而我们对系统安全有一定要求的,那我们还是要控制blueprint的使用,甚至是关闭它。
如果需要对blueprint进行配置,我们可以进入config/blueprints.js里面进行设置。我们试着把它的所有功能关闭掉,可以这样设置:

//config/blueprints.js
module.exports.blueprints = {
  actions: false,
  rest: false,
  shortcuts: false,
  autoWatch:false,
};

以上配置更加具体的说明见:https://sailsjs.com/documentation/concepts/blueprints

配置后大家可以在postman里面再次测试(需要保存后重新启动服务),如果成功,应该可以看到postman里面显示Not Found,并且我们的swaggerUI里面多出来的路由也没有了。

swaggerUI 里面的自动变化前提是你有修改swagger.json的生成路径(swaggerJsonPath)

local.js

关闭blueprint的操作会受到sails的Environment(环境)变量设置的影响。Env是Nodejs程序开发经常使用的做法。目的是为了实现开发周期内(development)和开发完成的运营周期(production)可以有不同的配置,比如是否跨域,stockets配置等。在sails里面,开发环境可以通过启动参数或是config/local.js来配置,这个文件同时也可以配置端口号等其它比较敏感的内容,所以一般是会在gitignore里面设置为不上传到git服务器的。(我们如果是私有库并且可信任,这个文件也是可以上传的)。
如果我们在local.js里面把环境变量设置为production如下:

//config/local.js
module.exports = {
  port:1898,
 environment: 'production'
};

这个时候,sails会加载config/env/production.js并且覆盖config里面的设置,这种情况,我们要关闭blueprint api的话,也需要同时修改config/env/production.js对应设置。

sails启动后的画面是有显示当前环境配置的在这里插入图片描述

进一步控制Swagger输出

路由过滤

就算屏蔽掉了blueprint的“影子路由”我们也依然有需要对其它路由进行过来,避免我们的Api文档多维护没有使用到的路由,那我们就需要使用路由过滤函数。这个函数在config/swaggergenerator.js

//config/swaggergenerator.js
module.exports['swagger-generator'] = {
  disabled: false,
  swaggerJsonPath: 'assets/swagger.json',
  ...
   includeRoute: (routeInfo) => {
    if (routeInfo.isShortcutBlueprintRoute == true) return false;
    if (routeInfo.verb == 'put' || routeInfo.verb == 'patch') return false;
    if (routeInfo.action == 'security/grant-csrf-token') return false;
    if (!routeInfo.path.startsWith('/api/')) return false;    
    return true;
  },

比如我们通过上面的过滤函数,过滤掉所有不是api开头的路由
另外,我们还可以对一些默认的response进行配置:

defaults: {
    responses: {
      '400': { description: '验证错误' },
      '403': { description: '没有权限访问' },
      '401': { description: '认证失败' },
      '200': { description: '请求成功' },
      '404': { description: '找不到请求,请检查url和method是否正确' },
      '500': { description: '服务器内部错误' }
    },    
  },

路由的Swagger配置

目前,我们看到的一些路由是类似这样的:
在这里插入图片描述
尽管里面许多信息还是空白的,但是try it out功能按钮是没有问题的,并且也真的可以做一些类似postman的测试了(有了Swagger,并不表示就不用postman了,它有它的不可替代),当然我们还需要进一步把Api里面具体Action的详细使用描述得更加清楚一些的。这些我们可以直接在swagger.json里面手动填写,但这不是个好主意,因为我们的Api在开发过程是不断变化的,某个action一开始时实现的功能和最终实现的功能可能相差十万八千里。
我们需要像写代码注释一样的方式,把这些描述就写在代码里面,如果有变动也应该在源代码里面变动,这样我们日后就可以和管理源代码一起管理这些Api文档了。甚至它们还应该一起被push到我们的git仓库里面去的。
有两个选项,一个是写在每个controller.ts源代码里面,一个是写在config/routes.js 我更倾向于后者因为它更集中,管理起来更加方便一些。

  • 打开config/routes.js,把原来的’POST /api/userCreate’: {action: ‘User/create’} 修改成如下代码:
'POST /api/userCreate': {
    swagger: {
      summary: '创建用户',
      description: `包含email,password等信息;post body里面包含email,其中email格式由前端验证,后端只负责检查是否非空。email
      字段具备唯一性,添加的email如果有重复系统将返回500错误。`,
      tags: ['用户管理'],//放到用户管理这个tag标签里面
      //post的时候body里面的内容
      requestBody: {
        content: {
          'application/json': {
            schema: {
              properties: {
                email: { type: 'string' },
                password: { type: 'string' },
                nickname: { type: 'string' }
              },
              required: ['email', 'password'],
            }
          }
        }
      },
      //参数
      parameters:[
        {in:'header', name:'justTest',required:false,schema:{type:'string',example:'what '}}
      ],
      //对响应的400代码做解析
      responses: {
        '400': { description: '验证错误' },
      },
      //关闭扩展文档显示
      externalDocs: false,
    },
    // action不变
    action: 'User/create'
  },
  • 打开http://localhost:1898/apidoc/ (dist文件夹的名称已经改成apiDoc)可以见到如下展示效果
    在这里插入图片描述

进一步优化

还是有一点小小的不舒服:

  • 代码太长:config/routes.js这个文件变得太长了,一个路由就需要30多行,路由多起来后代码会变成很长,不利于日后的维护。通常我们希望一个js文件长度尽量控制在500-1000行,如果我们把每个表的增删改查都做成路由,10个table就需要1200行了,最好能够把swagger独立出来,减少routes.js的代码长度。
  • swagger不支持typescript:通常并且我们再routes.js里面写swagger没有任何的类型提示和限制,如果可以优化成typescript就更好了。

做法也不难:
一、在根目录下创建swagger,增加UserSwagger.ts文件,import sails-hook-swagger-generator里面对应的interfaces,把原本写在routes.js里面的swagger相关代码移过来:

//swagger/UserSwagger.ts
//导入类型支持
import { SwaggerActionAttribute } from "sails-hook-swagger-generator/lib/interfaces"
//用户控制器所在Tag
const Tag1 = "用户管理";
//移植原来的代码
const userCreate:SwaggerActionAttribute={
  summary: '创建用户',
  description: `包含email,password等信息;post body里面包含email,其中email格式由前端验证,后端只负责检查是否非空。email
  字段具备唯一性,添加的email如果有重复系统将返回500错误。`,
  tags: [Tag1],
  requestBody: {
    content: {
      'application/json': {
        schema: {
          properties: {
            email: { type: 'string' },
            password: { type: 'string' },
            nickname: { type: 'string' }
          },
          required: ['email', 'password'],
        }
      }
    }
  },
  parameters:[
    {in:'header', name:'justTest',required:false,schema:{type:'string',example:'what '}}
  ],
  responses: {
    '400': { description: '验证错误' },
  },
  externalDocs: false,
};
export { userCreate};

二、修改config/routes.js

import { userCreaten } from 'swagger/UserSwagger';
'POST /api/userCreate': {swagger: userCreate,action: 'User/create'},

三、增加别名
在package.json里面增加路径别名如下:

  "_moduleAliases": {
    "utils": "./utils",
    "swagger":"./swagger"
  },

cool,现在的代码看起来清爽多了。

因为routes.js里面我们采用’swagger/UserSwagger’,如果没有别名,这个地方要改成import { userCreaten } from ‘…/swagger/UserSwagger’;但是这种相对路径的写法容易错,采用路径别名会舒服一些。路径别名请参考本系列文章之《二、在Sails中使用Typescript》

Authorization

还剩最后一个问题了,我们上一篇讲的Jwt认证之后,我们有一些控制器的动作是需要携带token的,遇到这种情况,Swagger上面的try it out功能就会受挫,因为我们没有可以让测试人员输入token的地方。

swagger.json上面是可以有安全选项的,在components这个节点里面,除了我们前面说的schemas和parameters之外,还可以添加一个securitySchemes项。具体的做法是在swagger.json创建之前做一个拦截函数,幸运的是config/swaggergenerator.js里面是有提供这样的hook的,我们通过修改postProcess函数就可以实现,做法和路由拦截类似:

//config/swaggergenerator.js 
module.exports['swagger-generator'] = {
  ....
 postProcess: function (specifications) {
    let sch = specifications.components;
    //在Top-Level Swagger Defintions中添加身份认证项Authorize
    sch.securitySchemes = {
      APIKeyHeader: {
        type: "apiKey",
        in: "header",
        name: "Authorization",
        description: "Bearer xxxx"
      }
    }
    //在Top-Level Swagger 中添加安全选项,如果在顶级属性中添加安全项,则每一个path上都可以执行Authorize
    //如果此处没有添加,可以在每个action中的swagger里面添加security,有添加的path才能进行身份认证
    //大小写敏感
    specifications.security = [
      {
        APIKeyHeader: []
      }
    ];

  },
  .....
}

在将最终生成的Swagger写入swagger.json之前,可以使用postProcess函数进行拦截

注意APIKeyHeader 这个名称,在swagger.json里面是大小写敏感的,这是一个小坑。

成功之后,刷新swaggerUI,可见如下:可以在try it out 的之前,先做身份认证:在弹窗的value里面输入Bearer xxxx… (登录后获取的token),并且我们可看到每个path后面都带一个锁的图标,如果需要修改token,可以点击任意一个path上lock图标实现再次输入token
在这里插入图片描述

可以尝试在api/userRetrieve这个path上面使用try it out 功能,没有Authorization的时候会显示“您没有权限访问本页面",Authorizations之后就有可以正常查询了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值