2026-06-09 03:36:30 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
|
|
namespace app\common\service;
|
|
|
|
|
|
|
2026-07-02 19:03:06 +08:00
|
|
|
|
use app\common\library\scrm\AbstractScrmSpider;
|
2026-06-09 03:36:30 +08:00
|
|
|
|
use app\common\library\scrm\ScrmSpiderInterface;
|
|
|
|
|
|
use app\common\library\scrm\spider\A2cSpider;
|
2026-06-20 04:47:34 +08:00
|
|
|
|
use app\common\library\scrm\spider\ChatknowSpider;
|
2026-06-09 03:36:30 +08:00
|
|
|
|
use app\common\library\scrm\spider\HaiwangSpider;
|
|
|
|
|
|
use app\common\library\scrm\spider\HuojianSpider;
|
|
|
|
|
|
use app\common\library\scrm\spider\SsCustomerSpider;
|
2026-06-20 04:47:34 +08:00
|
|
|
|
use app\common\library\scrm\spider\WhatshubSpider;
|
2026-06-09 03:36:30 +08:00
|
|
|
|
use app\common\library\scrm\spider\XingheSpider;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 工单类型 -> 云控蜘蛛工厂
|
|
|
|
|
|
*
|
|
|
|
|
|
* 新增云控类型:在 spider/ 下新增类并在此注册 ticket_type => Class
|
|
|
|
|
|
*/
|
|
|
|
|
|
class SplitScrmSpiderFactory
|
|
|
|
|
|
{
|
|
|
|
|
|
/** @var array<string, class-string<ScrmSpiderInterface>> */
|
|
|
|
|
|
private const MAP = [
|
|
|
|
|
|
'a2c' => A2cSpider::class,
|
|
|
|
|
|
'haiwang' => HaiwangSpider::class,
|
|
|
|
|
|
'huojian' => HuojianSpider::class,
|
|
|
|
|
|
'xinghe' => XingheSpider::class,
|
|
|
|
|
|
'ss_customer' => SsCustomerSpider::class,
|
2026-06-20 04:47:34 +08:00
|
|
|
|
'chatknow' => ChatknowSpider::class,
|
|
|
|
|
|
'whatshub' => WhatshubSpider::class,
|
2026-06-09 03:36:30 +08:00
|
|
|
|
// ceo_scrm 等未实现类型:新增 spider 类后在此注册
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2026-07-04 22:09:39 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* API 优先同步类型:常态走 PHP cURL,不占 Node Real Browser 槽位
|
|
|
|
|
|
*
|
|
|
|
|
|
* 调度层据此跳过 Node 队列门禁并允许并行投递;蜘蛛实现本身不在此修改。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @var list<string>
|
|
|
|
|
|
*/
|
|
|
|
|
|
private const API_FIRST_SYNC_TYPES = [
|
|
|
|
|
|
'whatshub',
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2026-06-09 03:36:30 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @return class-string<ScrmSpiderInterface>|null
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function resolveClass(string $ticketType): ?string
|
|
|
|
|
|
{
|
|
|
|
|
|
$ticketType = trim($ticketType);
|
|
|
|
|
|
return self::MAP[$ticketType] ?? null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 是否已实现蜘蛛
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function isSupported(string $ticketType): bool
|
|
|
|
|
|
{
|
|
|
|
|
|
return self::resolveClass($ticketType) !== null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-20 04:47:34 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 已注册且已实现蜘蛛的类型列表
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return list<string>
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function listSupportedTypes(): array
|
|
|
|
|
|
{
|
|
|
|
|
|
return array_keys(self::MAP);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-04 22:09:39 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 是否 API 优先类型(调度层可并行投递且不占 Node 队列)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function isApiFirstSyncType(string $ticketType): bool
|
|
|
|
|
|
{
|
|
|
|
|
|
return in_array(trim($ticketType), self::API_FIRST_SYNC_TYPES, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @return list<string>
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function listApiFirstSyncTypes(): array
|
|
|
|
|
|
{
|
|
|
|
|
|
return self::API_FIRST_SYNC_TYPES;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-02 19:03:06 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 同步是否依赖 Node Headless(纯 PHP cURL 等为 false)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function requiresNode(string $ticketType): bool
|
|
|
|
|
|
{
|
|
|
|
|
|
$class = self::resolveClass($ticketType);
|
|
|
|
|
|
if ($class === null) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return is_subclass_of($class, AbstractScrmSpider::class);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @return list<string>
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function listDirectSyncTypes(): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$types = [];
|
|
|
|
|
|
foreach (self::listSupportedTypes() as $ticketType) {
|
|
|
|
|
|
if (!self::requiresNode($ticketType)) {
|
|
|
|
|
|
$types[] = $ticketType;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $types;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @return list<string>
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function listNodeSyncTypes(): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$types = [];
|
|
|
|
|
|
foreach (self::listSupportedTypes() as $ticketType) {
|
|
|
|
|
|
if (self::requiresNode($ticketType)) {
|
|
|
|
|
|
$types[] = $ticketType;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $types;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-09 03:36:30 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @return ScrmSpiderInterface|null
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function create(
|
|
|
|
|
|
string $ticketType,
|
|
|
|
|
|
string $pageUrl,
|
|
|
|
|
|
string $account = '',
|
|
|
|
|
|
string $password = '',
|
|
|
|
|
|
string $nodeHost = ''
|
|
|
|
|
|
): ?ScrmSpiderInterface {
|
|
|
|
|
|
$class = self::resolveClass($ticketType);
|
|
|
|
|
|
if ($class === null) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
$host = $nodeHost !== '' ? $nodeHost : SplitSyncConfigService::getNodeHost();
|
|
|
|
|
|
return new $class($pageUrl, $account, $password, $host);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|