251 lines
8.3 KiB
PHP
251 lines
8.3 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace app\common\service;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 分流链接像素配置:解析、校验、合并保存、脱敏
|
||
|
|
*/
|
||
|
|
class SplitPixelConfigService
|
||
|
|
{
|
||
|
|
public const PLATFORM_FACEBOOK = 'facebook';
|
||
|
|
|
||
|
|
public const PLATFORM_TIKTOK = 'tiktok';
|
||
|
|
|
||
|
|
/** @var string[] */
|
||
|
|
public const EVENT_OPTIONS = [
|
||
|
|
'PageView',
|
||
|
|
'Lead',
|
||
|
|
'Contact',
|
||
|
|
'AddToCart',
|
||
|
|
'Purchase',
|
||
|
|
'Subscribe',
|
||
|
|
];
|
||
|
|
|
||
|
|
private const MAX_ITEMS_PER_PLATFORM = 20;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 解析存储 JSON 为规范结构
|
||
|
|
*
|
||
|
|
* @return array{facebook: array<int, array<string, mixed>>, tiktok: array<int, array<string, mixed>>}
|
||
|
|
*/
|
||
|
|
public static function parseStorage(?string $raw): array
|
||
|
|
{
|
||
|
|
$empty = [
|
||
|
|
self::PLATFORM_FACEBOOK => [],
|
||
|
|
self::PLATFORM_TIKTOK => [],
|
||
|
|
];
|
||
|
|
if ($raw === null || trim($raw) === '') {
|
||
|
|
return $empty;
|
||
|
|
}
|
||
|
|
$decoded = json_decode($raw, true);
|
||
|
|
if (!is_array($decoded)) {
|
||
|
|
return $empty;
|
||
|
|
}
|
||
|
|
|
||
|
|
return [
|
||
|
|
self::PLATFORM_FACEBOOK => self::normalizePlatformList(
|
||
|
|
$decoded[self::PLATFORM_FACEBOOK] ?? [],
|
||
|
|
self::PLATFORM_FACEBOOK
|
||
|
|
),
|
||
|
|
self::PLATFORM_TIKTOK => self::normalizePlatformList(
|
||
|
|
$decoded[self::PLATFORM_TIKTOK] ?? [],
|
||
|
|
self::PLATFORM_TIKTOK
|
||
|
|
),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 合并 POST 数据与已有配置(Token / 测试码留空保留原值)
|
||
|
|
*
|
||
|
|
* @param array<string, mixed> $incoming
|
||
|
|
* @param array<string, mixed> $existing
|
||
|
|
* @return array{facebook: array<int, array<string, mixed>>, tiktok: array<int, array<string, mixed>>}
|
||
|
|
*/
|
||
|
|
public static function mergeForSave(array $incoming, array $existing): array
|
||
|
|
{
|
||
|
|
$existingMap = self::indexById($existing);
|
||
|
|
$result = [
|
||
|
|
self::PLATFORM_FACEBOOK => [],
|
||
|
|
self::PLATFORM_TIKTOK => [],
|
||
|
|
];
|
||
|
|
|
||
|
|
foreach ([self::PLATFORM_FACEBOOK, self::PLATFORM_TIKTOK] as $platform) {
|
||
|
|
$rows = is_array($incoming[$platform] ?? null) ? $incoming[$platform] : [];
|
||
|
|
if (count($rows) > self::MAX_ITEMS_PER_PLATFORM) {
|
||
|
|
throw new \InvalidArgumentException('像素配置数量超出上限');
|
||
|
|
}
|
||
|
|
foreach ($rows as $row) {
|
||
|
|
if (!is_array($row)) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
$normalized = self::normalizeRow($row, $platform);
|
||
|
|
$id = (string) ($normalized['id'] ?? '');
|
||
|
|
if ($id !== '' && isset($existingMap[$id])) {
|
||
|
|
$old = $existingMap[$id];
|
||
|
|
if (trim((string) ($normalized['access_token'] ?? '')) === ''
|
||
|
|
|| strpos((string) ($normalized['access_token'] ?? ''), '***') !== false) {
|
||
|
|
$normalized['access_token'] = (string) ($old['access_token'] ?? '');
|
||
|
|
}
|
||
|
|
if (trim((string) ($normalized['test_code'] ?? '')) === '') {
|
||
|
|
$normalized['test_code'] = (string) ($old['test_code'] ?? '');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (trim((string) ($normalized['pixel_id'] ?? '')) === '') {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
$result[$platform][] = $normalized;
|
||
|
|
}
|
||
|
|
usort($result[$platform], static function (array $a, array $b): int {
|
||
|
|
return ((int) ($a['sort'] ?? 0)) <=> ((int) ($b['sort'] ?? 0));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
return $result;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param array{facebook: array<int, array<string, mixed>>, tiktok: array<int, array<string, mixed>>} $config
|
||
|
|
*/
|
||
|
|
public static function encodeStorage(array $config): string
|
||
|
|
{
|
||
|
|
$payload = [
|
||
|
|
self::PLATFORM_FACEBOOK => $config[self::PLATFORM_FACEBOOK] ?? [],
|
||
|
|
self::PLATFORM_TIKTOK => $config[self::PLATFORM_TIKTOK] ?? [],
|
||
|
|
];
|
||
|
|
|
||
|
|
return json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '{}';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 管理端 GET:脱敏 access_token
|
||
|
|
*
|
||
|
|
* @param array{facebook: array<int, array<string, mixed>>, tiktok: array<int, array<string, mixed>>} $config
|
||
|
|
* @return array{facebook: array<int, array<string, mixed>>, tiktok: array<int, array<string, mixed>>}
|
||
|
|
*/
|
||
|
|
public static function maskForAdmin(array $config): array
|
||
|
|
{
|
||
|
|
$mask = static function (array $rows): array {
|
||
|
|
$out = [];
|
||
|
|
foreach ($rows as $row) {
|
||
|
|
$item = $row;
|
||
|
|
$token = (string) ($item['access_token'] ?? '');
|
||
|
|
$item['access_token'] = $token === '' ? '' : self::maskToken($token);
|
||
|
|
$item['has_access_token'] = $token !== '';
|
||
|
|
unset($item['access_token_raw']);
|
||
|
|
$out[] = $item;
|
||
|
|
}
|
||
|
|
|
||
|
|
return $out;
|
||
|
|
};
|
||
|
|
|
||
|
|
return [
|
||
|
|
self::PLATFORM_FACEBOOK => $mask($config[self::PLATFORM_FACEBOOK] ?? []),
|
||
|
|
self::PLATFORM_TIKTOK => $mask($config[self::PLATFORM_TIKTOK] ?? []),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 获取已启用且按 sort 排序的条目(中转页 / 服务端回传)
|
||
|
|
*
|
||
|
|
* @param array{facebook: array<int, array<string, mixed>>, tiktok: array<int, array<string, mixed>>} $config
|
||
|
|
* @return array<int, array<string, mixed>>
|
||
|
|
*/
|
||
|
|
public static function getEnabledSorted(array $config, string $platform): array
|
||
|
|
{
|
||
|
|
$rows = $config[$platform] ?? [];
|
||
|
|
$enabled = [];
|
||
|
|
foreach ($rows as $row) {
|
||
|
|
if ((int) ($row['enabled'] ?? 0) === 1) {
|
||
|
|
$enabled[] = $row;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
usort($enabled, static function (array $a, array $b): int {
|
||
|
|
return ((int) ($a['sort'] ?? 0)) <=> ((int) ($b['sort'] ?? 0));
|
||
|
|
});
|
||
|
|
|
||
|
|
return $enabled;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function maskToken(string $token): string
|
||
|
|
{
|
||
|
|
$len = strlen($token);
|
||
|
|
if ($len <= 8) {
|
||
|
|
return '***';
|
||
|
|
}
|
||
|
|
|
||
|
|
return substr($token, 0, 3) . '***' . substr($token, -3);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param array<int, array<string, mixed>> $rows
|
||
|
|
* @return array<int, array<string, mixed>>
|
||
|
|
*/
|
||
|
|
private static function normalizePlatformList(array $rows, string $platform): array
|
||
|
|
{
|
||
|
|
$result = [];
|
||
|
|
foreach ($rows as $row) {
|
||
|
|
if (!is_array($row)) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
$normalized = self::normalizeRow($row, $platform);
|
||
|
|
if (trim((string) ($normalized['pixel_id'] ?? '')) === '') {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
$result[] = $normalized;
|
||
|
|
}
|
||
|
|
usort($result, static function (array $a, array $b): int {
|
||
|
|
return ((int) ($a['sort'] ?? 0)) <=> ((int) ($b['sort'] ?? 0));
|
||
|
|
});
|
||
|
|
|
||
|
|
return $result;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param array<string, mixed> $row
|
||
|
|
* @return array<string, mixed>
|
||
|
|
*/
|
||
|
|
private static function normalizeRow(array $row, string $platform): array
|
||
|
|
{
|
||
|
|
$event = (string) ($row['event'] ?? 'PageView');
|
||
|
|
if (!in_array($event, self::EVENT_OPTIONS, true)) {
|
||
|
|
$event = 'PageView';
|
||
|
|
}
|
||
|
|
$id = trim((string) ($row['id'] ?? ''));
|
||
|
|
if ($id === '') {
|
||
|
|
$id = $platform . '_' . uniqid('', true);
|
||
|
|
}
|
||
|
|
|
||
|
|
return [
|
||
|
|
'id' => $id,
|
||
|
|
'enabled' => (int) ($row['enabled'] ?? 0) === 1 ? 1 : 0,
|
||
|
|
'server_postback' => (int) ($row['server_postback'] ?? 0) === 1 ? 1 : 0,
|
||
|
|
'pixel_id' => trim((string) ($row['pixel_id'] ?? '')),
|
||
|
|
'access_token' => trim((string) ($row['access_token'] ?? '')),
|
||
|
|
'test_code' => trim((string) ($row['test_code'] ?? '')),
|
||
|
|
'event' => $event,
|
||
|
|
'sort' => max(0, (int) ($row['sort'] ?? 0)),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param array{facebook: array<int, array<string, mixed>>, tiktok: array<int, array<string, mixed>>} $config
|
||
|
|
* @return array<string, array<string, mixed>>
|
||
|
|
*/
|
||
|
|
private static function indexById(array $config): array
|
||
|
|
{
|
||
|
|
$map = [];
|
||
|
|
foreach ([self::PLATFORM_FACEBOOK, self::PLATFORM_TIKTOK] as $platform) {
|
||
|
|
foreach ($config[$platform] ?? [] as $row) {
|
||
|
|
$id = (string) ($row['id'] ?? '');
|
||
|
|
if ($id !== '') {
|
||
|
|
$map[$id] = $row;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $map;
|
||
|
|
}
|
||
|
|
}
|