Files
laoyang cede838b5e FeedLite 初始提交:通用轻量级反馈系统(PHP + SQLite,零依赖)
- 前台:问题列表 / 详情 / 发布与回复(趣味问答验证码、蜜罐、IP 限流)
- 后台:先审后显、站点设置(站点标题/描述、附件存储、OSS 参数)、操作日志
- 附件:关闭 / 本机硬盘 / 阿里云 OSS 浏览器直传(ECS 实例 RAM 角色)三种模式
- 品牌资源:全套图标(favicon.ico / SVG / PNG 各尺寸 / manifest)
- 安全:CSRF、session 加固、.htaccess 屏蔽敏感文件、口令仅存哈希
- 配置:config.example.php 为模板,config.php 不入库
2026-09-28 20:34:37 +08:00

131 lines
5.3 KiB
PHP
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
/**
* 人机验证:趣味问答
* ------------------------------------------------------------------
* 无需外部服务、无需图片库。每次生成一道随机小题(算术为主,
* 混入日历 / 生活常识题),答案存放在 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;
}