SpringBoot 快速實現(xiàn)IP地址解析

如果使用本地ip 解析的話,我們將會借助ip2region,該項目維護(hù)了一份較為詳細(xì)的本地ip 地址對應(yīng)表,如果為了離線環(huán)境的使用,需要導(dǎo)入該項目依賴,并指定版本,不同版本的方法可能存在差異。
<dependency>
????<groupId>org.lionsoulgroupId>
????<artifactId>ip2regionartifactId>
????<version>2.6.3version>
dependency>
官方gitee:gitee.com/lionsoul/ip…
開發(fā)
在使用時需要將 xdb 文件下載到工程文件目錄下,使用ip2region即使是完全基于 xdb 文件的查詢,單次查詢響應(yīng)時間在十微秒級別,可通過如下兩種方式開啟內(nèi)存加速查詢:
vIndex 索引緩存 :使用固定的 512KiB 的內(nèi)存空間緩存 vector index 數(shù)據(jù),減少一次 IO 磁盤操作,保持平均查詢效率穩(wěn)定在10-20微秒之間。 xdb 整個文件緩存:將整個 xdb 文件全部加載到內(nèi)存,內(nèi)存占用等同于 xdb 文件大小,無磁盤 IO 操作,保持微秒級別的查詢效率。
/**
?*?ip查詢
?*/
@Slf4j
public?class?IPUtil?{
????private?static?final?String?UNKNOWN?=?"unknown";
????protected?IPUtil(){?}
????/**
?????*?獲取?IP地址
?????*?使用?Nginx等反向代理軟件,?則不能通過?request.getRemoteAddr()獲取?IP地址
?????*?如果使用了多級反向代理的話,X-Forwarded-For的值并不止一個,而是一串IP地址,
?????*?X-Forwarded-For中第一個非?unknown的有效IP字符串,則為真實IP地址
?????*/
????public?static?String?getIpAddr(HttpServletRequest?request)?{
????????String?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.getRemoteAddr();
????????}
????????return?"0:0:0:0:0:0:0:1".equals(ip)???"127.0.0.1"?:?ip;
????}
????public?static??String?getAddr(String?ip){
????????String?dbPath?=?"src/main/resources/ip2region/ip2region.xdb";
????????// 1、從 dbPath 加載整個 xdb 到內(nèi)存。
????????byte[]?cBuff;
????????try?{
????????????cBuff?=?Searcher.loadContentFromFile(dbPath);
????????}?catch?(Exception?e)?{
????????????log.info("failed?to?load?content?from?`%s`:?%s\n",?dbPath,?e);
????????????return?null;
????????}
????????// 2、使用上述的 cBuff 創(chuàng)建一個完全基于內(nèi)存的查詢對象。
????????Searcher?searcher;
????????try?{
????????????searcher?=?Searcher.newWithBuffer(cBuff);
????????}?catch?(Exception?e)?{
????????????log.info("failed?to?create?content?cached?searcher:?%s\n",?e);
????????????return?null;
????????}
????????//?3、查詢
????????try?{
????????????String?region?=?searcher.searchByStr(ip);
????????????return?region;
????????}?catch?(Exception?e)?{
????????????log.info("failed?to?search(%s):?%s\n",?ip,?e);
????????}
????????return?null;
????}
}
這里我們將ip 解析封裝成一個工具類,包含獲取IP和ip 地址解析兩個方法,ip 的解析可以在請求中獲取。獲取到ip后,需要根據(jù)ip ,在xdb 中查找對應(yīng)的IP地址的解析,由于是本地數(shù)據(jù)庫可能存在一定的缺失,部分ip 存在無法解析的情況。
在線解析
如果想要獲取更加全面的ip 地址信息,可使用在線數(shù)據(jù)庫,這里提供的是 whois.pconline.com ?的IP解析,該IP解析在我的使用過程中表現(xiàn)非常流暢,而且只有少數(shù)的ip 存在無法解析的情況。
@Slf4j
public?class?AddressUtils?{
????//?IP地址查詢
????public?static?final?String?IP_URL?=?"http://whois.pconline.com.cn/ipJson.jsp";
????//?未知地址
????public?static?final?String?UNKNOWN?=?"XX?XX";
????public?static?String?getRealAddressByIP(String?ip)?{
????????String?address?=?UNKNOWN;
????????//?內(nèi)網(wǎng)不查詢
????????if?(IpUtils.internalIp(ip))?{
????????????return?"內(nèi)網(wǎng)IP";
????????}
????????if?(true)?{
????????????try?{
????????????????String?rspStr?=?sendGet(IP_URL,?"ip="?+?ip?+?"&json=true"?,"GBK");
????????????????if?(StrUtil.isEmpty(rspStr))?{
????????????????????log.error("獲取地理位置異常?{}"?,?ip);
????????????????????return?UNKNOWN;
????????????????}
????????????????JSONObject?obj?=?JSONObject.parseObject(rspStr);
????????????????String?region?=?obj.getString("pro");
????????????????String?city?=?obj.getString("city");
????????????????return?String.format("%s?%s"?,?region,?city);
????????????}?catch?(Exception?e)?{
????????????????log.error("獲取地理位置異常?{}"?,?ip);
????????????}
????????}
????????return?address;
????}
????public?static?String?sendGet(String?url,?String?param,?String?contentType)?{
????????StringBuilder?result?=?new?StringBuilder();
????????BufferedReader?in?=?null;
????????try?{
????????????String?urlNameString?=?url?+?"?"?+?param;
????????????log.info("sendGet?-?{}"?,?urlNameString);
????????????URL?realUrl?=?new?URL(urlNameString);
????????????URLConnection?connection?=?realUrl.openConnection();
????????????connection.setRequestProperty("accept"?,?"*/*");
????????????connection.setRequestProperty("connection"?,?"Keep-Alive");
????????????connection.setRequestProperty("user-agent"?,?"Mozilla/4.0?(compatible;?MSIE?6.0;?Windows?NT?5.1;SV1)");
????????????connection.connect();
????????????in?=?new?BufferedReader(new?InputStreamReader(connection.getInputStream(),?contentType));
????????????String?line;
????????????while?((line?=?in.readLine())?!=?null)?{
????????????????result.append(line);
????????????}
????????????log.info("recv?-?{}"?,?result);
????????}?catch?(ConnectException?e)?{
????????????log.error("調(diào)用HttpUtils.sendGet?ConnectException,?url="?+?url?+?",param="?+?param,?e);
????????}?catch?(SocketTimeoutException?e)?{
????????????log.error("調(diào)用HttpUtils.sendGet?SocketTimeoutException,?url="?+?url?+?",param="?+?param,?e);
????????}?catch?(IOException?e)?{
????????????log.error("調(diào)用HttpUtils.sendGet?IOException,?url="?+?url?+?",param="?+?param,?e);
????????}?catch?(Exception?e)?{
????????????log.error("調(diào)用HttpsUtil.sendGet?Exception,?url="?+?url?+?",param="?+?param,?e);
????????}?finally?{
????????????try?{
????????????????if?(in?!=?null)?{
????????????????????in.close();
????????????????}
????????????}?catch?(Exception?ex)?{
????????????????log.error("調(diào)用in.close?Exception,?url="?+?url?+?",param="?+?param,?ex);
????????????}
????????}
????????return?result.toString();
????}
}
場景
那么在開發(fā)的什么流程獲取ip 地址是比較合適的,這里就要用到我們的攔截器了。攔截進(jìn)入服務(wù)的每個請求,進(jìn)行前置操作,在進(jìn)入時就完成請求頭的解析,ip 獲取以及ip 地址解析,這樣在后續(xù)流程的全環(huán)節(jié),都可以復(fù)用ip 地址等信息。
/**
?*?對ip?進(jìn)行限制,防止IP大量請求
?*/
@Slf4j
@Configuration
public?class?IpUrlLimitInterceptor?implements?HandlerInterceptor{
????@Override
????public?boolean?preHandle(HttpServletRequest?httpServletRequest,?HttpServletResponse?httpServletResponse,?Object?o)?{
????????//更新全局變量
????????Constant.IP?=?IPUtil.getIpAddr(httpServletRequest);
????????Constant.IP_ADDR?=?AddressUtils.getRealAddressByIP(Constant.IP);
????????Constant.URL?=?httpServletRequest.getRequestURI();
????????return?true;
????}
????@Override
????public?void?postHandle(HttpServletRequest?httpServletRequest,?HttpServletResponse?httpServletResponse,?Object?o,?ModelAndView?modelAndView)?{
????????//通過本地獲取
????????//????????獲得ip
????????//????????String?ip?=?IPUtil.getIpAddr(httpServletRequest);
????????//解析具體地址
????????//????????String?addr?=?IPUtil.getAddr(ip);
????????//通過在線庫獲取
????????//????????String?ip?=?IpUtils.getIpAddr(httpServletRequest);
????????//????????String?ipaddr?=?AddressUtils.getRealAddressByIP(ipAddr);
????????//????????log.info("IP?>>?{},Address?>>?{}",ip,ipaddr);
????}
????@Override
????public?void?afterCompletion(HttpServletRequest?httpServletRequest,?HttpServletResponse?httpServletResponse,?Object?o,?Exception?e)?{
????}
}
如果想要執(zhí)行我們的ip 解析攔截器,
需要在spring boot的視圖層進(jìn)行攔截才會觸發(fā)我們的攔截器。
@Configuration
public?class?WebConfig?implements?WebMvcConfigurer?{
????@Autowired
????IpUrlLimitInterceptor?ipUrlLimitInterceptor;
????//執(zhí)行ip攔截器
????@Override
????public?void?addInterceptors(InterceptorRegistry?regivastry){
????????registry.addInterceptor(ipUrlLimitInterceptor)
????????????//?攔截所有請求
????????????.addPathPatterns("/**");
????}
}來源:https://juejin.cn/post/7130544273421762573
·················END·················
資料鏈接
清華學(xué)姐自學(xué)的Linux筆記,天花板級別! 新版鳥哥Linux私房菜資料 阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開放下載! Alibaba官方上線!SpringBoot+SpringCloud全彩指南 國內(nèi)最強(qiáng)的SpringBoot+Vue全棧項目天花板,不接受反駁!
