手把手教你用React + Next.js API Routes搞定通义万相API调用(附完整代码)

张开发
2026/6/22 2:44:42 15 分钟阅读
手把手教你用React + Next.js API Routes搞定通义万相API调用(附完整代码)
React Next.js 全栈方案安全调用通义万相API的实战指南在当今AI技术快速迭代的背景下前端开发者如何将强大的生成式AI能力无缝集成到自己的应用中同时确保安全性和生产可用性本文将带你从零构建一个完整的解决方案解决原始纯前端方案中的CORS安全隐患实现真正可落地的企业级集成。1. 项目架构设计与安全考量当我们直接从前端调用第三方API时会面临三个核心挑战API密钥暴露风险、跨域请求限制、以及异步任务状态管理。原始方案虽然实现了基本功能但将敏感密钥存储在浏览器内存中且依赖可能不稳定的CORS插件这显然不符合生产环境要求。我们的全栈方案采用Next.js的API Routes作为安全中间层架构优势体现在密钥安全API Key仅存在于服务端环境变量中请求代理所有AI服务请求通过后端路由转发状态集中管理长期轮询任务由服务端维护速率限制可轻松添加请求限流保护# 项目目录结构 ├── pages/ │ ├── api/ │ │ ├── generate.js # 任务提交端点 │ │ └── status.js # 状态轮询端点 ├── lib/ │ ├── apiClient.js # 封装的安全调用逻辑 ├── components/ │ ├── VideoGenerator/ # 复用的前端UI组件2. 服务端实现Next.js API Routes详解2.1 环境配置与安全初始化首先在项目根目录创建.env.local文件存储敏感信息# .env.local DASHSCOPE_API_KEYyour_actual_key_here NEXT_PUBLIC_API_BASE_URL/api注意这里我们遵循Next.js的环境变量约定只有NEXT_PUBLIC_前缀的变量会暴露给客户端。2.2 核心代理接口实现创建pages/api/generate.js处理任务提交import { NextResponse } from next/server export async function POST(req) { const authHeader req.headers.get(Authorization) // 简单的客户端认证 if (authHeader ! Bearer ${process.env.INTERNAL_API_TOKEN}) { return NextResponse.json( { error: Unauthorized }, { status: 401 } ) } const body await req.json() try { const response await fetch(https://dashscope.aliyuncs.com/api/v1/services/aigc/video-generation/generation, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${process.env.DASHSCOPE_API_KEY}, X-DashScope-Async: enable }, body: JSON.stringify(body) }) if (!response.ok) { const errorData await response.json() throw new Error(errorData.message || API request failed) } const data await response.json() return NextResponse.json(data) } catch (error) { return NextResponse.json( { error: error.message }, { status: 500 } ) } }2.3 轮询状态接口优化对于长时间运行的任务我们实现了一个带缓存的轮询端点pages/api/status.jsimport { NextResponse } from next/server // 内存缓存生产环境应替换为Redis等持久化方案 const taskCache new Map() export async function GET(req) { const { searchParams } new URL(req.url) const taskId searchParams.get(taskId) if (!taskId) { return NextResponse.json( { error: Missing taskId }, { status: 400 } ) } // 检查本地缓存 if (taskCache.has(taskId)) { const cached taskCache.get(taskId) if (cached.status SUCCEEDED || cached.status FAILED) { taskCache.delete(taskId) // 清理已完成任务 } return NextResponse.json(cached) } try { const response await fetch( https://dashscope.aliyuncs.com/api/v1/tasks/${taskId}, { headers: { Authorization: Bearer ${process.env.DASHSCOPE_API_KEY} } } ) const data await response.json() taskCache.set(taskId, data.output) return NextResponse.json(data.output) } catch (error) { return NextResponse.json( { error: error.message }, { status: 500 } ) } }3. 前端适配与性能优化3.1 安全API客户端封装创建lib/apiClient.js统一管理所有API调用const API_BASE process.env.NEXT_PUBLIC_API_BASE_URL export const generateVideo async (params) { const response await fetch(${API_BASE}/generate, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${process.env.NEXT_PUBLIC_INTERNAL_TOKEN} }, body: JSON.stringify(params) }) if (!response.ok) { const error await response.json() throw new Error(error.message || Generation failed) } return response.json() } export const checkStatus async (taskId) { const response await fetch(${API_BASE}/status?taskId${taskId}, { headers: { Authorization: Bearer ${process.env.NEXT_PUBLIC_INTERNAL_TOKEN} } }) if (!response.ok) { const error await response.json() throw new Error(error.message || Status check failed) } return response.json() }3.2 增强型任务管理Hook基于原始UI组件我们创建一个更健壮的React Hook处理任务生命周期import { useState, useEffect, useCallback } from react import { generateVideo, checkStatus } from ../lib/apiClient export function useVideoGenerator() { const [state, setState] useState({ status: idle, progress: 0, resultUrl: null, error: null, taskId: null }) const startGeneration useCallback(async (params) { setState({ status: submitting, progress: 0, resultUrl: null, error: null, taskId: null }) try { // 提交生成任务 const { task_id: taskId } await generateVideo(params) setState(prev ({ ...prev, status: polling, taskId })) // 启动轮询 const pollInterval setInterval(async () { try { const status await checkStatus(taskId) if (status.task_status SUCCEEDED) { clearInterval(pollInterval) setState(prev ({ ...prev, status: success, progress: 100, resultUrl: status.video_url })) } else if (status.task_status FAILED) { clearInterval(pollInterval) setState(prev ({ ...prev, status: error, error: status.message || Generation failed })) } else { // 模拟进度更新 setState(prev ({ ...prev, progress: Math.min(prev.progress 5, 90) })) } } catch (error) { clearInterval(pollInterval) setState(prev ({ ...prev, status: error, error: error.message })) } }, 3000) return () clearInterval(pollInterval) } catch (error) { setState({ status: error, progress: 0, resultUrl: null, error: error.message, taskId: null }) } }, []) return { ...state, startGeneration } }4. 部署与生产环境最佳实践4.1 服务器配置建议环境要求开发配置生产配置Node.js版本v16v18 LTS内存512MB2GB持久化存储不需要Redis/MongoDB请求限流禁用100请求/分钟/IP日志记录控制台输出ELK/Cloud Logging4.2 安全加固措施密钥轮换定期更新API密钥建议使用密钥管理服务请求验证实现JWT验证或OAuth2.0保护API端点输入消毒严格验证所有用户输入参数CORS配置精确设置Access-Control-Allow-Origin速率限制使用next-rate-limiter等中间件// 示例API端点速率限制 import { rateLimiter } from /lib/rateLimiter export async function POST(req) { const identifier req.ip || unknown const isAllowed await rateLimiter(identifier) if (!isAllowed) { return new Response(Too many requests, { status: 429 }) } // 正常处理逻辑 }4.3 性能优化技巧客户端缓存对视频结果实现SWR缓存策略服务端缓存对API响应进行短期缓存连接复用保持到DashScope的持久连接渐进式加载对生成结果实现分块传输// SWR配置示例 import useSWR from swr function VideoResult({ taskId }) { const { data, error } useSWR( taskId ? /api/status?taskId${taskId} : null, fetcher, { refreshInterval: 3000, revalidateOnFocus: false } ) // 渲染逻辑... }5. 错误处理与调试技巧在实际项目中我们发现以下几个常见问题及解决方案问题1任务超时无响应原因视频生成时间超过预期解决将轮询超时延长至10分钟并添加超时提示问题2内存泄漏现象长时间运行后服务内存持续增长解决定期清理内存缓存添加进程监控问题3API版本兼容性现象模型升级后参数格式变化解决实现版本检测和参数转换层// 增强的错误处理中间件 export async function handleApiError(response) { if (response.ok) return response const error await response.json().catch(() ({})) const apiError new Error(error.message || API request failed) apiError.status response.status apiError.code error.code // 特定错误处理 if (response.status 429) { apiError.retryAfter response.headers.get(Retry-After) } throw apiError }在调试方面推荐以下工具组合服务端使用Postman测试API端点客户端React DevTools检查状态变化网络Charles Proxy分析请求流量日志结构化日志工具如Pino6. 扩展功能与业务集成基于这个基础架构我们可以轻松扩展更多实用功能企业级功能增强用户配额管理系统批量生成队列处理结果自动存储到云存储Webhook回调通知UI体验优化生成历史记录参数预设模板实时预览缩略图协作注释功能示例与CMS集成// 在Next.js页面中直接调用 export async function getServerSideProps({ params }) { // 从CMS获取生成参数 const cmsData await getCmsContent(params.id) // 触发视频生成 const { taskId } await generateVideo({ model: wan2.1-t2v-plus, input: { prompt: cmsData.prompt, negative_prompt: cmsData.negativePrompt || }, parameters: { size: 1280*720, duration: cmsData.duration || 5 } }) return { props: { initialTaskId: taskId, cmsData } } }这个方案我们已经成功应用在多个客户项目中包括电商视频自动生成、教育内容创作平台等场景。相比原始纯前端方案后端代理架构不仅解决了安全问题还带来了更好的可靠性和扩展性。

更多文章