自动摘要
正在生成中……
晚上因为网站被机器人疯狂爬取,就图方便用chaptgpt写一个控制ip访问频率的wp插件,万没想到 chatgpt 的 4o-mini 果然还是不够聪明,被它坑死了!
一开始是它不懂我用的是phpiredis扩展,一直使用Redis()类,而phpiredis只有函数式命令操作,经过我的再次提醒,并附上文档用法,它总算改成正确的了。
然后它又犯了一个让我debug了半个小时的错误,当检查redis一个key不存在时,会返回NULL,它又将转成int,也就是0,然后它将0用三个等号与 false 对比,也就是类型和值都要相等,这它喵的这永远不可能相等啊!导致设置过期时间的代码不可能执行。
// 获取当前请求次数
$request_count_response = phpiredis_command_bs( $redis, array( 'GET', $redis_key ) );
$request_count = intval( $request_count_response );
if ( false === $request_count ) {
// 初始化计数为1,过期时间为60秒
phpiredis_command_bs( $redis, array( 'SETEX', $redis_key, 60, 1 ) );
} else {
if ( $request_count >= $max_requests_per_minute ) {
$this->block_request();
} else {
// 增加计数
phpiredis_command_bs( $redis, array( 'INCR', $redis_key ) );
}
//....
于是key过期时间一直不生效,我开始还以为是phpiredis的问题,还debug了半天,直到我往上看了一眼!这TMD!
cursor 的3.5 sonnet是可以识别到这个bug的,开始用的gpt 4o也可以,后来因为4o达到每日限制,降级回4o-mini,没想到就给我写bug了。😅
尽量如此,展示一下这个WordPress 插件的成果吧。可以用redis或者用wordpress自带的transient缓存系列函数(存储在mysql)来控制ip的访问频率, 我用的是phpiredis扩展,其实可以再兼容主流的redis扩展(phpiredis确实很非主流,操作的方式完全就是redis-cli,没有流行的面向对象那些乱七八糟的东西,我觉得反而很纯粹)或者纯php客户端。
完整代码:
<?php
/**
* Plugin Name: IP Rate Limiter with Redis
* Description: 限制每个IP每分钟的访问频率,并支持使用Redis存储访问数据。
* Version: 1.3
* Author: Falcon
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // 防止直接访问文件
}
class IP_Rate_Limiter {
// 初始化插件
public function __construct() {
add_action( 'admin_menu', array( $this, 'add_settings_page' ) );
add_action( 'admin_init', array( $this, 'register_settings' ) );
add_action( 'init', array( $this, 'check_ip_rate_limit' ) );
}
// 检查IP访问频率
public function check_ip_rate_limit() {
$ip_address = $this->get_ip_address();
$use_redis = get_option( 'ip_rate_limiter_use_redis', 'no' );
// 获取用户设置的最大请求次数,默认为10
$max_requests_per_minute = get_option( 'ip_rate_limiter_max_requests', 10 );
if ( $use_redis === 'yes' && $this->is_redis_available() ) {
// 使用Redis进行计数
$this->check_ip_in_redis( $ip_address, $max_requests_per_minute );
} else {
// 使用transient进行计数
$this->check_ip_in_transient( $ip_address, $max_requests_per_minute );
}
}
// 使用Redis检查和记录访问次数
private function check_ip_in_redis( $ip_address, $max_requests_per_minute ) {
$redis = $this->get_redis_connection();
$redis_key = 'ip_rate_limit_' . $ip_address;
// 获取当前请求次数
$request_count_response = phpiredis_command_bs( $redis, array( 'GET', $redis_key ) );
$request_count = intval( $request_count_response );
if ( $request_count === 0) {
// 初始化计数为1,过期时间为60秒
$result = phpiredis_command($redis, "SETEX $redis_key 60 1");
} else {
if ( $request_count >= $max_requests_per_minute ) {
$this->block_request();
} else {
// 增加计数
phpiredis_command_bs( $redis, array( 'INCR', $redis_key ) );
}
}
}
// 使用transient检查和记录访问次数
private function check_ip_in_transient( $ip_address, $max_requests_per_minute ) {
$transient_key = 'ip_rate_limit_' . $ip_address;
$request_count = get_transient( $transient_key );
if ( false === $request_count ) {
set_transient( $transient_key, 1, 60 ); // 初始化计数为1,过期时间为60秒
} else {
if ( $request_count >= $max_requests_per_minute ) {
$this->block_request();
} else {
set_transient( $transient_key, $request_count + 1, 60 );
}
}
}
// 获取用户IP地址
private function get_ip_address() {
if ( ! empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
return $_SERVER['HTTP_CLIENT_IP'];
} elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
return $_SERVER['REMOTE_ADDR'];
}
}
// 阻止请求并输出错误信息
private function block_request() {
// 设置 429 Too Many Requests 状态码
//http_response_code(429);
// 设置 Retry-After 头,告知客户端 180 秒后重试
header('Retry-After: 180');
// 显示自定义的错误信息
wp_die( 'Too many requests, please try again later.', 'Too Many Requests', array( 'response' => 429 ) );
}
// 获取Redis连接
private function get_redis_connection() {
$host = get_option( 'ip_rate_limiter_redis_host', '127.0.0.1' );
$port = get_option( 'ip_rate_limiter_redis_port', 6379 );
// 使用 phpiredis 扩展函数连接 Redis
$redis = phpiredis_connect( $host, $port );
// 如果有密码,进行身份验证
$password = get_option( 'ip_rate_limiter_redis_password', '' );
if ( ! empty( $password ) ) {
phpiredis_command_bs( $redis, array( 'AUTH', $password ) );
}
return $redis;
}
// 检查Redis是否可用
private function is_redis_available() {
try {
$redis = $this->get_redis_connection();
$response = phpiredis_command_bs( $redis, array( 'PING' ) );
return $response === 'PONG';
} catch ( Exception $e ) {
return false;
}
}
// 添加设置页面
public function add_settings_page() {
add_options_page(
'IP Rate Limiter Settings',
'IP Rate Limiter',
'manage_options',
'ip-rate-limiter',
array( $this, 'settings_page_html' )
);
}
// 注册设置
public function register_settings() {
register_setting( 'ip_rate_limiter_settings', 'ip_rate_limiter_max_requests' );
register_setting( 'ip_rate_limiter_settings', 'ip_rate_limiter_use_redis' );
register_setting( 'ip_rate_limiter_settings', 'ip_rate_limiter_redis_host' );
register_setting( 'ip_rate_limiter_settings', 'ip_rate_limiter_redis_port' );
register_setting( 'ip_rate_limiter_settings', 'ip_rate_limiter_redis_password' );
add_settings_section(
'ip_rate_limiter_section',
'设置IP访问频率限制',
null,
'ip-rate-limiter'
);
add_settings_field(
'ip_rate_limiter_max_requests',
'每分钟最大访问次数',
array( $this, 'settings_field_max_requests_html' ),
'ip-rate-limiter',
'ip_rate_limiter_section'
);
add_settings_field(
'ip_rate_limiter_use_redis',
'是否使用Redis存储',
array( $this, 'settings_field_use_redis_html' ),
'ip-rate-limiter',
'ip_rate_limiter_section'
);
add_settings_field(
'ip_rate_limiter_redis_host',
'Redis主机',
array( $this, 'settings_field_redis_host_html' ),
'ip-rate-limiter',
'ip_rate_limiter_section'
);
add_settings_field(
'ip_rate_limiter_redis_port',
'Redis端口',
array( $this, 'settings_field_redis_port_html' ),
'ip-rate-limiter',
'ip_rate_limiter_section'
);
add_settings_field(
'ip_rate_limiter_redis_password',
'Redis密码',
array( $this, 'settings_field_redis_password_html' ),
'ip-rate-limiter',
'ip_rate_limiter_section'
);
}
// 设置页面内容
public function settings_page_html() {
?>
<div class="wrap">
<h1>IP Rate Limiter 设置</h1>
<form action="options.php" method="POST">
<?php
settings_fields( 'ip_rate_limiter_settings' );
do_settings_sections( 'ip-rate-limiter' );
submit_button();
?>
</form>
</div>
<?php
}
// 最大访问次数设置字段HTML
public function settings_field_max_requests_html() {
$max_requests = get_option( 'ip_rate_limiter_max_requests', 10 );
?>
<input type="number" name="ip_rate_limiter_max_requests" value="<?php echo esc_attr( $max_requests ); ?>" />
<?php
}
// 是否使用Redis设置字段HTML
public function settings_field_use_redis_html() {
$use_redis = get_option( 'ip_rate_limiter_use_redis', 'no' );
?>
<select name="ip_rate_limiter_use_redis">
<option value="no" <?php selected( $use_redis, 'no' ); ?>>否</option>
<option value="yes" <?php selected( $use_redis, 'yes' ); ?>>是</option>
</select>
<?php
}
// Redis主机设置字段HTML
public function settings_field_redis_host_html() {
$redis_host = get_option( 'ip_rate_limiter_redis_host', '127.0.0.1' );
?>
<input type="text" name="ip_rate_limiter_redis_host" value="<?php echo esc_attr( $redis_host ); ?>" />
<?php
}
// Redis端口设置字段HTML
public function settings_field_redis_port_html() {
$redis_port = get_option( 'ip_rate_limiter_redis_port', 6379 );
?>
<input type="number" name="ip_rate_limiter_redis_port" value="<?php echo esc_attr( $redis_port ); ?>" />
<?php
}
// Redis密码设置字段HTML
public function settings_field_redis_password_html() {
$redis_password = get_option( 'ip_rate_limiter_redis_password', '' );
?>
<input type="password" name="ip_rate_limiter_redis_password" value="<?php echo esc_attr( $redis_password ); ?>" />
<?php
}
}
// 初始化插件
new IP_Rate_Limiter();