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 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])\\..*")); } }