mirror of
https://github.com/PlayEdu/PlayEdu
synced 2026-10-25 17:36:35 +08:00
fix(ip): IP 地理位置解析改用 ip2region 本地离线库 (#45)
* fix(ip): IP 地理位置解析改用 ip2region 本地离线库 替换原先依赖外部接口 whois.pconline.com.cn 的实现,消除 "Unexpected end of file from server" 等因外部接口抖动导致的 异常日志与出网调用。改用 org.lionsoul:ip2region 本地 xdb 离线库,全量加载到内存查询,保持 "省-市" 返回格式不变。
This commit is contained in:
4 files changed
+76
-19
No files matched your search
@@ -1,3 +1,7 @@
|
||||
## Unreleased
|
||||
|
||||
- 修复:IP 地理位置解析改用 `ip2region` 本地离线库,移除对外部接口 `whois.pconline.com.cn` 的依赖,消除 "Unexpected end of file from server" 等因外部接口抖动导致的异常日志与出网调用
|
||||
|
||||
## 2.1
|
||||
|
||||
- 优化:Dockerfile 拆分依赖层,前端先 `COPY package.json/pnpm-lock.yaml` 再 `pnpm i`,Java 先 `COPY pom.xml` 并 `dependency:go-offline` 预热依赖,源码变更不再触发依赖重装,显著缩短增量构建耗时
|
||||
|
||||
@@ -15,18 +15,46 @@
|
||||
*/
|
||||
package xyz.playedu.common.util;
|
||||
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.lionsoul.ip2region.xdb.Searcher;
|
||||
|
||||
@Slf4j
|
||||
public class IpUtil {
|
||||
|
||||
private static final String XDB_CLASSPATH = "ip2region/ip2region.xdb";
|
||||
private static volatile Searcher ipSearcher;
|
||||
|
||||
private static Searcher getSearcher() {
|
||||
if (ipSearcher == null) {
|
||||
synchronized (IpUtil.class) {
|
||||
if (ipSearcher == null) {
|
||||
try (InputStream in =
|
||||
IpUtil.class.getClassLoader().getResourceAsStream(XDB_CLASSPATH)) {
|
||||
if (in == null) {
|
||||
log.error("ip2region xdb 资源未找到: {}", XDB_CLASSPATH);
|
||||
return null;
|
||||
}
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||
byte[] chunk = new byte[8192];
|
||||
int n;
|
||||
while ((n = in.read(chunk)) != -1) {
|
||||
buf.write(chunk, 0, n);
|
||||
}
|
||||
ipSearcher = Searcher.newWithBuffer(buf.toByteArray());
|
||||
} catch (Exception e) {
|
||||
log.error("初始化 ip2region 失败 msg={}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ipSearcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端IP
|
||||
*
|
||||
@@ -67,35 +95,54 @@ public class IpUtil {
|
||||
* @author fzr
|
||||
*/
|
||||
public static String getRealAddressByIP(String ip) {
|
||||
String IP_URL = "https://whois.pconline.com.cn/ipJson.jsp";
|
||||
String UNKNOWN = "未知";
|
||||
|
||||
if (StringUtil.isEmpty(ip)) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
if (IpUtil.internalIp(ip)) {
|
||||
return "内网";
|
||||
}
|
||||
|
||||
Searcher searcher = getSearcher();
|
||||
if (searcher == null) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
try {
|
||||
String rspStr =
|
||||
HttpUtil.get(
|
||||
IP_URL,
|
||||
new HashMap<>() {
|
||||
{
|
||||
put("ip", ip);
|
||||
put("json", true);
|
||||
}
|
||||
});
|
||||
if (StringUtil.isEmpty(rspStr)) {
|
||||
log.error("获取地理位置异常1 {}", ip);
|
||||
// xdb 返回格式:国家|省份|城市|ISP|国家代码,例如 "中国|浙江省|杭州市|电信|CN"
|
||||
String region = searcher.search(ip);
|
||||
if (StringUtil.isEmpty(region)) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
JSONObject json = JSONUtil.parseObj(rspStr);
|
||||
return String.format("%s-%s", json.getStr("pro"), json.getStr("city"));
|
||||
String[] parts = region.split("\\|", -1);
|
||||
String country = parts.length > 0 ? cleanField(parts[0]) : "";
|
||||
String pro = parts.length > 1 ? cleanField(parts[1]) : "";
|
||||
String city = parts.length > 2 ? cleanField(parts[2]) : "";
|
||||
if (!StringUtil.isEmpty(pro) && !StringUtil.isEmpty(city)) {
|
||||
return String.format("%s-%s", pro, city);
|
||||
}
|
||||
if (!StringUtil.isEmpty(pro)) {
|
||||
return pro;
|
||||
}
|
||||
if (!StringUtil.isEmpty(country)) {
|
||||
return country;
|
||||
}
|
||||
return UNKNOWN;
|
||||
} catch (Exception e) {
|
||||
log.error("获取地理位置异常2 {} msg {}", ip, e.getMessage());
|
||||
log.warn("获取地理位置异常 ip={} msg={}", ip, e.getMessage());
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
private static String cleanField(String s) {
|
||||
if (s == null || "0".equals(s)) {
|
||||
return "";
|
||||
}
|
||||
return s.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为内部IP地址
|
||||
*
|
||||
|
||||
Binary file not shown.
@@ -109,6 +109,12 @@
|
||||
<version>5.8.32</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.lionsoul</groupId>
|
||||
<artifactId>ip2region</artifactId>
|
||||
<version>2.7.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot3-starter</artifactId>
|
||||
|
||||
Reference in new issue
Block a user