| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687 |
- package com.jtzx.crm.module.api.util;
- import cc.uncarbon.framework.core.context.UserContextHolder;
- import cc.uncarbon.framework.web.util.IPUtil;
- import cn.hutool.core.collection.CollUtil;
- import cn.hutool.core.text.CharSequenceUtil;
- import jakarta.servlet.http.HttpServletRequest;
- import java.util.List;
- public class ReqIpUtil {
- public static String getIpAddr(HttpServletRequest request) {
- String ip = UserContextHolder.getClientIP();
- // 2. 如果为空,或者拿到的明显是本地内网回环,则用 IPUtil 兜底获取
- if (CharSequenceUtil.isEmpty(ip) || isLoopbackOrInternal(ip)) {
- // 先尝试通过 IPUtil 获取整个未分割的 IP 串(避免因为固定的 index=0 错失公网IP)
- // 注意:因为工具类把 REMOTE_ADDR 排在 X-Real-IP 前面,我们先手动夹带一下核心请求头
- ip = request.getHeader("X-Forwarded-For");
- if (CharSequenceUtil.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
- ip = request.getHeader("X-Real-IP");
- }
- // 如果上面两个核心头都没拿到,再让 IPUtil 去遍历它那一堆头
- if (CharSequenceUtil.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
- ip = IPUtil.getClientIPAddress(request, 0);
- }
- }
- // 3. 【核心修复】深度解析获取到的 IP,确保拿到的是公网 IP
- if (CharSequenceUtil.isNotEmpty(ip)) {
- // 如果包含逗号(说明是多层代理返回的 IP 链)
- if (CharSequenceUtil.contains(ip, ",")) {
- List<String> ipList = CharSequenceUtil.split(ip, ",");
- // 遍历 IP 链,找到第一个真正的公网 IP
- String publicIp = null;
- for (String singleIp : ipList) {
- String cleanIp = CharSequenceUtil.cleanBlank(singleIp);
- if (CharSequenceUtil.isNotEmpty(cleanIp) && !isLoopbackOrInternal(cleanIp) && !"unknown".equalsIgnoreCase(cleanIp)) {
- publicIp = cleanIp;
- break; // 找到第一个外网 IP,跳出循环
- }
- }
- // 如果找到了公网 IP 就用它,否则降级取原本的第一个
- if (publicIp != null) {
- ip = publicIp;
- } else {
- ip = CharSequenceUtil.cleanBlank(CollUtil.get(ipList, 0));
- }
- } else if (isLoopbackOrInternal(ip)) {
- // 如果不是逗号分隔,但本身是个内网 IP,且代理传了 X-Real-IP,做最后一次尝试
- String realIp = request.getHeader("X-Real-IP");
- if (CharSequenceUtil.isNotEmpty(realIp) && !isLoopbackOrInternal(realIp) && !"unknown".equalsIgnoreCase(realIp)) {
- ip = realIp.trim();
- }
- }
- }
- return ip;
- }
- /**
- * 辅助方法:判断 IP 是否为回环地址或局域网内网地址
- */
- private static boolean isLoopbackOrInternal(String ip) {
- if (ip == null) return true;
- String cleanIp = ip.trim();
- if ("127.0.0.1".equals(cleanIp) || "0:0:0:0:0:0:0:1".equals(cleanIp) || "localhost".equalsIgnoreCase(cleanIp)) {
- return true;
- }
- // 匹配内网 A/B/C 类私有地址段:10.x.x.x, 192.168.x.x, 172.16.x.x - 172.31.x.x
- return cleanIp.startsWith("10.") || cleanIp.startsWith("192.168.") ||
- (cleanIp.startsWith("172.") && cleanIp.matches("^172\\.(1[6-9]|2[0-9]|3[0-1])\\..*"));
- }
- // 辅助方法:判断是否为内网 IP(10.x, 172.16.x-172.31.x, 192.168.x)
- private static boolean isInternalIp(String ip) {
- if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) return true;
- // 简单正则匹配常见的私有内网 IP 段
- return ip.startsWith("10.") || ip.startsWith("192.168.") ||
- (ip.startsWith("172.") && ip.matches("^172\\.(1[6-9]|2[0-9]|3[0-1])\\..*"));
- }
- }
|