Files

136 lines
5.4 KiB
PHP
Raw Permalink Normal View History

2026-06-14 14:00:24 +08:00
#!/usr/bin/env php
<?php
/**
* 广告来源限制 Guard 单元验证
*
* 用法:php tools/verify_ad_source_guard.php
*/
if (PHP_SAPI !== 'cli') {
fwrite(STDERR, "请在 CLI 下运行\n");
exit(1);
}
require_once dirname(__DIR__) . '/lib/CloakAdSourceGuard.php';
$passed = 0;
$failed = 0;
function vassert($name, $cond, $detail = '')
{
global $passed, $failed;
if ($cond) {
$passed++;
return;
}
$failed++;
echo "FAIL: {$name}\n";
if ($detail !== '') {
echo " {$detail}\n";
}
}
$spmId = 'Ab12Cd34Ef56';
$baseCfg = [
'ad_fb' => true,
'ad_google' => false,
'ad_tiktok' => false,
'ad_strict' => false,
'ad_spm_id' => $spmId,
];
// 优先级 1ad_spm_id 匹配
$r = CloakAdSourceGuard::evaluate($baseCfg, [
'referer' => '', 'query' => ['ad_spm_id' => $spmId], 'user_agent' => '',
]);
vassert('spm_id 匹配放行', $r['passed'] && $r['matched_by'] === 'ad_spm_id');
$r = CloakAdSourceGuard::evaluate($baseCfg, [
'referer' => '', 'query' => ['ad_spm_id' => 'wrongId12345'], 'user_agent' => '',
]);
vassert('spm_id 不匹配不单独放行', !$r['passed'] || $r['matched_by'] !== 'ad_spm_id');
// 优先级 2clid
$r = CloakAdSourceGuard::evaluate($baseCfg, [
'referer' => '', 'query' => ['fbclid' => 'IwAR1234567890123456789012'], 'user_agent' => '',
]);
vassert('fbclid 放行', $r['passed'] && strpos($r['matched_by'], 'clid:') === 0);
$r = CloakAdSourceGuard::evaluate($baseCfg, [
'referer' => '', 'query' => ['utm_campaign' => '__CAMPAIGN_ID__'], 'user_agent' => '',
]);
vassert('TikTok 宏放行', $r['passed'] && strpos($r['matched_by'], 'tiktok_macro:') === 0);
// 优先级 3referer
$r = CloakAdSourceGuard::evaluate($baseCfg, [
'referer' => 'https://www.facebook.com/ads', 'query' => [], 'user_agent' => '',
]);
vassert('referer facebook 放行', $r['passed'] && strpos($r['matched_by'], 'referer:') === 0);
// 优先级 4UA
$r = CloakAdSourceGuard::evaluate($baseCfg, [
'referer' => '', 'query' => [], 'user_agent' => 'Mozilla/5.0 TikTok 26.0',
]);
vassert('UA TikTok 放行', $r['passed'] && strpos($r['matched_by'], 'ua:') === 0);
// 严格模式拦截
$strictCfg = array_merge($baseCfg, ['ad_strict' => true]);
$r = CloakAdSourceGuard::evaluate($strictCfg, [
'referer' => '', 'query' => [], 'user_agent' => 'Mozilla/5.0 Chrome',
]);
vassert('严格模式未命中拦截', !$r['passed'] && $r['reason'] === '来源限制:未命中广告来源验证');
// 非严格模式
$r = CloakAdSourceGuard::evaluate($baseCfg, [
'referer' => '', 'query' => [], 'user_agent' => 'Mozilla/5.0 Chrome',
]);
vassert('非严格未命中', !$r['passed'] && $r['reason'] === '未确定流量来源');
// guard 未启用
$r = CloakAdSourceGuard::evaluate([
'ad_fb' => false, 'ad_google' => false, 'ad_tiktok' => false,
'ad_strict' => true, 'ad_spm_id' => $spmId,
], ['referer' => '', 'query' => [], 'user_agent' => '']);
vassert('无平台开启跳过', $r['passed'] && $r['matched_by'] === 'guard_disabled');
// 链接拼装:单平台
$q = CloakAdSourceGuard::buildAdLinkQuery([CloakAdSourceGuard::PLATFORM_FB], $spmId);
2026-06-27 23:58:54 +08:00
vassert('单平台 FB 含完整 UTM', strpos($q, 'utm_source=facebook') !== false);
2026-06-14 14:00:24 +08:00
vassert('单平台含 ad_spm_id', strpos($q, 'ad_spm_id=' . rawurlencode($spmId)) !== false);
2026-06-27 23:58:54 +08:00
$adjustFp = 'https://aindl.com/v305-8/?p0=1ynqxx65&p1=%7B%7Bcampaign.name%7D%7D&p2=%7B%7Bcampaign.id%7D%7D&p3=%7B%7Badset.name%7D%7D&p4=%7B%7Badset.id%7D%7D&p5=%7B%7Bad.name%7D%7D&p6=%7B%7Bad.id%7D%7D';
$qAdjust = CloakAdSourceGuard::buildAdLinkQuery([CloakAdSourceGuard::PLATFORM_FB], $spmId, [$adjustFp]);
vassert('Adjust DB_FP 追加 p1 宏', strpos($qAdjust, 'p1={{campaign.name}}') !== false, $qAdjust);
vassert('Adjust DB_FP 追加 p6 宏', strpos($qAdjust, 'p6={{ad.id}}') !== false, $qAdjust);
vassert('Adjust 广告链接仍含 UTM', strpos($qAdjust, 'utm_source=facebook') !== false);
2026-06-14 14:00:24 +08:00
// 链接拼装:多平台
$q = CloakAdSourceGuard::buildAdLinkQuery([
CloakAdSourceGuard::PLATFORM_FB,
CloakAdSourceGuard::PLATFORM_GOOGLE,
CloakAdSourceGuard::PLATFORM_TIKTOK,
], $spmId);
vassert('多平台含 FB 代表参数', strpos($q, 'campaign_id={{campaign.id}}') !== false);
vassert('多平台含 Google 代表参数', strpos($q, 'utm_campaign={campaignid}') !== false);
vassert('多平台含 TikTok 代表参数', strpos($q, 'adgroup_id=__CID__') !== false);
vassert('多平台不含完整 FB UTM', strpos($q, 'utm_medium=paid_social') === false);
// spm 生成
$id = CloakAdSourceGuard::generateSpmId();
vassert('generateSpmId 合法', CloakAdSourceGuard::isValidSpmId($id), $id);
// 临时链接计时触发
vassert('计时:referer 命中广告来源', CloakAdSourceGuard::shouldStartUrlArgsTimer($baseCfg, [
'referer' => 'https://www.facebook.com/', 'query' => [], 'user_agent' => '',
]));
vassert('计时:未命中不启动', !CloakAdSourceGuard::shouldStartUrlArgsTimer($baseCfg, [
'referer' => '', 'query' => [], 'user_agent' => 'Mozilla/5.0 Chrome',
]));
vassert('计时:guard 关闭时 clid 仍可触发', CloakAdSourceGuard::shouldStartUrlArgsTimer([
'ad_fb' => false, 'ad_google' => false, 'ad_tiktok' => false,
'ad_strict' => false, 'ad_spm_id' => '',
], ['referer' => '', 'query' => ['gclid' => 'abc'], 'user_agent' => '']));
$result = ['ok' => $failed === 0, 'passed' => $passed, 'failed' => $failed];
echo json_encode($result, JSON_UNESCAPED_UNICODE) . "\n";
exit($failed > 0 ? 1 : 0);