我们在刷微博,抖音、B站的时候,在每个评论下面会显示网络用户所在地。国内的用户显示的是省份,国外的用户显示是国家。公开显示网络用户所在地可以提醒用户谨慎发言、治理水军、减少冒充当事人等现象。
那么,这个功能是怎么实现的呢?
Java 中获取 IP 归属地,主要是分为以下两步:
1>通过 HttpServletRequest 获取 Ip2>根据 IP 查询获取对应的归属地下面我们详细讲解2部内容。
1、通过HttpServletRequest 获取 IP
public class IpUtil { private static final String UNKNOWN = "unknown"; private static final String HEADER_FORWARDED = "x-forwarded-for"; private static final String HEADER_PROXY = "Proxy-Client-IP"; private static final String HEADER_WL_PROXY = "WL-Proxy-Client-IP"; private static final String HEADER_HTTP = "HTTP_CLIENT_IP"; private static final String HEADER_HTTP_FORWARDED = "HTTP_X_FORWARDED_FOR"; private static final String LOCAL_IP = "127.0.0.1"; private static final String LOCAL_HOST = "localhost"; /** * 获取 IP 地址 * * @param request * @return */ public String getIpAddr(HttpServletRequest request) { String ip = request.getHeader(HEADER_FORWARDED); if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader(HEADER_PROXY); } if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader(HEADER_WL_PROXY); } if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader(HEADER_HTTP); } if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader(HEADER_HTTP_FORWARDED); } if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } // 本机访问 if (LOCAL_IP.equalsIgnoreCase(ip) || LOCAL_HOST.equalsIgnoreCase(ip) || "0:0:0:0:0:0:0:1".equalsIgnoreCase(ip)) { // 根据网卡取本机配置的 IP try { InetAddress localHost = InetAddress.getLocalHost(); ip = localHost.getHostAddress(); } catch (UnknownHostException e) { e.printStackTrace(); } } // 对于通过多个代理的情况,第一个 IP 为客户端真实 IP,多个 IP 按照','分割 if (ip != null && ip.length() > 15) { if (ip.indexOf(",") > 15) { ip = ip.substring(0, ip.indexOf(",")); } } return ip; }}2、根据 IP 查询获取对应的归属地
2.1、第一种实现,参照百度方式实现。
此种会返回json格式:
{"regionCode":"0","regionNames":"","proCode":"510000","err":"","city":"成都市","cityCode":"510100","ip":"218.88.83.8","pro":"四川省","region":"","addr":"四川省成都市 电信"}具体代码实现为:
import com.alibaba.fastjson.JSONObject;import java.io.BufferedReader;import java.io.InputStreamReader;import java.net.URL;import java.net.URLConnection;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 void main(String[] args) { System.out.println(getRealAddressByIP("218.88.83.8")); } public static String getRealAddressByIP(String ip) { // 内网不查询 if (internalIp(ip)) { return "内网IP"; } try { String rspStr = sendGet(IP_URL, "ip=" + ip + "&json=true", "GBK"); if ("".equals(rspStr)) { return UNKNOWN; } JSONObject obj = JSONObject.parseObject(rspStr); return obj.toJSONString(); } catch (Exception e) { e.printStackTrace(); } return UNKNOWN; } public static boolean internalIp(String ip) { byte[] addr = textToNumericFormatV4(ip); return internalIp(addr) || "127.0.0.1".equals(ip); } private static boolean internalIp(byte[] addr) { if (addr == null || addr.length < 2) { return true; } final byte b0 = addr[0]; final byte b1 = addr[1]; // 10.x.x.x/8 final byte SECTION_1 = 0x0A; // 172.16.x.x/12 final byte SECTION_2 = (byte) 0xAC; final byte SECTION_3 = (byte) 0x10; final byte SECTION_4 = (byte) 0x1F; // 192.168.x.x/16 final byte SECTION_5 = (byte) 0xC0; final byte SECTION_6 = (byte) 0xA8; switch (b0) { case SECTION_1: return true; case SECTION_2: if (b1 >= SECTION_3 && b1 <= SECTION_4) { return true; } case SECTION_5: if (b1 == SECTION_6) { return true; } default: return false; } } /** * 将IPv4地址转换成字节 * * @param text IPv4地址 * @return byte 字节 */ public static byte[] textToNumericFormatV4(String text) { if (text.length() == 0) { return null; } byte[] bytes = new byte[4]; String[] elements = text.split("\\.", -1); try { long l; int i; switch (elements.length) { case 1: l = Long.parseLong(elements[0]); if ((l < 0L) || (l > 4294967295L)) { return null; } bytes[0] = (byte) (int) (l >> 24 & 0xFF); bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); bytes[3] = (byte) (int) (l & 0xFF); break; case 2: l = Integer.parseInt(elements[0]); if ((l < 0L) || (l > 255L)) { return null; } bytes[0] = (byte) (int) (l & 0xFF); l = Integer.parseInt(elements[1]); if ((l < 0L) || (l > 16777215L)) { return null; } bytes[1] = (byte) (int) (l >> 16 & 0xFF); bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); bytes[3] = (byte) (int) (l & 0xFF); break; case 3: for (i = 0; i < 2; ++i) { l = Integer.parseInt(elements[i]); if ((l < 0L) || (l > 255L)) { return null; } bytes[i] = (byte) (int) (l & 0xFF); } l = Integer.parseInt(elements[2]); if ((l < 0L) || (l > 65535L)) { return null; } bytes[2] = (byte) (int) (l >> 8 & 0xFF); bytes[3] = (byte) (int) (l & 0xFF); break; case 4: for (i = 0; i < 4; ++i) { l = Integer.parseInt(elements[i]); if ((l < 0L) || (l > 255L)) { return null; } bytes[i] = (byte) (int) (l & 0xFF); } break; default: return null; } } catch (NumberFormatException e) { return null; } return bytes; } /** * 向指定 URL 发送GET方法的请求 * * @param url 发送请求的 URL * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @param contentType 编码类型 * @return 所代表远程资源的响应结果 */ public static String sendGet(String url, String param, String contentType) { StringBuilder result = new StringBuilder(); BufferedReader in = null; try { String urlNameString = url + "?" + param; 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); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (in != null) { in.close(); } } catch (Exception ex) { ex.printStackTrace(); } } return result.toString(); }}2.2、第二种实现
根据ip获取对应的归属地的写法很多以及第三方jar也很多,这里我们使用Ip2region第三方jar包。
据官网介绍,Ip2region的极速查询响应即使是完全基于 xdb 文件的查询,单次查询响应时间在十微秒级别。
引入依赖
<dependency> <groupId>org.lionsoul</groupId> <artifactId>ip2region</artifactId> <version>2.6.5</version></dependency>下载 ip2region.xdb
下载地址为:
https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region.xdb编写工具类
public class IpUtil { private static Searcher searcher; /** * 判断是否为合法 IP * @return */ public static boolean checkIp(String ipAddress) { String ip = "([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}"; Pattern pattern = Pattern.compile(ip); Matcher matcher = pattern.matcher(ipAddress); return matcher.matches(); } /** * 在服务启动时,将 ip2region 加载到内存中 */ @PostConstruct private static void initIp2Region() { try { InputStream inputStream = new ClassPathResource("/ipdb/ip2region.xdb").getInputStream(); byte[] bytes = FileCopyUtils.copyToByteArray(inputStream); searcher = Searcher.newWithBuffer(bytes); } catch (Exception exception) { exception.printStackTrace(); } } /** * 获取 ip 所属地址 * * @param ip ip * @return */ public static String getIpRegion(String ip) { boolean isIp = checkIp(ip); if (isIp) { initIp2Region(); try { // searchIpInfo 的数据格式:国家|区域|省份|城市|ISP String searchIpInfo = searcher.search(ip); String[] splitIpInfo = searchIpInfo.split("\\|"); if (splitIpInfo.length > 0) { if ("中国".equals(splitIpInfo[0])) { // 国内属地返回省份 return splitIpInfo[2]; } else if ("0".equals(splitIpInfo[0])) { if ("内网IP".equals(splitIpInfo[4])) { // 内网 IP return splitIpInfo[4]; } else { return ""; } } else { // 国外属地返回国家 return splitIpInfo[0]; } } } catch (Exception e) { e.printStackTrace(); } return ""; } else { throw new IllegalArgumentException("非法的IP地址"); } }}对于initIp2Region方法,大家可以自定义改造处理下以满足各自的项目场景,也就是不一定要放到resource目录下。
测试
@SpringBootTestpublic class IpUtilTest { private static final Logger LOGGER = LoggerFactory.getLogger(IpUtilTest.class); /** * 测试 ip 所属地址 */ @Test public void testGetIpRegion() { String ip = "220.248.12.158"; // IpRegion:上海// String ip = "47.52.236.180"; // IpRegion:香港// String ip = "172.22.12.123"; // IpRegion:内网IP// String ip = "164.114.53.60"; // IpRegion:美国 String ipRegion = IpUtil.getIpRegion(ip); LOGGER.info("IpRegion:{}", ipRegion); }}以上为全部内容。