2026-06-14 14:00:24 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 访客日志队列消费(INSERT / enrich UPDATE)
|
|
|
|
|
|
*/
|
|
|
|
|
|
require_once dirname(__DIR__) . '/DbHelper.php';
|
|
|
|
|
|
require_once __DIR__ . '/VisitorLogSchema.php';
|
|
|
|
|
|
require_once __DIR__ . '/VisitorLogQueue.php';
|
|
|
|
|
|
|
|
|
|
|
|
class VisitorLogWorker
|
|
|
|
|
|
{
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @return array{processed:int, failed:int}
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function processBatch(int $limit = 100)
|
|
|
|
|
|
{
|
|
|
|
|
|
$jobs = VisitorLogQueue::popBatch($limit);
|
|
|
|
|
|
$processed = 0;
|
|
|
|
|
|
$failed = 0;
|
|
|
|
|
|
|
|
|
|
|
|
foreach ($jobs as $job) {
|
|
|
|
|
|
$ok = false;
|
|
|
|
|
|
for ($attempt = 1; $attempt <= 3; $attempt++) {
|
|
|
|
|
|
if (self::processJob($job)) {
|
|
|
|
|
|
$ok = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
usleep(100000 * $attempt);
|
|
|
|
|
|
}
|
|
|
|
|
|
if ($ok) {
|
|
|
|
|
|
$processed++;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$failed++;
|
|
|
|
|
|
VisitorLogQueue::moveToDead($job, 'process failed after retries');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return ['processed' => $processed, 'failed' => $failed];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @param array $job
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function processJob(array $job)
|
|
|
|
|
|
{
|
|
|
|
|
|
$type = $job['type'] ?? '';
|
|
|
|
|
|
if ($type === 'insert_full') {
|
|
|
|
|
|
return self::insertFull($job['logs'] ?? []) !== null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if ($type === 'enrich') {
|
|
|
|
|
|
return self::enrich((int) ($job['log_id'] ?? 0), $job['logs'] ?? []);
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @param array $logs
|
|
|
|
|
|
* @return int|null insert_id
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function insertFull(array $logs)
|
|
|
|
|
|
{
|
|
|
|
|
|
$conn = self::connect();
|
|
|
|
|
|
if (!$conn) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
VisitorLogSchema::ensureColumns($conn);
|
|
|
|
|
|
VisitorLogSchema::ensureIndexes($conn);
|
|
|
|
|
|
|
|
|
|
|
|
$data = self::escapeLogs($conn, $logs);
|
|
|
|
|
|
$d = static function ($key) use ($data, $conn) {
|
|
|
|
|
|
return array_key_exists($key, $data) ? $data[$key] : cloak_db_escape($conn, '');
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-06-15 16:53:05 +08:00
|
|
|
|
$sql = "INSERT INTO `visitor_logs`(`campagin_id`, `visit_md5_code`, `domain`, `visit_date`, `IP`, `country`, `result`, `reason`, `referer`, `client`, `browser`, `page`, `language`, `user_agent`, `http_referer`, `accept_language_raw`, `judge_timing`, `fp_url`, `device`, `api_by_request`, `api_by_response`, `api_risk_request`, `api_risk_response`) VALUES ('"
|
2026-06-14 14:00:24 +08:00
|
|
|
|
. $d('campagin_id') . "','"
|
|
|
|
|
|
. $d('visit_md5_code') . "','"
|
|
|
|
|
|
. $d('site_name') . "','"
|
|
|
|
|
|
. ($data['visit_date'] ?? date('Y-m-d H:i:s')) . "','"
|
|
|
|
|
|
. $d('customers_ip') . "','"
|
|
|
|
|
|
. $d('country') . "','"
|
|
|
|
|
|
. $d('result') . "','"
|
|
|
|
|
|
. $d('reason') . "','"
|
|
|
|
|
|
. $d('v_referer') . "','"
|
|
|
|
|
|
. $d('Client') . "','"
|
|
|
|
|
|
. $d('v_Browser') . "','"
|
|
|
|
|
|
. $d('v_PageURL') . "','"
|
|
|
|
|
|
. $d('accept_language') . "','"
|
|
|
|
|
|
. $d('user_agent') . "','"
|
|
|
|
|
|
. $d('http_referer') . "','"
|
|
|
|
|
|
. $d('accept_language_raw') . "','"
|
|
|
|
|
|
. $d('judge_timing') . "','"
|
|
|
|
|
|
. $d('fp_url') . "','"
|
2026-06-15 16:53:05 +08:00
|
|
|
|
. $d('device') . "','"
|
|
|
|
|
|
. $d('api_by_request') . "','"
|
|
|
|
|
|
. $d('api_by_response') . "','"
|
|
|
|
|
|
. $d('api_risk_request') . "','"
|
|
|
|
|
|
. $d('api_risk_response') . "')";
|
2026-06-14 14:00:24 +08:00
|
|
|
|
|
|
|
|
|
|
if ($conn->query($sql) !== true) {
|
|
|
|
|
|
VisitorLogQueue::logError('insert_full: ' . $conn->error . ' | ' . $sql);
|
|
|
|
|
|
$conn->close();
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
$id = (int) $conn->insert_id;
|
|
|
|
|
|
$conn->close();
|
|
|
|
|
|
return $id > 0 ? $id : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 同步最小 INSERT(二次风控 wait,需立即返回 log_id)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param array $logs
|
|
|
|
|
|
* @return int|null
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function insertMinimal(array $logs)
|
|
|
|
|
|
{
|
|
|
|
|
|
$conn = self::connect();
|
|
|
|
|
|
if (!$conn) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
VisitorLogSchema::ensureColumns($conn);
|
|
|
|
|
|
VisitorLogSchema::ensureIndexes($conn);
|
|
|
|
|
|
|
|
|
|
|
|
$data = self::escapeLogs($conn, $logs);
|
|
|
|
|
|
$d = static function ($key) use ($data, $conn) {
|
|
|
|
|
|
return array_key_exists($key, $data) ? $data[$key] : cloak_db_escape($conn, '');
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
$sql = "INSERT INTO `visitor_logs`(`campagin_id`, `visit_md5_code`, `domain`, `visit_date`, `IP`, `country`, `result`, `reason`, `client`, `browser`, `language`, `fp_url`, `device`) VALUES ('"
|
|
|
|
|
|
. $d('campagin_id') . "','"
|
|
|
|
|
|
. $d('visit_md5_code') . "','"
|
|
|
|
|
|
. $d('site_name') . "','"
|
|
|
|
|
|
. date('Y-m-d H:i:s') . "','"
|
|
|
|
|
|
. $d('customers_ip') . "','"
|
|
|
|
|
|
. $d('country') . "','"
|
|
|
|
|
|
. $d('result') . "','"
|
|
|
|
|
|
. $d('reason') . "','"
|
|
|
|
|
|
. $d('Client') . "','"
|
|
|
|
|
|
. $d('v_Browser') . "','"
|
|
|
|
|
|
. $d('accept_language') . "','"
|
|
|
|
|
|
. $d('fp_url') . "','"
|
|
|
|
|
|
. $d('device') . "')";
|
|
|
|
|
|
|
|
|
|
|
|
if ($conn->query($sql) !== true) {
|
|
|
|
|
|
VisitorLogQueue::logError('insertMinimal: ' . $conn->error);
|
|
|
|
|
|
$conn->close();
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
$id = (int) $conn->insert_id;
|
|
|
|
|
|
$conn->close();
|
|
|
|
|
|
return $id > 0 ? $id : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @param int $logId
|
|
|
|
|
|
* @param array $logs
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function enrich($logId, array $logs)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ($logId <= 0) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
$conn = self::connect();
|
|
|
|
|
|
if (!$conn) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$data = self::escapeLogs($conn, $logs);
|
|
|
|
|
|
$sets = [];
|
|
|
|
|
|
$map = [
|
|
|
|
|
|
'referer' => 'v_referer',
|
|
|
|
|
|
'page' => 'v_PageURL',
|
|
|
|
|
|
'user_agent' => 'user_agent',
|
|
|
|
|
|
'http_referer' => 'http_referer',
|
|
|
|
|
|
'accept_language_raw' => 'accept_language_raw',
|
|
|
|
|
|
'judge_timing' => 'judge_timing',
|
2026-06-15 16:53:05 +08:00
|
|
|
|
'api_by_request' => 'api_by_request',
|
|
|
|
|
|
'api_by_response' => 'api_by_response',
|
|
|
|
|
|
'api_risk_request' => 'api_risk_request',
|
|
|
|
|
|
'api_risk_response' => 'api_risk_response',
|
2026-06-14 14:00:24 +08:00
|
|
|
|
];
|
|
|
|
|
|
foreach ($map as $col => $logKey) {
|
|
|
|
|
|
if (array_key_exists($logKey, $data)) {
|
|
|
|
|
|
$sets[] = "`{$col}`='" . $data[$logKey] . "'";
|
|
|
|
|
|
} elseif (array_key_exists($col, $data)) {
|
|
|
|
|
|
$sets[] = "`{$col}`='" . $data[$col] . "'";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if ($sets === []) {
|
|
|
|
|
|
$conn->close();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$sql = 'UPDATE `visitor_logs` SET ' . implode(', ', $sets) . ' WHERE `id`=' . (int) $logId;
|
|
|
|
|
|
$ok = $conn->query($sql) === true;
|
|
|
|
|
|
if (!$ok) {
|
|
|
|
|
|
VisitorLogQueue::logError('enrich: ' . $conn->error);
|
|
|
|
|
|
}
|
|
|
|
|
|
$conn->close();
|
|
|
|
|
|
return $ok;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @param mysqli $conn
|
|
|
|
|
|
* @param array $logs
|
|
|
|
|
|
* @return array<string, string>
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static function escapeLogs($conn, array $logs)
|
|
|
|
|
|
{
|
|
|
|
|
|
$data = [];
|
|
|
|
|
|
foreach ($logs as $key => $value) {
|
|
|
|
|
|
$data[$key] = cloak_db_escape($conn, $value);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!isset($data['visit_date'])) {
|
|
|
|
|
|
$data['visit_date'] = date('Y-m-d H:i:s');
|
|
|
|
|
|
}
|
|
|
|
|
|
return $data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @return mysqli|null
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static function connect()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!defined('DB_USERNAME') || !defined('DB_PASSWORD') || !defined('DB_NAME')) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
$conn = @new mysqli('localhost', DB_USERNAME, DB_PASSWORD, DB_NAME);
|
|
|
|
|
|
if ($conn->connect_error) {
|
|
|
|
|
|
VisitorLogQueue::logError('DB connect: ' . $conn->connect_error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
return $conn;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|