SAAS/README-API.md
2025-05-14 00:30:11 +08:00

482 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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>
);
}
```
---
文档由阿瑞创建和维护。如有问题,请联系系统管理员。