feat(download): switch download buttons to direct source links

The token relay still stood between the button and the file: every click
issued a short-lived token URL, so downloads never reached the real
source address directly.

- Render direct anchors (target=_blank) to the raw source URL in the
  download box and the paid card, with inline extract-code buttons
- Retire the relay: delete Content\Download plus its init wiring, header
  query-var branch and SEO noindex branch, and drop the dead
  download-request flow from cms-download.js (cashier, scan-pay and
  error views stay)
- Keep the dl_token table and the download logs page as read-only
  history; unscheduling of the orphaned cleanup cron is preserved
This commit is contained in:
kate522 committed 2026-09-24 19:12:45 +08:00
1 parent bc97c4ddc3
commit ab495854d5
10 files changed
+118 -841

No files matched your search

+3 -111
View File
@@ -2,9 +2,9 @@
* AKSRT CMS Download
*
* 功能:
* - 监听 [data-action="aksrt-download"] 按钮点击
* - 调 POST /aksrt/v1/download/request
* - 成功后直接打开生成的下载链接;失败/购买等场景仍走弹窗
* - 监听购买入口(data-aksrt-pay-content / data-action="aksrt-need-purchase")
* - 内容购买收银台、扫码支付与错误提示弹窗
* - 下载已改为直连:按钮为源地址锚点,不再经过本站中转请求
*
* 弹窗视觉走主题 design token(见 assets/css/frontend/cms-download.css),
* 付费收银台沿用参考实现的 colorful header 结构。
@@ -140,7 +140,6 @@
// ===== 统一视图壳(下载 / 错误 / 扫码共用主题化外观) =====
// 头部与按钮统一继承运行时主题色(--focus-color),状态用图标 + 标题区分。
const DL_VIEWS = {
success: { icon: 'fa-cloud-download-alt', title: '下载已就绪' },
login: { icon: 'fa-user-lock', title: '请先登录' },
purchase: { icon: 'fa-shopping-cart', title: '请先购买' },
vip: { icon: 'fa-gem', title: '会员专属' },
@@ -251,34 +250,8 @@
}
}
// ===== 成功:直接打开生成的下载链接,不再弹窗 =====
function startDownload(payload, returnFocusEl) {
const url = payload && payload.dl_url ? String(payload.dl_url) : '';
if (!url) {
renderError('下载链接生成失败,请稍后重试', {}, returnFocusEl);
return;
}
// 与旧版弹窗内「立即下载」一致:新标签页打开本站中转链接。
const link = document.createElement('a');
link.href = url;
link.target = '_blank';
link.rel = 'noopener noreferrer';
document.body.appendChild(link);
link.click();
link.remove();
}
// ===== 主流程 =====
function captchaScope(sourceEl) {
const box = sourceEl ? sourceEl.closest('[data-aksrt-download-box]') : null;
return box ? (box.querySelector('[data-aksrt-download-captcha]') || box) : sourceEl;
}
function isCaptchaActive(sourceEl) {
const card = sourceEl ? sourceEl.closest('[data-aksrt-download-box]') : null;
if (!card) return false;
return card.getAttribute('data-captcha') === '1';
}
function cashierHeader() {
return '<div class="modal-colorful-header colorful-bg jb-blue">' +
@@ -299,70 +272,8 @@
);
}
function ensureCaptchaThen(sourceEl, callback) {
if (!isCaptchaActive(sourceEl)) { callback({}); return; }
const captcha = window.AKSRTCaptcha;
if (!captcha) {
if (window.console && console.warn) console.warn('[AKSRT] download captcha not ready');
alert('验证码组件加载中,请稍候再试');
return;
}
captcha.run('download', captchaScope(sourceEl)).then(function (payload) {
if (payload !== null) callback(payload);
});
}
function performDownloadRequest(button, captchaPayload) {
const card = button.closest('[data-aksrt-download-box]');
if (!card) return;
const postId = parseInt(card.getAttribute('data-post-id'), 10);
const sourceIndex = parseInt(button.getAttribute('data-source-index'), 10);
if (!postId || isNaN(sourceIndex)) return;
button.disabled = true;
const original = button.innerHTML;
button.innerHTML = '<i class="fa fas fa-spinner fa-spin" aria-hidden="true"></i> 处理中…';
const restNonce = window.aksrtData.restNonce || '';
fetch(REST_BASE + '/download/request', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': restNonce,
},
body: JSON.stringify({
post_id: postId,
source_index: sourceIndex,
captcha: captchaPayload,
}),
})
.then(function (resp) { return resp.json().then(function (j) { return { ok: resp.ok, json: j }; }); })
.then(function (r) {
button.disabled = false;
button.innerHTML = original;
if (!r.ok || (r.json && r.json.code)) {
renderError(r.json.message || '请稍后重试', r.json.data || {}, button, r.json.code);
return;
}
startDownload(r.json, button);
})
.catch(function () {
button.disabled = false;
button.innerHTML = original;
renderError('网络错误,请稍后重试', {}, button);
})
.finally(function () {
if (window.AKSRTCaptcha) window.AKSRTCaptcha.reset('download', captchaScope(button));
});
}
function requestDownload(button) {
ensureCaptchaThen(button, function (captchaPayload) {
performDownloadRequest(button, captchaPayload);
});
}
function pollOrderPaid(orderNumber, returnFocusEl) {
if (!orderNumber) return;
@@ -387,12 +298,6 @@
}, 3000);
}
// ===== 积分不足按钮(前置拦截,不发请求) =====
function showLowCreditModal(button) {
const balance = parseInt(button.getAttribute('data-balance'), 10) || 0;
const cost = parseInt(button.getAttribute('data-cost'), 10) || 0;
renderError('', { balance: balance, cost: cost }, button, 'aksrt_points_insufficient');
}
// ===== 收银台(内容购买) =====
function restHeaders(idempotencyKey) {
@@ -807,13 +712,6 @@
// ===== 事件绑定 =====
document.addEventListener('click', function (e) {
const lowBtn = e.target.closest('[data-action="aksrt-need-credits"]');
if (lowBtn) {
e.preventDefault();
showLowCreditModal(lowBtn);
return;
}
const buyBtn = e.target.closest('[data-action="aksrt-need-purchase"], [data-aksrt-pay-content]');
if (buyBtn) {
e.preventDefault();
@@ -828,12 +726,6 @@
if (postId) openCashier(postId, buyBtn, priceType);
return;
}
const btn = e.target.closest('[data-action="aksrt-download"]');
if (!btn) return;
if (btn.disabled || btn.getAttribute('aria-disabled') === 'true') return;
e.preventDefault();
requestDownload(btn);
});
// Public cashier bridge (content pay).