Koa.js API 文档自动生成与 OpenAPI 实践
API 文档自动化概述
良好的 API 文档是前后端分离开发的基础。手动编写文档不仅繁琐,而且容易与实际代码脱节。本文介绍在 Koa.js 项目中如何实现 API 文档的自动生成,包括 Swagger/OpenAPI 集成、注解驱动文档、版本管理等功能。
技术选型对比
| 方案 | 特点 | 集成难度 | 适用场景 |
|---|---|---|---|
| Swagger UI | 可视化界面、在线调试 | 低 | 快速集成 |
| 注解驱动 | 代码即文档、自动生成 | 中 | 大型项目 |
| RAML | YAML 格式、RESTful API | 中 | 标准规范 |
| API Blueprint | Markdown 格式、易读 | 中 | 团队协作 |
Swagger 集成实现
在 Koa.js 中集成 Swagger UI:
// Swagger 集成中间件
const KoaSwagger = require('koa2-swagger-ui');
const swaggerJsdoc = require('swagger-jsdoc');
class SwaggerMiddleware {
constructor(options) {
this.options = options;
this.spec = null;
}
// 初始化 Swagger 规范
async init() {
const swaggerOptions = {
definition: {
openapi: '3.0.0',
info: {
title: this.options.title || 'API Documentation',
version: this.options.version || '1.0.0',
description: this.options.description || 'API Documentation',
contact: {
name: this.options.contactName || 'API Support',
email: this.options.contactEmail || 'support@example.com'
},
license: {
name: this.options.license || 'MIT',
url: this.options.licenseUrl || 'https://opensource.org/licenses/MIT'
},
servers: this.options.servers || []
},
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
},
apiKey: {
type: 'apiKey',
in: 'header',
name: 'X-API-Key'
}
},
schemas: this.options.schemas || {},
responses: this.options.responses || {}
},
tags: this.options.tags || []
},
apis: this.options.apis || ['./routes/**/*.js']
};
this.spec = swaggerJsdoc(swaggerOptions);
return this.spec;
}
// 中间件
middleware() {
return async (ctx, next) => {
// 拦截 /api-docs 请求
if (ctx.path === '/api-docs' || ctx.path.startsWith('/api-docs/')) {
// 返回 Swagger UI 页面
ctx.body = KoaSwagger({
swaggerOptions: {
spec: this.spec,
url: '/api-docs/json'
}
});
return;
}
// 返回 OpenAPI 规范 JSON
if (ctx.path === '/api-docs/json') {
ctx.set('Content-Type', 'application/json');
ctx.body = this.spec;
return;
}
await next();
};
}
}
// 使用示例
const swagger = new SwaggerMiddleware({
title: 'EIMS 企业管理系统 API',
version: '1.0.0',
description: '企业信息化管理系统 RESTful API 文档',
servers: [
{ url: 'http://localhost:3000', description: '开发环境' },
{ url: 'https://api.eims.com', description: '生产环境' }
],
apis: ['./routes/*.js', './controllers/*.js']
});
await swagger.init();
app.use(swagger.middleware());
路由注解实现
通过注解自动生成 API 文档:
// 路由注解解析器
const RouteAnnotation = require('./annotation');
class ApiDocGenerator {
constructor() {
this.paths = {};
this.definitions = {};
this.tags = [];
}
// 解析控制器类
parseController(ControllerClass) {
const controller = new ControllerClass();
const classDoc = RouteAnnotation.getClassDoc(ControllerClass);
const methods = RouteAnnotation.getMethodDocs(ControllerClass);
for (const method of methods) {
const path = this.buildPath(classDoc.basePath, method.path, method.method);
const operationId = `${ControllerClass.name}_${method.name}`;
this.paths[path] = this.paths[path] || {};
this.paths[path][method.method] = {
operationId,
summary: method.summary || method.description,
description: method.description,
tags: method.tags || [classDoc.tag],
deprecated: method.deprecated || false,
parameters: this.parseParameters(method.parameters),
requestBody: this.parseRequestBody(method.requestBody),
responses: this.parseResponses(method.responses),
security: method.security ? [{ bearerAuth: [] }] : []
};
// 添加到标签列表
if (method.tags && !this.tags.find(t => t.name === method.tags[0])) {
this.tags.push({ name: method.tags[0], description: classDoc.description });
}
}
return this;
}
// 构建路径
buildPath(basePath, path, method) {
const fullPath = (basePath || '') + path;
return fullPath.replace(/{(\w+)}/g, ':$1'); # 转换 :id 为 {id}
}
// 解析参数
parseParameters(params) {
if (!params) return [];
return params.map(param => ({
name: param.name,
in: param.in, // query, path, header, cookie
description: param.description,
required: param.required || false,
schema: {
type: param.type,
format: param.format,
enum: param.enum,
default: param.default
}
}));
}
// 解析请求体
parseRequestBody(requestBody) {
if (!requestBody) return null;
const { schema, description, required } = requestBody;
return {
description,
required: required !== false,
content: {
'application/json': {
schema: this.getSchemaRef(schema)
}
}
};
}
// 解析响应
parseResponses(responses) {
const result = {};
for (const [code, response] of Object.entries(responses)) {
result[code] = {
description: response.description,
content: response.content ? {
'application/json': {
schema: this.getSchemaRef(response.schema)
}
} : undefined
};
}
return result;
}
// 获取 Schema 引用
getSchemaRef(schema) {
if (!schema) return {};
if (schema.$ref) {
return { $ref: `#/components/schemas/${schema.$ref}` };
}
if (schema.type === 'array') {
return {
type: 'array',
items: this.getSchemaRef(schema.items)
};
}
if (schema.type === 'object') {
return {
type: 'object',
properties: Object.entries(schema.properties).reduce((acc, [key, prop]) => {
acc[key] = this.getSchemaRef(prop);
return acc;
}, {})
};
}
return schema;
}
// 生成 OpenAPI 规范
generate() {
return {
openapi: '3.0.0',
info: {
title: 'API Documentation',
version: '1.0.0'
},
paths: this.paths,
components: {
schemas: this.definitions,
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
}
}
},
tags: this.tags
};
}
}
// 注解定义
const api = {
// 类级别注解
Controller: (config) => {
return (target) => {
RouteAnnotation.setClassDoc(target, config);
return target;
};
},
// 方法级别注解
Get: (path, config = {}) => RouteAnnotation.createMethodDoc('get', path, config),
Post: (path, config = {}) => RouteAnnotation.createMethodDoc('post', path, config),
Put: (path, config = {}) => RouteAnnotation.createMethodDoc('put', path, config),
Delete: (path, config = {}) => RouteAnnotation.createMethodDoc('delete', path, config),
// 参数注解
Query: (name, config = {}) => RouteAnnotation.createParamDoc('query', name, config),
Param: (name, config = {}) => RouteAnnotation.createParamDoc('path', name, config),
Body: (config = {}) => RouteAnnotation.createParamDoc('body', 'body', config),
Header: (name, config = {}) => RouteAnnotation.createParamDoc('header', name, config),
// 响应注解
Success: (status, config = {}) => RouteAnnotation.createResponseDoc(status, config),
Error: (status, config = {}) => RouteAnnotation.createResponseDoc(status, config)
};
// 使用示例
@Controller({
basePath: '/api/users',
tag: '用户管理',
description: '用户相关接口'
})
class UserController {
@Get('/users', {
summary: '获取用户列表',
description: '分页获取用户列表,支持过滤和排序',
tags: ['用户管理']
})
@Query('page', { type: 'integer', description: '页码', default: 1 })
@Query('pageSize', { type: 'integer', description: '每页数量', default: 20 })
@Query('keyword', { type: 'string', description: '搜索关键词' })
@Success(200, {
description: '成功',
schema: { type: 'array', items: { $ref: 'User' } }
})
@Error(500, { description: '服务器错误' })
async listUsers(ctx) {
const { page, pageSize, keyword } = ctx.query;
const users = await this.userService.findAll({ page, pageSize, keyword });
ctx.body = users;
}
@Post('/users', {
summary: '创建用户',
description: '创建新用户',
tags: ['用户管理'],
security: true
})
@Body({
description: '用户信息',
schema: { $ref: 'CreateUserDTO' }
})
@Success(201, {
description: '创建成功',
schema: { $ref: 'User' }
})
@Error(400, { description: '参数错误' })
@Error(409, { description: '用户已存在' })
async createUser(ctx) {
const userData = ctx.request.body;
const user = await this.userService.create(userData);
ctx.status = 201;
ctx.body = user;
}
}
API 版本管理
支持多版本 API 文档管理:
// API 版本管理服务
class ApiVersionManager {
constructor() {
this.versions = new Map();
}
// 注册API版本
registerVersion(version, config) {
const generator = new ApiDocGenerator();
const spec = generator.parseController(config.controller).generate();
// 添加版本信息
spec.info.version = version;
spec.info.title = `${config.title} - v${version}`;
spec.servers = config.servers || [];
spec.deprecated = config.deprecated || false;
this.versions.set(version, {
spec,
controller: config.controller,
deprecated: config.deprecated || false,
sunsetDate: config.sunsetDate,
config
});
return this;
}
// 获取版本列表
getVersionList() {
return Array.from(this.versions.entries()).map(([version, data]) => ({
version,
title: data.config.title,
deprecated: data.deprecated,
sunsetDate: data.sunsetDate,
docsUrl: `/api-docs/v${version}`
}));
}
// 获取最新稳定版本
getLatestStable() {
const stable = Array.from(this.versions.entries())
.filter(([_, data]) => !data.deprecated)
.sort((a, b) => this.compareVersion(b[0], a[0]));
return stable[0] ? { version: stable[0][0], spec: stable[0][1].spec } : null;
}
// 版本兼容检查
checkCompatibility(currentVersion, targetVersion) {
const current = this.versions.get(currentVersion);
const target = this.versions.get(targetVersion);
if (!current || !target) {
return { compatible: false, reason: '版本不存在' };
}
// 主版本不同不兼容
if (this.getMajorVersion(currentVersion) !== this.getMajorVersion(targetVersion)) {
return { compatible: false, breaking: true, reason: '主版本不同,不兼容' };
}
// 次版本升级兼容
if (this.getMinorVersion(targetVersion) > this.getMinorVersion(currentVersion)) {
return { compatible: true, breaking: false, action: 'soft_warning' };
}
return { compatible: true, breaking: false };
}
// 版本废弃通知
async getDeprecationNotices() {
const notices = [];
const now = new Date();
for (const [version, data] of this.versions.entries()) {
if (data.deprecated && data.sunsetDate) {
const daysUntilSunset = Math.ceil(
(new Date(data.sunsetDate) - now) / (1000 * 60 * 60 * 24)
);
notices.push({
version,
sunsetDate: data.sunsetDate,
daysRemaining: daysUntilSunset,
latestStableVersion: this.getLatestStable()?.version
});
}
}
return notices;
}
// 版本比较
compareVersion(v1, v2) {
const parts1 = v1.split('.').map(Number);
const parts2 = v2.split('.').map(Number);
for (let i = 0; i < 3; i++) {
if (parts1[i] > parts2[i]) return 1;
if (parts1[i] < parts2[i]) return -1;
}
return 0;
}
getMajorVersion(version) {
return parseInt(version.split('.')[0]);
}
getMinorVersion(version) {
return parseInt(version.split('.')[1]);
}
}
总结
API 文档自动化是提升开发效率的重要手段,核心价值包括:
- 代码即文档:注解驱动保持文档与代码同步
- 在线调试:Swagger UI 提供可视化调试界面
- 版本管理:多版本 API 文档并存
- 团队协作:统一的 API 规范便于前后端对接
建议在项目初期就建立完善的 API 文档体系,并集成到 CI/CD 流程中。