租号玩代理申请
A- A+

WordPress无需插件 添加 评论区数学计算题 验证码的 新手详细教程

2026年06月24日 建站相关 评论 2 条 阅读 106 次 打印

最近,我把评论区放出来了,为了防止恶意评论,我想添加个验证码,不想用插件,仅靠代码实现。

经过一番搜索,代码如下:
修改模版里的functions.php,添加以下代码:

/**
 * 1-9 加减乘除算术验证码 - 输出HTML
 */
function math_captcha_show()
{
    if (current_user_can('edit_posts')) return;

    $ops = ['+', '-', '*', '/'];
    $op = $ops[rand(0, 3)];
    $num1 = rand(1, 9);
    $num2 = rand(1, 9);
    $result = 0;

    // 运算规则
    switch ($op) {
        case '-':
            if ($num1 < $num2) list($num1, $num2) = [$num2, $num1];
            $result = $num1 - $num2;
            break;
        case '/':
            while ($num2 === 0 || $num1 % $num2 !== 0) {
                $num1 = rand(1, 9);
                $num2 = rand(1, 9);
            }
            $result = (int)($num1 / $num2);
            break;
        case '*':
            $result = $num1 * $num2;
            break;
        default:
            $result = $num1 + $num2;
            break;
    }

    // 生成唯一令牌,有效期5分钟
    $token = md5(uniqid(mt_rand(), true));
    set_transient('math_captcha_' . $token, $result, 300);

    // 输出验证码HTML
    echo '<div class="math-captcha-wrap">';
    echo esc_html($num1) . ' ' . esc_html($op) . ' ' . esc_html($num2) . ' = ';
    echo '<input type="text" name="math_captcha" class="math-captcha-input" autocomplete="off" required>';
    echo '<input type="hidden" name="captcha_token" value="' . esc_attr($token) . '">';
    echo '</div>';
}

/**
 * 评论验证码校验 - 放在函数外部(正确写法)
 * 优先级10,在中文校验(5)之后执行
 */
add_filter('preprocess_comment', function ($commentdata) {
    if (current_user_can('edit_posts')) return $commentdata;

    $input = isset($_POST['math_captcha']) ? trim($_POST['math_captcha']) : '';
    $token = isset($_POST['captcha_token']) ? sanitize_text_field($_POST['captcha_token']) : '';

    if (empty($input) || empty($token)) {
        wp_die('Please fill in the calculation result.', 'Failed', ['response' => 403]);
    }

    $right_ans = get_transient('math_captcha_' . $token);
    if ($right_ans === false) {
        wp_die('Verification expired, please refresh page.', 'Failed', ['response' => 403]);
    }

    if (intval($input) !== intval($right_ans)) {
        wp_die('Wrong answer, please try again.', 'Failed', ['response' => 403]);
    }

    delete_transient('math_captcha_' . $token);
    return $commentdata;
}, 10);

2026.08.01更新:我觉得这些验证码5分钟写入一个,文章有访问就生成一个,搞的wp_options越来越臃肿,我想了下,还是单独建个表放验证码吧,可以随便删除。
第一步,先用phpmyadmin登录你的数据库,创建一个单独的表,比如yanzhengma,用phpmyadmin里的SQL语句创建就行了。

