前端分流页功能
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user