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>
离线数据库在项目的data文件夹下,名称为ip2region.db
,其他2个文件是用于生成离线库的,可不用下载。
https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region.db
下载到离线数据库后,我们需要读取这个数据库,我们可以放在项目的resources
目录,但是我不建议这样做,这样打包的jar会变得很大,部署时麻烦。
我们指定一个绝对路径,这个部署的时候也不用每次上传,这个数据库一般不会修改,如果数据库更新了,单独上传即可。
因为我们项目使用阿里云oss,服务器也在阿里云,所以我就把数据库存到oss中,阿里云内网读取还是很快的,这样免得我修改地址,更新数据库我也只需要在本地上传到oss即可。
-
记录映射实体 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工具类
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.TimeInterval; import cn.hutool.http.HttpUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import cn.hutool.log.StaticLog; import com.alibaba.fastjson.JSON; import com.yunding.vote.common.api.CommonResult; import com.yunding.vote.common.component.OkHttpUtil; import com.yunding.vote.constant.RegExpConstant; import lombok.extern.slf4j.Slf4j; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @Slf4j public class IpAddressUtil { /** * 从request对象中获取客户端真实的ip地址 * * @param request request对象 * @return 客户端的IP地址 */ public static String getIpAddr(HttpServletRequest request) { String ip = request.getHeader("Ali-cdn-Real-IP"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-real-ip"); } if (ip == null || "".equals(ip) || "null".equals(ip)) { ip = request.getHeader("X-Forwarded-For"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip; } /** * 验证IP字符串是否合规 * * @param ip ip字符串 * @return true标识合规 */ public static boolean validIp(String ip) { String pattern = "((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})(\\.((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})){3}"; Pattern r = Pattern.compile(pattern); Matcher m = r.matcher(ip); boolean matches = m.matches(); return matches; } }
-
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": "操作成功" }
原文链接:https://www.cnblogs.com/lixingwu/p/16317568.html
原创文章,作者:优速盾-小U,如若转载,请注明出处:https://www.cdnb.net/bbs/archives/4900