前端分流页功能
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use think\Log;
|
||||
|
||||
/**
|
||||
* 分流中转页服务端像素回传(Facebook CAPI / TikTok Events API)
|
||||
*/
|
||||
class SplitPixelPostbackService
|
||||
{
|
||||
/**
|
||||
* @param array{facebook: array<int, array<string, mixed>>, tiktok: array<int, array<string, mixed>>} $config
|
||||
*/
|
||||
public static function dispatch(array $config, string $clientIp, string $userAgent, string $eventSourceUrl = ''): void
|
||||
{
|
||||
foreach (SplitPixelConfigService::getEnabledSorted($config, SplitPixelConfigService::PLATFORM_FACEBOOK) as $row) {
|
||||
if ((int) ($row['server_postback'] ?? 0) !== 1) {
|
||||
continue;
|
||||
}
|
||||
$token = trim((string) ($row['access_token'] ?? ''));
|
||||
if ($token === '') {
|
||||
continue;
|
||||
}
|
||||
self::sendFacebook($row, $token, $clientIp, $userAgent, $eventSourceUrl);
|
||||
}
|
||||
|
||||
foreach (SplitPixelConfigService::getEnabledSorted($config, SplitPixelConfigService::PLATFORM_TIKTOK) as $row) {
|
||||
if ((int) ($row['server_postback'] ?? 0) !== 1) {
|
||||
continue;
|
||||
}
|
||||
$token = trim((string) ($row['access_token'] ?? ''));
|
||||
if ($token === '') {
|
||||
continue;
|
||||
}
|
||||
self::sendTikTok($row, $token, $clientIp, $userAgent, $eventSourceUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
*/
|
||||
private static function sendFacebook(array $row, string $accessToken, string $clientIp, string $userAgent, string $eventSourceUrl): void
|
||||
{
|
||||
$pixelId = trim((string) ($row['pixel_id'] ?? ''));
|
||||
if ($pixelId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$eventName = (string) ($row['event'] ?? 'PageView');
|
||||
$testCode = trim((string) ($row['test_code'] ?? ''));
|
||||
|
||||
$payload = [
|
||||
'data' => [[
|
||||
'event_name' => $eventName,
|
||||
'event_time' => time(),
|
||||
'action_source' => 'website',
|
||||
'user_data' => array_filter([
|
||||
'client_ip_address' => $clientIp !== '' ? $clientIp : null,
|
||||
'client_user_agent' => $userAgent !== '' ? $userAgent : null,
|
||||
]),
|
||||
]],
|
||||
];
|
||||
if ($eventSourceUrl !== '') {
|
||||
$payload['data'][0]['event_source_url'] = $eventSourceUrl;
|
||||
}
|
||||
if ($testCode !== '') {
|
||||
$payload['test_event_code'] = $testCode;
|
||||
}
|
||||
|
||||
$url = 'https://graph.facebook.com/v19.0/' . rawurlencode($pixelId) . '/events?access_token=' . rawurlencode($accessToken);
|
||||
self::postJson($url, $payload, [], 'facebook', $pixelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
*/
|
||||
private static function sendTikTok(array $row, string $accessToken, string $clientIp, string $userAgent, string $eventSourceUrl): void
|
||||
{
|
||||
$pixelId = trim((string) ($row['pixel_id'] ?? ''));
|
||||
if ($pixelId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$event = self::mapTikTokServerEvent((string) ($row['event'] ?? 'PageView'));
|
||||
$testCode = trim((string) ($row['test_code'] ?? ''));
|
||||
|
||||
$payload = [
|
||||
'pixel_code' => $pixelId,
|
||||
'event' => $event,
|
||||
'event_id' => uniqid('split_', true),
|
||||
'timestamp' => gmdate('c'),
|
||||
'context' => array_filter([
|
||||
'ip' => $clientIp !== '' ? $clientIp : null,
|
||||
'user_agent' => $userAgent !== '' ? $userAgent : null,
|
||||
'page' => $eventSourceUrl !== '' ? ['url' => $eventSourceUrl] : null,
|
||||
]),
|
||||
];
|
||||
if ($testCode !== '') {
|
||||
$payload['test_event_code'] = $testCode;
|
||||
}
|
||||
|
||||
$url = 'https://business-api.tiktok.com/open_api/v1.3/event/track/';
|
||||
self::postJson($url, $payload, [
|
||||
'Access-Token: ' . $accessToken,
|
||||
'Content-Type: application/json',
|
||||
], 'tiktok', $pixelId);
|
||||
}
|
||||
|
||||
private static function mapTikTokServerEvent(string $event): string
|
||||
{
|
||||
$map = [
|
||||
'PageView' => 'Pageview',
|
||||
'Lead' => 'SubmitForm',
|
||||
'Contact' => 'Contact',
|
||||
'AddToCart' => 'AddToCart',
|
||||
'Purchase' => 'CompletePayment',
|
||||
'Subscribe' => 'Subscribe',
|
||||
];
|
||||
|
||||
return $map[$event] ?? 'Pageview';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
* @param array<int, string> $headers
|
||||
*/
|
||||
private static function postJson(string $url, array $payload, array $headers, string $platform, string $pixelId): void
|
||||
{
|
||||
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
if ($json === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ch = curl_init($url);
|
||||
if ($ch === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$defaultHeaders = ['Content-Type: application/json'];
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $json,
|
||||
CURLOPT_HTTPHEADER => array_merge($defaultHeaders, $headers),
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 3,
|
||||
CURLOPT_CONNECTTIMEOUT => 2,
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$errno = curl_errno($ch);
|
||||
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($errno !== 0 || ($httpCode >= 400 && $httpCode !== 0)) {
|
||||
Log::error(sprintf(
|
||||
'Split pixel postback failed platform=%s pixel=%s http=%d curl=%d',
|
||||
$platform,
|
||||
$pixelId,
|
||||
$httpCode,
|
||||
$errno
|
||||
));
|
||||
} elseif (is_string($response) && strpos($response, '"error"') !== false) {
|
||||
Log::error(sprintf('Split pixel postback api error platform=%s pixel=%s', $platform, $pixelId));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user