Files
FeedLite/lib/captcha.php
T

131 lines
5.3 KiB
PHP
Raw Normal View History

<?php
/**
* 人机验证:趣味问答
* ------------------------------------------------------------------
* 无需外部服务、无需图片库。每次生成一道随机小题(算术为主,
* 混入日历 / 生活常识题),答案存放在 Session 中,一次性使用。
*/
if (!defined('FB_ROOT')) { http_response_code(403); exit('forbidden'); }
/** 静态趣味题库:[题目, 可接受的答案(数组,忽略大小写与标点)] */
function fb_captcha_bank() {
return [
['一年有几个月?', ['12', '十二']],
['闰年的二月有多少天?', ['29', '二十九']],
['一星期有几天?', ['7', '七']],
['一年有几个季节?', ['4', '四']],
['一个季度有几个月?', ['3', '三']],
['时针从 3 点走到 9 点,一共走了几个小时?', ['6', '六']],
['钟表从 12 点顺时针走 3 个小时,是几点?', ['3', '三点', '3点']],
['把 「789」 倒过来念,是多少?', ['987']],
['「轻反馈」这几个字,一共几个字?', ['3', '三']],
['汉字「五」表示的数字是几?', ['5', '五']],
['2、4、6、8、10 之后的下一个数是多少?', ['12', '十二']],
['从 10 倒着数,10 之后是几?', ['9', '九']],
['「日月」两个字里,有几个「日」字?', ['1', '一']],
['把 3 个 6 相加,结果是多少?', ['18', '十八']],
['100 减去 100 等于多少?', ['0', '零']],
['星期三之后是星期几?', ['4', '四', '星期四', '周四']],
['把 4 个苹果平均分给 2 个人,每人几个?', ['2', '二', '两']],
['「日历」这个词的第一个字是什么?', ['日']],
];
}
/** 生成一道动态算术题 */
function fb_captcha_arith() {
$op = random_int(1, 4);
if ($op === 1) {
$a = random_int(3, 30); $b = random_int(2, 30);
return ['题目: ' . $a . ' 加 ' . $b . ' 等于多少?', [(string)($a + $b)]];
}
if ($op === 2) {
$a = random_int(10, 40); $b = random_int(1, 9);
if ($b > $a) { $t = $a; $a = $b; $b = $t; }
return ['题目: ' . $a . ' 减 ' . $b . ' 等于多少?', [(string)($a - $b)]];
}
if ($op === 3) {
$a = random_int(2, 9); $b = random_int(2, 9);
return ['题目: ' . $a . ' 乘以 ' . $b . ' 等于多少?', [(string)($a * $b)]];
}
// 先乘后加
$a = random_int(1, 5); $b = random_int(2, 6); $c = random_int(2, 6);
return ['题目: ' . $a . ' 加 ' . $b . ' 乘 ' . $c . ' 等于多少?(先乘后加)', [(string)($a + $b * $c)]];
}
/** 归一化答案:全角转半角、去空白与常见标点、转小写 */
function fb_captcha_norm($s) {
$s = (string)$s;
if (function_exists('mb_convert_kana')) {
$s = mb_convert_kana($s, 'as', 'UTF-8'); // 全角字母/数字/空格 -> 半角
}
$s = str_replace(
['?', '?', '。', '.', '.', ',', ',', '!', '!', '、', ':', ':', ';', ';', '"', "'", '“', '”', '‘', '’', ' ', ' ', "\r", "\n", "\t"],
'',
$s
);
if (function_exists('mb_strtolower')) {
$s = mb_strtolower($s, 'UTF-8');
} else {
$s = strtolower($s);
}
return $s;
}
/** 生成新的验证题,写入 Session,返回题面文本 */
function fb_captcha_new() {
if (random_int(1, 100) <= 65) {
list($q, $answers) = fb_captcha_arith();
} else {
$bank = fb_captcha_bank();
list($q, $answers) = $bank[array_rand($bank)];
}
$_SESSION['fb_cap'] = [
'q' => $q,
'answers'=> $answers,
'ts' => time(),
'tries' => 0,
];
return $q;
}
/** 取当前题目(不存在则新建) */
function fb_captcha_question() {
if (empty($_SESSION['fb_cap']['q']) || !empty($_SESSION['fb_cap']['used'])) {
return fb_captcha_new();
}
// 超过 30 分钟重新出题
if (time() - (int)$_SESSION['fb_cap']['ts'] > 1800) {
return fb_captcha_new();
}
return (string)$_SESSION['fb_cap']['q'];
}
/** 校验用户答案;一次性使用,答错累计,超过 5 次作废需换题 */
function fb_captcha_check($input) {
if (empty($_SESSION['fb_cap']['answers'])) { return false; }
$cap = $_SESSION['fb_cap'];
if (time() - (int)$cap['ts'] > 1800) {
unset($_SESSION['fb_cap']);
return false;
}
$n = fb_captcha_norm($input);
$ok = false;
if ($n !== '') {
foreach ((array)$cap['answers'] as $a) {
$an = fb_captcha_norm($a);
if ($an === '') { continue; }
if ($n === $an) { $ok = true; break; }
if (is_numeric($n) && is_numeric($an) && (float)$n === (float)$an) { $ok = true; break; }
}
}
$_SESSION['fb_cap']['tries'] = (int)$cap['tries'] + 1;
if ($ok) {
unset($_SESSION['fb_cap']); // 用后即焚
return true;
}
if ($_SESSION['fb_cap']['tries'] >= 5) {
unset($_SESSION['fb_cap']); // 作废,下次自动换题
}
return false;
}