# API请求规范与最佳实践

## 目录

- [基本规范](#基本规范)
  - [响应格式](#响应格式)
  - [错误处理](#错误处理)
  - [参数验证](#参数验证)
  - [安全性考虑](#安全性考虑)
- [API路由实现](#api路由实现)
  - [基础API路由](#基础api路由)
  - [动态路由实现](#动态路由实现)
  - [Next.js 15.3+路由处理器](#nextjs-153路由处理器)
- [最佳实践](#最佳实践)
- [常见问题](#常见问题)

## 基本规范

### 响应格式

所有API响应应遵循统一的格式：

```typescript
// 成功响应
{
  success: true,
  data?: any,      // 通用数据字段
  [key: string]: any, // 或使用特定业务字段名（如users, products等）
  message?: string  // 可选，成功消息
}

// 错误响应
{
  success: false,
  error: string,  // 错误消息
  code?: string   // 可选，错误代码
}
```

**示例：通用数据字段**
```typescript
return NextResponse.json({
  success: true,
  data: rows
});
```

**示例：特定业务字段**
```typescript
return NextResponse.json({
  success: true,
  users: rows  // 使用更具描述性的字段名
});
```

### 错误处理

始终使用try-catch处理API操作，并返回适当的错误信息：

```typescript
try {
  // API操作
} catch (error) {
  console.error('操作描述失败:', error);
  return NextResponse.json(
    { 
      success: false, 
      error: error instanceof Error ? error.message : '未知错误' 
    },
    { status: 500 }
  );
}
```

### 参数验证

在执行操作前验证输入参数：

```typescript
// 获取参数
const { id } = params;

// 验证参数
if (!id || isNaN(Number(id))) {
  return NextResponse.json(
    { success: false, error: '无效的ID参数' },
    { status: 400 }
  );
}

// 继续操作
```

### 安全性考虑

- 使用参数化查询防止SQL注入
- 不要在响应中返回敏感信息（如密码、令牌）
- 对数据库操作使用最小权限原则
- 验证用户权限和身份认证信息

```typescript
// 良好实践 - 使用参数化查询
const [user] = await req.db.query(
  'SELECT id, username FROM users WHERE id = ?', 
  [userId]
);

// 不良实践 - 容易遭受SQL注入
const [user] = await req.db.query(
  `SELECT * FROM users WHERE id = ${userId}`
);
```

## API路由实现

### 基础API路由

使用数据库连接中间件的API路由基本实现：

```typescript
// API路由文件（app/api/your-route/route.ts）
import { NextResponse } from 'next/server';
import { connectSystemDB, RequestWithDB } from '@/lib/db';

async function handler(req: RequestWithDB) {
  try {
    // 使用req.db访问数据库连接
    const [rows] = await req.db.query('SELECT * FROM your_table');
    
    return NextResponse.json({
      success: true,
      data: rows
    });
  } catch (error) {
    console.error('操作失败:', error);
    return NextResponse.json(
      { success: false, error: '数据库操作失败' },
      { status: 500 }
    );
  }
}

// 导出使用中间件包装的处理函数
export const GET = connectSystemDB(handler);
```

### 必要的导入说明

在Next.js的API路由中，通常需要以下两个核心导入：

```typescript
import { NextRequest, NextResponse } from 'next/server';
```

- **NextRequest**: 扩展了原生Request对象，提供了额外的便利方法和属性，如：
  - `nextUrl`: 获取解析后的URL对象，可访问`pathname`、`searchParams`等
  - `cookies`: 访问和操作请求的Cookie
  - `headers`: 获取请求头
  - `json()`: 解析JSON请求体

- **NextResponse**: 用于创建和返回响应，提供了多种便利方法：
  - `NextResponse.json()`: 创建JSON响应，自动设置Content-Type
  - `NextResponse.redirect()`: 创建重定向响应
  - `NextResponse.rewrite()`: 创建重写响应
  - `NextResponse.next()`: 继续请求处理链

这两个对象是构建API路由的基础，几乎在所有API路由实现中都需要使用。

### 动态路由实现

在Next.js的动态路由中，必须**正确处理动态参数**。在13.4以后的版本中，动态路由参数需要使用`await`来获取：

```typescript
// 正确的动态路由实现
// API路由文件（app/api/your-route/[param]/route.ts）
import { NextRequest, NextResponse } from 'next/server';
import { connectSystemDB, RequestWithDB } from '@/lib/db';

export const GET = async (
  req: NextRequest,
  context: { params: { param: string } }
) => {
  try {
    // 正确获取动态参数 - 使用await
    const params = await context.params;
    const param = params.param;
    
    // 验证参数
    if (!param) {
      return NextResponse.json(
        { success: false, error: '无效的参数' },
        { status: 400 }
      );
    }
    
    // 处理函数
    const handler = async (dbReq: RequestWithDB) => {
      // 数据库操作
      const [rows] = await dbReq.db.query(
        'SELECT * FROM table WHERE id = ?',
        [param]
      );
      
      return NextResponse.json({
        success: true,
        data: rows
      });
    };
    
    // 执行处理函数
    return await connectSystemDB(handler)(req);
    
  } catch (error) {
    console.error('操作失败:', error);
    return NextResponse.json(
      { success: false, error: '操作失败' },
      { status: 500 }
    );
  }
};
```

**注意事项**：
1. 使用箭头函数定义路由处理器：`export const GET = async (...) => {...}`
2. 正确获取动态参数：`const params = await context.params;`
3. 使用ES6解构后再使用参数：`const param = params.param;`
4. 不要直接解构：`const { param } = context.params;` - 这会导致错误

#### 路由处理函数参数说明

在Next.js App Router中，路由处理函数（如GET、POST、PUT、DELETE等）需要接收两个参数：

1. **第一个参数**：`request: NextRequest` - 包含请求信息的对象，包括请求头、URL、搜索参数等
2. **第二个参数**：`context: { params: { [key: string]: string } }` - 包含动态路由参数的上下文对象

对于动态路由如`/api/users/[id]`，路由处理函数会接收如下参数：

```typescript
export async function GET(
  request: NextRequest,
  context: { params: { id: string } }
) {
  const userId = context.params.id;
  // 使用userId处理请求...
}
```

这种双参数模式对于获取动态路由参数非常有用，但在Next.js 15.3+版本中已被弃用，转而使用单参数模式，从URL中提取参数（见下文）。

### Next.js 15.3+路由处理器

在Next.js 15.3+版本中，路由处理器的类型定义发生了变化。为避免类型错误，应使用以下方式实现API路由：

#### 单参数路由处理器模式

对于动态路由，应使用单参数函数并从URL中提取路径参数，避免使用带context的双参数函数：

```typescript
/**
 * 符合Next.js 15.3+的路由处理器签名
 */
export async function GET(request: NextRequest) {
  try {
    // 从URL路径中提取动态参数
    const pathname = request.nextUrl.pathname;
    const parts = pathname.split('/');
    const paramValue = parts[3]; // 例如: /api/path/[param]
    
    // 验证参数
    if (!paramValue) {
      return NextResponse.json(
        { success: false, error: '参数不能为空' },
        { status: 400 }
      );
    }
    
    // 处理逻辑
    // ...
    
  } catch (error) {
    // 错误处理
  }
}
```

#### 路由处理器函数声明方式

推荐使用函数声明而非箭头函数：

```typescript
// 推荐 ✅
export async function GET(request: NextRequest) {
  // 处理逻辑
}

// 不推荐 ❌
export const GET = async (request: NextRequest) => {
  // 处理逻辑
};
```

#### 修复类型错误示例

如果构建时出现以下类型错误：

```
Type '{ __tag__: "GET"; __param_position__: "second"; __param_type__: { params: { paramName: string; }; }; }' 
does not satisfy the constraint 'ParamCheck<RouteContext>'.
```

应将路由函数从：

```typescript
// 可能导致类型错误的实现 ❌
export async function GET(
  request: NextRequest,
  { params }: { params: { paramName: string } }
) {
  const value = params.paramName;
  // ...
}
```

修改为：

```typescript
// 修复后的实现 ✅
export async function GET(request: NextRequest) {
  // 从URL中提取参数
  const pathname = request.nextUrl.pathname;
  const paramName = pathname.split('/')[3]; // 适当调整索引位置
  
  // ...继续处理
}
```

## 最佳实践

1. **使用标准格式**：始终使用统一的响应格式
2. **错误处理**：所有API路由都应包含try-catch块
3. **参数验证**：在处理前验证所有输入参数
4. **日志记录**：记录关键操作和错误信息
5. **权限验证**：确保用户有权限执行请求的操作
6. **状态码使用**：使用正确的HTTP状态码
   - 200: 成功
   - 201: 创建成功
   - 400: 请求错误/参数无效
   - 401: 未授权
   - 403: 禁止访问
   - 404: 资源不存在
   - 500: 服务器内部错误
7. **客户端组件中使用useSearchParams**：始终将使用useSearchParams的组件包裹在Suspense边界中，避免构建警告

## 常见问题

### 问：如何处理文件上传？

**答**：在Next.js的App Router中，使用`formData`来处理文件上传：

```typescript
export const POST = async (req: NextRequest) => {
  try {
    const formData = await req.formData();
    const file = formData.get('file') as File;
    
    // 处理文件上传
    // ...
    
    return NextResponse.json({ success: true });
  } catch (error) {
    return NextResponse.json(
      { success: false, error: '文件上传失败' },
      { status: 500 }
    );
  }
};
```

### 问：如何实现分页API？

**答**：使用查询参数实现分页：

```typescript
export const GET = async (req: NextRequest) => {
  try {
    const { searchParams } = new URL(req.url);
    const page = parseInt(searchParams.get('page') || '1');
    const limit = parseInt(searchParams.get('limit') || '10');
    const offset = (page - 1) * limit;
    
    // 查询数据
    // ...
    
    return NextResponse.json({
      success: true,
      data: rows,
      pagination: {
        page,
        limit,
        total: totalCount
      }
    });
  } catch (error) {
    // 错误处理
  }
};
```

### 问：如何处理动态路由中的错误？

**答**：确保正确使用await获取参数，并使用try-catch处理可能的错误：

```typescript
try {
  const params = await context.params;
  // 使用params...
} catch (error) {
  console.error('参数获取失败:', error);
  return NextResponse.json(
    { success: false, error: '参数处理错误' },
    { status: 400 }
  );
}
```

### 问：如何修复Next.js 15.3+中的路由处理器类型错误？

**答**：使用单参数路由处理器，从请求URL中提取路径参数：

```typescript
export async function GET(request: NextRequest) {
  try {
    // 从URL路径中提取动态参数
    const pathname = request.nextUrl.pathname;
    const paramValue = pathname.split('/')[3]; // 根据路径结构调整索引
    
    // 继续处理...
  } catch (error) {
    // 错误处理
  }
}
```

### 问：如何处理useSearchParams导致的构建警告？

**答**：将使用useSearchParams的组件包裹在Suspense边界中：

```tsx
// 正确处理useSearchParams的方式
'use client';

import { Suspense } from 'react';
import { useSearchParams } from 'next/navigation';

// 创建一个独立的组件处理搜索参数
function SearchParamsHandler({ onParamsChange }) {
  const searchParams = useSearchParams();
  
  // 使用searchParams的逻辑
  // ...
  
  return null;
}

export default function Page() {
  // 页面主要内容
  return (
    <div>
      {/* 包裹在Suspense中 */}
      <Suspense fallback={null}>
        <SearchParamsHandler onParamsChange={handleParamsChange} />
      </Suspense>
      
      {/* 页面其他内容 */}
    </div>
  );
}
```

---

文档由阿瑞创建和维护。如有问题，请联系系统管理员。 