
先说结论判断客户端IP是国内还是国外本质不是很难但真正难的是把方案做得可靠、准、快还要在真实业务里扛得住各种边界情况。做后端或者前端的朋友大概率都碰到过这类需求用户访问网站你想在页面上给他展示中文版还是英文版用户下单你想判断订单来自境内还是境外做数据报表、访问统计、营销活动分桶时你也想知道访问来源到底分布在哪儿。这个时候绕不开的一个底层能力就是判断客户端IP是国内还是国外。这个需求在圈子里常被叫做客户端IP归属地判断、IP判断、IP归属地查询看着简单做起来方案一大堆坑也不少。我这两年帮别人做过好几个类似的小项目也维护过一个每天千万级请求的归属地判断服务累计踩了不少坑。这篇文章就把我实际验证过的几种方式、选型思路、实现细节和问题排查经验系统地写出来。不管你是后端开发、运维还是只写前端页面的朋友照着这份思路都能落地一套可用的方案。1. 内容整体设计与思路拆解1.1 先搞懂需求我们要判断的到底是什么首先要明确我们说的“判断IP是国内还是国外”业务上真正要的通常不是IP本身而是“这个访问者大概在哪个地理区域”。它只需要粗粒度到国家或地区级别就够用不需要精确到城市、街道。但是很多人一上来就陷入误区想找一个“百分百准确”的方案。实际上这是不存在的。IP地址和地理位置之间是“大概率对应”的关系不是“恒等对应”的关系。原因后面我会详细讲这里先记住一句话IP判断只能给一个置信度不能当成绝对的精确事实。从需求类型来看常见的有这么几类内容分流根据用户所在区域展示不同的语言、价格、活动页面。报表统计对访问量、订单量做境内境外分布分析。风控辅助识别来自境外的异常访问、恶意请求、刷单账号。合规要求某些内容或产品只面向特定区域开放。这些场景对准确率的要求不一样。做内容分流偶尔误判一两个无所谓做风控误判率太高就会淹没真正的风险做合规那就必须非常谨慎。所以在动手之前先想清楚你的业务对误判的容忍度是多大这直接决定选哪套方案。1.2 常见方案总览四条路线对比我实际用过、也帮读者验证过的方案基本可以分成四类离线IP库、在线API、CDN请求头、前端辅助信号。下面这张表把优缺点和适用场景都列出来了。方案核心原理优点缺点适合场景离线IP库如ip2region、GeoLite2把IP段映射到地理信息本地查询速度快、无外部依赖、免费、适合高并发库有更新滞后需要定期维护后端接口、批量统计、内网部署在线API如ip-api.com、ipinfo.io、国内云厂商服务远程请求第三方服务返回归属地准确率高、实时性好、接入简单网络耗时、有并发限制、可能产生费用低频查询、兜底校验、本地库未命中CDN请求头Cloudflare、阿里云CDN等边缘节点识别IP并附加国家码请求头性能最好、零额外查询、全球覆盖好依赖CDN厂商未命中CDN时拿不到已经接入CDN的站点、边缘函数前端辅助信号时区、语言、区域偏好浏览器环境信息推断用户区域纯前端可用、不依赖后端和IP库只能做辅助不能确定易被修改静态站点、前端默认语言、第一层初筛看到这里你应该明白了没有哪一个方案是完美的。我的建议是组合使用。用离线库处理大部分请求用在线API做兜底用前端辅助信号做好体验层的默认值再用CDN方案把性能优势吃满。1.3 选型之前先回答好这四个问题我在给朋友出方案的时候从来不直接推荐某个工具而是先让他回答几个问题你的服务有没有后端纯静态托管还是自建服务器你现在的流量规模有多大一天几千次还是几百万次你的请求是从浏览器直接进来还是经过了CDN、负载均衡、网关一次误判的成本有多高是影响一个用户看到什么页面还是影响一笔交易能不能完成这四个问题答完方案基本就浮出水面了。举个例子一个纯前端静态博客没有后端那就别上离线库了用前端时区判断加一个免费在线API就够一个电商网站流量大又接入了CDN那肯定是优先读CDN请求头离线库做后端兜底一个高并发API服务请求来源比较固定那离线库加内存缓存是最稳的。选型没有标准答案但有标准思路先搞清楚你的运行环境和业务容忍度再反推方案这样才不会做出一套“看起来很完美但根本跑不动”的东西。2. 核心细节解析与实操要点2.1 IP地址为什么能反映地理位置聊原理之前先说个生活化的类比。IP地址有点像手机号的前几位你能大致判断出这个号码属于哪个运营商、哪个地区但不是说一定准。因为有人携号转网有人拿着一张异地卡用了好几年还有人在境外漫游。IP地址和地理位置的关系也是如此。IP地址不是随机分配的而是由全球互联网数字分配机构按照地域和管理体系把一段一段的IPv4、IPv6地址分配给各大区域的地址注册机构再往下分配给运营商、云厂商、企业、学校。所以每一个IP段在分配的时候就有了明确的归属方和归属地域。IP归属地数据库做的事情就是把这些“IP段到哪里”的映射关系整理成一份表。查询的时候把你的IP放进去做范围匹配就能找到它对应的地理信息。说白了IP判断就是个查表问题不是算法问题。但这个表不是一成不变的。运营商会做地址段调整云厂商会重新规划公网IP企业会归还不再使用的地址段。如果一个IP地址段被重新分配了而地址库没有更新判断就会出错。这也是为什么地址库方案必须定期更新的根本原因。2.2 IPv4和IPv6要分开处理我刚做IP判断那会儿犯过一个很低级的错误只处理了IPv4结果一段时间后看到数据报表里“境外访问比例暴涨”排查了半天发现全是IPv6用户被默认归到了“国外”。这个问题很典型。大量地址库和早期代码只支持IPv4遇到IPv6地址要么返回空要么直接走默认分支。而IPv6地址是明文可解析的IPv6的地址分配比IPv4更规范尤其是运营商分配的大段地址很多时候甚至可以直接通过前缀判断出区域归属。所以不管你选哪套方案动手前先确认三件事地址库是否支持IPv6查询不支持的话你的代码对IPv6是否有兜底规则你的CDN或反向代理是否在真实IP透传时保留了IPv6原始地址如果暂时没有好的IPv6地址库可以先按“IPv6默认按国内处理”还是“默认按国外处理”做策略但一定要留出后续升级接口别写死在业务代码里。2.3 获取真实客户端IP的几个关键前置条件这一步绝对是最重要、也最容易被忽略的。判断客户端IP之前你得先确保拿到的真的是客户端IP而不是CDN节点IP或负载均衡器IP。很多项目刚开始做IP判断时直接读后端语言的 remote address 字段。这个字段拿到的是和你服务器建立TCP连接的那台机器的IP。如果你的服务是直连公网那没问题但一旦前面加了CDN、SLB、Nginx反向代理remote address 就变成了最后一跳入口设备的IP。这时候你判断出来的结果就是“CDN节点在美国”或者“负载均衡器在北京”毫无意义。解决方法是利用标准请求头。CDN和反向代理在转发请求时会把原始客户端IP放在 X-Forwarded-For、X-Real-IP 这类头里。不同的CDN厂商还会有自己的专属请求头比如 Cloudflare 会附加 CF-Connecting-IP阿里云CDN也会有类似的自定义头。这里有一个非常重要的安全点X-Forwarded-For 是客户端可以伪造的。如果服务器直接信任这个头攻击者只要手动加一个 X-Forwarded-For: 8.8.8.8你看到的就是Google的IP了。正确做法是在接入层配置可信代理让网关在转发请求时覆盖或过滤掉客户端传入的不可信头。以Nginx为例如果你用的是Cloudflare可以在Nginx配置里做类似这样的设置set_real_ip_from 173.245.48.0/20; set_real_ip_from 103.21.244.0/22; real_ip_header CF-Connecting-IP;set_real_ip_from 是宣称这些IP段是可信接入层real_ip_header 是告诉Nginx从哪个请求头里取真实IP。这样做之后你的后端代码再拿 remote address就是真实客户端IP了。切记这一步不搞对后面所有地址库、API、CDN头判断做得再精细都是白搭。2.4 前端辅助信号时区、语言和区域偏好除了IP浏览器还自带一套“区域信号”用得好能起到很好的辅助作用。时区通过 Intl.DateTimeFormat().resolvedOptions().timeZone 可以拿到用户当前时区比如 Asia/Shanghai、America/New_York。语言偏好通过 navigator.language 和 navigator.languages 可以拿到用户浏览器的语言排序比如 zh-CN、en-US。区域偏好某些浏览器还暴露了区域信息比如 navigator.language 里经常包含地区码。这套信号最大的优点是不需要后端参与不需要查数据库纯浏览器JS就能完成。它是做“默认体验”的利器。比如一个静态网站用户时区是东八区、语言是中文那你完全有理由把默认语言设为中文。但要注意这套信号只能辅助不能确认。技术用户完全可以修改浏览器设置而且同一个时区可以对应多个地区语言偏好也不等于常住地。所以我在项目里从来不会单独用它做决策只把它作为IP判断结果的一个补充维度。3. 实操过程与核心环节实现3.1 方案一离线IP库快速接入以ip2region为例如果要我推荐一个自建方案首选是ip2region。它是开源项目本地查询速度快有xdb和旧版db两种格式支持Python、Java、Go、PHP等几乎所有主流语言。关键是它把IP段映射文件做成了二进制格式查询性能很好单机每秒能跑几十万次。接入步骤很简单。先下载最新的xdb数据文件把它放到服务器某个固定目录。然后初始化一个查询器查询IP就能拿到地理位置字符串。下面是一段Python示例使用的查询库是官方的xdbSearcherfrom xdbSearcher import XdbSearcher # 读取整个库文件到内存查询速度最快 with open(ip2region.xdb, rb) as f: db_content f.read() searcher XdbSearcher(contentdb_content) def judge_ip_location(ip: str) - str: region searcher.search(ip) print(fIP: {ip} - {region}) # 返回结构一般是国家|区域|省份|城市|ISP # 例如中国|0|浙江省|杭州市|阿里云 # 判断是否包含“中国”关键字更稳妥的是解析国家字段 if region and 中国 in region: return domestic return overseas print(judge_ip_location(47.93.24.132))这里最需要注意的是返回字段的解析。ip2region不同版本返回的字段分隔符和内容不完全一样有些版本会把“中国”写成“中国”有些版本可能是“China”。我建议你第一次接入的时候先打印几条已知国内、国外的IP结果确认格式后再写解析规则。另外一个经验是尽量用 xdb 格式别用老版的 db 格式。xdb是ip2region作者新设计的索引结构查询更快文件更小而且支持热更新索引。无论你用哪个语言SDK优先找支持xdb的版本。更新频率方面ip2region本身会不定期更新数据但更新的节奏不稳定。如果你对准确率要求高我在生产环境中通常的做法是ip2region做第一层快速判断每月或每季度手动更新一次数据文件同时留好在线API的兜底入口发现某个IP判断可疑时实时查一次在线API做校正。3.2 方案二在线API查询归属地以ip-api.com为例在线API比较简单适合做一个“兜底”或者低频查询。我经常用的一个免费接口是ip-api.com不需要API key就能查返回JSON格式支持中文。先看一个最简单的curl调用curl http://ip-api.com/json/8.8.8.8?langzh-CNfieldsstatus,message,country,countryCode,regionName,city,query返回结果大概是这样的{ status: success, country: 美国, countryCode: US, regionName: California, city: Mountain View, query: 8.8.8.8 }在Python里调用也很直接import requests def query_online(ip: str) - str: url http://ip-api.com/json/ ip params { lang: zh-CN, fields: status,message,countryCode,query, } try: resp requests.get(url, paramsparams, timeout3) data resp.json() if data.get(status) success: return data[countryCode] except Exception as e: print(fquery online failed: {e}) return # 用countryCode CN 判断国内 print(query_online(8.8.8.8))用在线API有几点必须注意。第一免费版有限频。ip-api.com免费版限频是每分钟45次请求超过会被封IP一段时间。生产环境用免费版基本不现实只能做低频率兜底。如果想要更高配额要么付费要么换成国内云厂商的付费服务稳定性会好很多。第二网络依赖。请求第三方接口意味着你的服务多了一个外部依赖必须设置超时时间、失败重试次数和降级策略。我一般把超时设为2到3秒失败后直接走本地地址库的结果绝不让在线API的失败拖垮主流程。第三一定要做缓存。同一个IP在短时间内反复查结果几乎不会变。我的做法是查完之后在本地内存缓存24小时用LRU淘汰命中率能到95%以上。这样既省钱又降低限频风险。3.3 方案三读CDN请求头直接拿国家码如果你的站点已经接入CDN这个方法几乎是零成本。CDN在全球各地都有节点用户访问你的站点时DNS会把他调度到最近的CDN边缘节点。边缘节点在回源时通常会把识别出的客户端IP、所属国家或地区码放在请求头里一起发给源站。拿Cloudflare举例它在回源请求里会加入 CF-IPCountry 这个请求头值是一个两位字母的国家码比如 CN、US、JP。还有一些CDN厂商会提供 CF-Connecting-IP 这类头方便源站获取真实IP。后端代码读起来非常简单。拿PHP举例$country $_SERVER[HTTP_CF_IPCOUNTRY] ?? ; if ($country CN) { echo 国内访问; } else { echo 海外访问; }Node.js的Express项目里写法也类似const country req.headers[cf-ipcountry] || ; if (country CN) { res.send(国内访问); } else { res.send(海外访问); }这里的关键问题是你需要确认你用的CDN厂商到底提供哪个请求头以及它的取值规范。不同的厂商命名不同而且有的厂商默认不开启回源IP识别需要开通对应功能。我的建议是去官方文档里查清楚然后写一个适配层把这个请求头统一包装成自己的标准字段别在业务代码里到处散落CDN厂商的名字否则以后换CDN会很痛苦。补充一点如果服务在边缘函数或Serverless环境中运行判断可以更早完成连回源都不需要。比如Cloudflare Workers里可以直接读取request.cf.country字段拿到国家码直接在边缘做内容分流性能和体验都是最优的。这也是一种典型的边缘计算应用。3.4 方案四前端时区语言辅助判别前端方案适合做体验层的兜底。代码我贴一段可以直接用的function guessRegionByClientEnv() { // 1. 先看时区 let tz ; try { tz Intl.DateTimeFormat().resolvedOptions().timeZone; } catch (e) { tz ; } // 常见的中国相关时区按需扩展 const cnTimezones [Asia/Shanghai, Asia/Urumqi, Asia/Chongqing, Asia/Harbin]; if (cnTimezones.includes(tz)) { return domestic; } // 2. 再看语言偏好 const lang navigator.language || navigator.languages[0] || ; const langRegion (lang.split(-)[1] || ).toUpperCase(); const cnRegions [CN, HK, MO, TW]; if (cnRegions.includes(langRegion)) { return domestic; } // 3. 不确定就返回unknown由上层决定 return unknown; }这段代码的用意是如果你判断用户大概率在国内就可以先把中文版页面渲染出来避免第一次闪烁英文版如果返回unknown再走IP查询接口决定。说句实话这套方案很多前端同学容易过度依赖我见过有人直接用navigator.language判断用户是否国内结果海外华人用中文浏览器被当成国内用户国内英文系统用户被当成海外。记住前端辅助信号适合做初筛和默认值不适合做精确身份认定尤其不要拿它去做限制性逻辑。3.5 综合落地一个先查库再查API的完整流程在我维护的高频服务里最终跑通的流程是一个“本地优先、在线兜底、结果缓存”的组合方案用代码描述大概长这样def judge_ip(client_ip: str) - str: # 1. 先查缓存 cached cache.get(client_ip) if cached: return cached # 2. 查本地地址库 region local_searcher.search(client_ip) if region: country extract_country(region) if country CN: cache.set(client_ip, domestic, ttl86400) return domestic elif country: cache.set(client_ip, overseas, ttl86400) return overseas # 3. 本地库没命中或结果不明确走在线API country_code query_online_with_retry(client_ip) if country_code CN: cache.set(client_ip, domestic, ttl3600) return domestic elif country_code: cache.set(client_ip, overseas, ttl3600) return overseas # 4. 都失败返回默认值 return unknown这套流程有几个设计细节值得说一说。本地库没有命中时才会去查在线API这样做的好处是既不牺牲绝大多数请求的速度又能弥补本地库更新滞后的缺陷。缓存时间设得不一样也有讲究在线API的结果缓存时间可以短一些比如1小时因为在线结果更接近实时本地库结果缓存一天没问题因为本地库本身更新慢缓存太久也无所谓。有个容易被忽略的点在线API兜底查询一定要做并发控制。如果某一天本地库文件损坏或者线上突然涌入大量新IP可能导致瞬时几百个请求同时打到外部API上直接触发限频。我建议用一个信号量或者简单的令牌桶把在线API的并发限制在10以内宁可排队多等几毫秒也别触发风控封禁。4. 常见问题与排查技巧实录4.1 所有用户都被判断成同一个地区这个现象我见得最多。某个服务突然发现IP判断结果里90%以上的IP都来自同一个城市不用怀疑一定是真实IP获取链路出了问题。排查步骤很简单先看请求日志里记录的 remote address 和请求头里的 X-Forwarded-For 是否一致。如果 remote address 永远是同一个说明前面CDN或负载均衡没有正确透传IP如果 X-Forwarded-For 存在但和客户端实际IP对不上说明有服务把它覆盖成了自己的内网IP。我之前遇到过一个案例前端是阿里云SLB后端是Nginx。所有请求转发到后端的时候Nginx拿到的remote address是SLB的内网IP。后来检查发现SLB透传的是 X-Forwarded-For但Nginx配置里没有设置 real_ip_header导致Nginx一直用的是socket连接地址而不是头里的真实IP。加一行real_ip_header X-Forwarded-For;就解决了。4.2 本地地址库把国内IP判成国外这种问题大多是地址库的老化和运营商地址变动导致的。比如一些宽带运营商拥有多个省级出口用户拨号时动态分配到不同地区的出口IPIP段归属地可能被数据库标记成另一个省甚至出现“国内出口但数据库查出来在国外”的情况。处理方法我总结为三步。第一步确认是单点误判还是大范围误判。单点误判直接忽略大范围误判说明地址库该更新了。第二步更新地址库到最新版本。第三步对仍然频繁出错的IP段在业务侧维护一个小型的人工修正表用数据库或配置文件覆盖默认结果。人工修正表要定期清理因为IP段会被重新分配。4.3 IPv6用户全部走默认分支这个问题前面提过。排查的时候先确认用户的IPv6地址有没有正确到达后端。如果通过了CDN或反代要检查是否把IPv6地址透传出来了。然后看地址库是否支持IPv6。如果不支持最稳妥的方案是换一个支持IPv6的地址库或者对IPv6做单独规则。我见过一个比较取巧的处理IPv6的全球单播地址前面几个bit已经有地区分配的基础规则比如2001:0xxx是亚太地区分配的早期旧地址段2600::是北美分配段2a00::是欧洲分配段。按这个粗糙规则只能做到洲际级别精度不如地址库但至少能避免把全网IPv6用户一刀切。4.4 在线API忽然大面积超时或失败在线API一旦大面积超时基本可以断定是网络连通性问题或者对方服务触发了限频。这时候主流程千万别因为等待外部接口而卡住超时一定要设置失败了一定要降级。我现在的实践是在线API的超时时间设2秒失败后直接返回本地地址库的结果并把本次“未知”结果写入一个短暂的失败缓存10分钟内不重复请求同一个IP。这样即使外部API连续挂了半小时业务也无感知。4.5 动态IP和云厂商出口IP带来的误判移动网络用户的IP经常变而且很多省份的移动用户出口IP会集中到少数几个出口节点导致IP归属地和用户实际位置偏差很大。还有一类情况是某个用户人在国内但他的请求经过了海外云服务器中转这种情况下IP判断结果肯定是国外但实际上他就是一个普通国内用户。这种误判几乎无法通过IP本身解决。我的建议是如果误判会影响关键决策就不要把IP判断结果当“真值”而是结合用户行为、账号信息、访问习惯等多维度做综合判断。IP归属地判断只是其中一个信号。下面整理了一份排查速查表方便遇到问题直接对号入座。问题现象常见原因排查方向解决方案所有IP归属地相同真实IP未穿透CDN或反代检查remote address和转发头配置real_ip_header和可信IP段国内IP被判国外地址库太旧或运营商IP变动核对库版本和IP段更新库、人工修正表、API兜底IPv6全部走默认库不支持IPv6打印IPv6查询结果换支持IPv6的库或单独规则在线查询大面积超时外部接口网络异常或限频观察错误日志和响应码设置超时、失败降级、并发限制人肉核对偏差大动态IP、云厂商出口IP关注IP段运营主体结合其他信号综合判断4.6 上线前后怎么验证判断结果这个很多人会忽略。代码写完了怎么能确定判断是准的我每次上线IP判断功能都会做两件事。第一件事准备一份“已知答案”的测试样本。手动收集20个左右国内IP和20个左右国外IP这些IP的归属地我可以大致确认然后用脚本批量跑一遍统计准确率。准确率在95%以上才能上线。第二件事上线后加两个星期的双写日志。每次判断除了记录结果还把原始IP、地址库返回原文、在线API返回结果全部打到日志里。过几天抽样看场景如果发现“地址库返回中国但API返回国外”这种矛盾情况特别多就要警惕库版本太旧如果两类结果一致但实际体验不对就要考虑是不是IP获取环节就错了。验证不是一次性的。地址库会变CDN配置会变网络环境会变。我建议每季度做一次小规模抽样校验用脚本随机抽100个IP做人工抽查形成习惯之后很多隐患都能在变成事故之前被发现。5. 场景扩展与选型建议5.1 纯前端静态站点怎么做如果你的项目是静态托管在GitHub Pages、OSS或对象存储上的没有后端那离线和后端API方案都不适用。这时候只能在前端做。我的建议是第一层用前端时区和语言判断做默认值第二层如果业务允许再通过浏览器向一个在线API发起查询。需要特别注意的是在线API的查询请求直接暴露在浏览器里要考虑跨域是否允许、限频会不会把用户IP封掉。有些API对浏览器直接调用支持得不好遇到跨域拦截就只能换一个或放弃。纯前端方案能解决的场景是“体验优化”不是“精确判断”。想靠纯前端做到高准确率是不现实的这点要认清。5.2 后端高频接口怎么选后端接口的流量特点是请求量大、并发高、对延迟敏感。我强烈建议不要在主线程上同步调用外部API而是用离线库加内存缓存这条路线。在实现上把地址库文件加载到内存查询全部用内存查询单次查询耗时微秒级并发上百万都没有压力。缓存层再配一个LRU把热门IP的结果缓存起来进一步减少地址库查询压力。整个服务的瓶颈不在查询而在你的框架本身。我遇到过一个极端场景某个视频网站的灰度发布需要判断用户区域网关层每秒要处理几万次请求。最后就是用一个Go写的本地地址库插件在网关里直接查单机跑满没问题延时增加几乎可以忽略。5.3 已经接入CDN的站点怎么做如果你已经接了CDN我的第一推荐永远是读CDN的请求头而不是自己再查一遍地址库。原因很简单CDN已经帮你识别了IP归属你再查一遍是重复劳动还增加延迟和出错概率。更进一步如果业务允许尽量把判断放到边缘计算层在边缘节点直接决定返回什么内容。这样效果最好用户访问的第一个请求就能得到正确版本回源次数也大大减少。使用CDN方案需要注意的是不同厂商的请求头名称和规范不一样而且如果你同时用了多个CDN做容灾各家的头可能不统一。我的做法是在网关或入口层写一个统一的“区域识别中间件”把不同CDN的请求头统一解析成标准字段业务侧只认这个标准字段。5.4 数据分析场景怎么做报表统计和数据分析场景对实时性要求不高但对覆盖率和一致性要求高。这种场景适合离线批量处理。拿日志或者订单数据里的IP列表批量用本地地址库跑一遍输出结果存到数据仓库。这类场景要特别注意历史数据回溯的问题。IP归属地数据库是动态的“当年的IP归属地”和“现在的IP归属地”可能不一样。如果你在分析前年的日志用今年的库去回填归属地结果会有偏差。严格的做法是保留当时查询用的数据库版本或者至少记录查询日期方便后续校正。5.5 最后分享一个我踩过的坑这个坑我记忆特别深刻也特别值得写在最后。有一次我给一个客户做订单区域统计方案本身没问题本地地址库加在线API兜底做得都挺好结果上线当天数据就乱了。排查了一下午最后发现是Nginx配置里的 real_ip_header 写错了导致所有请求的真实IP都没有被正确识别。真实IP不对后面所有判断都成了空中楼阁。所以在做IP判断时我强烈建议你先把真实IP获取链路单独验证一遍。最简单的方法就是写一个临时接口把请求进来时能看到的 remote address、所有转发相关请求头都原样打印出来然后用你的手机4G、5G网络访问一次对比一下打印出来的IP是不是你手机的公网IP。这一步走通了后面的事情会顺很多。也正因为如此我在给任何项目设计方案时永远把“真实IP解析”放在地址库和API之前这部分是根根不对枝叶再茂盛也是白搭。