获取用户ip所在城市
整体流程图
获取当前登录用户所在城市,是一个非常常见的需求,在很多业务场景中用到。
比如:导航的定位功能默认选择的城市,或者一些防盗系统中识别用户两次登录的城市不一样的会有报警提示。
下载geoip2数据库
geoip2是国外的一个免费的IP库,只要注册账号就能下载IP数据库了。
官网地址:https://www.maxmind.com/。
其他下载:https://kohler.lanzouo.com/i3Fkb36wobuh
将IP库保存到电脑的某个目录下
之后使用绝对路径
引入相关依赖
引入geoip相关的依赖包:
<dependency>
<groupId>com.maxmind.geoip2</groupId>
<artifactId>geoip2</artifactId>
<version>2.16.1</version>
</dependency>
增加获取城市接口
增加一个根据ip获取所在城市的接口。
创建一个GeoIpController类:
@Tag( name= "geoip操作", description = "geoip操作")
@RestController
@RequestMapping("/v1/web/geoip")
@Validated
public class GeoIpController {
@Autowired
private GeoIpHelper geoIpHelper;
/**
* 根据ip获取所在城市
*
* @param ip ip地址
* @return 城市
*/
@NoLogin
@Operation(summary = "根据ip获取所在城市", description = "根据ip获取所在城市")
@GetMapping("/getCity")
public CityDTO getCity(@RequestParam(value = "ip") String ip) {
return geoIpHelper.getCity(ip);
}
}
这个类中只包含了getCity这一个接口,该接口就是通过ip获取所在国家、省份和城市。
创建GeoIpHelper类:
@Slf4j
@Component
public class GeoIpHelper {
private static final String GEO_IP_FILE_NAME = "GeoLite2-City.mmdb";
@Value("${shop.mgt.geoIpFilePath:}")
private String geoIpFilePath;
@Value("${shop.mgt.taobaoIpUrl:}")
private String taobaoIpUrl;
@Value("${shop.mgt.taobaoIpRequestOff:false}")
private Boolean taobaoIpRequestOff;
@Autowired
private HttpHelper httpHelper;
/**
* 根据ip获取所在城市
*
* @param ip ip地址
* @return 城市
*/
public CityDTO getCity(String ip) {
CityDTO cityFromGeoIp = getCityFromGeoIp(ip);
if (Objects.nonNull(cityFromGeoIp) && Objects.nonNull(cityFromGeoIp.getCity())) {
return cityFromGeoIp;
}
if (taobaoIpRequestOff) {
return null;
}
return getCityFromApi(ip);
}
private CityDTO getCityFromGeoIp(String ip) {
String fileUrl = getFileUrl();
File file = new File(fileUrl);
if (!file.exists()) {
log.warn(String.format("%s文件不存在", fileUrl));
return null;
}
try {
DatabaseReader reader = new DatabaseReader.Builder(file).build();
//解析IP地址
InetAddress ipAddress = InetAddress.getByName(ip);
// 获取查询结果
CityResponse response = reader.city(ipAddress);
if (response == null) {
return null;
}
// 国家
String country = response.getCountry().getNames().get("zh-CN");
// 省份
String province = response.getMostSpecificSubdivision().getNames().get("zh-CN");
//城市
String city = response.getCity().getNames().get("zh-CN");
return new CityDTO(ip, country, province, city);
} catch (Exception e) {
log.error("从GeoIp库中获取城市失败,原因:", e);
}
return null;
}
private CityDTO getCityFromApi(String ip) {
String url = String.format(taobaoIpUrl, ip);
TaoboCityEntity taoboCityEntity = httpHelper.doGet(url, TaoboCityEntity.class);
if (Objects.nonNull(taoboCityEntity)) {
TaoboCityEntity.TaoboAreaEntity data = taoboCityEntity.getData();
if (Objects.nonNull(data)) {
return new CityDTO(ip, data.getCountry(), data.getRegion(), data.getCity());
}
}
return null;
}
private String getFileUrl() {
return geoIpFilePath + "/" + GEO_IP_FILE_NAME;
}
}
这个类主要是来用读取IP库的数据,通过ip查询国家、省份和城市。
由于ip库有可能不全,或者不是最新的数据。可能会导致根据ip获取不到我们想要的数据的情况。
因此需要做兼容处理,如果从ip库查询不到数据,则再调用一下远程接口获取数据。
调用淘宝接口
创建HttpHelper类:
@Slf4j
@Component
public class HttpHelper {
@Autowired
private RestTemplate restTemplate;
/**
* 发送get请求
*
* @param url url地址
* @param tClass 返回值实体
* @param <T> 泛型
* @return 返回值实体
*/
public <T> T doGet(String url, Class<T> tClass) {
return restTemplate.getForObject(url, tClass);
}
}
定义了doGet方法获取用户请求。
创建了RestTemplateConfig类:
@Configuration
public class RestTemplateConfig {
@Value("${shop.mgt.rest.template.connectTimeout:3000}")
private int connectTimeout;
@Value("${shop.mgt.rest.template.readTimeout:50000}")
private int readTimeout;
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(connectTimeout);
factory.setReadTimeout(readTimeout);
return factory;
}
/**
* 忽略证书配置
*/
public static HttpComponentsClientHttpRequestFactory generateHttpRequestFactory()
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException {
TrustStrategy acceptingTrustStrategy = (x509Certificates, authType) -> true;
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
SSLConnectionSocketFactory connectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
new NoopHostnameVerifier());
HttpClientBuilder httpClientBuilder = HttpClients.custom();
httpClientBuilder.setSSLSocketFactory(connectionSocketFactory);
CloseableHttpClient httpClient = httpClientBuilder.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(httpClient);
return factory;
}
}
这个类是一个配置类,主要配置了RestTemplate的一些参数,以及https请求忽略证书的情况。
如果不忽略证书,有些情况下,比如:调用测试环境的请求,使用的不是有效的证书,会请求失败。
application.yml文件中增加配置:
shop:
mgt:
geoIpFilePath: D:\workplace\SpringBoot\kailong_shop\shop_business\src\main\resources\files\geoip2
taobaoIpUrl: https://ip.taobao.com/outGetIpInfo?ip=%s&accessKey=alibaba-inc
其中accessKey使用的淘宝的测试key。
测试
代码开发好之后,接下来进行测试。
在浏览器上访问:http://localhost:8011/v1/web/geoip/getCity?ip=123.245.11.177(我的测试路径)
返回了正确的城市
说明根据ip获取所在城市的功能OK了。
需要特别注意的是目前调用淘宝的测试接口,每天有数量限制,达到一定测试就会返回失败。
也可以换成其他平台的接口,或者申请一个付费的accessKey。
