perf(全链路): 连接池/事务边界/轮询与 IO 效率收口

Java:
- LLM 180s 读超时不再被全局 call-timeout 静默截断成 90s(长思考请求被掐断→重试→付费网关二次计费)
- 代理 HttpClient 缓存改有界 LRU(jikip 每次提取新 IP,无界缓存持续泄漏 selector 线程与连接池)
- 12 个 service 的 Redis 任务锁移出 @Transactional(自旋最坏 10s 白占 DB 连接,池仅 30),远端对象删/传改 afterCommit
- 结果文件 Job 闸门拒绝时不再回退内联执行(改重新入队,避免把背压转嫁给 MQ 消费线程)
- imagevideo 每秒扫描加列投影、过期清理加 LIMIT;权限页整表查询改列投影(不再拉回密码哈希)
- 哈希改 HexFormat;补 5 处"不能改"的技术依据注释(批量插入会丢回填主键、流式丢模板与图片等)

前端:4 个工具页轮询改轻量端点(带 fallback);PriceTrack 快照节流写盘;候选店铺表分页;页面隐藏时停表

客户端:HTTP 连接池按出口复用(Session 仍每请求新建,保持无跨请求状态);品牌检测 WIPO 逐请求握手;
代理配置按 mtime 缓存;串行任务改专属池;异常降级为标签页重连;紫鸟启动改端口轮询;模板编译缓存;
日志上报连接与落盘收口;Flask 版本 API 改按请求复用连接
This commit is contained in:
2026-09-15 23:01:59 +08:00
parent d6f8368493
commit 228d481211
44 changed files with 1550 additions and 423 deletions
@@ -467,10 +467,37 @@ function loadTaskSnapshotsFromStorage() {
}
}
function saveTaskSnapshotsToStorage() {
/** 快照落盘最小间隔:快照可达数百 KB,全量 JSON.stringify + setItem 会阻塞主线程 */
const SNAPSHOT_SAVE_MIN_INTERVAL_MS = 30000
let lastSnapshotSaveAt = 0
let snapshotSavePending = false
/**
* 快照写盘(节流)。
*
* 每轮轮询都序列化整份快照会周期性卡顿主线程,因此:
* - 内容未变时 setStorageJson 内部已会跳过写入;
* - 这里再按 SNAPSHOT_SAVE_MIN_INTERVAL_MS 节流,被节流掉的写入挂起,
* 下一次(或 force / 组件卸载前的 flush)落盘时一并写入最新内容。
*/
function saveTaskSnapshotsToStorage(force = false) {
if (typeof window === 'undefined') return
const now = Date.now()
if (!force && lastSnapshotSaveAt > 0 && now - lastSnapshotSaveAt < SNAPSHOT_SAVE_MIN_INTERVAL_MS) {
snapshotSavePending = true
return
}
snapshotSavePending = false
lastSnapshotSaveAt = now
setStorageJson(taskSnapshotsStorageKey(), taskSnapshots.value, Object.keys(taskSnapshots.value).length === 0)
}
/** 卸载前把节流挂起的快照补写一次,避免最后一次变更丢失 */
function flushTaskSnapshotsToStorage() {
if (!snapshotSavePending) return
saveTaskSnapshotsToStorage(true)
}
function loadMatchedItemsFromStorage() {
try {
const raw = typeof window !== 'undefined' ? window.localStorage.getItem(matchedItemsStorageKey()) : null
@@ -1147,7 +1174,7 @@ async function processMatchedQueue() {
...taskDetails.value,
[taskVo.taskId]: 'RUNNING',
}
saveTaskSnapshotsToStorage()
saveTaskSnapshotsToStorage(true)
saveTaskDetailsToStorage()
const queuePayload = await buildQueuePayload(taskVo, row)
queuePayloadText.value = JSON.stringify(queuePayload, null, 2)
@@ -1233,7 +1260,7 @@ function recordCreatedTask(taskVo: PriceTrackCreateTaskVo) {
...taskDetails.value,
[taskVo.taskId]: 'RUNNING',
}
saveTaskSnapshotsToStorage()
saveTaskSnapshotsToStorage(true)
saveTaskDetailsToStorage()
}
@@ -1418,37 +1445,68 @@ function getPollIntervalMs() {
return getTaskPollIntervalMs()
}
/** 轮询可比对的轻量字段(与 /tasks/progress/light 白名单一致,外加只有重型端点才带的起止时间) */
function taskProgressSignature(task?: PriceTrackTaskDetailVo['task'] | null) {
if (!task) return ''
return [
task.status ?? '',
task.createdAt ?? '',
task.finishedAt ?? '',
task.updatedAt ?? '',
].join('|')
}
async function refreshTaskBatch() {
const ids = pollingTaskIds.value.filter((id) => id > 0)
if (!ids.length) return
try {
const batch = await getPriceTrackTaskProgressBatch(ids)
let changed = false
// F5:主轮询只读 status/fileReady 等轻量白名单字段,走 light 端点减小轮询响应体;
// 轻量端点异常或返回空结果时适配器自动回退重型 batch(否则会漏刷新结果)
const batch = await getPollingProgressBatch('priceTrack', ids, {
fallback: () => getPriceTrackTaskProgressBatch(ids),
})
let settled = false
let snapshotsChanged = false
const nextSnapshots = { ...taskSnapshots.value }
for (const missingId of batch.missingTaskIds || []) {
console.log(`[price-track] 任务 ${missingId} 在服务端已不存在,停止轮询`)
removePollingTask(missingId)
delete nextSnapshots[missingId]
snapshotsChanged = true
}
for (const detail of batch.items || []) {
const taskId = detail.task?.id
const status = detail.task?.status
if (typeof taskId !== 'number' || taskId <= 0) continue
const prev = nextSnapshots[taskId]
nextSnapshots[taskId] = prev ? { ...prev, task: { ...(prev.task || {}), ...(detail.task || {}) } } : detail
const merged: PriceTrackTaskDetailVo = prev
? { ...prev, task: { ...(prev.task || {}), ...(detail.task || {}) } }
: detail
// 只有轮询字段真的变了才重写快照:整份快照数百 KB,无变化时重建对象 + 落盘纯属浪费
if (!prev || taskProgressSignature(prev.task) !== taskProgressSignature(merged.task)) {
nextSnapshots[taskId] = merged
snapshotsChanged = true
}
if (status) {
taskDetails.value[taskId] = status
if (status === 'SUCCESS' || status === 'FAILED') {
console.log(`[price-track] 任务 ${taskId} 已到终态 ${status},移出轮询集合并刷新列表`)
removePollingTask(taskId)
changed = true
settled = true
}
}
}
taskSnapshots.value = nextSnapshots
saveTaskSnapshotsToStorage()
if (snapshotsChanged) {
taskSnapshots.value = nextSnapshots
saveTaskSnapshotsToStorage()
}
saveTaskDetailsToStorage()
await loadHistory()
syncPollingIdsWithHistory()
if (changed) {
// 历史列表只在任务落定(或快照真变化)时拉取:此前每 10s 无条件重查一次,纯属浪费
if (settled || snapshotsChanged) {
await loadHistory()
syncPollingIdsWithHistory()
}
if (settled) {
await loadDashboard()
void resumeLoopExecutionIfNeeded()
}
@@ -1508,7 +1566,8 @@ function removePollingTask(taskId: number) {
const next = { ...taskSnapshots.value }
delete next[taskId]
taskSnapshots.value = next
saveTaskSnapshotsToStorage()
// 终态清理属于关键变更,跳过节流立即落盘
saveTaskSnapshotsToStorage(true)
}
}
@@ -1679,6 +1738,8 @@ onUnmounted(() => {
clearSleepTimers()
timers.clearScope()
if (countryPrefSaveTimer) timers.clearTimer('preference-save', countryPrefSaveTimer)
// 补写被节流挂起的快照,避免最后一次变更丢失
flushTaskSnapshotsToStorage()
})
/**