CREATE TABLE `yanzhengma` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `captcha_key` varchar(64) NOT NULL COMMENT '验证码唯一标识token',
  `answer` int(11) NOT NULL COMMENT '算术验证码正确答案',
  `expire` int(10) unsigned NOT NULL COMMENT '过期时间戳',
  `createtime` int(10) unsigned NOT NULL COMMENT '创建时间戳',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_captcha_key` (`captcha_key`),
  KEY `idx_expire` (`expire`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='算术验证码独立存储表';

字段说明:
token:前端隐藏域传递的唯一标识,唯一索引快速查询
answer:算式正确结果
expire:时间戳,5 分钟有效期
used:验证成功标记为已使用,杜绝同一个 token 反复刷评论
idx_expire索引:定时清理过期数据更快

下面的代码放入你的functions.php里就行了,就替换上面的代码:

// 算术验证码【独立数据表版本】
if (!defined('MATH_CAPTCHA_TABLE')) {
    define('MATH_CAPTCHA_TABLE', 'yanzhengma');
}

function math_captcha_show()
{
    if (current_user_can('edit_posts')) return;
    global $wpdb;
    $now = time();

    // 5%概率清理过期数据,限制单次删除行数,防止锁表
    if (mt_rand(1, 20) <= 1) {
        $wpdb->query($wpdb->prepare("DELETE FROM " . MATH_CAPTCHA_TABLE . " WHERE expire < %d LIMIT 100", $now));
    }

    $ops = ['+', '-', '*', '/'];
    $op = $ops[rand(0, 3)];
    $num1 = rand(1, 9);
    $num2 = rand(1, 9);
    $result = 0;

    switch ($op) {
        case '-':
            if ($num1 < $num2) list($num1, $num2) = [$num2, $num1];
            $result = $num1 - $num2;
            break;
        case '/':
            while ($num2 === 0 || $num1 % $num2 !== 0) {
                $num1 = rand(1, 9);
                $num2 = rand(1, 9);
            }
            $result = (int)($num1 / $num2);
            break;
        case '*':
            $result = $num1 * $num2;
            break;
        default:
            $result = $num1 + $num2;
            break;
    }

    $token = bin2hex(random_bytes(16));
    $expire = $now + 300; //5分钟

    $wpdb->insert(
        MATH_CAPTCHA_TABLE,
        [
            'captcha_key' => $token,
            'answer'      => $result,
            'expire'      => $expire,
            'createtime'  => $now
        ],
        ['%s','%d','%d','%d']
    );

    echo '<div class="math-captcha-wrap">';
    echo esc_html($num1) . ' ' . esc_html($op) . ' ' . esc_html($num2) . ' = ';
    echo '<input type="text" name="math_captcha" class="math-captcha-input" autocomplete="off" required>';
    echo '<input type="hidden" name="captcha_token" value="' . esc_attr($token) . '">';
    echo '</div>';
}

// 评论验证码校验【适配独立数据表 + AJAX】
add_filter('preprocess_comment', function ($commentdata) {
	if (current_user_can('edit_posts')) return $commentdata;
    global $wpdb;
	$now = time();

	$input = isset($_POST['math_captcha']) ? trim($_POST['math_captcha']) : '';
	$token = isset($_POST['captcha_token']) ? sanitize_text_field($_POST['captcha_token']) : '';

	if (empty($input) || empty($token)) {
        if(wp_doing_ajax()){
            wp_send_json_error('<strong>错误</strong>:请填写计算结果。');
        }
		wp_die('<strong>错误</strong>:请填写计算结果。', '验证失败', ['response' => 403]);
	}

    $row = $wpdb->get_row($wpdb->prepare(
        "SELECT answer FROM " . MATH_CAPTCHA_TABLE . " WHERE captcha_key = %s AND expire > %d LIMIT 1",
        $token, $now
    ));

	if (!$row) {
        if(wp_doing_ajax()){
            wp_send_json_error('<strong>错误</strong>:验证码已过期,请刷新页面。');
        }
		wp_die('<strong>错误</strong>:验证码已过期,请刷新页面。', '验证失败', ['response' => 403]);
	}

	if ((int)$input !== (int)$row->answer) {
        if(wp_doing_ajax()){
            wp_send_json_error('<strong>错误</strong>:答案错误,请重试。');
        }
		wp_die('<strong>错误</strong>:答案错误,请重试。', '验证失败', ['response' => 403]);
	}

    //验证通过直接删除,一次性token
    $wpdb->delete(MATH_CAPTCHA_TABLE, ['captcha_key' => $token], ['%s']);
	return $commentdata;
}, 8);

1.彻底移除 set_transient / get_transient / delete_transient,不再读写wp_options
2.独立表 yanzhengma 单独存放验证码,不和站点配置混杂
30增加 used=1 核销机制:验证成功后标记,同一个 token 无法重复提交评论(原生 transient 无法天然实现)
4.定时任务 SQL 修改:不再清理 options 表,直接删除表内过期记录
5.写入时自动清理当前 IP 旧验证码,减少无效数据堆积

如果你想立刻手动清理过期数据,直接执行 SQL:

TRUNCATE TABLE yanzhengma;

如果你和我一样,把上面的PHP代码想保存在单独的文件里,不放入functions.php里,使用下面的代码

<?php
/**
 * 算术验证码独立文件
 * 独立数据表 yanzhengma
 */
if (!defined('ABSPATH')) {
    exit;
}

// 定义验证码数据表名
if (!defined('MATH_CAPTCHA_TABLE')) {
    define('MATH_CAPTCHA_TABLE', 'yanzhengma');
}

/**
 * 1-9 加减乘除算术验证码 - 输出HTML
 */
function math_captcha_show()
{
    if (current_user_can('edit_posts')) return;
    global $wpdb;
    $now = time();

    // 5%概率清理过期数据,限制单次删除行数,防止锁表
    if (mt_rand(1, 500) <= 1) {
        $wpdb->query($wpdb->prepare("DELETE FROM " . MATH_CAPTCHA_TABLE . " WHERE expire < %d LIMIT 100", $now));
    }

    $ops = ['+', '-', '*', '/'];
    $op = $ops[rand(0, 3)];
    $num1 = rand(1, 9);
    $num2 = rand(1, 9);
    $result = 0;

    switch ($op) {
        case '-':
            if ($num1 < $num2) list($num1, $num2) = [$num2, $num1];
            $result = $num1 - $num2;
            break;
        case '/':
            while ($num2 === 0 || $num1 % $num2 !== 0) {
                $num1 = rand(1, 9);
                $num2 = rand(1, 9);
            }
            $result = (int)($num1 / $num2);
            break;
        case '*':
            $result = $num1 * $num2;
            break;
        default:
            $result = $num1 + $num2;
            break;
    }

    $token = bin2hex(random_bytes(16));
    $expire = $now + 300; //5分钟有效期

    $wpdb->insert(
        MATH_CAPTCHA_TABLE,
        [
            'captcha_key' => $token,
            'answer'      => $result,
            'expire'      => $expire,
            'createtime'  => $now
        ],
        ['%s','%d','%d','%d']
    );

    echo '<div class="math-captcha-wrap">';
    echo esc_html($num1) . ' ' . esc_html($op) . ' ' . esc_html($num2) . ' = ';
    echo '<input type="text" name="math_captcha" class="math-captcha-input" autocomplete="off" required>';
    echo '<input type="hidden" name="captcha_token" value="' . esc_attr($token) . '">';
    echo '</div>';
}

/**
 * 评论验证码校验【适配独立数据表 + AJAX评论】
 */
add_filter('preprocess_comment', function ($commentdata) {
	if (current_user_can('edit_posts')) return $commentdata;
    global $wpdb;
	$now = time();

	$input = isset($_POST['math_captcha']) ? trim($_POST['math_captcha']) : '';
	$token = isset($_POST['captcha_token']) ? sanitize_text_field($_POST['captcha_token']) : '';

	if (empty($input) || empty($token)) {
        if(wp_doing_ajax()){
            wp_send_json_error('<strong>错误</strong>:请填写计算结果。');
        }
		wp_die('<strong>错误</strong>:请填写计算结果。', '验证失败', ['response' => 403]);
	}

    $row = $wpdb->get_row($wpdb->prepare(
        "SELECT answer FROM " . MATH_CAPTCHA_TABLE . " WHERE captcha_key = %s AND expire > %d LIMIT 1",
        $token, $now
    ));

	if (!$row) {
        if(wp_doing_ajax()){
            wp_send_json_error('<strong>错误</strong>:验证码已过期,请刷新页面。');
        }
		wp_die('<strong>错误</strong>:验证码已过期,请刷新页面。', '验证失败', ['response' => 403]);
	}

	if ((int)$input !== (int)$row->answer) {
        if(wp_doing_ajax()){
            wp_send_json_error('<strong>错误</strong>:答案错误,请重试。');
        }
		wp_die('<strong>错误</strong>:答案错误,请重试。', '验证失败', ['response' => 403]);
	}

    //验证成功立刻销毁token,一次性使用
    $wpdb->delete(MATH_CAPTCHA_TABLE, ['captcha_key' => $token], ['%s']);
	return $commentdata;
}, 8);

可选优化(高并发场景)
增加 MySQL 事件自动清理,脱离 WP 定时任务(WP 定时任务依赖访客访问触发)

CREATE EVENT IF NOT EXISTS evt_clear_yanzhengma
ON SCHEDULE EVERY 6 HOUR
STARTS CURRENT_TIMESTAMP
DO DELETE FROM yanzhengma WHERE expire < UNIX_TIMESTAMP();

最后还是
修改comments.php,在合适的位置添加调用代码:

<?php math_captcha_show(); ?>

这样就可以了。

2 条留言  访客:2 条  站长:0 条

  1. 爱你永远

    写的很好,学习一下

  2. 热衷于

    我发现一个现象, 就是 AI 出来以后像极验的滑块几乎都被破解了, 5 分钱一次打码, 通过率还接近 100%, 而好多老站采用的都是自己写的验证, 图片和文字暴露出来却一点事都没有, 理应更容易遭 Span 的, 看来还是老一辈的技术强

给我留言

4 - 2 =
评论规范提醒