最新多种方式, 判断客户端IP是国内还是国外?

1 、前言

如何根据IP判断是国内的IP还是国外的IP呢?

应用场景大多是网站开发时中英文版本的自动判断。

相信大多数人肯定会推荐淘宝的免费API,但是目前已经无法访问,并且也很不稳定。

也会有人推荐跳过IP判断,根据当前系统语言判断,虽然速度快,但不准确:

var Browser_Agent = navigator.userAgent;// 浏览器为IE的情况 if (Browser_Agent.indexOf(" MSIE ") !=- 1 ){var a = navigator.browserLanguage;if (a != " zh-cn " ){// 英文网站console.log("英文网站");}}// 浏览器非IE的情况 else {var b = navigator.language;if (b != " zh-CN " ){// 英文网站console.log("英文网站");}}
}

那么,问题来了,除了这些看起来不太靠谱的方法,还有其他实现方法或者第三方方法吗?

答案是当然有,付费的有,我们这里只讲一些免费第三方的方法和可以自己实现的方法。

我们的需求是只需要判断IP是国内外,而不需要判断IP的具体城市,所以问题变得简单了许多。

2、实现方法

2.1、第三方库

比较强大的第三库,不得不推荐MaxMind的GeoIP®Databases and Services,他们有自己的IP库,提供各种准确的接口,付费的可以根据定位很准确,不付费的只可以模糊定位到国家,不过已经符合我们的需求。



基于javaScript实现

https://dev.maxmind.com/geoip/geoip2/javascript/tutorial/

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><div>目前所在:<span id="result"></span></div><script src="//geoip-js.com/js/apis/geoip2/v2.1/geoip2.js" type="text/javascript"></script><script>var test = (function () {var onSuccess = function (geoipResponse) {/* There's no guarantee that a successful response object* has any particular property, so we need to code defensively. */if (!geoipResponse.country.iso_code) {return;}/* ISO country codes are in upper case. */var code = geoipResponse.country.iso_code.toLowerCase();document.getElementById('result').innerHTML = code;};var onError = function (error) {};return function () {geoip2.country(onSuccess, onError);};}());test();</script>
</body></html>

通过这个免费的javascript API,就可以判断当前IP是否是国内外,因为我开代理测试的,所以显示当前IP是新加坡。

MaxMind的其他实现方式,有的是需要付费,按次收费,获取到的数据也会更加详细更加准确。

2.2、自己实现

上一个方式是借助第三方免费API判断IP所在国家,那么如果是我们做,该怎么做呢?

我们只需要判断IP所在国家是国内外即可。

首先我们需要获取IP库,通过IP库判断;

IP库

ip库在apnic的官方网站上可以下载

http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest

那么我们该如何处理呢?总体上分为以下几个部分:

1、使用脚本定期从apnic``下载IP库,筛选出所有apnic|CN|ipv4, 生成china_ip.txt其实这里如果只判断CN不太准确,HK、MO和TW被apnic分成单独的,他们都是中国的领土不可被分割,不过我们这里不增加这个逻辑了,所以确切这个文本内的IP是中国大陆的IP);

2、基于java解析生成的china_ip.txt,这样的话相比解析全部IP,解析的成本就低了不少;

3、解析IP列表,存储到Redis, 定时N小时过期,保证实时IP库的更新;

4、根据IP到Redis中存储的数据做判断,如果在各个地址段范围内,表示是国内ip, 否则是国外ip;

5、判断过的IP也会保存在Redis,避免重复的判断。

2.2.1 定时更新下载IP库

download.sh

每次下载耗时10mins

每次生成时,会先生成一个china_ip_new文件,成功之后才会替换原来文件china_ip

