KOA技术分享

专注 Koa.js 框架的编程知识分享

Koa.js API 文档自动生成与 Swagger 集成

API 文档概述

良好的 API 文档是提高开发效率、促进团队协作的关键。传统的手写文档方式存在更新不及时、格式不统一等问题。通过自动化工具,可以实现代码与文档的同步更新,确保文档的准确性和时效性。本文介绍 Koa.js 项目中 API 文档的自动生成方案,基于 Swagger/OpenAPI 规范实现。

技术选型

API 文档自动化技术栈:

工具/库 功能描述 特点
koa-swagger-doctornpm 从代码生成Swagger文档 零配置、注解驱动
swagger-ui-express 提供Swagger UI界面 可视化、在线测试
@apidevtools/swagger-cli 验证和合并OpenAPI文档 CI/CD 集成
openapi-schema-validator OpenAPI 规范验证 保证文档质量

核心功能实现

1. 安装和基础配置

安装相关依赖:

npm install koa2-swagger-ui swagger-jsdoc

// swagger-jsdoc: 根据 JSDoc 注释生成 Swagger 文档
// koa2-swagger-ui: 在 Koa 中集成 Swagger UI
// swagger.js - 文档配置
const swaggerJsdoc = require('swagger-jsdoc');

const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'Koa.js REST API',
      version: '1.0.0',
      description: 'Koa.js RESTful API 文档',
      contact: {
        name: 'API Support',
        email: 'support@example.com'
      },
      license: {
        name: 'MIT',
        url: 'https://opensource.org/licenses/MIT'
      }
    },
    servers: [
      {
        url: 'http://localhost:3000',
        description: '开发环境'
      },
      {
        url: 'https://api.example.com',
        description: '生产环境'
      }
    ],
    components: {
      securitySchemes: {
        bearerAuth: {
          type: 'http',
          scheme: 'bearer',
          bearerFormat: 'JWT'
        },
        apiKey: {
          type: 'apiKey',
          in: 'header',
          name: 'X-API-Key'
        }
      }
    },
    security: [{
      bearerAuth: [],
      apiKey: []
    }]
  },
  apis: ['./routes/*.js', './controllers/*.js'] // 需要扫描的路由文件
};

const swaggerSpec = swaggerJsdoc(options);

module.exports = swaggerSpec;

2. 路由注解配置

在路由文件中使用 JSDoc 注解:

/**
 * @swagger
 * /api/users:
 *   get:
 *     summary: 获取用户列表
 *     description: 分页获取用户列表,支持过滤和排序
 *     tags:
 *       - 用户管理
 *     parameters:
 *       - in: query
 *         name: page
 *         schema:
 *           type: integer
 *           default: 1
 *         description: 页码
 *       - in: query
 *         name: pageSize
 *         schema:
 *           type: integer
 *           default: 10
 *         description: 每页数量
 *       - in: query
 *         name: status
 *         schema:
 *           type: string
 *           enum: [active, inactive, all]
 *           default: active
 *         description: 用户状态过滤
 *       - in: query
 *         name: sort
 *         schema:
 *           type: string
 *           default: createdAt
 *         description: 排序字段
 *     responses:
 *       200:
 *         description: 成功获取用户列表
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 code:
 *                   type: integer
 *                   example: 0
 *                 message:
 *                   type: string
 *                   example: success
 *                 data:
 *                   type: object
 *                   properties:
 *                     list:
 *                       type: array
 *                       items:
 *                         $ref: '#/components/schemas/User'
 *                     pagination:
 *                       $ref: '#/components/schemas/Pagination'
 *       400:
 *         description: 请求参数错误
 *       500:
 *         description: 服务器内部错误
 *
 *   post:
 *     summary: 创建用户
 *     tags:
 *       - 用户管理
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             $ref: '#/components/schemas/CreateUserRequest'
 *     responses:
 *       201:
 *         description: 用户创建成功
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/UserResponse'
 *       400:
 *         description: 请求参数错误
 *       409:
 *         description: 用户已存在
 */

// 在路由处理函数中使用
router.get('/api/users', async (ctx) => {
  const { page = 1, pageSize = 10, status = 'active', sort = 'createdAt' } = ctx.query;

  const result = await userService.getUsers({
    page: parseInt(page),
    pageSize: parseInt(pageSize),
    status,
    sort
  });

  ctx.body = {
    code: 0,
    message: 'success',
    data: result
  };
});

router.post('/api/users', async (ctx) => {
  const userData = ctx.request.body;
  const user = await userService.createUser(userData);

  ctx.status = 201;
  ctx.body = {
    code: 0,
    message: 'success',
    data: user
  };
});

3. 数据模型注解

定义 Schema 数据模型:

