diff --git a/blog-admin/src/lib/errors.ts b/blog-admin/src/lib/errors.ts index 679f911c..7f23f623 100644 --- a/blog-admin/src/lib/errors.ts +++ b/blog-admin/src/lib/errors.ts @@ -52,7 +52,7 @@ export function friendlyError(e: unknown): FriendlyError { return { status: 503, code: 'd1_quota', - msg: '评论服务暂时不可用:数据库今日读取额度已用尽,北京时间明早 8:00 自动恢复。给你带来不便,可以先看看文章,稍后再来。', + msg: '数据库今日读取额度已用尽,明早 8:00 自动恢复。你可以先看看文章,稍后再来~', detail, }; } @@ -61,7 +61,7 @@ export function friendlyError(e: unknown): FriendlyError { return { status: 503, code: 'd1_unavailable', - msg: '评论服务暂时不可用(数据库连接异常),我们正在处理,请稍后再试。', + msg: '数据库连接异常,我们正在处理,请稍后再试。', detail, }; } @@ -79,7 +79,7 @@ export function friendlyError(e: unknown): FriendlyError { return { status: 503, code: 'unavailable', - msg: '评论服务暂时不可用,请稍后重试。', + msg: '服务或网络有点问题,请稍后重试。', detail, }; } diff --git a/themes/Ying/assets/js/modules/artalk.js b/themes/Ying/assets/js/modules/artalk.js index 7d04f865..1a1ba0e5 100644 --- a/themes/Ying/assets/js/modules/artalk.js +++ b/themes/Ying/assets/js/modules/artalk.js @@ -6,6 +6,39 @@ // (见 artalk-cf/src/lib/errors.ts:d1_quota / d1_unavailable / timeout / ...), // 所以这里把它换成一句人话 + 可展开的技术详情(留着给站长排查)。 // ============================================================================ + +// ── 本地调试开关:给 URL 加 ?atk_svc= 强制复现服务故障态 ────────────── +// 例:/comment.html?atk_svc=d1_quota +// 仅 localhost 生效(生产环境这段完全不执行),用来本地看禁用态/错误卡片的观感, +// 不必等真的把 D1 额度跑满。支持的 code 见下面的 INFO 表。 +(function () { + var host = location.hostname; + if (host !== 'localhost' && host !== '127.0.0.1' && host !== '') return; + var m = /[?&]atk_svc=([a-z0-9_]+)/.exec(location.search || ''); + if (!m) return; + var code = m[1]; + var MSG = { + d1_quota: '数据库今日读取额度已用尽,明早 8:00 自动恢复。你可以先看看文章,稍后再来~', + d1_unavailable: '数据库连接异常,我们正在处理,请稍后再试。', + timeout: '评论服务响应超时,请稍后重试。', + unavailable: '服务或网络有点问题,请稍后重试。', + internal: '评论服务出了点小问题,请稍后重试。' + }; + if (!MSG[code]) return; + var orig = window.fetch; + window.fetch = function (input, init) { + var url = String(typeof input === 'string' ? input : (input && input.url) || ''); + if (/\/api\/v2\/comments(\?|$)/.test(url)) { + return Promise.resolve(new Response( + JSON.stringify({ code: code, msg: MSG[code], detail: '[demo] forced by ?atk_svc=' + code }), + { status: 503, statusText: 'Service Unavailable', headers: { 'Content-Type': 'application/json; charset=utf-8' } } + )); + } + return orig.apply(this, arguments); + }; + try { console.info('[artalk] 调试模式:已强制评论接口返回 ' + code); } catch (e) {} +})(); + (function () { if (window.__atkErrGuard) return; // PJAX 会重复执行本文件,只装一次 window.__atkErrGuard = true; @@ -13,7 +46,7 @@ var last = null; // 最近一次 /api/v2 失败:{ status, code, msg, detail, at } var INFO = { - d1_quota: { ic: '😴', title: '评论暂时加载不出来', desc: '数据库今日读取额度已用尽,北京时间明早 8:00 自动恢复。你可以先看看文章,稍后再来~' }, + d1_quota: { ic: '😴', title: '评论暂停中', desc: '数据库今日读取额度已用尽,明早 8:00 自动恢复。你可以先看看文章,稍后再来~' }, d1_unavailable: { ic: '🛠️', title: '评论服务正在维护', desc: '数据库连接异常,我们已经在处理,请稍后再试。' }, timeout: { ic: '🐢', title: '评论服务响应有点慢', desc: '这次请求超时了,稍后再试一次。' }, unavailable: { ic: '📡', title: '评论服务暂时不可用', desc: '服务或网络有点问题,请稍后再试。' }, @@ -126,9 +159,11 @@ }); } catch (e) { /* clone 不支持就放弃记录,不影响请求 */ } } else if (res && res.ok) { - // 某些端点不读 D1(/human/* 走 KV),它们 200 不代表服务恢复; - // 只有真正读 D1 的端点成功才算恢复 - if (svcDown && /\/(conf|comments|captcha|votes|pages)/.test(url)) setSvcUp(); + // 「服务恢复」只认评论列表本身:禁用的直接原因就是列表挂了, + // 列表回来了才算好。votes/pv/captcha 这些端点不读 D1 或只读 + // 一行(KV),它们成功≠列表能拉出来 —— 曾据此误判恢复,把 + // 置灰和横幅撤了,只剩一张孤零零的错误卡片挂在页面底部。 + if (svcDown && /\/comments(\?|$)/.test(url)) setSvcUp(); } return res; }).catch(function (e) { @@ -165,7 +200,8 @@ '.atk-err-retry:active{transform:scale(.96)}' + '@media (max-width:640px){.atk-error-layer{margin:10px 0 30px!important}.atk-err-card{padding:20px 14px}.atk-err-ic{font-size:26px}' + '.atk-err-t{font-size:14px}.atk-err-d{font-size:12.5px}}' + - '.atk-svc-down .atk-main-editor{opacity:.55!important;filter:grayscale(1)!important}' + + // 禁用态不改编辑器外观(降透明/灰化整块很难看);只禁交互, + // 为什么点不动由上方横幅说明 '.atk-svc-down .atk-copyright{display:none!important}' + '.atk-svc-down .atk-main-editor *{pointer-events:none}' + '.atk-svc-down .atk-human-tf{display:none}' + @@ -235,20 +271,11 @@ bar = document.createElement('div'); bar.id = 'atk-editor-off-bar'; bar.className = 'atk-editor-off-bar uk-animation-slide-top-small'; // UIKit 入场动画 + // 只说一句状态 + 恢复时间:详细解释由错误卡片负责(列表区),这里不重复 bar.innerHTML = code === 'd1_quota' - ? '⏸ 评论服务暂时不可用(数据库今日额度已用尽),预计明早 8:00 自动恢复,先看看文章吧' - : '⏸ 评论服务暂时不可用,暂时无法发表评论,请稍后再试'; + ? '⏸ 评论暂停中,预计明早 8:00 恢复' + : '⏸ 评论暂停中,请稍后再试'; ed.parentNode.insertBefore(bar, ed); - // 编辑器全部子元素已 pointer-events:none,点击会落到编辑器容器本身 —— - // 借这个事件弹 toast 告诉用户为什么点不动(幂等:节点换新才重挂) - if (!ed.dataset.svcGuard) { - ed.dataset.svcGuard = '1'; - ed.addEventListener('click', function () { - svcToast(downCode === 'd1_quota' - ? '评论服务暂时不可用(额度已用尽),预计明早 8:00 自动恢复' - : '评论服务暂时不可用,请稍后再试'); - }); - } } function removeBar() { @@ -268,7 +295,10 @@ if (!probeTimer) { probeTimer = setInterval(function () { if (!apiBase) return; - fetch(apiBase + '/api/v2/captcha/status').then(function (r) { + // 探针必须真读 D1:/captcha/status 走 KV,额度没恢复它也 200, + // 会把故障态 60s 一轮地误解除。评论列表 limit=1 是最便宜的 + // 「真读 D1」请求(rows_read 个位数),它通了才算恢复。 + fetch(apiBase + '/api/v2/comments?page_key=' + encodeURIComponent('/') + '&limit=1').then(function (r) { if (r.ok) setSvcUp(); }).catch(function () { /* 还没恢复 */ }); }, 60000); @@ -322,9 +352,13 @@ var info = INFO[code] || INFO.internal; // 服务端文案是给「直接调 API 的人」写的,自带一句"评论服务暂时不可用:", - // 放到卡片里会和标题重复,这里去掉冗余前缀 + // 放到卡片里会和标题重复,这里去掉冗余前缀;顺带规整两句别扭的措辞 + // (服务端旧文案,下次 Worker 部署后这两条 replace 自然变成空操作) var desc = fresh && fresh.msg - ? fresh.msg.replace(/^评论服务暂时不可用(?:[((][^))]*[))])?[::]\s*/, '') + ? fresh.msg + .replace(/^评论服务暂时不可用(?:[((][^))]*[))])?[::,,]\s*/, '') + .replace('北京时间明早', '明早') + .replace(/给你带来不便,可以先看看文章,稍后再来[。~~]?/, '你可以先看看文章,稍后再来~') : info.desc; // 服务端故障(非读者自身网络问题)→ 禁用编辑器并挂说明横幅。