Files
links/application/common/service/SplitTicketNumberSyncService.php
T

213 lines
6.9 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace app\common\service;
use app\admin\model\split\Number;
use app\admin\model\split\Ticket;
use app\common\library\scrm\UnifiedScrmData;
use think\Db;
/**
* 工单同步结果写入号码表
*
* 同步策略(全量镜像):
* - 爬虫返回的号码:写入 platform_status / inbound_countstatus 由后续 applyNumberRules 裁决
* - 爬虫未返回且 manual_manage=0:物理删除
* - manual_manage=1:不删除,仅当仍在爬虫结果中时更新 platform_status
*/
class SplitTicketNumberSyncService
{
/**
* 将蜘蛛返回的号码列表同步到号码管理(全量镜像,以爬虫为准)
*/
public function syncFromUnifiedData(Ticket $ticket, UnifiedScrmData $data): void
{
$adminId = (int) $ticket['admin_id'];
$linkId = (int) $ticket['split_link_id'];
$ticketName = (string) $ticket['ticket_name'];
if ($linkId <= 0 || $ticketName === '') {
return;
}
$randomShuffle = SplitNumberWeighService::isRandomShuffleEnabled($linkId);
// 使用独立 number 字段存储,避免纯数字号码作为数组 key 被 PHP 自动转为 int
$syncedNumbers = [];
foreach ($data->numbers as $row) {
$number = self::normalizeNumber($row['number'] ?? '');
if ($number === '') {
continue;
}
$syncedNumbers[] = [
'number' => $number,
'row' => $row,
];
}
$existingList = Number::where('admin_id', $adminId)
->where('split_link_id', $linkId)
->where('ticket_name', $ticketName)
->select();
$existingMap = [];
foreach ($existingList as $item) {
$existingMap[(string) $item['number']] = $item;
}
$syncedNumberSet = [];
$pendingInserts = [];
foreach ($syncedNumbers as $entry) {
$number = $entry['number'];
$row = $entry['row'];
$syncedNumberSet[$number] = true;
$platformStatus = ($row['status'] ?? '') === 'online' ? 'online' : 'offline';
$newFollowers = (int) ($row['newFollowersToday'] ?? 0);
if (isset($existingMap[$number])) {
$this->updateExistingNumber($ticket, $existingMap[$number], $platformStatus, $newFollowers);
continue;
}
$pendingInserts[] = [
'number' => $number,
'platform_status' => $platformStatus,
'new_followers' => $newFollowers,
];
}
if ($pendingInserts !== []) {
// 随机打乱:打乱待插入批次顺序,按随机顺序逐条 insert 以获得乱序自增 id
if ($randomShuffle && count($pendingInserts) > 1) {
shuffle($pendingInserts);
}
foreach ($pendingInserts as $item) {
$this->insertNumber(
$ticket,
$item['number'],
$item['platform_status'],
$item['new_followers']
);
}
}
$deleteIds = [];
foreach ($existingMap as $number => $item) {
if (isset($syncedNumberSet[$number])) {
continue;
}
if ((int) $item['manual_manage'] === 1) {
continue;
}
$deleteIds[] = (int) $item['id'];
}
if ($deleteIds !== []) {
Number::where('id', 'in', $deleteIds)->delete();
}
}
/**
* 工单手动开启等非同步场景:按业务规则对齐号码开关(平台 + 单号上限 + 下号比率)
*/
public function applyStatusFromPlatformSnapshot(Ticket $ticket): void
{
(new SplitTicketRuleService())->applyNumberRules($ticket);
}
/**
* 云控在线 → 开启;离线 → 关闭;工单关闭时一律关闭
*/
public static function resolveSwitchStatus(Ticket $ticket, string $platformStatus): string
{
if ((string) ($ticket['status'] ?? 'hidden') !== 'normal') {
return 'hidden';
}
return $platformStatus === 'online' ? 'normal' : 'hidden';
}
/**
* @param Number $row
*/
private function updateExistingNumber(
Ticket $ticket,
$row,
string $platformStatus,
int $newFollowers
): void {
$update = [
'platform_status' => $platformStatus,
'updatetime' => time(),
];
if ((int) $row['manual_manage'] === 1) {
Number::where('id', (int) $row['id'])->update($update);
return;
}
$update['inbound_count'] = max(0, $newFollowers);
Number::where('id', (int) $row['id'])->update($update);
}
private function insertNumber(
Ticket $ticket,
string $number,
string $platformStatus,
int $newFollowers
): void {
$now = time();
$data = [
'admin_id' => (int) $ticket['admin_id'],
'split_link_id' => (int) $ticket['split_link_id'],
'ticket_name' => (string) $ticket['ticket_name'],
'number' => $number,
'number_type' => (string) $ticket['number_type'],
'number_type_custom' => (string) ($ticket['number_type_custom'] ?? ''),
'visit_count' => 0,
'inbound_count' => max(0, $newFollowers),
'manual_manage' => 0,
'platform_status' => $platformStatus,
'ratio_deferred' => 0,
'createtime' => $now,
'updatetime' => $now,
];
try {
Db::name('split_number')->insert($data);
} catch (\Throwable $e) {
$exists = Number::where('split_link_id', (int) $ticket['split_link_id'])
->where('ticket_name', (string) $ticket['ticket_name'])
->where('number', $number)
->find();
if ($exists) {
$this->updateExistingNumber($ticket, $exists, $platformStatus, $newFollowers);
}
}
}
/**
* 统一号码为字符串(云控 API 可能返回 int)
*/
private static function normalizeNumber($value): string
{
if ($value === null || $value === '') {
return '';
}
return trim((string) $value);
}
/**
* 汇总工单进线人数(仅开启状态的号码)
*/
public function sumInboundForTicket(Ticket $ticket): int
{
$sum = Number::where('admin_id', (int) $ticket['admin_id'])
->where('split_link_id', (int) $ticket['split_link_id'])
->where('ticket_name', (string) $ticket['ticket_name'])
->where('status', 'normal')
->sum('inbound_count');
return (int) $sum;
}
}