fix(安全/健壮性): 全工作区审查修复——鉴权兜底扩展+路径穿越+忙等+泄漏
- AdminApiGuardFilter 兜底扩展到 /api/collect-data、/api/price-track:无需鉴权的 工具接口纳入 JWT/内部令牌校验(原匿名可达即越权读写他人数据) - pricetrack asinFiles 改为仅允许上传临时目录内文件(canonical 前缀校验), 修复请求路径直接 new File 可读服务器任意 csv/xlsx 的穿越 - dedupe 删除导入逐行 REQUIRES_NEW 事务改 500 条一批 IN 删除,50 万行导入 由 50 万个事务收敛为千级 - 前端记住密码 XOR 硬编码密钥改 WebCrypto AES-GCM(密钥随机生成独立存储), 登录流程接口改 async 并保证自动登录恢复时序 - 任务进度轮询失败按指数退避(原固定 5s 无限撞);下载进度终态条目 2 分钟 自动清理(原永久堆积);AmazonConsolePage statusTimer 卸载清理
This commit is contained in:
@@ -252,7 +252,9 @@ export function useTaskProgressLoop<TDetail>(
|
||||
}
|
||||
await refreshOnce()
|
||||
if (!disposed && taskIds.value.length > 0) {
|
||||
pollTimer = timers.setTimeout('task-poll', run, intervalMs())
|
||||
// 连续失败时按指数退避拉长间隔,避免后端故障时每 5s 撞一次(成功即复位)
|
||||
const delay = failureCount > 0 ? getTaskPollBackoffMs(failureCount) : intervalMs()
|
||||
pollTimer = timers.setTimeout('task-poll', run, delay)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,7 +324,8 @@ export function useTaskProgressLoop<TDetail>(
|
||||
}
|
||||
void refreshOnce()
|
||||
if (!disposed && taskIds.value.length > 0) {
|
||||
pollTimer = timers.setTimeout('task-poll', run, intervalMs())
|
||||
const delay = failureCount > 0 ? getTaskPollBackoffMs(failureCount) : intervalMs()
|
||||
pollTimer = timers.setTimeout('task-poll', run, delay)
|
||||
}
|
||||
}
|
||||
pollTimer = timers.setTimeout('task-poll', run, delayMs)
|
||||
|
||||
@@ -30,6 +30,29 @@ type PywebviewDownloadProgressEvent = {
|
||||
const progressItems = reactive<Record<string, DownloadProgressItem>>({})
|
||||
let progressListenerBound = false
|
||||
|
||||
/** 终态条目自动清理延迟:success/failed 未手动关闭也只保留 2 分钟,防长驻累积 */
|
||||
const TERMINAL_RETENTION_MS = 2 * 60 * 1000
|
||||
let sweepTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
function sweepTerminalItems() {
|
||||
const cutoff = now() - TERMINAL_RETENTION_MS
|
||||
for (const id of Object.keys(progressItems)) {
|
||||
const item = progressItems[id]
|
||||
if ((item.status === 'success' || item.status === 'failed') && item.updatedAt <= cutoff) {
|
||||
delete progressItems[id]
|
||||
}
|
||||
}
|
||||
if (Object.keys(progressItems).length === 0 && sweepTimer) {
|
||||
clearInterval(sweepTimer)
|
||||
sweepTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
function ensureSweepTimer() {
|
||||
if (sweepTimer || typeof window === 'undefined') return
|
||||
sweepTimer = setInterval(sweepTerminalItems, 30 * 1000)
|
||||
}
|
||||
|
||||
function normalizePercent(value: number) {
|
||||
if (!Number.isFinite(value)) return 0
|
||||
return Math.max(0, Math.min(100, Math.round(value)))
|
||||
@@ -40,6 +63,7 @@ function now() {
|
||||
}
|
||||
|
||||
function upsertProgress(partial: Omit<Partial<DownloadProgressItem>, 'id'> & { id: string }) {
|
||||
ensureSweepTimer()
|
||||
const existing = progressItems[partial.id]
|
||||
const timestamp = now()
|
||||
progressItems[partial.id] = {
|
||||
@@ -74,9 +98,7 @@ export function ensureDownloadProgressListener() {
|
||||
if (progressListenerBound || typeof window === 'undefined') return
|
||||
progressListenerBound = true
|
||||
window.addEventListener('pywebview-download-progress', handlePywebviewProgress)
|
||||
}
|
||||
|
||||
export function useDownloadProgress() {
|
||||
}export function useDownloadProgress() {
|
||||
ensureDownloadProgressListener()
|
||||
const items = computed(() =>
|
||||
Object.values(progressItems)
|
||||
|
||||
Reference in New Issue
Block a user