Files
links/application/common/library/scrm/AntiBotConfigBuilder.php
T
2026-07-02 03:13:27 +08:00

243 lines
7.5 KiB
PHP
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\library\scrm;
use think\Config;
/**
* 云控蜘蛛 antiBot 配置构建器
*
* 约定:sessionKey = "{ticketType}:{host}"profile=real 当 ticket_type 在 site 配置列表中
*/
class AntiBotConfigBuilder
{
/**
* 为指定工单类型与落地页 URL 构建 Node antiBot 配置;未启用类型返回 null
*
* @param string $landingUrlHint HTTP 预解析或历史上次落地的长链(可选)
* @return array<string, mixed>|null
*/
public static function build(string $ticketType, string $pageUrl, string $landingUrlHint = ''): ?array
{
$enabledTypes = self::getEnabledTypes();
if (!in_array($ticketType, $enabledTypes, true)) {
return null;
}
$sessionScope = self::resolveSessionScope($ticketType, $pageUrl, $landingUrlHint);
if ($sessionScope === '') {
return null;
}
$sessionKey = $ticketType . ':' . $sessionScope;
$ttlMinutes = self::getSessionTtlMinutes();
$businessHostHint = self::resolveBusinessHostHint($ticketType, $pageUrl, $landingUrlHint);
return [
'enabled' => true,
'profile' => 'real',
'turnstile' => self::defaultTurnstile($ticketType),
'solverFallback' => self::defaultSolverFallback($ticketType),
'cfClearanceRequired' => self::defaultCfClearanceRequired($ticketType),
'sessionKey' => $sessionKey,
'challengeTimeoutMs' => 60000,
'sessionTtlMinutes' => $ttlMinutes,
'businessHostHint' => $businessHostHint,
'landingUrlHint' => $landingUrlHint !== '' ? $landingUrlHint : '',
] + self::postCfReadyExtras($ticketType);
}
/**
* 会话 scope:A2C 短链固定业务域;火箭长链用动态 host、短链用 share.{token}
*/
public static function resolveSessionScope(string $ticketType, string $pageUrl, string $landingUrlHint = ''): string
{
if ($ticketType === 'a2c') {
return self::resolveA2cSessionHost($pageUrl, $landingUrlHint);
}
if ($ticketType === 'huojian') {
return self::resolveHuojianSessionScope($pageUrl, $landingUrlHint);
}
$host = strtolower(trim((string) parse_url($pageUrl, PHP_URL_HOST)));
return $host !== '' ? $host : '';
}
/**
* @deprecated 兼容旧调用,等价 resolveSessionScope
*/
public static function resolveSessionHost(string $ticketType, string $pageUrl): string
{
return self::resolveSessionScope($ticketType, $pageUrl, '');
}
/**
* 解析 sessionKey(供 cron 分组调度使用)
*/
public static function resolveSessionKey(string $ticketType, string $pageUrl, string $landingUrlHint = ''): string
{
$scope = self::resolveSessionScope($ticketType, $pageUrl, $landingUrlHint);
return $ticketType . ':' . ($scope !== '' ? $scope : 'unknown');
}
/**
* 是否对该工单类型启用 antiBot
*/
public static function isEnabledForType(string $ticketType): bool
{
return in_array($ticketType, self::getEnabledTypes(), true);
}
/**
* @return list<string>
*/
public static function getEnabledTypes(): array
{
$raw = (string) Config::get('site.split_scrm_antibot_types');
if ($raw === '') {
return ['whatshub', 'chatknow'];
}
$parts = array_map('trim', explode(',', $raw));
return array_values(array_filter($parts, static function (string $v): bool {
return $v !== '';
}));
}
public static function getSessionTtlMinutes(): int
{
$minutes = (int) Config::get('site.split_scrm_session_ttl_minutes');
return $minutes > 0 ? $minutes : 120;
}
/**
* 按工单类型附加 CF 过盾后的业务页就绪等待
*
* @return array<string, mixed>
*/
private static function postCfReadyExtras(string $ticketType): array
{
if ($ticketType === 'whatshub') {
return [
'postCfReadyUrlContains' => 'work-order-sharing',
'postCfReadySelector' => '.el-input__inner',
'postCfReadyTimeoutMs' => 30000,
'postCfSettleMs' => 500,
'postCfSpaWaitMs' => 4000,
];
}
if ($ticketType === 'a2c') {
return [
'postCfReadyUrlContains' => '/visitors/counter/share',
'postCfReadySelector' => '.btn-next',
'postCfReadyTimeoutMs' => 30000,
'postCfSettleMs' => 500,
'postCfSpaWaitMs' => 4000,
];
}
if ($ticketType === 'huojian') {
return [
'postCfReadyUrlContains' => HuojianUrlHelper::BUSINESS_PATH_NEEDLE,
'postCfReadySelector' => '.el-message-box__input',
'postCfReadyTimeoutMs' => 45000,
'postCfSettleMs' => 500,
'postCfSpaWaitMs' => 8000,
];
}
return [];
}
private static function defaultTurnstile(string $ticketType): bool
{
return true;
}
private static function defaultSolverFallback(string $ticketType): bool
{
if ($ticketType === 'a2c') {
return false;
}
return true;
}
private static function defaultCfClearanceRequired(string $ticketType): bool
{
if ($ticketType === 'a2c') {
return false;
}
return true;
}
/**
* 火箭/A2C 业务域 hint:优先长链动态 host,其次 landing 预解析
*/
private static function resolveBusinessHostHint(string $ticketType, string $pageUrl, string $landingUrlHint): string
{
if ($ticketType === 'a2c') {
return self::resolveA2cSessionHost($pageUrl, $landingUrlHint);
}
if ($ticketType === 'huojian') {
$host = HuojianUrlHelper::extractBusinessHost($pageUrl);
if ($host !== '') {
return $host;
}
if ($landingUrlHint !== '') {
$hintHost = HuojianUrlHelper::extractBusinessHost($landingUrlHint);
if ($hintHost !== '') {
return $hintHost;
}
}
return '';
}
return '';
}
private static function resolveA2cSessionHost(string $pageUrl, string $landingUrlHint = ''): string
{
$candidates = [];
if ($landingUrlHint !== '') {
$candidates[] = (string) parse_url($landingUrlHint, PHP_URL_HOST);
}
$candidates[] = (string) parse_url($pageUrl, PHP_URL_HOST);
foreach ($candidates as $host) {
$host = strtolower(trim($host));
if ($host !== '' && strpos($host, 'a2c.chat') !== false) {
return $host;
}
}
return 'user.a2c.chat';
}
private static function resolveHuojianSessionScope(string $pageUrl, string $landingUrlHint = ''): string
{
$scope = HuojianUrlHelper::resolveSessionScope($pageUrl);
if ($scope !== '') {
return $scope;
}
if ($landingUrlHint !== '') {
$fromLanding = HuojianUrlHelper::resolveSessionScope($landingUrlHint);
if ($fromLanding !== '') {
return $fromLanding;
}
}
return '';
}
}