v1.9.2 缓存默认时效改为7天 + 支持-1永久缓存 + 重试间隔默认15秒

1) 缓存时效: 默认 ttl_seconds 3600→604800(7天); ttl_seconds=-1 表示永久有效(不按时间过滤, 历史有成功记录就一直命中), 全局配置与 per-request cache_ttl_seconds 均支持
2) 重试间隔: 默认 delay_seconds 2→15秒
3) 前端: 徽章/弹窗显示7天或永久(fmtTtl), 有效期输入框支持-1, 保存换算-1原样存储, API文档同步(-1=永久说明)
验证: 7天TTL命中今日记录 / ttl=-1命中超7天旧记录(0.0098s返回PNG, 源站已挂仍可用) / 恢复默认
This commit is contained in:
2026-09-11 23:13:26 +08:00
parent 4f1b7c341c
commit 96b7b5dd7a
2 changed files with 52 additions and 31 deletions
+25 -16
View File
@@ -748,8 +748,8 @@
<p><strong>重要提示:</strong></p>
<ul style="margin: 10px 0; line-height: 1.8;">
<li>🧠 <strong>按需截图</strong>:每次截图后用视觉大模型实时判断是否已截全主题内容、是否还需继续滚动,自动滚动到内容结束并拼接成长图;支持<strong>多视觉大模型接口配置</strong>(「⚙️ 智能配置」中增删改,列表顺序=优先级,失败/报错自动降级到下一个接口)</li>
<li>📦 <strong>网址缓存</strong>:默认开启——同一网址先查提取历史(同动作、成功、在有效期内)直接返回,不重复提取;未命中才实时提取。可「⚙️ 缓存/重试」关开关/改有效期,或请求带 <code>refresh:true</code> 强制绕过</li>
<li>🔁 <strong>失败自动重试</strong>:提取失败(网络/超时/浏览器错误)自动重试,次数可设(默认 2 次),响应带 <code>attempts</code> 字段</li>
<li>📦 <strong>网址缓存</strong>:默认开启——同一网址先查提取历史(同动作、成功、在有效期内)直接返回,不重复提取;有效期默认 <strong>7 天</strong>,填 <strong>-1 则永久有效</strong>。可「⚙️ 缓存/重试」关开关/改有效期,或请求带 <code>refresh:true</code> 强制绕过</li>
<li>🔁 <strong>失败自动重试</strong>:提取失败(网络/超时/浏览器错误)自动重试,次数可设(默认 2 次,间隔 15 秒),响应带 <code>attempts</code> 字段</li>
<li>📊 <strong>提取历史统计</strong>:历史区点「📊 统计」,可按天/操作类型/后端/状态/调用者/调用方式等角度切换图表(柱状/饼图可换,支持 7/30/90 天范围,可下载 PNG</li>
<li>📝 <strong>提取文本</strong>:自动剔除脚本/样式/标签,得到干净的前端可读文本,适合直接用于分析/喂给大模型</li>
<li>⏱️ <strong>页面加载等待</strong>:某些网站需要验证过程(如 Cloudflare),请设置较长时间(10000-30000ms</li>
@@ -774,7 +774,7 @@
// ---- 📦 网址缓存(默认开启,全局配置在 /api/capture/settings----
"refresh": false, // 可选:true=强制实时提取(绕过缓存)
"cache": true, // 可选:false=本次请求禁用缓存
"cache_ttl_seconds": 3600, // 可选:本次缓存时效(秒),覆盖全局有效期,命中判定也用它
"cache_ttl_seconds": 604800, // 可选:本次缓存时效(秒, -1=永久),覆盖全局有效期,命中判定也用它
"smart_config": { // 可选:临时覆盖按需截图配置(接口列表按顺序=优先级,失败自动降级)
"endpoints": [
{"name":"本地Qwen", "base_url":"http://121.40.164.32:18008/v1", "api_key":"xxxx", "model":"unsloth/Qwen3.8-27B-NVFP4", "enabled":true},
@@ -792,8 +792,8 @@ GET /api/capture/settings
POST /api/capture/settings
{
"settings": {
"cache": {"enabled": true, "ttl_seconds": 3600}, // enabled=缓存开关, ttl_seconds=有效期(秒)
"retry": {"max_retries": 2, "delay_seconds": 2} // max_retries=失败重试次数(默认2), delay_seconds=重试间隔(秒)
"cache": {"enabled": true, "ttl_seconds": 604800}, // enabled=缓存开关, ttl_seconds=有效期(秒, 默认7天=604800, -1=永久有效)
"retry": {"max_retries": 2, "delay_seconds": 15} // max_retries=失败重试次数(默认2), delay_seconds=重试间隔(秒, 默认15)
}
}
@@ -873,9 +873,9 @@ DELETE /api/history/&lt;id&gt; // 删除历史</code></pre>
</div>
<div style="display:flex;gap:14px;flex-wrap:wrap;">
<div style="flex:1;min-width:180px;">
<label for="cacheTtl" style="font-size:13px;">缓存有效期(分钟)</label>
<input type="number" id="cacheTtl" min="1" max="10080" value="60" style="width:100%;padding:10px;border:1.5px solid #e0e0e0;border-radius:6px;">
<small style="color:#999;display:block;margin-top:4px;">超期后再次提取自动刷新缓存。默认 60 分钟</small>
<label for="cacheTtl" style="font-size:13px;">缓存有效期(分钟-1 = 永久有效</label>
<input type="number" id="cacheTtl" min="-1" max="525600" value="10080" style="width:100%;padding:10px;border:1.5px solid #e0e0e0;border-radius:6px;">
<small style="color:#999;display:block;margin-top:4px;">默认 7 天(10080 分钟);填 <strong>-1</strong> 则缓存永久有效,只要历史里有该网址的成功记录就一直命中,不再重新提取</small>
</div>
</div>
</div>
@@ -890,8 +890,8 @@ DELETE /api/history/&lt;id&gt; // 删除历史</code></pre>
<small style="color:#999;display:block;margin-top:4px;">0 = 不重试。2 即最多尝试 3 次。</small>
</div>
<div style="flex:1;min-width:150px;">
<label for="retryDelay" style="font-size:13px;">重试间隔(秒)</label>
<input type="number" id="retryDelay" min="0" max="60" value="2" style="width:100%;padding:10px;border:1.5px solid #e0e0e0;border-radius:6px;">
<label for="retryDelay" style="font-size:13px;">重试间隔(秒,默认 15</label>
<input type="number" id="retryDelay" min="0" max="300" value="15" style="width:100%;padding:10px;border:1.5px solid #e0e0e0;border-radius:6px;">
</div>
</div>
</div>
@@ -973,12 +973,18 @@ DELETE /api/history/&lt;id&gt; // 删除历史</code></pre>
loadCaptureSettings();
});
function fmtTtl(sec) {
if (sec < 0) return '永久';
const min = Math.round(sec / 60);
if (min >= 1440) return (min / 1440).toFixed(1).replace(/\.0$/, '') + ' 天';
return min + ' 分钟';
}
function updateCacheBadge() {
const en = captureSettings.cache && captureSettings.cache.enabled;
document.getElementById('cacheStateBadge').textContent = en ? '✅ 开启' : '⛔ 关闭';
document.getElementById('cacheStateBadge').style.color = en ? '#2e7d32' : '#c62828';
const min = Math.round((captureSettings.cache && captureSettings.cache.ttl_seconds || 3600) / 60);
document.getElementById('cacheTtlBadge').textContent = min + ' 分钟';
document.getElementById('cacheTtlBadge').textContent = fmtTtl((captureSettings.cache && captureSettings.cache.ttl_seconds) || 3600);
}
async function loadCaptureSettings() {
@@ -991,10 +997,11 @@ DELETE /api/history/&lt;id&gt; // 删除历史</code></pre>
}
function openCaptureCfg() {
const ttlSec = (captureSettings.cache && captureSettings.cache.ttl_seconds != null) ? captureSettings.cache.ttl_seconds : 604800;
document.getElementById('cacheEnabled').checked = !!(captureSettings.cache && captureSettings.cache.enabled);
document.getElementById('cacheTtl').value = Math.round(((captureSettings.cache && captureSettings.cache.ttl_seconds) || 3600) / 60);
document.getElementById('cacheTtl').value = ttlSec < 0 ? -1 : Math.round(ttlSec / 60);
document.getElementById('retryCount').value = (captureSettings.retry && captureSettings.retry.max_retries != null) ? captureSettings.retry.max_retries : 2;
document.getElementById('retryDelay').value = (captureSettings.retry && captureSettings.retry.delay_seconds != null) ? captureSettings.retry.delay_seconds : 2;
document.getElementById('retryDelay').value = (captureSettings.retry && captureSettings.retry.delay_seconds != null) ? captureSettings.retry.delay_seconds : 15;
document.getElementById('captureCfgResult').innerHTML = '';
document.getElementById('captureCfgOverlay').classList.add('active');
}
@@ -1005,10 +1012,12 @@ DELETE /api/history/&lt;id&gt; // 删除历史</code></pre>
async function saveCaptureCfg() {
const el = document.getElementById('captureCfgResult');
const ttlMin = parseInt(document.getElementById('cacheTtl').value);
const ttlSec = ttlMin === -1 ? -1 : (Math.max(1, ttlMin || 1)) * 60;
const settings = {
cache: {
enabled: document.getElementById('cacheEnabled').checked,
ttl_seconds: (parseInt(document.getElementById('cacheTtl').value) || 60) * 60
ttl_seconds: ttlSec
},
retry: {
max_retries: Math.max(0, parseInt(document.getElementById('retryCount').value) || 0),
@@ -1027,7 +1036,7 @@ DELETE /api/history/&lt;id&gt; // 删除历史</code></pre>
captureSettings = data.settings;
updateCacheBadge();
const en = data.settings.cache.enabled;
el.innerHTML = `✅ 已保存:网址缓存${en ? '开启' : '关闭'}(有效期 ${Math.round(data.settings.cache.ttl_seconds / 60)} 分钟)| 失败重试 ${data.settings.retry.max_retries} 次(间隔 ${data.settings.retry.delay_seconds}s`;
el.innerHTML = `✅ 已保存:网址缓存${en ? '开启' : '关闭'}(有效期 ${fmtTtl(data.settings.cache.ttl_seconds)})| 失败重试 ${data.settings.retry.max_retries} 次(间隔 ${data.settings.retry.delay_seconds}s`;
} else {
el.innerHTML = `<span style="color:#c62828;">❌ 保存失败:${escapeHtml(data.error || '未知错误')}</span>`;
}