节流私有素材换签请求

限制大量不同素材同时换签时的首批派发和后续间隔

补充 read-url 换签爆发导致发布入口 429 的排障记录
This commit is contained in:
2026-06-23 23:11:37 +08:00
parent 562366820d
commit f1b4e70bda
4 changed files with 129 additions and 1 deletions
+56
View File
@@ -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(