修改Whatshub工单的完成数量总计的计算方法
This commit is contained in:
Executable → Regular
+82
-6
@@ -31,6 +31,10 @@ class AntiBotConfigBuilder
|
||||
$landingUrlHint = SplitPageUrlResolver::resolveFinalUrl($pageUrl);
|
||||
}
|
||||
|
||||
if ($ticketType === 'whatshub' && $landingUrlHint === '') {
|
||||
$landingUrlHint = SplitPageUrlResolver::resolveFinalUrl($pageUrl);
|
||||
}
|
||||
|
||||
$sessionScope = self::resolveSessionScope($ticketType, $pageUrl, $landingUrlHint);
|
||||
if ($sessionScope === '') {
|
||||
return null;
|
||||
@@ -58,7 +62,7 @@ class AntiBotConfigBuilder
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话 scope:A2C 业务域+分享 id;火箭长链用动态 host、短链用 share.{token}
|
||||
* 会话 scope:A2C 业务域+分享 id;Whatshub host+shareCode/workId;火箭长链用动态 host
|
||||
*/
|
||||
public static function resolveSessionScope(string $ticketType, string $pageUrl, string $landingUrlHint = ''): string
|
||||
{
|
||||
@@ -66,6 +70,10 @@ class AntiBotConfigBuilder
|
||||
return self::resolveA2cSessionScope($pageUrl, $landingUrlHint);
|
||||
}
|
||||
|
||||
if ($ticketType === 'whatshub') {
|
||||
return self::resolveWhatshubSessionScope($pageUrl, $landingUrlHint);
|
||||
}
|
||||
|
||||
if ($ticketType === 'huojian') {
|
||||
return self::resolveHuojianSessionScope($pageUrl, $landingUrlHint);
|
||||
}
|
||||
@@ -92,6 +100,10 @@ class AntiBotConfigBuilder
|
||||
$landingUrlHint = SplitPageUrlResolver::resolveFinalUrl($pageUrl);
|
||||
}
|
||||
|
||||
if ($ticketType === 'whatshub' && $landingUrlHint === '') {
|
||||
$landingUrlHint = SplitPageUrlResolver::resolveFinalUrl($pageUrl);
|
||||
}
|
||||
|
||||
$scope = self::resolveSessionScope($ticketType, $pageUrl, $landingUrlHint);
|
||||
|
||||
return $ticketType . ':' . ($scope !== '' ? $scope : 'unknown');
|
||||
@@ -137,7 +149,8 @@ class AntiBotConfigBuilder
|
||||
return [
|
||||
'postCfReadyUrlContains' => 'work-order-sharing',
|
||||
'postCfReadySelector' => '.el-input__inner',
|
||||
'postCfReadyTimeoutMs' => 30000,
|
||||
'postCfReadyApiUrl' => '/api/whatshub-counter/workShare/open/statistics',
|
||||
'postCfReadyTimeoutMs' => 60000,
|
||||
'postCfSettleMs' => 500,
|
||||
'postCfSpaWaitMs' => 4000,
|
||||
];
|
||||
@@ -179,7 +192,7 @@ class AntiBotConfigBuilder
|
||||
|
||||
private static function defaultCfClearanceRequired(string $ticketType): bool
|
||||
{
|
||||
if ($ticketType === 'a2c') {
|
||||
if ($ticketType === 'a2c' || $ticketType === 'whatshub') {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -187,11 +200,11 @@ class AntiBotConfigBuilder
|
||||
}
|
||||
|
||||
/**
|
||||
* A2C 等为 CF Managed Challenge(5s 盾),非独立 Turnstile 挂件
|
||||
* A2C / Whatshub 等为 CF Managed Challenge(5s 盾),非独立 Turnstile 挂件
|
||||
*/
|
||||
private static function defaultCfChallengeMode(string $ticketType): string
|
||||
{
|
||||
if ($ticketType === 'a2c') {
|
||||
if ($ticketType === 'a2c' || $ticketType === 'whatshub') {
|
||||
return 'managed';
|
||||
}
|
||||
|
||||
@@ -203,7 +216,7 @@ class AntiBotConfigBuilder
|
||||
*/
|
||||
private static function defaultCfCloudflareSolver(string $ticketType): bool
|
||||
{
|
||||
return $ticketType === 'a2c';
|
||||
return $ticketType === 'a2c' || $ticketType === 'whatshub';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,6 +246,69 @@ class AntiBotConfigBuilder
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Whatshub 会话 scope:host + shareCode 或 workId,避免多工单共用温池 Browser
|
||||
*/
|
||||
private static function resolveWhatshubSessionScope(string $pageUrl, string $landingUrlHint = ''): string
|
||||
{
|
||||
$host = strtolower(trim((string) parse_url($pageUrl, PHP_URL_HOST)));
|
||||
if ($host === '' && $landingUrlHint !== '') {
|
||||
$host = strtolower(trim((string) parse_url($landingUrlHint, PHP_URL_HOST)));
|
||||
}
|
||||
if ($host === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$shareCode = self::extractWhatshubShareCode($pageUrl);
|
||||
if ($shareCode === '' && $landingUrlHint !== '') {
|
||||
$shareCode = self::extractWhatshubShareCode($landingUrlHint);
|
||||
}
|
||||
if ($shareCode !== '') {
|
||||
return $host . ':' . $shareCode;
|
||||
}
|
||||
|
||||
$workId = self::extractWhatshubWorkId($pageUrl);
|
||||
if ($workId === '' && $landingUrlHint !== '') {
|
||||
$workId = self::extractWhatshubWorkId($landingUrlHint);
|
||||
}
|
||||
if ($workId !== '') {
|
||||
return $host . ':' . $workId;
|
||||
}
|
||||
|
||||
return $host;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Whatshub 短链 /m/{shareCode}/ 提取 shareCode
|
||||
*/
|
||||
private static function extractWhatshubShareCode(string $url): string
|
||||
{
|
||||
$path = (string) parse_url($url, PHP_URL_PATH);
|
||||
if ($path === '') {
|
||||
return '';
|
||||
}
|
||||
if (preg_match('#/m/([^/]+)/#', $path, $matches)) {
|
||||
return trim($matches[1]);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Whatshub 长链 work-order-sharing?workId= 提取 workId
|
||||
*/
|
||||
private static function extractWhatshubWorkId(string $url): string
|
||||
{
|
||||
$query = (string) parse_url($url, PHP_URL_QUERY);
|
||||
if ($query === '') {
|
||||
return '';
|
||||
}
|
||||
parse_str($query, $params);
|
||||
$workId = isset($params['workId']) ? trim((string) $params['workId']) : '';
|
||||
|
||||
return $workId !== '' ? $workId : '';
|
||||
}
|
||||
|
||||
private static function resolveA2cSessionHost(string $pageUrl, string $landingUrlHint = ''): string
|
||||
{
|
||||
$candidates = [];
|
||||
|
||||
Executable → Regular
+66
-12
@@ -12,7 +12,7 @@ use app\common\service\SplitTicketSyncLogger;
|
||||
/**
|
||||
* Whatshub 云控蜘蛛
|
||||
*
|
||||
* 时序:detailByShareCode API 直抓(优先)→ 失败则 Real Browser + Turnstile + UI 翻页
|
||||
* 时序:cURL detailByShareCode 拉号码 → Node 监听 statistics 算完成量 → 失败则完整 Node 爬虫
|
||||
*/
|
||||
class WhatshubSpider extends AbstractScrmSpider
|
||||
{
|
||||
@@ -21,7 +21,7 @@ class WhatshubSpider extends AbstractScrmSpider
|
||||
|
||||
/** 详情/统计 API 路径 */
|
||||
private const API_DETAILS = '/api/whatshub-counter/workShare/open/statistics';
|
||||
|
||||
|
||||
/** shareCode + password 直连接口(一次返回全量号码,无需翻页) */
|
||||
private const API_DETAIL_BY_SHARE_CODE = '/api/whatshub-counter/workShare/open/detailByShareCode';
|
||||
|
||||
@@ -52,25 +52,81 @@ class WhatshubSpider extends AbstractScrmSpider
|
||||
}
|
||||
|
||||
/**
|
||||
* API 优先:detailByShareCode 直抓;失败则回退 Node 爬虫
|
||||
* 混合同步:cURL 拉号码 + Node 监听 statistics 算完成量;任一失败则完整 Node fallback
|
||||
*/
|
||||
public function run(): UnifiedScrmData
|
||||
{
|
||||
$apiResult = $this->tryFetchViaShareCodeApi();
|
||||
if ($apiResult instanceof UnifiedScrmData) {
|
||||
SplitTicketSyncLogger::log('spider', 'whatshub api path ok', [
|
||||
'count' => count($apiResult->numbers),
|
||||
$statsCount = $this->tryFetchStatisticsViaNode();
|
||||
|
||||
if ($apiResult instanceof UnifiedScrmData && $statsCount !== null) {
|
||||
$apiResult->todayNewCount = $statsCount;
|
||||
SplitTicketSyncLogger::log('spider', 'whatshub hybrid path ok', [
|
||||
'numberCount' => count($apiResult->numbers),
|
||||
'todayNewCount' => $statsCount,
|
||||
]);
|
||||
return $apiResult;
|
||||
}
|
||||
|
||||
SplitTicketSyncLogger::log('spider', 'whatshub api skipped or failed, fallback node', [
|
||||
'pageUrl' => $this->pageUrl,
|
||||
SplitTicketSyncLogger::log('spider', 'whatshub hybrid incomplete, fallback full node', [
|
||||
'pageUrl' => $this->pageUrl,
|
||||
'apiOk' => $apiResult instanceof UnifiedScrmData,
|
||||
'statisticsOk' => $statsCount !== null,
|
||||
]);
|
||||
|
||||
return parent::run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Node 监听 statistics API,返回 dayNewFans + reDayNewFans 总和;失败返回 null
|
||||
*/
|
||||
private function tryFetchStatisticsViaNode(): ?int
|
||||
{
|
||||
$config = $this->getSpiderConfig();
|
||||
$antiBot = $config['antiBot'] ?? null;
|
||||
|
||||
SplitTicketSyncLogger::log('spider', 'whatshub stats node request', [
|
||||
'pageUrl' => $this->pageUrl,
|
||||
'api' => self::API_DETAILS,
|
||||
]);
|
||||
|
||||
$result = $this->requestNode('/api/auth-and-intercept', [
|
||||
'pageUrl' => $config['pageUrl'],
|
||||
'apiUrls' => [self::API_DETAILS],
|
||||
'authActions' => $config['authActions'] ?? [],
|
||||
'antiBot' => $antiBot,
|
||||
], $this->resolveAuthInterceptTimeout(is_array($antiBot) ? $antiBot : null));
|
||||
|
||||
if (empty($result['success'])) {
|
||||
SplitTicketSyncLogger::log('spider', 'whatshub stats node failed', [
|
||||
'error' => $result['error'] ?? '未知',
|
||||
'code' => $result['code'] ?? null,
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$intercepted = $result['interceptedApis'] ?? [];
|
||||
if (!isset($intercepted[self::API_DETAILS]['data']) || !is_array($intercepted[self::API_DETAILS]['data'])) {
|
||||
SplitTicketSyncLogger::log('spider', 'whatshub stats node missing payload', [
|
||||
'intercepted' => array_keys(is_array($intercepted) ? $intercepted : []),
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$detailData = $intercepted[self::API_DETAILS]['data'];
|
||||
$dayNewFans = (int) ($detailData['data']['dayNewFans'] ?? 0);
|
||||
$reDayNewFans = (int) ($detailData['data']['reDayNewFans'] ?? 0);
|
||||
$total = $dayNewFans + $reDayNewFans;
|
||||
|
||||
SplitTicketSyncLogger::log('spider', 'whatshub stats node ok', [
|
||||
'dayNewFans' => $dayNewFans,
|
||||
'reDayNewFans' => $reDayNewFans,
|
||||
'total' => $total,
|
||||
]);
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从短链 pageUrl 解析 shareCode,例如 /m/KMYBBInp2066/1 → KMYBBInp2066
|
||||
*/
|
||||
@@ -202,7 +258,6 @@ class WhatshubSpider extends AbstractScrmSpider
|
||||
private function parseShareCodeApiResponse(array $payload): UnifiedScrmData
|
||||
{
|
||||
$unifiedData = new UnifiedScrmData();
|
||||
$todayNewSum = 0;
|
||||
|
||||
foreach ($payload['data'] as $item) {
|
||||
if (!is_array($item) || empty($item['account'])) {
|
||||
@@ -213,11 +268,10 @@ class WhatshubSpider extends AbstractScrmSpider
|
||||
$isOnline = isset($item['isOnline']) && (int) $item['isOnline'] === 1;
|
||||
$dayNewFans = (int) ($item['dayNewFans'] ?? 0);
|
||||
|
||||
// 号码同步仅使用 dayNewFans,不含 reDayNewFans;完成量由 Node statistics 提供
|
||||
$unifiedData->addNumber($number, $isOnline, $dayNewFans);
|
||||
$todayNewSum += $dayNewFans;
|
||||
}
|
||||
|
||||
$unifiedData->todayNewCount = $todayNewSum;
|
||||
$unifiedData->total = count($unifiedData->numbers);
|
||||
|
||||
return $unifiedData;
|
||||
@@ -279,7 +333,7 @@ class WhatshubSpider extends AbstractScrmSpider
|
||||
$unifiedData = $this->unifiedData;
|
||||
|
||||
if (is_array($detailData)) {
|
||||
$unifiedData->todayNewCount = (int) ($detailData['data']['dayNewFans'] ?? 0);
|
||||
$unifiedData->todayNewCount = (int) ($detailData['data']['dayNewFans'] ?? 0) + (int) ($detailData['data']['reDayNewFans'] ?? 0);
|
||||
}
|
||||
|
||||
foreach ($allListPagesData as $pageRaw) {
|
||||
|
||||
Reference in New Issue
Block a user