必须清洗IP、配置可信代理、校验公网地址、切换本地MaxMind数据库并加缓存,否则GeoIP定位高并发下易失败。

直接用 GeoIP::getLocation() 拿不到真实地理位置,90% 的失败都卡在 IP 源不可靠、服务超时或配置没生效上。必须先清洗 IP、选对后端、设好兜底,否则线上一压就崩。
request()->ip() 返回的 IP 为什么经常不准
它只读 $_SERVER['REMOTE_ADDR'],在 Nginx + PHP-FPM 或云 WAF 后面时,这个值通常是反向代理内网地址(如 10.0.2.15 或 127.0.0.1),不是用户真实公网 IP。
- 用
Request::getClientIp()替代,它已内置对X-Forwarded-For和X-Real-IP的解析逻辑 - 但必须配置可信代理列表:在
config/trustedproxy.php中填入你实际使用的代理 IP 段,否则X-Forwarded-For可被伪造 - 拿到 IP 后立刻校验是否为公网地址:
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE),不通过就跳过定位,别硬查
torann/geoip 默认走 ip-api.com,高并发下容易 429
免费 tier 限速 45 次/分钟,单台机器压测几十个并发就触发限流,返回 429 Too Many Requests,且包默认不抛异常,getLocation() 静默返回空对象。
- 切到本地数据库方案:改
config/geoip.php中'service' => 'maxmind_database' - 装依赖:
composer require geoip2/geoip2,再下载GeoLite2-City.mmdb到storage/app/geoip/(注意路径要和配置里maxmind_database的database_path一致) - 加一层缓存:用 Laravel 的
Cache::remember()包裹查询,键名包含 IP 和数据库修改时间(filemtime($dbPath)),避免 DB 更新后缓存永远不刷新
stevebauman/location 不支持 IPv6 是个隐藏坑
遇到 ::1 或真实 IPv6 地址时,Location::get() 直接返回 null,日志里连 warning 都没有,前端看着就是“位置未知”。
- 初始化时必须显式传参:
Location::get(['ip' => $ip]),不能依赖自动检测 - IPv6 场景下优先降级用
geoip2/geoip2,它的Reader::city()原生支持 IPv6 - 若坚持用 stevebauman/location,得自己写 wrapper:先
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)判断,是 IPv6 就跳过它,走本地库
真正难的不是调哪个函数,而是把 IP 清洗、服务降级、缓存键设计、数据库更新这四件事串成一条不掉链子的流水线——少一环,凌晨三点的告警就来了。











