JAVA使用Ip2region獲取IP定位資訊
ip2region - 是國內開發者開發的離線IP地址定位庫,針對國內IP效果較好,國外的部分IP只能顯示國家。
專案gitee地址:
https://gitee.com/lionsoul/ip2region.git
先安裝依賴
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>1.7.2</version>
</dependency>
下載離線IP定位庫
離線資料庫在專案的data資料夾下,名稱為ip2region.db
https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region.db
下載到離線資料庫後,我們需要讀取這個資料庫,我們可以放在專案的resources
目錄,但是我不建議這樣做,這樣打包的jar會變得很大,部署時麻煩。
我們指定一個絕對路徑,這個部署的時候也不用每次上傳,這個資料庫一般不會修改,如果資料庫更新了,單獨上傳即可。
因為我們專案使用阿里雲oss,伺服器也在阿里雲,所以我就把資料庫存到oss中,阿里雲內網讀取還是很快的,這樣免得我修改地址,更新資料庫我也只需要在本地上傳到oss即可。
下面我們定義類封裝ip2region
-
記錄對映實體 IpInfo.java
該類用於接受解析後的資料,我們也可以使用map來接收,我這裡就使用模型來組裝資料。
類使用 lombok ,如果不喜歡的自行修改為普通類。
import lombok.Data; /** * 域名資訊. * * @author https://www.cnblogs.com/lixingwu * @date 2022-05-24 15:07:47 */ @Data public class IpInfo { /*** 國家 */ private String country; /*** 地區 */ private String region; /*** 省 */ private String province; /*** 市 */ private String city; /*** 運營商 */ private String isp; }
-
ip解析工具類 Ip2regionAnalysis.java
該類主要用於載入資料庫和解析IP資訊,然後把查詢的結果組裝為IpInfo的;
這個類我使用了使用單例模式(雙重校驗鎖DCL)進行編寫,在建構函式里加載IP資料庫,這樣資料庫就只會載入一遍。
類中還用到了工具包
hutool
,需要自行引入,具體操作自行百度。
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.StreamProgress;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.yunding.vote.domain.IpInfo;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.DataBlock;
import org.lionsoul.ip2region.DbConfig;
import org.lionsoul.ip2region.DbMakerConfigException;
import org.lionsoul.ip2region.DbSearcher;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Optional;
/**
* 使用單例模式(雙重校驗鎖DCL)
*
* @author https://www.cnblogs.com/lixingwu
* @date 2022-05-24 10:45:42
*/
@Slf4j
public class Ip2regionAnalysis {
private volatile static Ip2regionAnalysis analysis;
/**
* TODO 這個資料庫地址,改成自己的,不要用這地址啊,這個需要登入才能下載
* ip資料庫地址
*/
public static final String IP_REGION_DB_URL = "https://gitee.com/lionsoul/ip2region/raw/master/data/ip2region.db";
/**
* ip資料庫位元組
*/
private final byte[] dbBinStr;
/**
* 初始化,下載ip資料庫檔案轉為為檔案輸出流
*/
private Ip2regionAnalysis() {
// 下載IP資料庫檔案
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
HttpUtil.download(IP_REGION_DB_URL, outputStream, false, new StreamProgress() {
@Override
public void start() {
log.info("下載IP資料庫檔案...");
}
@Override
public void progress(long progressSize) {
}
@Override
public void finish() {
double fileSize = NumberUtil.div(outputStream.size(), (1024 * 1024), 2);
log.info("IP資料庫檔案下載成功,資料庫檔案大小[{}MB]", fileSize);
}
});
dbBinStr = outputStream.toByteArray();
IoUtil.close(outputStream);
}
/**
* 獲取IP解析單例
*
* @return Ip2regionAnalysis
*/
public static Ip2regionAnalysis getInstance() {
if (analysis == null) {
synchronized (Ip2regionAnalysis.class) {
if (analysis == null) {
analysis = new Ip2regionAnalysis();
}
}
}
return analysis;
}
/**
* <p>方法名稱:解析Ip資訊.</p>
* <p>詳細描述:.</p>
* <p>建立時間:2022-05-24 11:26:59</p>
* <p>建立作者:lixingwu</p>
* <p>修改記錄:</p>
*
* @param ip IP地址
* @return 國家|區域|省份|城市|ISP
*/
public Optional<String> getIpInfo(String ip) {
if (IpAddressUtil.validIp(ip)) {
try {
DbConfig config = new DbConfig();
DbSearcher searcher = new DbSearcher(config, dbBinStr);
// 搜尋資料
DataBlock search = searcher.memorySearch(ip);
// 資料格式:國家|區域|省份|城市|ISP
return Optional.of(search.getRegion());
} catch (DbMakerConfigException | IOException e) {
e.printStackTrace();
log.error("Ip解析失敗:{}", e.toString());
}
}
return Optional.empty();
}
/**
* <p>方法名稱:解析Ip資訊.</p>
* <p>詳細描述:.</p>
* <p>建立時間:2022-05-24 11:26:59</p>
* <p>建立作者:lixingwu</p>
* <p>修改記錄:</p>
*
* @param ips IP地址集合
* @return Dict({ ip : info })
*/
public Dict getIpInfo(HashSet<String> ips) {
try {
DbConfig config = new DbConfig();
DbSearcher searcher = new DbSearcher(config, dbBinStr);
DataBlock search;
Dict dataset = Dict.create();
for (String ip : ips) {
if (IpAddressUtil.validIp(ip)) {
search = searcher.memorySearch(ip);
dataset.set(ip, search.getRegion());
}
}
return dataset;
} catch (DbMakerConfigException | IOException e) {
e.printStackTrace();
log.error("Ip解析失敗:{}", e.toString());
}
return Dict.create();
}
/**
* <p>方法名稱:數字ip獲取資訊.</p>
* <p>詳細描述:.</p>
* <p>建立時間:2022-05-24 13:15:23</p>
* <p>建立作者:lixingwu</p>
* <p>修改記錄:</p>
*
* @param ip 數字IP
* @return 國家|區域|省份|城市|ISP
*/
public Optional<String> getIpInfo(long ip) {
String longIpv4 = NetUtil.longToIpv4(ip);
return getIpInfo(longIpv4);
}
/**
* <p>方法名稱:獲取請求物件的IP資訊.</p>
* <p>詳細描述:.</p>
* <p>建立時間:2022-05-24 11:50:59</p>
* <p>建立作者:lixingwu</p>
* <p>修改記錄:</p>
*
* @param request 請求物件
* @return 國家|區域|省份|城市|ISP
*/
public Optional<String> getIpInfo(HttpServletRequest request) {
String ip = IpAddressUtil.getIpAddr(request);
return getIpInfo(ip);
}
/**
* <p>方法名稱:獲取IP資訊的字典.</p>
* <p>詳細描述:.</p>
* <p>建立時間:2022-05-24 11:52:58</p>
* <p>建立作者:lixingwu</p>
* <p>修改記錄:</p>
*
* @param ip IP地址
* @return the dict
*/
public IpInfo getIpInfoBean(String ip) {
Optional<String> ipInfo = getIpInfo(ip);
IpInfo info = new IpInfo();
if (ipInfo.isPresent()) {
//國家|區域|省份|城市|ISP
String[] split = StrUtil.split(ipInfo.get(), "|");
info.setCountry(split[0]);
info.setRegion(split[1]);
info.setProvince(split[2]);
info.setCity(split[3]);
info.setIsp(split[4]);
}
return info;
}
/**
* <p>方法名稱:數字ip獲取資訊字典.</p>
* <p>詳細描述:.</p>
* <p>建立時間:2022-05-24 13:15:23</p>
* <p>建立作者:lixingwu</p>
* <p>修改記錄:</p>
*
* @param ip 數字IP
* @return 國家|區域|省份|城市|ISP
*/
public IpInfo getIpInfoBean(long ip) {
String longIpv4 = NetUtil.longToIpv4(ip);
return getIpInfoBean(longIpv4);
}
/**
* <p>方法名稱:獲取IP資訊的字典.</p>
* <p>詳細描述:.</p>
* <p>建立時間:2022-05-24 11:52:58</p>
* <p>建立作者:lixingwu</p>
* <p>修改記錄:</p>
*
* @param request 請求物件
* @return the dict
*/
public IpInfo getIpInfoBean(HttpServletRequest request) {
String ip = IpAddressUtil.getIpAddr(request);
return getIpInfoBean(ip);
}
/**
* 測試
*/
public static void main(String[] args) {
log.info("121.8.215.106 \t {}", Ip2regionAnalysis.getInstance().getIpInfoBean("121.8.215.106"));
log.info("183.247.152.98 \t {}", Ip2regionAnalysis.getInstance().getIpInfoBean("183.247.152.98"));
log.info("14.29.139.251 \t {}", Ip2regionAnalysis.getInstance().getIpInfoBean("14.29.139.251"));
log.info("183.247.152.98 \t {}", Ip2regionAnalysis.getInstance().getIpInfoBean("183.247.152.98"));
log.info("27.105.130.93 \t {}", Ip2regionAnalysis.getInstance().getIpInfoBean("27.105.130.93"));
log.info("124.205.155.147 \t {}", Ip2regionAnalysis.getInstance().getIpInfoBean("124.205.155.147"));
// 批量解析,返回字典資料:ip:解析資訊
final HashSet<String> ipSet = CollUtil.newHashSet(
"47.92.113.71", "221.226.75.86", "124.205.155.155",
"47.57.188.208", "121.8.215.106", "121.8.215.106"
);
Dict dict = Ip2regionAnalysis.getInstance().getIpInfo(ipSet);
log.info("{}", dict.toString());
log.info("{}\t{}", "121.8.215.106", dict.getStr("121.8.215.106"));
}
}
測試輸出
14:19:12.791 [main] DEBUG cn.hutool.log.LogFactory - Use [Slf4j] Logger As Default.
14:19:14.150 [main] INFO util.Ip2regionAnalysis - 下載IP資料庫檔案...
14:19:14.633 [main] INFO util.Ip2regionAnalysis - IP資料庫檔案下載成功,資料庫檔案大小[8.33MB]
14:19:14.645 [main] INFO util.Ip2regionAnalysis - 121.8.215.106 IpInfo(country=中國, region=0, province=廣東省, city=廣州市, isp=電信)
14:19:14.646 [main] INFO util.Ip2regionAnalysis - 183.247.152.98 IpInfo(country=中國, region=0, province=浙江省, city=杭州市, isp=移動)
14:19:14.646 [main] INFO util.Ip2regionAnalysis - 14.29.139.251 IpInfo(country=中國, region=0, province=廣東省, city=深圳市, isp=電信)
14:19:14.646 [main] INFO util.Ip2regionAnalysis - 183.247.152.98 IpInfo(country=中國, region=0, province=浙江省, city=杭州市, isp=移動)
14:19:14.646 [main] INFO util.Ip2regionAnalysis - 27.105.130.93 IpInfo(country=中國, region=0, province=臺灣省, city=臺北, isp=So-Net)
14:19:14.646 [main] INFO util.Ip2regionAnalysis - 124.205.155.147 IpInfo(country=中國, region=0, province=北京, city=北京市, isp=鵬博士)
14:19:14.648 [main] INFO util.Ip2regionAnalysis - {221.226.75.86=中國|0|江蘇省|南京市|電信, 47.57.188.208=中國|0|香港|0|阿里雲, 47.92.113.71=中國|0|河北省|張家口市|阿里雲, 121.8.215.106=中國|0|廣東省|廣州市|電信, 124.205.155.155=中國|0|北京|北京市|鵬博士}
14:19:14.682 [main] INFO util.Ip2regionAnalysis - 121.8.215.106 中國|0|廣東省|廣州市|電信
在第一次呼叫getInstance時會去下載資料庫檔案會比較耗時,其他後面的操作就很快了,基本上幾毫秒就查詢到了。
所以如果嫌第一次慢的,可以在程式啟動完成後手動呼叫,預熱一下,在實際業務就會使用快取的資料庫了。
實際使用
在專案中我編寫了一個 CommonController.java
,然後使用編寫的類提供了一個介面,用於獲取IP的資訊。
import cn.hutool.extra.servlet.ServletUtil;
import com.yunding.vote.common.api.CommonResult;
import com.yunding.vote.common.limiter.RInterfaceLimit;
import com.yunding.vote.domain.IpInfo;
import com.yunding.vote.util.Ip2regionAnalysis;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
* 公共控制層,該類方法不會記錄日誌
*/
@RestController
@Api(tags = "公共控制層", description = "公共控制層")
@RequestMapping("/common")
@Slf4j
public class CommonController {
@Resource
private HttpServletRequest request;
@ApiOperation("獲取IP的資訊,100QPS")
@GetMapping(value = "/ipInfo/{ip}")
@RInterfaceLimit(rate = 100)
public CommonResult<IpInfo> getIpInfo(@PathVariable String ip) {
String clientIp = ServletUtil.getClientIP(request);
IpInfo ipInfo = Ip2regionAnalysis.getInstance().getIpInfoBean(ip);
return CommonResult.success(ipInfo);
}
}
上面這個類只是告訴大家是怎麼使用Ip2regionAnalysis
這個類的,大家根據自己的專案自行調整。
GET http://127.0.0.1:8080/api/v1/common/ipInfo/121.8.215.106
>>>
{
"code": 200,
"data": {
"country": "中國",
"region": 0,
"province": "廣東省",
"city": "廣州市",
"isp": "電信"
},
"message": "操作成功"
}