Merge pull request 'perf(revenue): server-render the promotion link and stop settling on reads' (#37) from worktree-revenue-card-rework into main

Reviewed-on: #37
This commit was merged in pull request #37.
This commit is contained in:
2026-09-27 15:36:26 +00:00
6 changed files with 39 additions and 49 deletions
+7 -7
View File
@@ -115,6 +115,8 @@
}
// ===== Forward-only affiliate revenue center =====
/* 推广链接由 PHP 直出,首屏即可见、可复制;这里只补异步才拿得到的
* 海报配置与佣金比例,取失败也不影响复制。 */
function initAffiliateRevenue() {
var root = document.querySelector('[data-aksrt-revenue-page]');
if (!root || root.dataset.affiliateBound) return;
@@ -123,16 +125,14 @@
var copy = root.querySelector('[data-affiliate-copy]');
var feedback = root.querySelector('[data-affiliate-feedback]');
function setFeedback(text, error) { if (feedback) { feedback.textContent = text; feedback.classList.toggle('is-error', !!error); } }
var url = input ? input.value : '';
if (copy) copy.disabled = !url;
api('/revenue/me').then(function (r) {
if (!r.ok) { setFeedback((r.json && r.json.message) || '推广数据加载失败。', true); return; }
if (!r.ok) return;
var j = r.json || {};
if (input) {
input.value = j.url || '';
}
if (copy) copy.disabled = !j.url;
var ratio = root.querySelector('[data-affiliate-ratio]'); if (ratio) ratio.textContent = String(j.current_ratio || 0) + '%';
initAffiliatePoster(root, j.url || '', j.poster || {}, j.site_name || document.title);
}).catch(function () { setFeedback('推广数据加载失败。', true); });
initAffiliatePoster(root, url || j.url || '', j.poster || {}, j.site_name || document.title);
}).catch(function () { /* 海报与比例为增强项,静默失败即可。 */ });
if (copy) copy.addEventListener('click', function () {
if (!input || !input.value) return;
var done = function () { setFeedback('推广链接已复制。'); };
@@ -56,6 +56,11 @@ $details_title = trim((string) ($affiliate['details_title'] ?? ''));
if ($details_title === '') {
$details_title = __('返佣详解', 'aksrt');
}
/* 推广链接服务端直出:避免为了一个本地拼接的 URL 多绕一次 REST 往返。 */
$affiliate_link_url = '';
if (class_exists('\AKSRT\Payment\ReferralAttribution')) {
$affiliate_link_url = (string) (\AKSRT\Payment\ReferralAttribution::get_instance()->create_link($user_id)['url'] ?? '');
}
$revenue_request = is_array($context['revenue_request'] ?? null) ? $context['revenue_request'] : [];
$active_reward_tab = sanitize_key((string) ($revenue_request['reward_tab'] ?? ($_GET['reward_tab'] ?? 'detail')));
@@ -144,7 +149,7 @@ $status_labels = [
<div class="uc-revenue-summary-list">
<div class="uc-revenue-summary-row uc-revenue-summary-row--link">
<span><?php esc_html_e('推广链接', 'aksrt'); ?></span>
<input type="text" readonly data-affiliate-link aria-label="<?php esc_attr_e('推广链接', 'aksrt'); ?>">
<input type="text" readonly data-affiliate-link value="<?php echo esc_attr($affiliate_link_url); ?>" aria-label="<?php esc_attr_e('推广链接', 'aksrt'); ?>">
<button type="button" class="btn-primary btn-sm" data-affiliate-copy><?php esc_html_e('复制链接', 'aksrt'); ?></button>
<?php if (!empty($affiliate['poster']['enabled'])) : ?><button type="button" class="btn-outline btn-sm" data-affiliate-poster hidden><?php esc_html_e('推广海报', 'aksrt'); ?></button><?php endif; ?>
<small data-affiliate-feedback role="status" aria-live="polite"></small>
-9
View File
@@ -103,14 +103,6 @@ final class CommissionLedger {
foreach ((array) $rows as $row) if ($this->settle_one((int)$row['id'])) $count++;
return $count;
}
/** 懒结算兜底:收益相关入口顺带结算到期佣金,弥补 wp-cron 在低流量站点的时效缺口。 */
private static bool $lazy_settled = false;
public function lazy_settle_due(): void {
if (self::$lazy_settled) return;
self::$lazy_settled = true;
if (!\AKSRT\Settings\Settings::affiliate_enabled()) return;
$this->settle_due(20);
}
private function settle_one(int $id, string $operation_key = '') {
global $wpdb; $table = $this->table('commission_ledger');
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id=%d", $id), ARRAY_A);
@@ -241,7 +233,6 @@ final class CommissionLedger {
return $negative ? -$minor : $minor;
}
public function overview(int $user_id): array {
$this->lazy_settle_due();
global $wpdb; $table=$this->table('commission_ledger');
$row=$wpdb->get_row($wpdb->prepare("SELECT COALESCE(SUM(original_amount+adjustment_total),0) total, COALESCE(SUM(CASE WHEN status='pending' THEN original_amount+adjustment_total ELSE 0 END),0) pending, COALESCE(SUM(CASE WHEN status='settled' THEN original_amount+adjustment_total ELSE 0 END),0) settled FROM {$table} WHERE promoter_id=%d",$user_id),ARRAY_A) ?: [];
$withdrawn = '0.00';
-21
View File
@@ -291,24 +291,6 @@ final class ReferralAttribution {
return $bound > 0 && $bound !== $user_id ? $bound : 0;
}
public function stats(int $promoter_id): array {
global $wpdb;
$table = \AKSRT\Core\DBMigrator::table('affiliate_relation');
if ($promoter_id <= 0 || $table === '') {
return ['promoters' => 0, 'effective_payers' => 0];
}
$promoters = (int) $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$table} WHERE promoter_id = %d AND status = 'bound'",
$promoter_id
));
$ledger = \AKSRT\Core\DBMigrator::table('commission_ledger');
$payers = $ledger !== '' ? (int) $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(DISTINCT source_user_id) FROM {$ledger} WHERE promoter_id = %d AND original_amount > 0",
$promoter_id
)) : 0;
return ['promoters' => $promoters, 'effective_payers' => $payers];
}
/** @return array{items:array<int,array<string,mixed>>,total:int,page:int,per_page:int,max_pages:int} */
public function list_referred_users(int $promoter_id, int $page = 1, int $per_page = 10): array {
global $wpdb;
@@ -361,10 +343,8 @@ final class ReferralAttribution {
}
public function rest_me(): \WP_REST_Response {
\AKSRT\Payment\CommissionLedger::get_instance()->lazy_settle_due();
$user_id = get_current_user_id();
$link = $this->create_link($user_id);
$stats = $this->stats($user_id);
$affiliate = \AKSRT\Settings\Settings::get_affiliate_settings();
$registration = $this->registration_settings();
$reward = $this->reward_settings();
@@ -386,7 +366,6 @@ final class ReferralAttribution {
'window_days' => (int) ($this->shared_settings()['window_days'] ?? 30),
'settlement_days' => (int) ($affiliate['settlement_days'] ?? 7),
],
'stats' => $stats,
], 200);
}
@@ -61,19 +61,19 @@ test('commission base is the discounted payable amount, not the list price', ()
assert.match(ledger, /AssetAmount::balance\(is_string\(\$order\['amount'\] \?\? null\) \? \$order\['amount'\] : ''\)/)
})
test('lazy settlement backstop guards the earnings entry points', () => {
test('settlement stays off the read path and runs on the cron schedule', () => {
const ledger = read('src', 'Payment', 'CommissionLedger.php')
const attribution = read('src', 'Payment', 'ReferralAttribution.php')
assert.match(ledger, /private static bool \$lazy_settled = false/)
assert.match(ledger, /public function lazy_settle_due\(\): void/)
assert.match(ledger, /if \(!\\AKSRT\\Settings\\Settings::affiliate_enabled\(\)\) return;/)
assert.ok(
ledger.indexOf('lazy_settle_due()') < ledger.indexOf('global \$wpdb; \$table=\$this->table\(\'commission_ledger\'\); \$row=\$wpdb->get_row'),
'overview() must trigger the backstop before aggregating balances'
)
// 收益总览与我的推广两个入口都接入了兜底。
assert.match(attribution, /CommissionLedger::get_instance\(\)->lazy_settle_due\(\)/)
// 懒结算兜底已移除:读路径不得顺带写库(收益页打开慢的主因)。
assert.doesNotMatch(ledger, /lazy_settle_due|\$lazy_settled/)
// overview() 与 rest_me() 都不再触发结算。
assert.doesNotMatch(ledger, /\$this->lazy_settle_due\(\)/)
assert.doesNotMatch(attribution, /lazy_settle_due/)
assert.doesNotMatch(attribution, /->stats\(/)
// 结算只由 hourly 的 wp-cron 事件驱动。
assert.match(ledger, /add_action\('aksrt_affiliate_settle_due', \[\$this, 'settle_due'\]\)/)
assert.match(ledger, /wp_schedule_event\(time\(\) \+ HOUR_IN_SECONDS, 'hourly', 'aksrt_affiliate_settle_due'\)/)
})
test('referral codes are self-serve: no admin ops page, granted at registration', () => {
@@ -51,6 +51,21 @@ test('revenue detail card is a four-row info list with no standalone QR block',
assert.match(script, /data-affiliate-poster-download/)
assert.match(script, /openModal\(modal\)/)
assert.match(script, /link\.download = 'referral-poster\.png'/)
// 链接由 PHP 直出:复制按钮立即可用,REST 只负责海报配置与佣金比例。
assert.match(script, /var url = input \? input\.value : '';/)
assert.match(script, /if \(copy\) copy\.disabled = !url;/)
assert.doesNotMatch(script, /input\.value = j\.url/)
})
test('promotion link is server-rendered so it needs no round trip', () => {
const template = read('inc', 'helpers', 'user-center-renderers', 'profile-revenue.php')
// input 带服务端 value,不依赖 JS 回填。
assert.match(template, /data-affiliate-link value="<\?php echo esc_attr\(\$affiliate_link_url\); \?>"/)
assert.match(template, /create_link\(\$user_id\)\['url'\]/)
const attribution = read('src', 'Payment', 'ReferralAttribution.php')
// rest_me 不再做 COUNT 聚合,也不触发结算。
assert.doesNotMatch(attribution, /\$this->stats\(\$user_id\)/)
assert.doesNotMatch(attribution, /lazy_settle_due\(\)/)
})
test('poster canvas is built from admin-configured copy and gated on the poster switch', () => {
@@ -67,7 +82,7 @@ test('poster canvas is built from admin-configured copy and gated on the poster
const script = read('assets', 'js', 'credits.js')
// 海报仅在开关开启时暴露按钮;绘制含顶部图、文案换行、二维码与站点名。
assert.match(script, /initAffiliatePoster\(root, j\.url \|\| '', j\.poster \|\| \{\}, j\.site_name \|\| document\.title\)/)
assert.match(script, /initAffiliatePoster\(root, url \|\| j\.url \|\| '', j\.poster \|\| \{\}, j\.site_name \|\| document\.title\)/)
assert.match(script, /poster\.enabled !== true\) return/)
assert.match(script, /function buildPosterCanvas/)
assert.match(script, /function wrapText\(ctx, text, maxWidth\)/)