Files
crawler-plugin/frontend-vue/src/shared/progress-response-cache.ts
T
huangzd1997 d9e3f34d37 chore: 移除项目内无用代码(28 文件)
- 清理任务面板/SPA 化改造遗留的未使用变量与函数(模板管理配套 6 函数、
  payloadForDisplay/showTip/hasRunning/schedulePoll/removeMatchedRowLocally 等 30+ 处)
- 清理未使用 import(getBrandTemplateXlsxUrl/uploadTempFileToJava/getProductRiskTasksBatch 等)
- useTaskProgressLoop 改为仅类型导入;progress/task-request 缓存未用参数精简
- DeleteBrandRunService 删除永不执行的 taskItems 死分支
- 全量 vue-tsc(含 --noUnusedLocals/--noUnusedParameters)0 错误;648 测试全过
2026-09-08 10:20:30 +08:00

142 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 进度响应 MapTask 82)。
*
* 为前端任务轮询的进度响应快照提供带 TTL 与最大条目数的内存缓存:
* - 条目在写入后 ttlMs 毫秒过期,读取/枚举/清理时惰性移除;
* - 条目数超过 maxEntries 时驱逐最旧条目(首次写入顺序),缓存始终有界;
* - 时钟可注入(now),测试可用虚拟时钟验证 TTL 边界,不依赖真实时间。
*
* 纯 TS 无副作用模块;set 对非法 key 抛出「key 必须是正整数」错误(fail-fast),
* 时钟抛错时 set 不留下任何部分写入;读取路径(get/has/delete)对非法 key 宽容。
* 供 useTaskProgressLoop 的可选缓存选项复用。
*/
export interface ProgressResponseCacheOptions {
/** 条目过期时间(毫秒),必须为正数 */
ttlMs: number
/** 缓存最大条目数,必须为正数 */
maxEntries: number
/** 时钟来源;默认 Date.now(),测试可注入虚拟时钟 */
now?: () => number
}
interface Entry<V> {
value: V
createdAt: number
}
export function createProgressResponseCache<V>(options: ProgressResponseCacheOptions) {
const ttlMs = options.ttlMs
const maxEntries = options.maxEntries
const now = options.now ?? Date.now
if (!(ttlMs > 0)) {
throw new Error('ttlMs 必须为正数: ' + ttlMs)
}
if (!(maxEntries > 0)) {
throw new Error('maxEntries 必须为正数: ' + maxEntries)
}
const map = new Map<number, Entry<V>>()
function validateKey(key: number): boolean {
return Number.isFinite(key) && key > 0 && Number.isInteger(key)
}
function isExpired(entry: Entry<V>, at: number): boolean {
return at - entry.createdAt >= ttlMs
}
function purgeExpired(): number {
const at = now()
let removed = 0
for (const [key, entry] of map) {
if (isExpired(entry, at)) {
map.delete(key)
removed += 1
}
}
return removed
}
function evictIfNeeded(_at: number) {
while (map.size > maxEntries) {
const oldest = map.keys().next().value as number | undefined
if (oldest == null) break
map.delete(oldest)
}
}
function set(key: number, value: V) {
if (!validateKey(key)) {
throw new Error('key 必须是正整数: ' + key)
}
const createdAt = now()
if (map.has(key)) {
map.set(key, { value, createdAt })
} else {
map.set(key, { value, createdAt })
evictIfNeeded(createdAt)
}
}
function get(key: number): V | undefined {
if (!validateKey(key)) return undefined
const at = now()
const entry = map.get(key)
if (!entry) return undefined
if (isExpired(entry, at)) {
map.delete(key)
return undefined
}
return entry.value
}
function has(key: number): boolean {
if (!validateKey(key)) return false
const at = now()
const entry = map.get(key)
if (!entry) return false
if (isExpired(entry, at)) {
map.delete(key)
return false
}
return true
}
function deleteEntry(key: number): boolean {
if (!validateKey(key)) return false
return map.delete(key)
}
function clear() {
map.clear()
}
function entries(): Array<[number, V]> {
const at = now()
const result: Array<[number, V]> = []
for (const [key, entry] of map) {
if (isExpired(entry, at)) {
map.delete(key)
continue
}
result.push([key, entry.value])
}
return result
}
return {
get size() {
return map.size
},
set,
get,
has,
delete: deleteEntry,
clear,
entries,
purgeExpired,
}
}
export type ProgressResponseCache<V> = ReturnType<typeof createProgressResponseCache<V>>