Files
novalon-website/docs/api-versioning-guide.md
T
张翔 f5dec95a83 feat: 添加管理后台页面和功能,优化测试和性能配置
refactor: 重构页面导航和滚动逻辑,提升用户体验

test: 更新测试配置和用例,增加覆盖率和稳定性

perf: 优化性能指标和阈值,适应开发环境需求

ci: 添加Lighthouse CI工作流,集成性能测试

docs: 更新API文档和健康检查端点

fix: 修复登录页面和表单提交问题

style: 调整响应式布局和可访问性改进

chore: 更新依赖项和脚本配置
2026-03-24 10:11:30 +08:00

11 KiB
Raw Blame History

API版本控制指南

概述

API版本控制是API设计的重要部分,它允许我们在不破坏现有客户端的情况下演进API。本项目采用URL路径版本控制策略。

版本控制策略

URL路径版本控制

使用URL路径中的版本号来区分不同版本的API:

/api/v1/endpoint  # 版本1
/api/v2/endpoint  # 版本2

优点

  • 清晰明了,易于理解
  • 便于缓存和路由
  • 支持多版本并存
  • 客户端易于使用

缺点

  • URL较长
  • 需要维护多个版本

版本命名规则

  • 主版本号v1, v2, v3...
  • 格式/api/v{major}/
  • 示例
    • /api/v1/content
    • /api/v1/admin/users

目录结构

当前结构(向后兼容)

src/app/api/
├── admin/
│   ├── config/
│   ├── content/
│   ├── upload/
│   └── users/
├── auth/
├── config/
├── content/
├── docs/
└── health/

版本化结构(推荐)

src/app/api/
├── v1/                    # 版本1 API
│   ├── admin/
│   │   ├── config/
│   │   ├── content/
│   │   ├── upload/
│   │   └── users/
│   ├── auth/
│   ├── config/
│   ├── content/
│   └── health/
├── admin/                 # 向后兼容(重定向到v1)
├── auth/
├── config/
├── content/
├── docs/                  # OpenAPI文档(无版本)
└── health/

实施步骤

步骤1:创建版本化API

创建v1目录

mkdir -p src/app/api/v1

迁移现有API

将现有API复制到v1目录:

# 复制admin API
cp -r src/app/api/admin src/app/api/v1/

# 复制其他API
cp -r src/app/api/auth src/app/api/v1/
cp -r src/app/api/config src/app/api/v1/
cp -r src/app/api/content src/app/api/v1/
cp -r src/app/api/health src/app/api/v1/

步骤2:更新API路由

更新v1 API路由

在v1版本的API中,更新路由路径:

// src/app/api/v1/admin/content/route.ts

/**
 * @openapi
 * /api/v1/admin/content:
 *   get:
 *     tags:
 *       - Admin
 *       - Content
 *     summary: 获取内容列表 (v1)
 *     description: 管理员获取内容列表,支持分页、筛选和搜索
 *     operationId: getAdminContentV1
 *     ...
 */
export async function GET(request: NextRequest) {
  // 实现代码
}

步骤3:创建向后兼容层

创建重定向中间件

// src/middleware.ts

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // 如果访问旧API路径,重定向到v1版本
  const legacyApiPaths = [
    '/api/admin',
    '/api/auth',
    '/api/config',
    '/api/content',
    '/api/health',
  ];

  const isLegacyApi = legacyApiPaths.some(path => 
    pathname.startsWith(path) && !pathname.includes('/v1/')
  );

  if (isLegacyApi) {
    const url = request.nextUrl.clone();
    url.pathname = pathname.replace('/api/', '/api/v1/');
    
    // 返回重定向响应(可选:也可以内部重写)
    // return NextResponse.redirect(url);
    
    // 或者内部重写(URL不变,但使用新路径)
    return NextResponse.rewrite(url);
  }

  return NextResponse.next();
}

export const config = {
  matcher: '/api/:path*',
};

步骤4:更新客户端代码

更新API客户端

// src/lib/api-client.ts

const API_VERSION = 'v1';
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || '';

export class ApiClient {
  private baseUrl: string;

  constructor(version: string = API_VERSION) {
    this.baseUrl = `${API_BASE_URL}/api/${version}`;
  }

  async get(endpoint: string, options?: RequestInit) {
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      ...options,
      method: 'GET',
    });
    return response.json();
  }

  async post(endpoint: string, data: any, options?: RequestInit) {
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      ...options,
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        ...options?.headers,
      },
      body: JSON.stringify(data),
    });
    return response.json();
  }
}

// 使用示例
const apiClient = new ApiClient('v1');
const content = await apiClient.get('/admin/content');

版本生命周期

版本状态

状态 描述 持续时间
Current 当前推荐版本 无限期
Supported 仍受支持,但不推荐新功能 6-12个月
Deprecated 即将废弃,计划移除 3-6个月
Sunset 已移除,不再可用 -

版本废弃流程

  1. 公告:提前6个月通知废弃计划
  2. 警告:在响应头中添加DeprecationSunset
  3. 迁移期:提供迁移指南和工具
  4. 移除:在预定日期移除旧版本

添加废弃头

// src/app/api/v1/admin/content/route.ts

