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

12 KiB
Raw Permalink Blame History

API请求规范与最佳实践

目录

基本规范

响应格式

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

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

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

示例:通用数据字段

return NextResponse.json({
  success: true,
  data: rows
});

示例:特定业务字段

return NextResponse.json({
  success: true,
  users: rows  // 使用更具描述性的字段名
});

错误处理

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

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

参数验证

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

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

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

// 继续操作

安全性考虑

  • 使用参数化查询防止SQL注入
  • 不要在响应中返回敏感信息(如密码、令牌)
  • 对数据库操作使用最小权限原则
  • 验证用户权限和身份认证信息
// 良好实践 - 使用参数化查询
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路由基本实现

// 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路由中通常需要以下两个核心导入

import { NextRequest, NextResponse } from 'next/server';
  • NextRequest: 扩展了原生Request对象提供了额外的便利方法和属性

    • nextUrl: 获取解析后的URL对象可访问pathnamesearchParams
    • cookies: 访问和操作请求的Cookie
    • headers: 获取请求头
    • json(): 解析JSON请求体
  • NextResponse: 用于创建和返回响应,提供了多种便利方法:

    • NextResponse.json(): 创建JSON响应自动设置Content-Type
    • NextResponse.redirect(): 创建重定向响应
    • NextResponse.rewrite(): 创建重写响应
    • NextResponse.next(): 继续请求处理链

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

动态路由实现

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

// 正确的动态路由实现
// 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],路由处理函数会接收如下参数:

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的双参数函数

/**
 * 符合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) {
    // 错误处理
  }
}

路由处理器函数声明方式

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

// 推荐 ✅
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>'.

应将路由函数从:

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

修改为:

// 修复后的实现 ✅
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来处理文件上传:

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

:使用查询参数实现分页:

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处理可能的错误

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中提取路径参数

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

如何处理useSearchParams导致的构建警告

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

// 正确处理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>
  );
}

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