Files
xianyan/docs/toolsapi/application/index/controller/Queue.php
Developer bd937b02f3 php
2026-06-25 23:28:34 +08:00

214 lines
6.6 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
/**
* 排队控制器
* 创建时间: 2026-06-25
* 更新时间: 2026-06-25
* 作用: 处理排队页面的验证码验证和队列状态查询
*/
namespace app\index\controller;
use think\Config;
use think\Cookie;
use think\Request;
use think\Controller;
class Queue extends Controller
{
/**
* 检查队列状态
* GET /queue/check
*/
public function check()
{
$config = Config::get('rate_limit');
if (!$config || !is_array($config)) {
$config = [];
}
$queueFile = isset($config['queue_counter_file']) ? $config['queue_counter_file'] : RUNTIME_PATH . 'rate_limit/queue_count.txt';
$queueCount = 0;
if (file_exists($queueFile)) {
$content = @file_get_contents($queueFile);
if ($content !== false && $content !== '') {
$data = json_decode($content, true);
if (!$data || !is_array($data)) {
$data = array('count' => 0, 'updated' => 0);
}
} else {
$data = array('count' => 0, 'updated' => 0);
}
// 如果超过10秒未更新认为队列已清空
if (time() - $data['updated'] > 10) {
$queueCount = 0;
} else {
$queueCount = isset($data['count']) ? intval($data['count']) : 0;
}
}
// 计算预计等待时间每人约0.5秒)
$waitSeconds = max(1, ceil($queueCount * 0.5));
$maxQueries = isset($config['max_queries_per_second']) ? intval($config['max_queries_per_second']) : 10;
return json(array(
'code' => 1,
'msg' => 'ok',
'data' => array(
'queue_count' => $queueCount,
'wait_seconds' => $waitSeconds,
'max_queries_per_second' => $maxQueries
)
));
}
/**
* 生成验证码
* GET /queue/captcha
*/
public function captcha()
{
// 生成数学题
$num1 = rand(1, 20);
$num2 = rand(1, 20);
$answer = $num1 + $num2;
// 生成签名
$timestamp = time();
$dbConfig = Config::get('database');
$dbName = isset($dbConfig['database']) ? $dbConfig['database'] : '';
$sign = md5($answer . '_' . $timestamp . '_' . $dbName);
// 存储验证码答案到临时文件
$captchaDir = RUNTIME_PATH . 'rate_limit/';
if (!is_dir($captchaDir)) {
@mkdir($captchaDir, 0755, true);
}
// 使用 sign 本身作为文件名(不再二次 md5
$captchaFile = $captchaDir . 'captcha_' . $sign . '.txt';
@file_put_contents($captchaFile, json_encode(array(
'answer' => $answer,
'created' => $timestamp
)));
return json(array(
'code' => 1,
'msg' => 'ok',
'data' => array(
'question' => $num1 . ' + ' . $num2 . ' = ?',
'sign' => $sign,
'expire' => 300
)
));
}
/**
* 验证验证码
* POST /queue/verify
*/
public function verify()
{
$request = Request::instance();
$answer = $request->post('answer');
$sign = $request->post('sign');
if (!$answer || !$sign) {
return json(array('code' => 0, 'msg' => 'param error'));
}
// 验证签名格式
if (!preg_match('/^[a-f0-9]{32}$/', $sign)) {
return json(array('code' => 0, 'msg' => 'sign format error'));
}
// 读取验证码文件(使用 sign 本身作为文件名)
$captchaFile = RUNTIME_PATH . 'rate_limit/captcha_' . $sign . '.txt';
if (!file_exists($captchaFile)) {
return json(array('code' => 0, 'msg' => 'captcha expired'));
}
$content = @file_get_contents($captchaFile);
$data = json_decode($content, true);
if (!$data || !isset($data['answer']) || !isset($data['created'])) {
@unlink($captchaFile);
return json(array('code' => 0, 'msg' => 'captcha data error'));
}
// 检查是否过期5分钟
if (time() - $data['created'] > 300) {
@unlink($captchaFile);
return json(array('code' => 0, 'msg' => 'captcha expired'));
}
// 验证答案
if (intval($answer) !== intval($data['answer'])) {
return json(array('code' => 0, 'msg' => 'wrong answer'));
}
// 验证通过,删除验证码文件
@unlink($captchaFile);
// 设置免排队Cookie30分钟有效
$config = Config::get('rate_limit');
if (!$config || !is_array($config)) {
$config = array();
}
$cookieName = isset($config['bypass_cookie_name']) ? $config['bypass_cookie_name'] : 'queue_bypass';
$validDuration = isset($config['captcha_valid_duration']) ? intval($config['captcha_valid_duration']) : 1800;
$timestamp = time();
$dbConfig = Config::get('database');
$dbName = isset($dbConfig['database']) ? $dbConfig['database'] : '';
$cookieSign = md5($timestamp . '_' . $dbName);
$cookieValue = $timestamp . '_' . $cookieSign;
Cookie::set($cookieName, $cookieValue, $validDuration);
return json(array(
'code' => 1,
'msg' => 'ok',
'data' => array(
'bypass_duration' => $validDuration
)
));
}
/**
* 队列状态
* GET /queue/status
*/
public function status()
{
$config = Config::get('rate_limit');
if (!$config || !is_array($config)) {
$config = array();
}
$counterPath = isset($config['counter_path']) ? $config['counter_path'] : RUNTIME_PATH . 'rate_limit/';
$counterDir = rtrim($counterPath, '/');
$timeWindow = isset($config['time_window']) ? intval($config['time_window']) : 1;
$currentSecond = floor(time() / $timeWindow);
$counterFile = $counterDir . '/counter_' . $currentSecond . '.txt';
$queryCount = 0;
if (file_exists($counterFile)) {
$queryCount = intval(@file_get_contents($counterFile));
}
$maxQueries = isset($config['max_queries_per_second']) ? intval($config['max_queries_per_second']) : 10;
return json(array(
'code' => 1,
'msg' => 'ok',
'data' => array(
'current_queries' => $queryCount,
'max_queries_per_second' => $maxQueries,
'is_limited' => $queryCount >= $maxQueries
)
));
}
}