ip_url=http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest
ip_txt=china_ip.txt
ip_txt_new=china_ip_new.txt
ip_backup_folder=backup
cur_date=$(date +'%Y%m%d%H%M%S')
cur_path=/home/project/ip# 删除上一个ip_txt_new文本
if [ -f ${ip_txt_new} ]; thenrm -rf ${ip_txt_new}
fi# 除第一个生成ip.txt 文本外,第二次更新,要先生成新的ip_new.txt,不能直接删除ip.text, 文本下载需要12min,以免此期间影响业务代码访问该文本
curl ${ip_url} | grep ipv4 | grep CN | awk -F\| '{ printf("%s/%d\n", $4, log($5)/log(2)) }' > ${ip_txt_new}echo 'download ip text complete'echo 'start backup and replace'# 生成ip_new.txt后, 加上日期后缀备份上一份ip.txt, 然后替换当前ip.txt为最新的 ip_new.txt
if [ ! -d ${ip_backup_folder} ]; thenmkdir ${ip_backup_folder}
fiif [ -f ${ip_txt} ]; thenmv ${ip_txt} ${cur_path}/${ip_backup_folder}/${ip_txt}_${cur_date}
fiif [ -f ${ip_txt_new} ]; thenmv ${ip_txt_new} ${ip_txt}
fiecho 'generate new ip text complete'

加上定时任务

[root@10 ip]# crontab -e
0 0 * * * sh /home/project/ip/download.sh >> /home/project/ip/download.log 2>&1

0 0 * * *为cron表达式,代表着每天凌晨0:00更新下载

    cron表达式	*    *    *    *    *    -    -    -    -    -    |    |    |    |    |    |    |    |    |    +----- 星期几 (0 - 7) (Sunday=0 or 7)|    |    |    +---------- 月份 (1 - 12)|    |    +--------------- 几号 (1 - 31)|    +-------------------- 小时 (0 - 23)+------------------------- 分钟 (0 - 59)

保存即可;

查询定时任务:

crontab -l


china_ip.txt

保存文件格式类似为 222.126.128.0/15

在IP库中:

比如222.126.128.0/15对应的文本应该是apnic|CN|ipv4|222.126.128.0|32768|20060830|allocated中的222.126.128.0|32768;

在IP库中,所有的IP不分国家,按照IP的顺序从上而下排列着;

   apnic|CN|ipv4|222.126.128.0|32768|20060830|allocated-    -    -        -         -    |    |    |        |         |    |    |    |        |         +----- 代表着该IP段下有32768个地址|    |    |        +--------------- IP地址|    |    +------------------------ ipv4|    +----------------------------- CN代表中国+---------------------------------- 代表apnic那么为什么`222.126.128.0`下有`32768`个地址呢,是怎么计算的呢?