export async function GET(request: NextRequest) {
  const response = NextResponse.json(data);
  
  // 添加废弃警告
  response.headers.set('Deprecation', 'true');
  response.headers.set('Sunset', 'Sat, 31 Dec 2026 23:59:59 GMT');
  response.headers.set('Link', '</api/v2/admin/content>; rel="successor-version"');
  
  return response;
}

版本间差异处理

向后兼容的变更

以下变更不需要增加主版本号:

  • 添加新的可选参数
  • 添加新的响应字段
  • 添加新的端点
  • 修复bug

需要新版本的变更

以下变更需要增加主版本号:

  • 移除或重命名端点
  • 移除或重命名请求/响应字段
  • 修改必填参数
  • 修改认证方式
  • 修改错误响应格式

多版本并存示例

场景:修改内容API响应格式

v1版本(旧)

// src/app/api/v1/admin/content/route.ts

/**
 * @openapi
 * /api/v1/admin/content:
 *   get:
 *     responses:
 *       200:
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 items:
 *                   type: array
 *                 pagination:
 *                   type: object
 */
export async function GET(request: NextRequest) {
  const items = await db.select().from(content);
  
  return NextResponse.json({
    items,
    pagination: { page: 1, limit: 20, total: items.length },
  });
}

v2版本(新)

// src/app/api/v2/admin/content/route.ts

/**
 * @openapi
 * /api/v2/admin/content:
 *   get:
 *     responses:
 *       200:
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 data:
 *                   type: array
 *                 meta:
 *                   type: object
 */
export async function GET(request: NextRequest) {
  const items = await db.select().from(content);
  
  return NextResponse.json({
    data: items,  // 改名:items -> data
    meta: {       // 改名:pagination -> meta
      page: 1,
      limit: 20,
      total: items.length,
      hasNext: items.length === 20,
    },
  });
}

测试策略

版本兼容性测试

// src/app/api/__tests__/version-compatibility.test.ts

import { describe, it, expect } from '@jest/globals';

describe('API Version Compatibility', () => {
  it('should return same data structure in v1 and v2', async () => {
    const v1Response = await fetch('/api/v1/admin/content');
    const v2Response = await fetch('/api/v2/admin/content');
    
    const v1Data = await v1Response.json();
    const v2Data = await v2Response.json();
    
    // 验证数据一致性
    expect(v1Data.items.length).toBe(v2Data.data.length);
    expect(v1Data.pagination.total).toBe(v2Data.meta.total);
  });
  
  it('should redirect legacy API to v1', async () => {
    const response = await fetch('/api/admin/content');
    expect(response.url).toContain('/api/v1/admin/content');
  });
});

文档更新

更新OpenAPI文档

// src/app/api/docs/route.ts

const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: '睿新致远 API',
      version: '1.0.0',
      description: `
## API版本

当前支持以下版本:

- **v1** (Current): 当前推荐版本
- **v2** (Beta): 测试版本,包含新功能

### 版本状态

| 版本 | 状态 | 发布日期 | 废弃日期 |
|------|------|----------|----------|
| v1 | Current | 2024-01-01 | - |
| v2 | Beta | 2024-06-01 | - |
      `,
    },
    servers: [
      {
        url: '/api/v1',
        description: 'API v1 (Current)',
      },
      {
        url: '/api/v2',
        description: 'API v2 (Beta)',
      },
    ],
  },
};

最佳实践

推荐做法

  1. 提前规划版本策略

    • 在API设计初期就考虑版本控制
    • 为未来变更预留空间
  2. 保持向后兼容

    • 尽可能保持旧版本可用
    • 提供充足的迁移时间
  3. 清晰的文档

    • 明确标注版本差异
    • 提供迁移指南
  4. 版本废弃通知

    • 提前通知用户
    • 使用HTTP头传递废弃信息

避免的做法

  1. 不要频繁变更主版本

    • 主版本变更应该谨慎
    • 考虑向后兼容的替代方案
  2. 不要突然移除旧版本

    • 给用户足够的迁移时间
    • 提供迁移工具和文档
  3. 不要忽略版本测试

    • 确保多版本并存时功能正常
    • 测试版本兼容性

监控和分析

版本使用统计

// src/lib/api-analytics.ts

export async function trackApiVersion(request: NextRequest) {
  const { pathname } = request.nextUrl;
  const version = pathname.match(/\/api\/v(\d+)\//)?.[1] || 'legacy';
  
  // 发送到分析服务
  await analytics.track('api_request', {
    version,
    endpoint: pathname,
    method: request.method,
    timestamp: new Date().toISOString(),
  });
}

版本使用报告

定期生成版本使用报告:

## API版本使用报告(2024年6月)

### 请求分布

| 版本 | 请求数 | 占比 | 趋势 |
|------|--------|------|------|
| v1 | 150,000 | 75% | ↓ |
| v2 | 50,000 | 25% | ↑ |

### 废弃版本使用

| 版本 | 请求数 | 废弃日期 | 建议 |
|------|--------|----------|------|
| legacy | 1,000 | 2024-12-31 | 尽快迁移到v1 |

参考资源

总结

API版本控制已集成到项目中,提供了:

清晰的版本管理 向后兼容支持 平滑的版本迁移 版本使用监控 完善的文档支持

通过合理的版本控制策略,可以:

  • 保护现有客户端
  • 安全地演进API
  • 提供良好的开发者体验
  • 维护API的长期健康