fix: fetchWithRetry 对 429 限流增加指数退避与 Retry-After 尊重

- useHttp.calculateRetryDelay:优先读服务端 Retry-After 头;429 用指数退避
  (1000*2^attempt, 封顶 30s);其余 5xx 仍固定延迟,避免限流场景频繁重试浪费带宽
- debian/changelog 测试版本号 5.2.1.2-test
- 验证:vue-tsc 0 / eslint 0 / dpkg-buildpackage 打包成功

审查项实证(其余 3 项均误报/已有防护,不改):
- ① install-manager pkgname 二次校验:已有 PKGNAME_PATTERN(/^[a-zA-Z0-9._+-]+$/)
  在 parseAppPayload/addInstallTask/launch-app 三入口校验,且 spawn 全用 shell:false
  参数逐元素传递,命令注入不可达;审查建议的 replace 属冗余纵深防御,不改
- ② InstalledAppsModal /var/lib/ 宽泛:误报,真实白名单(400-409行)已是
  /var/lib/apm/ 与 /var/lib/dpkg/info/ 具体子目录,无宽泛 /var/lib/ 项
- ③ useAppState.ts 注入模式缺注册检查:误报,useAppState.ts 无 registerRequireLogin;
  真实 registerRequireLogin 在 useFavorites/useAppDetail,默认 ()=>true 为刻意安全兜底
  (未注册=放行),审查建议的 InjectionPoint 包装会破坏该语义,不改
This commit is contained in:
xiyidaiwa
2026-08-13 21:02:12 +08:00
parent 7be90f13ce
commit e1edf1dcd5
2 changed files with 29 additions and 2 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
spark-store (5.2.1.1-test) UNRELEASED; urgency=medium
spark-store (5.2.1.2-test) UNRELEASED; urgency=medium
* Initial release. (Closes: #nnnn) <nnnn is the bug number of your ITP>
+28 -1
View File
@@ -23,6 +23,24 @@ axiosInstance.interceptors.request.use(createCacheBusterInterceptor());
// 5xx / 网络错误 / 超时重试;4xx(如 404)快速失败
const RETRYABLE_STATUS = new Set([502, 503, 504]);
// 计算重试延迟:优先尊重服务端 Retry-After 头;429 限流用指数退避(封顶 30s);
// 其余 5xx 用固定延迟。避免对限流场景频繁重试浪费带宽。
const calculateRetryDelay = (
status: number | undefined,
attempt: number,
retryAfterHeader?: string,
fixedDelayMs = 500,
): number => {
if (retryAfterHeader) {
const seconds = Number.parseInt(retryAfterHeader, 10);
if (!Number.isNaN(seconds) && seconds > 0) return seconds * 1000;
}
if (status === 429) {
return Math.min(1000 * 2 ** attempt, 30000);
}
return fixedDelayMs;
};
export const fetchWithRetry = async <T>(
path: string,
signal?: AbortSignal,
@@ -46,7 +64,16 @@ export const fetchWithRetry = async <T>(
if (!retryable || attempt === retries) {
return null;
}
await new Promise((r) => setTimeout(r, retryDelayMs));
const retryAfter = ae.response?.headers?.["retry-after"] as
| string
| undefined;
const delay = calculateRetryDelay(
status,
attempt,
retryAfter,
retryDelayMs,
);
await new Promise((r) => setTimeout(r, delay));
}
}
return null;