添加whatshub工单

This commit is contained in:
root
2026-06-20 04:47:34 +08:00
parent a6c87e8e25
commit d5a0ffa6db
271 changed files with 9377 additions and 303 deletions
+55 -7
View File
@@ -30,9 +30,10 @@ class SplitTicketSyncService
/**
* 同步单条工单
*
* @param bool $dueValidated 为 true 时跳过 shouldSkip(由 syncDueTickets 预筛后传入)
* @return array{success:bool,message:string,skipped?:bool}
*/
public function syncOne(int $ticketId, bool $force = false): array
public function syncOne(int $ticketId, bool $force = false, bool $dueValidated = false): array
{
$ticket = Ticket::get($ticketId);
if (!$ticket) {
@@ -50,7 +51,7 @@ class SplitTicketSyncService
'nodeHost' => SplitSyncConfigService::getNodeHost(),
]);
if (!$force) {
if (!$force && !$dueValidated) {
$skip = $this->shouldSkip($ticket);
if ($skip !== null) {
SplitTicketSyncLogger::log('sync', 'skipped', ['reason' => $skip]);
@@ -76,23 +77,47 @@ class SplitTicketSyncService
}
/**
* 扫描到期工单并同步
* 扫描到期工单并同步(限流 + Node 队列感知)
*/
public function syncDueTickets(): int
{
$count = 0;
$maxPerRun = SplitSyncConfigService::getMaxTicketsPerCronRun();
$autoSyncTypes = $this->resolveAutoSyncTicketTypes();
if ($autoSyncTypes === []) {
SplitTicketSyncLogger::log('cron', 'scan skip', ['reason' => 'no auto-sync ticket types']);
return 0;
}
if (!SplitNodeHealthService::canAcceptWork()) {
SplitTicketSyncLogger::log('cron', 'scan skip', [
'reason' => 'node queue busy',
'queue' => SplitNodeHealthService::getQueueSnapshot(),
]);
return 0;
}
$failThreshold = SplitSyncConfigService::getFailPauseThreshold();
$query = Ticket::where('status', 'normal');
$query = Ticket::where('status', 'normal')->whereIn('ticket_type', $autoSyncTypes);
if ($failThreshold > 0) {
$query->where('sync_fail_count', '<', $failThreshold);
}
$list = $query->select();
// 多取候选:间隔过滤在 PHP 完成,优先同步最久未更新的工单
$list = $query->order('sync_time', 'asc')->order('id', 'asc')
->limit($maxPerRun * 5)
->select();
SplitTicketSyncLogger::log('cron', 'scan start', [
'candidateCount' => count($list),
'maxPerRun' => $maxPerRun,
'autoSyncTypes' => $autoSyncTypes,
'nodeQueue' => SplitNodeHealthService::getQueueSnapshot(),
]);
$count = 0;
foreach ($list as $ticket) {
if ($count >= $maxPerRun) {
break;
}
$skip = $this->shouldSkip($ticket);
if ($skip !== null) {
SplitTicketSyncLogger::log('cron', 'candidate skipped', [
@@ -102,7 +127,14 @@ class SplitTicketSyncService
]);
continue;
}
$result = $this->syncOne((int) $ticket['id'], false);
if (!SplitNodeHealthService::canAcceptWork()) {
SplitTicketSyncLogger::log('cron', 'node busy, stop batch', [
'processed' => $count,
'queue' => SplitNodeHealthService::getQueueSnapshot(),
]);
break;
}
$result = $this->syncOne((int) $ticket['id'], false, true);
if (!empty($result['skipped'])) {
continue;
}
@@ -113,6 +145,22 @@ class SplitTicketSyncService
return $count;
}
/**
* 已配置自动同步周期且已实现蜘蛛的工单类型
*
* @return list<string>
*/
private function resolveAutoSyncTicketTypes(): array
{
$types = [];
foreach (SplitScrmSpiderFactory::listSupportedTypes() as $ticketType) {
if (SplitSyncConfigService::getIntervalMinutes($ticketType) > 0) {
$types[] = $ticketType;
}
}
return $types;
}
/**
* @return array{success:bool,message:string}
*/