# 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'. ``` 应将路由函数从: ```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 (
{/* 包裹在Suspense中 */} {/* 页面其他内容 */}
); } ``` --- 文档由阿瑞创建和维护。如有问题,请联系系统管理员。