/**
 * @swagger
 * components:
 *   schemas:
 *     User:
 *       type: object
 *       required:
 *         - id
 *         - username
 *         - email
 *       properties:
 *         id:
 *           type: string
 *           format: uuid
 *           description: 用户ID
 *         username:
 *           type: string
 *           description: 用户名
 *         email:
 *           type: string
 *           format: email
 *           description: 邮箱
 *         status:
 *           type: string
 *           enum: [active, inactive]
 *           description: 用户状态
 *         role:
 *           type: string
 *           enum: [admin, user, guest]
 *           description: 用户角色
 *         createdAt:
 *           type: string
 *           format: date-time
 *           description: 创建时间
 *         updatedAt:
 *           type: string
 *           format: date-time
 *           description: 更新时间
 *       example:
 *         id: 550e8400-e29b-41d4-a716-446655440000
 *         username: john_doe
 *         email: john@example.com
 *         status: active
 *         role: user
 *         createdAt: 2025-01-01T00:00:00.000Z
 *
 *     CreateUserRequest:
 *       type: object
 *       required:
 *         - username
 *         - email
 *         - password
 *       properties:
 *         username:
 *           type: string
 *           minLength: 3
 *           maxLength: 20
 *         email:
 *           type: string
 *           format: email
 *         password:
 *           type: string
 *           minLength: 6
 *           format: password
 *         role:
 *           type: string
 *           enum: [admin, user, guest]
 *           default: user
 *
 *     Pagination:
 *       type: object
 *       properties:
 *         page:
 *           type: integer
 *         pageSize:
 *           type: integer
 *         total:
 *           type: integer
 *         totalPages:
 *           type: integer
 *
 *     UserResponse:
 *       type: object
 *       properties:
 *         code:
 *           type: integer
 *           example: 0
 *         message:
 *           type: string
 *           example: success
 *         data:
 *           $ref: '#/components/schemas/User'
 *
 *     ErrorResponse:
 *       type: object
 *       properties:
 *         code:
 *           type: integer
 *           example: 1
 *         message:
 *           type: string
 *         errors:
 *           type: array
 *           items:
 *             type: object
 *             properties:
 *               field:
 *                 type: string
 *               message:
 *                 type: string
 */

4. 集成 Swagger UI

在 Koa 中启用 Swagger UI:

// app.js
const Koa = require('koa');
const Router = require('koa-router');
const koaStatic = require('koa-static');
const path = require('path');
const swaggerJsdoc = require('swagger-jsdoc');
const { swaggerUi, swaggerSpec } = require('./swagger');

const app = new Koa();
const router = new Router();

// 静态文件服务( Swagger UI 资源)
app.use(koaStatic(path.join(__dirname, 'public')));

// API 路由
router.get('/api/users', userController.list);
router.post('/api/users', userController.create);
// ... 其他路由

// Swagger 文档 JSON 端点
router.get('/swagger.json', async (ctx) => {
  ctx.body = swaggerSpec;
});

// Swagger UI 中间件
router.get('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));

// API 文档页面(旧路径兼容)
router.get('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));

app.use(router.routes());
app.use(router.allowedMethods());

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
  console.log('Swagger docs: http://localhost:3000/docs');
});
// swagger.js - 导出 swagger-ui 中间件
const swaggerUi = require('swagger-ui-express');
const swaggerJsdoc = require('swagger-jsdoc');

// swaggerSpec 配置
const swaggerSpec = swaggerJsdoc({
  definition: {
    openapi: '3.0.0',
    info: {
      title: '我的Koa API',
      version: '1.0.0',
      description: 'API documentation'
    },
    servers: [
      { url: 'http://localhost:3000' }
    ]
  },
  apis: ['./routes/*.js']
});

module.exports = {
  swaggerUi,
  swaggerSpec
};

5. 高级:多版本 API 文档

支持 API 版本管理:

// 多版本 API 配置
const versions = {
  'v1': {
    swagger: swaggerJsdoc({
      definition: {
        openapi: '3.0.0',
        info: {
          title: '我的Koa API - v1',
          version: '1.0.0',
          description: '第一版API'
        },
        servers: [{ url: '/api/v1' }]
      },
      apis: ['./routes/v1/*.js']
    }),
    prefix: '/api/v1'
  },
  'v2': {
    swagger: swaggerJsdoc({
      definition: {
        openapi: '3.0.0',
        info: {
          title: '我的Koa API - v2',
          version: '2.0.0',
          description: '第二版API,新增xxx功能'
        },
        servers: [{ url: '/api/v2' }]
      },
      apis: ['./routes/v2/*.js']
    }),
    prefix: '/api/v2'
  }
};

// 版本选择 UI
const swaggerOptions = {
  explorer: true,
  swaggerOptions: {
    urls: [
      {
        url: '/api/v1/swagger.json',
        name: 'API v1.0'
      },
      {
        url: '/api/v2/swagger.json',
        name: 'API v2.0'
      }
    ]
  }
};

// 各版本文档路由
router.get('/api/v1/swagger.json', (ctx) => {
  ctx.body = versions.v1.swagger;
});

router.get('/api/v2/swagger.json', (ctx) => {
  ctx.body = versions.v2.swagger;
});

// Swagger UI(支持版本切换)
router.get('/docs', swaggerUi.serve, swaggerUi.setup(null, swaggerOptions));

6. CI/CD 集成

在持续集成中验证和发布文档:

// package.json 配置脚本
{
  "scripts": {
    "docs:validate": "swagger-cli validate ./swagger.json",
    "docs:serve": "node scripts/serve-docs.js",
    "docs:build": "rm -rf docs && swagger-cli bundle -r ./swagger.json -o docs/swagger.json"
  }
}
# .gitlab-ci.yml 或 .github/workflows/api-docs.yml
stages:
  - validate
  - deploy

validate-api-docs:
  stage: validate
  script:
    - npm install
    - npm run docs:validate
  only:
    - merge_requests
    - master

deploy-api-docs:
  stage: deploy
  script:
    - npm run docs:build
    # 部署到静态网站托管
    - npx vercel deploy --prod --dir docs
  only:
    - master
  when: manual

使用效果

自动生成文档的优势:

功能 描述
在线测试 可在 UI 中直接发送请求测试 API
代码即文档 更新代码时自动更新文档
团队协作 前端可并行开发,减少沟通成本
自动化测试 CI 中自动验证 API 规范

最佳实践

API 文档编写建议:

总结

通过 Swagger/OpenAPI 规范和自动化工具,可以实现 API 文档与代码的同步更新,显著提高开发效率和 API 质量。建议将文档验证加入 CI/CD 流程,确保文档的准确性和规范性。

← 上一篇:Koa.js WebSocket实时通信与IM系统开发 上篇:Koa.js异步编程深入实践与性能优化 →