那么我们需要根据下一个IP判断,如上图所示下一个IP是    
`apnic|PH|ipv4|222.127.0.0|32768|20060913|allocated`
PH是菲律宾,说明这个IP就是菲律宾了。`apnic|CN|ipv4|222.126.128.0|32768|20060830|allocated`
`apnic|PH|ipv4|222.127.0.0|32768|20060913|allocated`IP:
IP是Internet Protocol(网际互连协议)的缩写,是TCP/IP体系中的网络层协议;
`IP地址是一个32位的二进制数,通常被分割为4个“8位二进制数”(也就是4个字节)。IP地址通常用“点分十进制”表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~255之间的十进制整数`;
所有a,b,c,d都有2的32次方随机匹配地址数。
所以:
从`222.126.128.0``222.127.0.0`,有多少种可能?
`a相同,b=126到b=127地址数加1,c=128到c=0剩余128个选择,d有256种选择`
`计算公式=1*128*256=32768个地址`我们还可以这么假设,
由于c*d=256*256=65536,当`apnic|CN|ipv4|222.160.0.0|131072|20031212|allocated``131072`大于65536说明,a.b.*.*也就是222.160.*.*无论都两个整数是什么,都属于中国IP; 并且由于131072=65536*2,说明b+1后的a.b+1.*.*也就是222.161.*.*全部是中国IP;
那么这个结果正确吗,让我们从IP库数据看下:
`apnic|CN|ipv4|222.160.0.0|131072|20031212|allocated`
`apnic|CN|ipv4|222.162.0.0|65536|20031212|allocated`
下一条数据222.162.0.0,所以我们的假设是正确的。虽然不知道为什么Apnic没有把222.162.0.0一并合并到222.160.0.0,但是我们的假设是正确的,第五位的数字代表着基于当前IP按正顺序下的地址数。所以我们可以这么处理数据:
`首先判断第五位的数字,``如果第五位数字超过65536,` 
`代表a.b.*.*都是中国IP,如果是65536的N倍,那么代表着`
a.b.*.*
a.b+1.*.*
....
a.b+n-1.*.*
`全部都是中国IP,可以标记为ALL, 后续比较前两位就可以判断是否是中国IP`。
存储数据结构为:
{a: {b: "all",b+1: "all",....b+n-1: "all"}
}`如果第五位数字不超过65536呢?`
apnic|CN|ipv4|1.2.16.0|4096|20110412|allocated
apnic|CN|ipv4|1.2.32.0|8192|20110412|allocated
apnic|CN|ipv4|1.2.64.0|16384|20110412|allocatedIP转换十进制(a.b.c.d)= a*256^3+b*256^2+c*256+d
这里以当前IP转化为十进制为起始范围,加上第五位地址书为终止范围。
如果要判断的IP转化的十进制数后,判断是否在这个范围内,范围内则为中国IP;
并且同一个a.b,后面会有多端地址,如:
apnic|CN|ipv4|14.1.0.0|1024|20110414|allocated
apnic|JP|ipv4|14.1.4.0|1024|20100910|allocated
apnic|JP|ipv4|14.1.8.0|2048|20100910|allocated
apnic|AU|ipv4|14.1.16.0|1024|20100916|allocated
apnic|HK|ipv4|14.1.20.0|1024|20100920|allocated
apnic|CN|ipv4|14.1.24.0|1024|20151214|allocated
中间还掺杂着其他国家的IP,所以需要分段存储如下:
存储数据结构为:
{a: {b: ["16777472-16777728""16777728-16778240""16779264-16781312""16785408-16793600"]}
}`全部数据存储在Redis里面,2小时有效期(再次读取自动生成的IP库更新数据),结构如下:`
{a: {b: "all",b+1: "all",....b+n-1: "all",b: ["16777472-16777728""16777728-16778240""16779264-16781312""16785408-16793600"]}
}
那么如何根据以上的数据判断一个IP是否属于国内IP呢?
`首先根据a查询是否存在,再根据b查询是否存在;`
`如果b存在,如果是all, 直接返回true;`
`如果是数组,需要判断地址是否存在于数组中的某一个范围内,存在就返回true;`
`其他返回false;`这样的话,查询效率变得快了很多。
每次被查询的IP结果也会被存储在redis中,下次查询同样的IP,可以直接返回结果。



2.2.2、代码实现

com.scaffold.test.utils.IpUtils

工具类

