Unity Cloud Save API与Next.js 15的Fetch请求超时问题
解决Next.js路由调用Unity Cloud Save API的连接超时及CSV下载问题
问题根源
你当前的代码通过Promise.all一次性发起最多1000个请求到Unity Cloud Save API,这种高并发会触发API限流、耗尽服务器连接资源,直接导致ConnectTimeoutError;同时大量请求的处理时间会拖长接口响应,引发前端CSV下载超时。
解决方案
1. 限制并发请求数量
用p-limit库控制同时发起的请求数,避免一次性打满API接口:
首先安装依赖:
npm install p-limit
修改后的路由代码:
import pLimit from 'p-limit'; import { NextResponse } from 'next/server'; export async function GET() { const bearerToken = await getBearerToken(); // 获取排行榜数据 const res = await fetch( `https://leaderboards.services.api.unity.com/v1/projects/${UNITY_PROJECT_ID_FRED}/leaderboards/luxembourg/scores?limit=1000`, { headers: { Authorization: `Bearer ${bearerToken}`, 'Content-Type': 'application/json', }, } ); if (!res.ok) { return NextResponse.json( { error: 'Failed to fetch leaderboard' }, { status: res.status } ); } const leaderboard: LeaderboardResponse = await res.json(); const playerIds = leaderboard.results?.map((r) => r.playerId) ?? []; // 限制同时并发请求数为5,可根据Unity API限流规则调整 const limit = pLimit(5); // 包装请求并限制并发 const emailRequests = playerIds.map((playerId) => limit(async () => { try { const emailRes = await fetch( `https://cloud-save.services.api.unity.com/v1/data/projects/${UNITY_PROJECT_ID_FRED}/players/${playerId}/public/items?keys=Email`, { headers: { Authorization: `Bearer ${bearerToken}`, 'Content-Type': 'application/json', }, // 设置合理超时时间,避免过早断开 signal: AbortSignal.timeout(30000), } ); if (!emailRes.ok) { return { playerId, email: null }; } const data = await emailRes.json(); const emailItem = data.results?.find((r) => r.key === 'Email'); return { playerId, email: emailItem?.value ?? null }; } catch (err) { console.error(`Failed to fetch email for player ${playerId}:`, err); return { playerId, email: null }; } }) ); const emails = await Promise.all(emailRequests); return NextResponse.json({ emails }); }
2. 优先使用批量API(推荐)
检查Unity Cloud Save官方文档,确认是否支持批量获取多个玩家公开数据的接口。如果存在,将多个playerId打包成单个请求,能把请求次数从1000次压缩到几次,彻底解决超时问题。
示例批量请求逻辑(需以官方API为准):
// 按50个玩家一组拆分请求 const batchSize = 50; const emailBatchRequests = []; for (let i = 0; i < playerIds.length; i += batchSize) { const batch = playerIds.slice(i, i + batchSize); emailBatchRequests.push( fetch( `https://cloud-save.services.api.unity.com/v1/data/projects/${UNITY_PROJECT_ID_FRED}/players/public/items?keys=Email&playerIds=${batch.join(',')}`, { headers: { Authorization: `Bearer ${bearerToken}`, 'Content-Type': 'application/json', }, } ).then(res => res.json()) ); } // 处理批量返回的数据 const batchResults = await Promise.all(emailBatchRequests); const emails = batchResults.flatMap(batch => batch.results.map(item => ({ playerId: item.playerId, email: item.key === 'Email' ? item.value : null })) );
3. 优化CSV下载体验
如果后端响应仍偏长,可改用Next.js流式响应,让前端逐步接收数据,避免整体超时:
import { Readable } from 'stream'; import { NextResponse } from 'next/server'; export async function GET() { // ... 前面的排行榜、邮件获取逻辑 ... // 生成CSV内容 const csvRows = [ ['Player ID', 'Email'], ...emails.map(e => [e.playerId, e.email || '']), ]; const csvContent = csvRows.map(row => row.join(',')).join('\n'); // 流式返回CSV const stream = Readable.from([csvContent]); return new NextResponse(stream, { headers: { 'Content-Type': 'text/csv', 'Content-Disposition': 'attachment; filename="player-emails.csv"', }, }); }
4. 其他补充优化
- 增加错误重试:用
p-retry库对失败的请求进行重试,避免单次网络波动导致失败 - 缓存Bearer Token:如果
getBearerToken()每次请求都重新获取,可缓存一段时间,减少认证请求开销 - 对齐API限流规则:查阅Unity官方文档的速率限制,调整并发数到允许范围内
内容的提问来源于stack exchange,提问作者Eren
相关产品推荐
相关产品推荐

