$_SERVER['REMOTE_ADDR']不准是因为它仅返回直连代理的IP(如Nginx或CDN内网地址),真实客户端IP需结合可信代理校验后,从HTTP_X_FORWARDED_FOR等头中安全提取,并配合Nginx的set_real_ip_from和real_ip_header配置。

$_SERVER['REMOTE_ADDR']不准,先别急着写黑名单逻辑
直接用 $_SERVER['REMOTE_ADDR'] 做判断,在生产环境基本等于没拦——CDN、Cloudflare、AWS ALB、Nginx 反向代理都会把它变成内网地址(比如 10.0.0.1 或 127.0.0.1)。真实客户端 IP 藏在 HTTP_X_FORWARDED_FOR 里,但这个头可被伪造,不能无条件信任。
必须配合 Nginx 的 real_ip_header X-Forwarded-For 和 set_real_ip_from(只允许来自可信代理网段的请求修改 REMOTE_ADDR)。PHP 层再调用带 $adv = true 的 get_client_ip(),才可能拿到真实 IP。否则数据库存了一堆 127.0.0.1,查了也白查。
黑名单存在数据库里,但别用 SELECT * FROM blacklist WHERE ip = ? 全表扫
IP 字段建索引只是基础;更关键的是匹配方式。如果规则含通配符(如 192.168.*.*)或 CIDR 段(如 10.0.0.0/8),纯等值查询完全失效。
- 对 CIDR 段:存成
subnet(如10.0.0.0)和bits(如8)两个整数字段,查询时用ip2long($ip) & (-1 ,MySQL 可走索引计算 - 对通配符:提前把
192.168.*.*转成正则^192\.168\.\d{1,3}\.\d{1,3}$并缓存到内存(如 APCu),避免每次拼接 +preg_match();千万别把一堆规则塞进一个大正则里,PCRE 回溯容易超限报PREG_BACKTRACK_LIMIT_ERROR - IPv6 必须单独处理:
inet_pton()+inet_ntop(),ip2long()完全无效
哈希表匹配比数据库快,但只适合静态小名单
如果黑名单规模稳定在几百条以内,且不频繁更新,PHP 关联数组(本质是哈希表)做内存级匹配最省事:isset($blacklist[$ip]) 是 O(1)。但注意两点:
立即学习“PHP免费学习笔记(深入)”;
- 键必须是完整 IPv4 字符串(如
"192.168.1.100"),不能存 CIDR 或通配符——那些得另外跑匹配逻辑 - 动态增删需考虑进程隔离:PHP-FPM 多 worker 下,改数组只影响当前进程;要用 Redis 或 APCu 做跨进程共享,否则添加后部分请求仍能通过
别忽略过期与自动清理机制
数据库里的黑名单不是“一加永逸”。运营需要临时封禁、定时解封,硬编码的永久黑名单会拖垮维护效率。
建议表结构至少包含:ip(VARCHAR 45,兼容 IPv6)、expires_at(DATETIME,NULL 表示永不过期)、reason(TEXT)、created_at。每次查询前加 WHERE expires_at IS NULL OR expires_at > NOW(),并配一个低频定时任务(如每 5 分钟)清理过期记录。否则半年后表里堆了几万条僵尸规则,SELECT 即使有索引也会变慢。
真正难的不是“怎么存”,而是“怎么确保每次取到的 IP 是真的、匹配逻辑不漏不误、过期不堆积”——这三个点任何一个出问题,黑名单就形同虚设。



















