节流私有素材换签请求
限制大量不同素材同时换签时的首批派发和后续间隔 补充 read-url 换签爆发导致发布入口 429 的排障记录
This commit is contained in:
@@ -362,6 +362,62 @@ describe('assetReadUrlService', () => {
|
||||
expect(globalThis.fetch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('getSignedAssetReadUrl throttles distinct signed url bursts', async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date('2099-01-01T00:00:00Z'));
|
||||
|
||||
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(
|
||||
async (input) => {
|
||||
const url = new URL(
|
||||
String(input),
|
||||
globalThis.location?.origin ?? 'https://www.genarrative.world',
|
||||
);
|
||||
const objectKey = url.searchParams.get('objectKey') ?? 'missing.png';
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
ok: true,
|
||||
data: {
|
||||
read: {
|
||||
objectKey,
|
||||
signedUrl: `https://signed.example.com/${encodeURIComponent(objectKey)}`,
|
||||
expiresAt: '2099-01-01T00:10:00Z',
|
||||
},
|
||||
},
|
||||
error: null,
|
||||
meta: {
|
||||
apiVersion: '2026-06-16',
|
||||
routeVersion: '2026-06-16',
|
||||
latencyMs: 1,
|
||||
timestamp: '2099-01-01T00:00:00Z',
|
||||
},
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const requests = Array.from({ length: 28 }, (_, index) =>
|
||||
getSignedAssetReadUrl({
|
||||
objectKey: `generated-character-drafts/editor/ui-design-assets/result/asset-${String(index + 1).padStart(3, '0')}.png`,
|
||||
}),
|
||||
);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
expect(fetchSpy).toHaveBeenCalledTimes(24);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(16);
|
||||
expect(fetchSpy).toHaveBeenCalledTimes(25);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(48);
|
||||
expect(fetchSpy).toHaveBeenCalledTimes(28);
|
||||
await expect(Promise.all(requests)).resolves.toHaveLength(28);
|
||||
});
|
||||
|
||||
test('getSignedAssetReadUrl caches not-found failures for the same legacy path', async () => {
|
||||
vi.spyOn(globalThis, 'fetch').mockResolvedValue(
|
||||
new Response(
|
||||
|
||||
Reference in New Issue
Block a user