273 lines
8.0 KiB
PHP
273 lines
8.0 KiB
PHP
|
|
<?php
|
|||
|
|
require_once __DIR__ . '/ClientIpResolver.php';
|
|||
|
|
require_once __DIR__ . '/GeoIpCountryCache.php';
|
|||
|
|
require_once __DIR__ . '/GeoIpCountryResolver.php';
|
|||
|
|
require_once __DIR__ . '/FpUrlHelper.php';
|
|||
|
|
require_once __DIR__ . '/RiskSecondaryGate.php';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 客户端 Session(PHPSESSID)统一判定缓存:byApi 阶段缓存 + byApiRisk 最终缓存。
|
|||
|
|
*/
|
|||
|
|
class CloakJudgmentCache
|
|||
|
|
{
|
|||
|
|
const SESSION_KEY = 'cloak_judgment_cache';
|
|||
|
|
const STAGE_BYAPI = 'byapi';
|
|||
|
|
const STAGE_FINAL = 'final';
|
|||
|
|
|
|||
|
|
public static function hasSession()
|
|||
|
|
{
|
|||
|
|
return !empty($_SESSION[self::SESSION_KEY])
|
|||
|
|
&& is_array($_SESSION[self::SESSION_KEY]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @param string|null $currentIp
|
|||
|
|
* @return array|null
|
|||
|
|
*/
|
|||
|
|
public static function get($currentIp = null)
|
|||
|
|
{
|
|||
|
|
if (!self::hasSession()) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$cached = $_SESSION[self::SESSION_KEY];
|
|||
|
|
if (!self::isConfigValid($cached)) {
|
|||
|
|
self::clear();
|
|||
|
|
$_SESSION['visit_to_3'] = '1';
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$ip = ClientIpResolver::normalizeIp(
|
|||
|
|
$currentIp !== null && $currentIp !== '' ? $currentIp : ClientIpResolver::resolve()
|
|||
|
|
);
|
|||
|
|
$cachedIp = ClientIpResolver::normalizeIp($cached['ip'] ?? '');
|
|||
|
|
|
|||
|
|
if ($ip !== null && $cachedIp !== null && $ip !== $cachedIp) {
|
|||
|
|
self::refreshCountryForCurrentIp($ip);
|
|||
|
|
$cached = $_SESSION[self::SESSION_KEY];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $cached;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 二次风控关闭(CLOAK_RISK_NUMBER=0 等)时,byApi 阶段缓存即视为最终缓存。
|
|||
|
|
*
|
|||
|
|
* @param array $cached
|
|||
|
|
*/
|
|||
|
|
public static function isEffectivelyFinal(array $cached)
|
|||
|
|
{
|
|||
|
|
$stage = (string) ($cached['stage'] ?? '');
|
|||
|
|
if ($stage === self::STAGE_FINAL) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
if ($stage === self::STAGE_BYAPI && !RiskSecondaryGate::isEnabled()) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 仅返回最终缓存(可短路全流程)。
|
|||
|
|
*
|
|||
|
|
* @param string|null $currentIp
|
|||
|
|
* @return array|null
|
|||
|
|
*/
|
|||
|
|
public static function getFinal($currentIp = null)
|
|||
|
|
{
|
|||
|
|
$cached = self::get($currentIp);
|
|||
|
|
if ($cached === null || !self::isEffectivelyFinal($cached)) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
return $cached;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @param array|null $cached
|
|||
|
|
*/
|
|||
|
|
public static function isFinal($cached = null)
|
|||
|
|
{
|
|||
|
|
if ($cached === null) {
|
|||
|
|
$cached = self::get();
|
|||
|
|
}
|
|||
|
|
return is_array($cached) && self::isEffectivelyFinal($cached);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @param array|null $cached
|
|||
|
|
*/
|
|||
|
|
public static function isPartial($cached = null)
|
|||
|
|
{
|
|||
|
|
if ($cached === null) {
|
|||
|
|
$cached = self::get();
|
|||
|
|
}
|
|||
|
|
if (!is_array($cached) || ($cached['stage'] ?? '') !== self::STAGE_BYAPI) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
return RiskSecondaryGate::isEnabled();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* byApi 完成后写入;按风险系数决定 stage。
|
|||
|
|
* CLOAK_RISK_NUMBER=0(或未启用二次风控)时,byApi 结果直接记为 final。
|
|||
|
|
*
|
|||
|
|
* @param array{result:string,reason?:string,ip?:string,country?:string,fp_url?:string} $payload
|
|||
|
|
*/
|
|||
|
|
public static function storeFromByApi(array $payload)
|
|||
|
|
{
|
|||
|
|
$result = ($payload['result'] ?? '') === 'true' ? 'true' : 'false';
|
|||
|
|
$stage = self::STAGE_FINAL;
|
|||
|
|
if ($result === 'true' && RiskSecondaryGate::isEnabled()) {
|
|||
|
|
$stage = self::STAGE_BYAPI;
|
|||
|
|
}
|
|||
|
|
self::writeCache($stage, $payload);
|
|||
|
|
self::applyToSession($_SESSION[self::SESSION_KEY]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* byApiRisk 完成后写入最终缓存。
|
|||
|
|
*
|
|||
|
|
* @param array{result:string,reason?:string,fp_url?:string,ip?:string,country?:string} $payload
|
|||
|
|
*/
|
|||
|
|
public static function storeFromByApiRisk(array $payload)
|
|||
|
|
{
|
|||
|
|
self::writeCache(self::STAGE_FINAL, $payload);
|
|||
|
|
self::applyToSession($_SESSION[self::SESSION_KEY]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @param array $cached
|
|||
|
|
*/
|
|||
|
|
public static function applyToSession(array $cached)
|
|||
|
|
{
|
|||
|
|
$result = ($cached['result'] ?? '') === 'true' ? 'true' : 'false';
|
|||
|
|
$_SESSION['check_result'] = $result;
|
|||
|
|
$_SESSION['visit_to_2'] = '2';
|
|||
|
|
|
|||
|
|
if (self::isEffectivelyFinal($cached)) {
|
|||
|
|
$_SESSION['visit_to_3'] = '3';
|
|||
|
|
} else {
|
|||
|
|
$_SESSION['visit_to_3'] = '1';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!empty($cached['ip'])) {
|
|||
|
|
$_SESSION['gcu_ip'] = $cached['ip'];
|
|||
|
|
}
|
|||
|
|
if (array_key_exists('country', $cached)) {
|
|||
|
|
$_SESSION['gcu_country'] = (string) $cached['country'];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @param object $v_info visitorInfo
|
|||
|
|
* @param array $cached
|
|||
|
|
*/
|
|||
|
|
public static function applyToVisitorInfo($v_info, array $cached)
|
|||
|
|
{
|
|||
|
|
if (!is_object($v_info)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (!empty($cached['ip'])) {
|
|||
|
|
$v_info->v_ip = $cached['ip'];
|
|||
|
|
}
|
|||
|
|
if (array_key_exists('country', $cached) && (string) $cached['country'] !== '') {
|
|||
|
|
$v_info->v_country = (string) $cached['country'];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static function clear()
|
|||
|
|
{
|
|||
|
|
unset($_SESSION[self::SESSION_KEY]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static function hitReason()
|
|||
|
|
{
|
|||
|
|
return '缓存';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @param array $cached
|
|||
|
|
* @return array{result:bool,reason:string,link:string}
|
|||
|
|
*/
|
|||
|
|
public static function toApiResponse(array $cached)
|
|||
|
|
{
|
|||
|
|
$passed = ($cached['result'] ?? '') === 'true';
|
|||
|
|
$link = (string) ($cached['fp_url'] ?? '');
|
|||
|
|
|
|||
|
|
if ($passed && $link === '') {
|
|||
|
|
$link = FpUrlHelper::fallbackUrl();
|
|||
|
|
}
|
|||
|
|
if (!$passed && $link === '' && defined('DB_ZP')) {
|
|||
|
|
$link = (string) DB_ZP;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return [
|
|||
|
|
'result' => $passed,
|
|||
|
|
'reason' => self::hitReason(),
|
|||
|
|
'link' => $link,
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @param string $stage
|
|||
|
|
* @param array $payload
|
|||
|
|
*/
|
|||
|
|
private static function writeCache($stage, array $payload)
|
|||
|
|
{
|
|||
|
|
$ip = ClientIpResolver::normalizeIp($payload['ip'] ?? ClientIpResolver::resolve());
|
|||
|
|
if ($ip === null) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$country = (string) ($payload['country'] ?? '');
|
|||
|
|
if ($country === '' && !empty($_SESSION['gcu_country'])) {
|
|||
|
|
$country = (string) $_SESSION['gcu_country'];
|
|||
|
|
}
|
|||
|
|
if ($country === '') {
|
|||
|
|
$country = (string) (GeoIpCountryResolver::resolve($ip) ?? '');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$_SESSION[self::SESSION_KEY] = [
|
|||
|
|
'stage' => $stage,
|
|||
|
|
'ip' => $ip,
|
|||
|
|
'country' => $country,
|
|||
|
|
'result' => ($payload['result'] ?? '') === 'true' ? 'true' : 'false',
|
|||
|
|
'reason' => (string) ($payload['reason'] ?? ''),
|
|||
|
|
'fp_url' => (string) ($payload['fp_url'] ?? ''),
|
|||
|
|
'risk_number' => defined('CLOAK_RISK_NUMBER') ? (int) CLOAK_RISK_NUMBER : 0,
|
|||
|
|
'campaign_id' => defined('COSTM_IP_SCORE') ? (string) COSTM_IP_SCORE : '',
|
|||
|
|
'cached_at' => time(),
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @param string $currentIp
|
|||
|
|
*/
|
|||
|
|
private static function refreshCountryForCurrentIp($currentIp)
|
|||
|
|
{
|
|||
|
|
if (!self::hasSession()) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
$cached = $_SESSION[self::SESSION_KEY];
|
|||
|
|
$cached['ip'] = $currentIp;
|
|||
|
|
$cached['country'] = (string) (GeoIpCountryResolver::resolve($currentIp) ?? '');
|
|||
|
|
$_SESSION[self::SESSION_KEY] = $cached;
|
|||
|
|
$_SESSION['gcu_ip'] = $currentIp;
|
|||
|
|
$_SESSION['gcu_country'] = $cached['country'];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @param array $cached
|
|||
|
|
*/
|
|||
|
|
private static function isConfigValid(array $cached)
|
|||
|
|
{
|
|||
|
|
if (defined('CLOAK_RISK_NUMBER') && (int) ($cached['risk_number'] ?? -1) !== (int) CLOAK_RISK_NUMBER) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
if (defined('COSTM_IP_SCORE') && (string) ($cached['campaign_id'] ?? '') !== (string) COSTM_IP_SCORE) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|