详解根据IP查询所在国家地区的后台实现方案
文章目录
- 摘要
- 实现过程
- 思路
- GeoLite2 IP库文件引入
- 准备工作
- 代码实现
- Ipdata API
- 准备工作
- 官网账号注册
- 创建API Key
- API文档:
- 代码实现
- 完整代码
- 总结
摘要
本文详解如何封装一个根据IP获取所在国家地区的接口。不管是网站、APP还是PC软件,如果你的目标客户包含国内和海外,那必然有多语言等需要区分用户所在区域的功能,虽然说IP定位会有开VPN的特殊场景影响导致不准确(国内被定位到海外,一般来说产品可接受),并且APP本身有其他判断用户所在地的方式,比如安卓可以通过GPS,但是对于一个完整的方案来说,在客户端其他更准确的方式失效或者由于用户不给APP权限导致无法使用时,有一个IP查询的接口对接也是一个必要的兜底方式。
实现过程
思路
世界范围内IP的数据是不停在更新的,为确保尽可能多的IP能通过接口查询到对应的所在区域,我们需要更新尽量频繁的数据源,ipdata是个不错的选择,它提供API,传入IP即可返回IP相关的国家、城市、时区等信息,但是Ipdata免费调用次数只有一天1500次,需要更多调用次数得花钱。所以我们搭配一个固定的ip数据文件GeoLite2-City.mmdb查询,优先从项目集成的数据文件查询,查询不到才去调用ipdata,并且还可以做个ipdata调用结果的缓存设计,毕竟一个ip属于什么国家是不会频繁变化的,你哪怕缓存几天,都能大大降低ipdata调用次数的浪费。
GeoLite2 IP库文件引入
准备工作
你可以直接从MaxMind的官方网站上下载GeoLite2-City.mmdb数据库文件:下载链接。
服务器部署的时候将文件放到服务器上,如果是容器部署服务将文件挂载到容器的自定义目录中,比如我举例叫/home/my_dir/目录。
代码实现
Maven依赖:
<dependency>
<groupId>com.maxmind.geoip2</groupId>
<artifactId>geoip2</artifactId>
<version>2.8.1</version>
</dependency>
写一个根据GeoLite2-City.mmdb数据文件查询IP所在国家编码的方法,如果是IP属于中国并且IP在数据文件中,就会返回”CN“:
private String getCountry(String ip) throws IOException, GeoIp2Exception {
// 容器中读取数据文件的路径
File database = new File("/home/my_dir/GeoLite2-City.mmdb");
// 读取数据库内容
DatabaseReader reader = new DatabaseReader.Builder(database).build();
InetAddress ipAddress = InetAddress.getByName(ip);
// 获取查询结果
CityResponse response = reader.city(ipAddress);
//获取country
Country country = response.getCountry();
return country.getIsoCode();
}
Ipdata API
准备工作
接下来进入我们的重点,我们来看ipdata的调用方式和实现。
官网账号注册
官网注册页地址:官网。
创建API Key
登录后你能看到官方为每个用户账号分配了一个API key:
API文档:
从接口文档可以看出调用方式非常简单,Ip+API key GET请求即可,接口文档地址:文档。
代码实现
我们来看下ipdata调用逻辑的代码实现:
private Map<String, String> getCountryByIpData(String ip) {
String url = "https://api.ipdata.co/" + ip + "?api-key=" + ipDataSecret;
String resultStr = HttpUtils.get(url, null);
JSONObject resultObject = JSON.parseObject(resultStr);
Map<String, String> result = new HashMap<>();
if (resultObject != null) {
result.put("countryCode", resultObject.getString("country_code"));
result.put("countryName", resultObject.getString("country_name"));
result.put("regionCode", resultObject.getString("region_code"));
result.put("city", resultObject.getString("city"));
String timeZoneStr = resultObject.getString("time_zone");
if (timeZoneStr != null) {
JSONObject timeZoneObject = JSON.parseObject(timeZoneStr);
result.put("timeZone", timeZoneObject.getString("name"));
}
}
return result;
}
完整代码
接下来给读者提供完整的代码实现:
// 优先查询数据库文件,查询不到才使用ipdata
public String getUserCountryCodeByIp(String ip) {
log.info("Enter service.");
String code = null;
try {
code = getCountry(ip);
if (StringUtils.isBlank(code)) {
code = getUserCountryCodeByIpData(ip).getCountryCode();
}
} catch (IOException | GeoIp2Exception e) {
log.error("Get country code error, msg is [{}]", e.getMessage());
code = getUserCountryCodeByIpData(ip).getCountryCode();
} catch (Exception ex) {
ex.printStackTrace();
code = getUserCountryCodeByIpData(ip).getCountryCode();
}
return code;
}
private String getCountry(String ip) throws IOException, GeoIp2Exception {
// 容器中读取数据文件的路径
File database = new File("/home/my_dir/GeoLite2-City.mmdb");
// 读取数据库内容
DatabaseReader reader = new DatabaseReader.Builder(database).build();
InetAddress ipAddress = InetAddress.getByName(ip);
// 获取查询结果
CityResponse response = reader.city(ipAddress);
//获取country
Country country = response.getCountry();
return country.getIsoCode();
}
public GetCountryCodeVo getUserCountryCodeByIpData(String ip) {
log.info("Enter service.");
GetCountryCodeVo result = new GetCountryCodeVo();
result.setIp(ip);
result.setCountryCode("");
try {
long timestamp = System.currentTimeMillis();
String redisStr = redisUtil.getString("Data_all:" + ip);
// 如果缓存里有则不查ipdata了
if (redisStr != null) {
log.info("ip:{}, redis IP_ipData_all result:{}.", ip, redisStr);
result.setCountryCode(redisStr.split(":")[0].equals("-") ? "" : redisStr.split(":")[0]);
result.setCountryName(redisStr.split(":")[1].equals("-") ? "" : redisStr.split(":")[1]);
result.setRegionCode(redisStr.split(":")[2].equals("-") ? "" : redisStr.split(":")[2]);
result.setCity(redisStr.split(":")[3].equals("-") ? "" : redisStr.split(":")[3]);
result.setTimeZone(redisStr.split(":")[4].equals("-") ? "" : redisStr.split(":")[4]);
} else {
Map<String, String> container = getCountryByIpData(ip);
result.setCountryCode(container.get("countryCode"));
result.setCountryName(container.get("countryName"));
result.setRegionCode(container.get("regionCode"));
result.setCity(container.get("city"));
result.setTimeZone(container.get("timeZone"));
String allInfoStr = (StringUtils.isBlank(result.getCountryCode()) ? "-" : result.getCountryCode()) + ":"
+ (StringUtils.isBlank(result.getCountryName()) ? "-" : result.getCountryName()) + ":"
+ (StringUtils.isBlank(result.getRegionCode()) ? "-" : result.getRegionCode()) + ":"
+ (StringUtils.isBlank(result.getCity()) ? "-" : result.getCity()) + ":"
+ (StringUtils.isBlank(result.getTimeZone()) ? "-" : result.getTimeZone());
redisUtil.setString("Data_all:" + ip, allInfoStr, 7 * 24 * 60 * 60);
}
} catch (Exception ex) {
ex.printStackTrace();
return result;
}
log.info("Leave service.");
return result;
}
private Map<String, String> getCountryByIpData(String ip) {
String url = "https://api.ipdata.co/" + ip + "?api-key=" + ipDataSecret;
String resultStr = HttpUtils.get(url, null);
JSONObject resultObject = JSON.parseObject(resultStr);
Map<String, String> result = new HashMap<>();
if (resultObject != null) {
result.put("countryCode", resultObject.getString("country_code"));
result.put("countryName", resultObject.getString("country_name"));
result.put("regionCode", resultObject.getString("region_code"));
result.put("city", resultObject.getString("city"));
String timeZoneStr = resultObject.getString("time_zone");
if (timeZoneStr != null) {
JSONObject timeZoneObject = JSON.parseObject(timeZoneStr);
result.put("timeZone", timeZoneObject.getString("name"));
}
}
return result;
}
总结
这个方案已经是比较完整的实现了,实际项目中按此方案实现后,基本上只需观察ipdata的使用量,我的例子把结果缓存了七天,也就是说同一个IP七天内最多只会触发一次ipdata查询调用,基本上只需要按日活去买合适的调用次数套餐就行了,不会造成浪费,算是给老板省老鼻子钱了,当然,不差钱的话可以不做缓存方案。
原文地址:https://blog.csdn.net/jspyth/article/details/142653776
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!