package com.scaffold.test.utils;import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.nio.charset.StandardCharsets;
import java.util.*;public class IpUtils {public static final String IPURL = "http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest";/*** 获取客户端IP* @return*/public static String getIpAddress() {String UNKNOWN = "unknown";String LOCALHOST = "127.0.0.1";String LOCALHOST2 = "0:0:0:0:0:0:0:1";String SEPARATOR = ",";String ipAddress = "";try {HttpServletRequest request = HttpUtils.getRequest();ipAddress = request.getHeader("x-forwarded-for");if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {ipAddress = request.getHeader("Proxy-Client-IP");}if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {ipAddress = request.getHeader("WL-Proxy-Client-IP");}if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {ipAddress = request.getRemoteAddr();if (LOCALHOST.equalsIgnoreCase(ipAddress) || LOCALHOST2.equalsIgnoreCase(ipAddress)) {InetAddress inetAddress = null;try {inetAddress = InetAddress.getLocalHost();} catch (Exception e) {e.printStackTrace();}ipAddress = inetAddress.getHostAddress();}}} catch (Exception e) {e.printStackTrace();}return ipAddress;}/*** 获取IP Map数据*/public static Map<String, Object> getIpList() {// 集合存放Ip第一段Map<String, Object> ipMap = new HashMap<>();try {InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream("static/china_ip.txt");List<String> lines = IOUtils.readLines(input, StandardCharsets.UTF_8);// 读取文件内所有的中国IPfor (String line : lines) {if (!StringUtils.isEmpty(line)) {JSONObject parentObj = new JSONObject();String[] ips = line.split("\\.");// 得到一个ip地址段的起始范围 101int ip1 = Integer.parseInt(ips[0]);int ip2 = Integer.parseInt(ips[1]);/** 101.80.0.0/20 等于  apnic|CN|ipv4|101.80.0.0|1048576(2的20次方)* 101.96.0.0/11 等于  apnic|CN|ipv4|101.96.0.0|2048(2的11次方)* 类似如此数据,IP网端每个地址数256也就是2的8次方,总共是2的32次方* 所以如果最后一个数值超过 16,意味着后两个网络被占满,前面的网段需要递增* 101.80.0.0/20 中 20 意味着后两个网段已满, 第二个网络端递增 2的(20-16)次方等于16* 101.80.0.0/20 = 以下IP从 80 ~ 95 全网端都是中国IP* 101.80.0.0* 101.81.0.0* 101.82.0.0* ...* 101.95.0.0*/// 获取从当前IP段开始的总地址数String[] strs = line.split("\\/");long addressCount = Long.parseLong(strs[1]);// 存储各个网络段JSONObject object = new JSONObject();if (ipMap.get(String.valueOf(ip1)) != null) {object = (JSONObject) ipMap.get(String.valueOf(ip1));}// 判断是否后两个字段被占满if (addressCount > 16) {// 后两个字段被占满时,也就是地址数大于 256*256=65536=2的16次方double pow = Math.pow(2, addressCount - 16);for (int i = 0; i < pow; i++) {object.put(String.valueOf(ip2 + i), "all");}} else {/*** apnic CN三个连续数据如下* 101.96.0.0/11* 101.96.8.0/10* 101.96.16.0/12* ---------------------* 如上在第二网段相同的情况* 101.96.0.0/11 等于* 101.96.0.0* ...* 101.96.7.0* 共8个*----------------------* 101.96.8.0/10 等于* 101.96.8.0* ...* 101.96.11.0* 共4个*----------------------* 101.96.16.0/12 等于* 101.96.16.0* ...* 101.96.31.0* 共16个* ---------------------* 从上述数据中看到 101.96.11.0 到 101.96.16.0 出现了断层,中间内容不属于中国的IP* 所以都需要被记录下来,多个IPRange 我们使用数组存储*/// 转换IP为longlong start_ip = ipv4ToLong(strs[0]);long ip_range = (long) Math.pow(2, addressCount);long end_ip = start_ip + ip_range;String ipRange = start_ip + "-" + end_ip;// 判断是否已存在已有数据JSONArray ipRangeExist = (JSONArray) object.get(String.valueOf(ip2));if (ipRangeExist == null) {ipRangeExist = new JSONArray();}ipRangeExist.add(ipRange);object.put(String.valueOf(ip2), ipRangeExist);}ipMap.put(String.valueOf(ip1), object);}}System.out.println(ipMap);} catch (Exception e) {e.printStackTrace();}return ipMap;}// 求出 IPV4 IP地址所对应的整数,比如 192.168.66.6 对应整数 3232252422// 192*256*256*256 + 168*256*256 + 66*256 + 6 = 3232252422// IP转换十进制(a.b.c.d)= a*256^3+b*256^2+c*256+dpublic static long ipv4ToLong(String ip) {String[] ips = ip.split("\\.");long result = 0;for (int i = 0; i < ips.length; i++) {result += Long.parseLong(ips[i]) * Math.pow(256, 3 - i);}return result;}/*** 判断IP是不是在中国** @param ipMap 中国ip集合* @param ip    传入的ip* @return true*/public static boolean ipInChina(Map<String, Object> ipMap, String ip) {if (ipMap == null) {ipMap = getIpList();}// 判断 IP 是否存在if (StringUtils.isEmpty(ip)) {return false;}// 第一个IP端作为keyString[] ipArr = ip.split("\\.");String key = ipArr[0];String childKey = ipArr[1];// 当前IP转换为整数long ip_long = ipv4ToLong(ip);// 判断第一个IP端存在if (ipMap.containsKey(key)) {JSONObject parentObj = (JSONObject) ipMap.get(key);// 判断第二个IP段是否存在if (parentObj.getString(childKey) != null) {String ipRange = parentObj.getString(childKey);if (ipRange.equals("all")) {// 整个其余网段都是中国IPreturn true;} else {JSONArray ipRangeArray = JSONArray.parseArray(ipRange);for (Object range : ipRangeArray) {String[] ipRanges = String.valueOf(range).split("\\-");if (ipRanges.length == 2) {long ipRange_start = Long.parseLong(ipRanges[0]);long ipRange_end = Long.parseLong(ipRanges[1]);// 判断是否在范围内return ip_long >= ipRange_start && ip_long <= ipRange_end;}}}}}return false;}}

com.scaffold.test.redis.RedisUtils

redis工具类

package com.scaffold.test.redis;import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;/*** @author alex* Redis工具类*/@Component
public class RedisUtils {@Resourceprivate RedisTemplate<String, Object> redisTemplate;/*** 设置key value** @param key key* @param value value*/public void set(String key, Object value) {redisTemplate.opsForValue().set(key, value);}/*** 设置key value,并设置过期时间** @param key key* @param value value* @param timeout 过期时间* @param unit 时间单位, 天:TimeUnit.DAYS 小时:TimeUnit.HOURS 分钟:TimeUnit.MINUTES*            秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS*/public void set(String key, Object value, long timeout, TimeUnit unit) {redisTemplate.opsForValue().set(key, value, timeout, unit);}/*** 获取** @param key* @return*/public Object get(String key) {return redisTemplate.opsForValue().get(key);}/*** 删除一个** @param key 键 可以是一个,也可以是','连接的多个,比如1,2,3,4* @return true*/public Object delete(String key) {String splitChar = ",";if (key.contains(splitChar)) {String[] keys = key.split(splitChar);List<String> keyList = Arrays.asList(keys);return redisTemplate.delete(keyList);} else {return redisTemplate.delete(key);}}/*** 是否存在key** @param key* @return*/public Boolean hasKey(String key) {return redisTemplate.hasKey(key);}
}

redis配置

com.scaffold.test.config.RedissonConfig

可以存储list

package com.scaffold.test.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import javax.annotation.Resource;@Configuration
public class RedissonConfig {@Resourceprivate RedisTemplate<String, Object> redisTemplate;@Beanpublic RedisTemplate<String, Object> setRedisTemplate(){// 使用 Jackson2JsonRedisSerialize 替换默认序列化Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper objectMapper = new ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(objectMapper);// 字符串序列化RedisSerializer stringSerializer = new StringRedisSerializer();redisTemplate.setKeySerializer(stringSerializer);// value 使用 Jackson2JsonRedisSerialize 序列化redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);redisTemplate.setHashKeySerializer(stringSerializer);redisTemplate.setHashValueSerializer(stringSerializer);return redisTemplate;}}

com.scaffold.test.controller.RedisController

控制层

package com.scaffold.test.controller;import com.alibaba.fastjson.JSONObject;
import com.scaffold.test.base.Result;
import com.scaffold.test.base.ResultGenerator;
import com.scaffold.test.config.annotation.PassToken;
import com.scaffold.test.redis.RedisUtils;
import com.scaffold.test.utils.IpUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;/*** @author alex*/@RestController
@RequestMapping("redis")
public class RedisController {@Autowiredprivate RedisUtils redisUtils;/*** (耗时1~2S)* 获取IP并存入redis, 判断是国内外IP** @return*/@GetMapping("ip")public Result setIp(String ip) {if (ip == null) {ip = IpUtils.getIpAddress();}// 判断IP判断结果是否在缓存数据中Object exist = redisUtils.get(ip);JSONObject object = new JSONObject();object.put("ip", ip);if (exist == null) {Map<String, Object> ipData;// 从缓存中获取数据JSONObject ip_map = (JSONObject) redisUtils.get("ip_map");if (ip_map != null) {ipData = JSONObject.parseObject(ip_map.toJSONString());} else {ipData = IpUtils.getIpList();redisUtils.set("ip_map", ipData, 2, TimeUnit.HOURS);}Boolean inChina = IpUtils.ipInChina(ipData, ip);object.put("country", inChina ? "CN" : "other");redisUtils.set(ip, inChina);} else {object.put("country", exist.equals(true) ? "CN" : "other");}return ResultGenerator.setSuccessResult(object);}
}

以上方案代码实现,初次只需2s,再次查询会更快。

2.3、ip2region

数据库是ip2region.db,放到静态资源目录下

<!--ip2region-->
<dependency><groupId>org.lionsoul</groupId><artifactId>ip2region</artifactId><version>1.7.2</version>
</dependency>

工具类方法

    /*** ip2region** @param ip* @return*/public static String getCityInfo(String ip) ClassPathResource classPathResource = new ClassPathResource("/data/ip2region.db");InputStream inputStream = null;File file = null;try {inputStream = classPathResource.getInputStream();file = File.createTempFile("ip2region_tmp", ".db");FileUtils.copyInputStreamToFile(inputStream, file);} catch (IOException e) {e.printStackTrace();} finally {IOUtils.closeQuietly(inputStream);}if (!file.exists()) {log.error("Error: Invalid ip2region.db file");}//算法int algorithm = DbSearcher.BTREE_ALGORITHM;try {DbConfig config = new DbConfig();DbSearcher searcher = new DbSearcher(config, file.getPath());// 方法Method method = null;switch (algorithm) {case DbSearcher.BTREE_ALGORITHM:method = searcher.getClass().getMethod("btreeSearch", String.class);break;case DbSearcher.BINARY_ALGORITHM:method = searcher.getClass().getMethod("binarySearch", String.class);break;case DbSearcher.MEMORY_ALGORITYM:method = searcher.getClass().getMethod("memorySearch", String.class);break;default:break;}DataBlock dataBlock = null;if (!Util.isIpAddress(ip)) {log.error("Error: Invalid ip address");}dataBlock = (DataBlock) method.invoke(searcher, ip);return dataBlock.getRegion();} catch (Exception e) {e.printStackTrace();}return null;}/*** 获取IP地址** @return*/public static String getIpAddress() {String UNKNOWN = "unknown";String LOCALHOST = "127.0.0.1";String LOCALHOST2 = "0:0:0:0:0:0:0:1";String ipAddress = "";String SEPARATOR = ",";try {HttpServletRequest request = HttpUtils.getRequest();ipAddress = request.getHeader("x-forwarded-for");if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {ipAddress = request.getHeader("Proxy-Client-IP");}if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {ipAddress = request.getHeader("WL-Proxy-Client-IP");}if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {ipAddress = request.getRemoteAddr();if (LOCALHOST.equalsIgnoreCase(ipAddress) || LOCALHOST2.equalsIgnoreCase(ipAddress)) {InetAddress inetAddress = null;try {inetAddress = InetAddress.getLocalHost();} catch (Exception e) {e.printStackTrace();}ipAddress = inetAddress.getHostAddress();}}// 多层转发时,获取到的是多个IP,第一个是真实IPif(ipAddress.contains(SEPARATOR)){ipAddress = ipAddress.split(SEPARATOR)[0];}} catch (Exception e) {e.printStackTrace();}return ipAddress;}

控制器

	/*** 获取IP归属地** @return Object*/@GetMapping("me")public Result setIp(String ip) {if (ip == null) {ip = IpUtils.getIpAddress();}log.info("The current ip is " + ip);JSONObject object = new JSONObject();if (!Util.isIpAddress(ip)) {object.put("ip", ip);log.error("Error: Invalid ip address");object.put("error", "Error: Invalid ip address");object.put("country", null);} else {object.put("ip", ip);String cityInfo = IpUtils.getCityInfo(ip);if (cityInfo.length() > 0) {String[] strings = cityInfo.split("\\|");object.put("origin", cityInfo);object.put("country", strings[0]);}}return ResultGenerator.setSuccessResult(object);}

目前最推荐的就是当前方法,简单易行。

3、总结

以上代码算是比较准确计算IP位置,但是目前只获取了CN。如果更加准确的话,应该加上HK、MO和TW的数据。

他们都是中国的领土,不可分割。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/60482.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

九龙证券|AI重塑半导体基础设施,人工智能发展持续加速

近四十年来&#xff0c;摩尔定律一直是推进核算机职业开展的中心驱动力。随着摩尔定律接近极限&#xff0c;在成本和功耗的两层制约下&#xff0c;指数级的功用增加逐步放缓。而AI和加快核算则是在算力需求激增、数据中心功耗暴升背景下延续摩尔定律&#xff0c;完成可继续开展…

人工智能AI主题汇总(至2023年02月11日)

人工智能&#xff08;AI&#xff09;是指机器执行人类能够轻松完成的感知、推理、学习和解决问题等认知功能的能力。过去20年以来&#xff0c;由于互联网产生的海量数据的可用性&#xff0c;人工智能在全球范围内获得了关注。最近&#xff0c;OpenAI上线了ChatGPT超级机器人对话…

腾讯微信,今年取消了24届秋招。。。

大家好&#xff0c;我是菜哥&#xff01; 算算我也是混迹IT多年的老菜鸟&#xff0c;今天聊聊互联网圈的一个八卦事情。 最近&#xff0c;有一则新闻在互联网行业内引起了广泛关注&#xff0c;那就是腾讯微信团队宣布取消了2024年秋季的校园招聘&#xff0c;转而全面采取实习生…

实用工具篇(二):终端连接神器Tabby

1.Tabby概述 一名 Java 后端开发&#xff0c;日常工作中免不了要和 Linux 服务器打交道&#xff0c;因为生产环境基本上都是部署在 Linux 环境下的。以前我使用的终端工具都是FinalShell&#xff0c;但我又发现一款更好更方便的终端工具---Tabby。下面将介绍这款终端工具的安装…

我最喜欢的油猴脚本——可以追跑某盘SVIP

如图是使用中的画面 1、下载aria2的工具_可以自行github_aria2 当然你也可以抄个小路&#xff08;此路是我栽&#xff0c;喜欢的话赞一个呗&#xff09; 链接&#xff1a;https://pan.baidu.com/s/10ejQGoNd2pN7fzsF-8po7w 提取码&#xff1a;9pxh 下载完点开启&#xff0c;…

如何同步油猴脚本(非常简单,实践可行,不要看其他博客了,看我的就行)

如何同步油猴脚本(非常简单&#xff0c;实践可行&#xff0c;不要看其他博客了&#xff0c;看我的就行) 背景 查了一些别的博客&#xff0c;他们写得很全很强大&#xff0c;但是写了这么多方式&#xff0c;读者只需要最简单的方式 我这篇文章2分钟就教会你了。(嫌麻烦跳过 “…

第一台实际运行程序的计算机诞生 | 历史上的今天

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 5 月 6 日&#xff0c;在 1998 年&#xff0c;奔驰汽车公司与克莱斯勒公司合并&#xff0c;成为历年来汽车制造业最大的一起合并。两家公司将以换股方式进行合并…

做大模型领域的“Linux”!智源 FlagOpen 大模型技术栈全部开源

AI 时代封闭生态正在形成&#xff0c;需要建立开源开放的“新 Linux”生态体系。 作者 | 唐门教主 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 2023 年 2 月 28 日&#xff0c;在位于北京海淀的智源人工智能研究院里&#xff0c;FlagOpen 飞智大模型技术开源体…

干货分享|编辑视频有哪些选择,常见视频剪辑软件分享

如果我们想更好地制作精彩的视频&#xff0c;精彩的剪辑是绝对必要的。我们可以通过编辑在日常生活中达到无法实现的效果&#xff0c;巧妙地使用编辑也会给我们的视频增加很多分数。 说到视频剪辑&#xff0c;adobe全家桶应该是绕不开的话题了。PR&#xff0c;AE&#xff0c;AU…

视频剪辑软件哪个比较好用?这几款常用的视频剪辑软件你要知道

众所周知&#xff0c;一个好的视频肯定离不开好的剪辑&#xff0c;我们需要用各种剪辑技巧来让自己的视频在短视频中脱颖而出&#xff0c;这样我们的视频才能被更多人看见&#xff0c;被更多人喜欢。那你们知道好的视频剪辑用什么软件吗&#xff1f;今天我来分享我个人觉得还不…

Ubuntu 如何启动、停止或重启服务

在本文中&#xff0c;我们向您介绍在 Ubuntu 中启动、停止和重启服务的方法。 列出 Ubuntu 中的所有服务 在开始之前&#xff0c;先获取计算机上所有服务的列表&#xff0c;因为我们需要知道服务名称来管理服务。 service --status-all 它将显示 Ubuntu 上的完整服务列表。…

chatgpt赋能python:Python中等待一秒的语句:让你的程序暂停与等待

Python中等待一秒的语句&#xff1a;让你的程序暂停与等待 当编写Python程序时&#xff0c;经常需要添加暂停或延迟功能&#xff0c;以使程序能够在执行某些操作之前或之后等待一段时间。Python拥有一个内置的语句可以实现这种延迟&#xff1a;time.sleep()。 什么是 time.sl…

Android TTS的暂停与恢复功能

最近有同事做项目遇到这样一个需求&#xff0c;就是通过原生TTS播报文本希望能有暂停的功能&#xff0c;目前原生TTS接口TextToSpeech没有提供相应的接口&#xff0c;所以只能通过其他途径进行解决&#xff0c;目前初步的解决思路是&#xff1a; &#xff08;1&#xff09;通过…

为什么好多人说uc浏览器是最牛的浏览器?

UC浏览器最强大的功能是视频缓存&#xff0c;很多人都因为它可以视频缓存而下载使用。但是&#xff0c;随着它这项功能的消失&#xff0c;越来越多的人开始选择舍弃UC浏览器了&#xff0c;为何呢&#xff1f; 1.广告增多 我们发现UC浏览器的广告开始增多&#xff0c;整体的浏…

继 QQ 被曝扫描浏览器历史记录之后,微信也被曝出会扫描所有 Chromium cookies

| 快讯 文章目录 微信被曝出扫描所有 chromium cookies腾讯技术人员最新回复 微信被曝出扫描所有 chromium cookies 1 月 18 日&#xff0c;PC 端 QQ 被曝出启动 QQ 时&#xff0c;QQ会自动扫描所有浏览器的历史记录。 1 月 19 日&#xff0c;经火绒证实&#xff0c;QQ 的确…

夸克、QQ浏览器、简单搜索竞品分析报告

作者&#xff1a;迷航 &#xff08;转载已取得作者授权&#xff09; 一、竞品分析目的 伴随着中国移动互联网的人口红利逐渐到顶&#xff0c;手机浏览器市场的马太效应也越发强烈。在激烈的竞争环境下&#xff0c;仍有新价值要素正在带来新的机会&#xff1a;Z时代、00后用户逐…

告别用了8年的QQ浏览器,这也许是我换浏览器的原因吧

手机浏览器作为高频率使用的应用之一&#xff0c;几乎每个人都会用到&#xff0c;相信大家也一定会有自己常用的一个手机浏览器。不管别人说这个浏览器多不好、多流氓&#xff0c;因为用习惯了&#xff0c;你可能都不愿意去换。 就像我一样&#xff0c;用QQ浏览器用了8年。虽然…

这几款手机浏览器真的牛,比夸克更好用

以前给大家推荐过不少浏览器&#xff0c;比如Alook浏览器、UC浏览器等。一些网友都喜欢轻便干净、极简大方的浏览器。但是&#xff0c;也有一些网友则喜欢功能全面的浏览器。今天&#xff0c;再给大家推荐3款浏览器&#xff0c;这3款浏览器真是牛的离谱&#xff0c;比夸克更好用…

夸克真的实用吗,其实除了它还有更好用的浏览器

夸克是一个非常强大的浏览器&#xff0c;之前写过一篇关于夸克的文章&#xff0c;很多人在评论区列举了夸克的各个优点&#xff0c;由此可见夸克是符合时代的优秀工具。然而&#xff0c;也有人表示&#xff0c;要卸载夸克&#xff0c;因为它脱离了浏览器路线&#xff0c;越来越…

高等数学学习笔记——第七十一讲——多元函数的泰勒公式

1. 问题引入——“以平代曲”与“以曲代曲” 2. 一元函数的导数&#xff0c;二元函数一阶导数&#xff0c;梯度及二元函数的二阶矩阵&#xff08;海塞矩阵&#xff09; 3. 海塞矩阵计算示例 4. 二元函数的带拉格朗日余项的麦克劳林公式 5. 二元函数的泰勒公式及拉格朗日中值公式…