Laravel如何获取地理位置信息?(IP定位插件)

6次阅读

ip定位需先校验公网地址、信任代理头、设超时并兜底缓存;推荐stevebauman/location但注意ipv6支持,高并发下用redis限流或降级geolite2;优先采用前端gps经纬度并验证格式。

Laravel如何获取地理位置信息?(IP定位插件)

request()->ip() 拿到 IP 后,别直接扔给第三方 API

IP 定位不是 laravel 自带能力,得靠外部服务。但很多人一上来就写 file_get_contents("https://api.ipapi.com/{$ip}?key=xxx"),结果在生产环境卡死或超时——没加超时、没处理 DNS 失败、没兜底缓存。真实场景里,IP 可能是内网地址(如 127.0.0.1192.168.x.x),也可能是反向代理转发来的(X-forwarded-For 被伪造),直接用会返回错误城市甚至空数据。

  • 先校验 IP 是否为公网地址:filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)
  • 若不满足,回退到请求头里的 X-Real-IPX-Forwarded-For(但要白名单信任代理,否则可被篡改)
  • 调用前必须设超时(建议 1500ms 内),并 catch GuzzleHttpExceptionConnectExceptionClientException
  • 本地开发时用 127.0.0.1 测试?提前 mock 返回值,别等部署了才发现接口全挂

推荐用 stevebauman/location 包,但注意它默认不支持 IPv6

这个包封装了多个免费/付费定位源(如 ip-api.com、ipgeolocation.io),省去手动拼 URL 和解析 json 的麻烦。但它默认只处理 IPv4,遇到 ::1 或真实 IPv6 地址会静默失败,日志里连 warning 都没有。

  • 初始化时显式传入 ['ip' => $ip],别依赖自动检测
  • IPv6 场景下,优先降级用 geoip2/geoip2(需配合 MaxMind 数据库本地部署)
  • 它的 Location::get() 返回对象,字段名和实际 API 不一致:比如 ip-api 返回 countryCode,它映射成 country_code,查文档比猜快
  • 免费 tier 的 ip-api.com 有 45 次/分钟限制,高并发时容易触发 429 Too Many Requests,得自己加 Redis 计数器或降级到本地 GeoLite2

GeoLite2-City.mmdb 本地数据库要定期更新,不然城市信息过期

MaxMind 的免费数据库每月更新一次,但很多人放服务器上就再也不管。结果某天发现所有用户都显示“北京市”(其实是旧库把大量 CDN IP 归到了北京机房)。

  • 下载地址固定为 https://github.com/maxmind/geoipupdate/releases/latest,用脚本自动拉取 + geoipupdate 命令刷新
  • Laravel 中加载路径必须绝对:不能写 storage_path('app/GeoLite2-City.mmdb'),而要用 base_path('storage/app/GeoLite2-City.mmdb'),否则队列 worker 可能找不到文件
  • 首次加载耗时明显(约 80–200ms),别在每次请求里 new Reader,应绑定到容器里单例复用
  • 注意 PHP 扩展要求:maxminddb 扩展比纯 PHP 解析快 5 倍以上,没装的话 CPU 会突然飙高

前端传经纬度比后端 IP 定位准得多,但得处理权限拒绝

用户手机或笔记本的 GPS 经纬度误差常在 5–10 米,而 IP 定位动辄几公里。所以只要业务允许(比如外卖、打车),优先让前端用 navigator.geolocation.getCurrentPosition() 上报坐标。

  • 前端必须加 Error handler:用户点“拒绝”后,error.code === 1,这时才 fallback 到后端 IP 定位
  • 后端接收时验证格式:is_numeric($lat) && is_numeric($lng) && $lat >= -90 && $lat ,防止恶意提交 <code>999,999 炸掉地图 SDK
  • 别存原始字符串,用 DECIMAL(10,8) 存纬度、DECIMAL(11,8) 存经度,避免浮点精度丢失
  • 如果用户开了飞行模式或禁用了定位,getCurrentPosition 会超时(默认 60s),前端要设 timeout: 5000 并主动降级

IP 定位最麻烦的不是代码怎么写,而是边界 case 太多:代理链长度、NAT 网关、CDN 回源 IP、教育网出口池……真要高准确率,得组合前端坐标 + IP + 用户历史位置做加权,而不是指望一个包 or 一个 API 解决所有问题。

text=ZqhQzanResources