Skip to content

uniapp 怎么获取用户 IP 归属地?属地展示与地域化功能实现 ​

小程序和 App 里经常要展示「用户来自哪里」:评论区挂个属地标签、进首页按地区推频道、同城业务要根据城市分发内容。H5 和微信小程序里 uni.getLocation 拿的是 GPS 定位,得用户授权,还经常被拒;而 IP 属地在服务端随手可得,前端也能直接调免费接口拿到——不用授权、不弹窗、不依赖基站。这篇讲 uniapp 里调 IP 归属地接口的完整做法,附可直接拷贝的工具函数。

一、三条路的取舍 ​

前端要拿「当前用户的 IP 属地」,有三种姿势:

  1. 前端直调免费接口:uni.request 打 https://ip9.com.cn/get(不传参就是查请求方 IP),返回国家/省市/运营商。最快,一个请求搞定,适合对精确度要求不高的展示场景。
  2. 后端封装一层:前端请求自己后端,后端调 https://ip9.com.cn/get?ip=<真实IP> 再返回。能拿真实客户端 IP(前端直调时,接口看到的是出口 IP,和你后端拿到的一致,但后端可以做缓存、加签名、控制频率),适合生产。
  3. 原生定位兜底:uni.getLocation 拿 GPS,按经纬度反查城市。精度高但要授权弹窗,且室内/用户拒绝时拿不到,适合「IP 粗筛 + GPS 细定位」的组合。

展示类需求(评论区属地、地区标签)用方案 1 就够;涉及业务规则(风控、限流)务必放后端。下面是前端直调的完整实现。

二、uniapp 实现 ​

封装一个 getIpRegion() 工具函数:调免费接口拿归属地,用 uni.setStorageSync 缓存一天,避免每次进页面都发请求;失败时返回兜底文案,不让 UI 报错。放在 utils/ip.js 里全局引用。

javascript
// utils/ip.js —— uniapp 获取用户 IP 归属地(H5 / 小程序通用)
const GEO_API = 'https://ip9.com.cn/get'   // 不传 ip = 查询当前请求方 IP
const CACHE_KEY = 'ip_region_cache'

function getIpRegion() {
  return new Promise((resolve) => {
    // 1. 先读缓存:24 小时内不重复请求
    const cached = uni.getStorageSync(CACHE_KEY)
    if (cached && cached.expire > Date.now()) {
      resolve(cached.data)
      return
    }
    // 2. 请求免费接口
    uni.request({
      url: GEO_API,
      method: 'GET',
      timeout: 5000,
      success: (res) => {
        const r = res.data
        if (r && r.ret === 200 && r.data) {
          const d = r.data
          const region = {
            prov: d.prov || '',
            city: d.city || '',
            isp: d.isp || '',
            text: `${d.prov || ''} ${d.city || ''}`.trim() || '未知地区'
          }
          uni.setStorageSync(CACHE_KEY, { data: region, expire: Date.now() + 24 * 3600 * 1000 })
          resolve(region)
        } else {
          resolve({ text: '未知地区' })
        }
      },
      fail: () => {
        // 3. 网络失败兜底:不给用户看错误,只显示占位
        resolve({ text: '未知地区' })
      }
    })
  })
}
export { getIpRegion }

页面里这样用:评论区每条评论后面挂属地标签,或者进首页时根据 prov/city 分发地区频道:

javascript
// 页面示例:展示属地标签 + 按城市分发内容
import { getIpRegion } from '@/utils/ip.js'

export default {
  data() { return { myRegion: '', nearbyStore: null } },
  async onLoad() {
    const region = await getIpRegion()
    this.myRegion = region.text                 // 例:"广东 深圳"
    // 按城市匹配同城门店(city 字段与门店表 city 字段精确匹配)
    this.nearbyStore = await this.fetchStore(region.prov, region.city)
  },
  methods: {
    fetchStore(prov, city) {
      // 调自己后端:/api/store?prov=..&city=..
      return new Promise((resolve) => {
        uni.request({ url: '/api/store', data: { prov, city }, success: (res) => resolve(res.data) })
      })
    }
  }
}

两个要点:缓存放 Storage 而不是放全局变量——小程序页面卸载后 JS 变量会丢,刷新页面时 Storage 还在,24 小时内只花了 1 次接口额度;success 里要判 ret === 200——接口文档里 ret 才是业务状态码,HTTP 200 不代表查询成功,非法参数(ret=400)、限速(ret=429)都要兜住。

三、放后端时的写法 ​

如果要做生产级(缓存、限速、不外泄接口),把查询挪到后端,前端只调自己的接口。后端任一语言都行,核心是把客户端真实 IP 传进去:https://ip9.com.cn/get?ip=<客户端IP>(支持 IPv4/IPv6)。注意小程序里 uni.request 的合法域名要在微信后台配置白名单(HTTPS 域名),H5 端如果遇到跨域问题,走后端转发即可,免费接口本身不承诺 CORS。

四、注意事项 ​

  • 属地 ≠ 精确位置:IP 归属地是城市级,lng/lat 是城市中心点坐标,做「3 公里内门店」要用 GPS 定位,别拿 IP 坐标去算距离。
  • IP 会变:用户从 WiFi 切到 4G,出口 IP 和归属地都可能变,缓存设短一点(我给的 24 小时偏长,展示场景够用,业务规则场景别缓存)。
  • 展示粒度:属地标签展示到省份最稳妥,城市级会引发「为什么我在 A 城显示 B 城」的投诉——尤其是挂了 VPN 和公司出口的用户。
  • 隐私合规:IP 属地的收集要写进隐私政策,展示给用户的内容只做聚合或模糊处理,不把明细 IP 暴露给其他用户。

总结 ​

uniapp 里拿用户 IP 属地,核心就一个请求:uni.request 调 https://ip9.com.cn/get,免费、免注册、免授权,返回省份/城市/运营商字段,配一个带缓存的工具函数就是完整实现。展示场景前端直调,业务场景后端转发。想进一步了解接口字段(long_ip、big_area 大区、IPv6 支持)和 VIP 的区县/ip_type 能力,看官网 https://www.ip9.com.cn 的文档即可。