From 751f4cedfc16b9b08bcf4f0d642f56b050d9dc7e Mon Sep 17 00:00:00 2001 From: daxpay Date: Mon, 10 Aug 2026 13:53:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(ip-region):=20ip2region=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20IPv6=20=E5=8F=8C=E6=A0=88=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E4=B8=8E=E5=95=86=E4=B8=9A=E7=89=88=E6=95=B0=E6=8D=AE=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=85=BC=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - IpToRegionService 双栈分发: v4/v6 各自独立 Searcher 与缓存, 按 IP 格式路由 - 新增 Ip2regionDataVersion 枚举(开源/基础/高级/专业版), v4/v6 可独立配置数据版本 - IpRegion.init 按下标映射表解析, 商业三档统一 country=1 province=2 city=3 isp=5 国家码取末段 - yml 新增 data-version / ipv6-data-version 配置, 默认 open_source 向后兼容 - IpRegionTest 新增 6 个商业版下标映射用例(基础/高级/专业/国外/直辖市/重载一致性) --- .../controller/IpRegionDemoController.java | 2 +- .../audit/log/service/ip2region/IpRegion.java | 42 ++++- .../service/ip2region/IpToRegionService.java | 170 +++++++++++++----- .../log/service/ip2region/IpRegionTest.java | 92 ++++++++++ .../properties/PlatformStarterProperties.java | 28 ++- .../src/main/resources/application-dev.yml | 8 +- .../src/main/resources/application-prod.yml | 4 + 7 files changed, 288 insertions(+), 58 deletions(-) diff --git a/daxpay-demo/src/main/java/cn/daxpay/open/demo/ipregion/controller/IpRegionDemoController.java b/daxpay-demo/src/main/java/cn/daxpay/open/demo/ipregion/controller/IpRegionDemoController.java index bb1c882ef..3e9008d23 100644 --- a/daxpay-demo/src/main/java/cn/daxpay/open/demo/ipregion/controller/IpRegionDemoController.java +++ b/daxpay-demo/src/main/java/cn/daxpay/open/demo/ipregion/controller/IpRegionDemoController.java @@ -38,7 +38,7 @@ public class IpRegionDemoController { @Operation(summary = "根据 IP 查询归属地") @GetMapping("/query") public Result query( - @Parameter(description = "IPv4 地址") @RequestParam("ip") String ip) { + @Parameter(description = "IP 地址(支持 IPv4 / IPv6)") @RequestParam("ip") String ip) { return Res.ok(buildResult(ip)); } diff --git a/daxpay-platform/daxpay-platform-capability/capability-audit-log/src/main/java/cn/daxpay/open/platform/capability/audit/log/service/ip2region/IpRegion.java b/daxpay-platform/daxpay-platform-capability/capability-audit-log/src/main/java/cn/daxpay/open/platform/capability/audit/log/service/ip2region/IpRegion.java index 841117823..3a1b2b792 100644 --- a/daxpay-platform/daxpay-platform-capability/capability-audit-log/src/main/java/cn/daxpay/open/platform/capability/audit/log/service/ip2region/IpRegion.java +++ b/daxpay-platform/daxpay-platform-capability/capability-audit-log/src/main/java/cn/daxpay/open/platform/capability/audit/log/service/ip2region/IpRegion.java @@ -1,5 +1,6 @@ package cn.daxpay.open.platform.capability.audit.log.service.ip2region; +import cn.daxpay.open.platform.common.config.properties.PlatformStarterProperties.AuditLog.Ip2regionDataVersion; import cn.hutool.core.collection.CollUtil; import lombok.Data; import lombok.experimental.Accessors; @@ -8,9 +9,10 @@ import java.util.List; /// # IP对应地址区域信息 /// -/// 官方 ip2region v4.xdb 数据格式: `国家|省份|城市|ISP|国家码(iso-alpha2)`, 五段定长。 +/// 兼容开源版与商业版 xdb 数据。开源版格式: `国家|省份|城市|ISP|国家码(iso-alpha2)`, 五段定长; +/// 商业版格式: `大洲|国家|省份|城市|区县|ISP|...|国家码`, 16-18 段。由 [Ip2regionDataVersion] 决定下标映射。 /// -/// v4 名称格式规律(实证): 普通省/市返回全称(广东省/深圳市); 直辖市与自治区返回短名(北京/内蒙古); +/// v4/v6 名称格式规律(实证): 普通省/市返回全称(广东省/深圳市); 直辖市与自治区返回短名(北京/内蒙古); /// 港澳台返回全称(香港特别行政区/台湾省); 无归属地返回 "0"。 @Data @Accessors(chain = true) @@ -18,6 +20,14 @@ public class IpRegion { private static final List BIG_CHINA = List.of("香港","澳门","台湾"); private static final List PROVINCE_LEVEL_CITY = List.of("北京","上海","重庆","天津"); + /// 字段下标映射表: 每行 {country, province, city, isp, countryCode} + /// + /// 开源版(5段): country=0 province=1 city=2 isp=3 countryCode=4 + /// 商业三档(16-18段): country=1 province=2 city=3 isp=5 countryCode=末段(-1, 运行时按 size-1 计算) + /// 商业三档的 country/province/city/isp 下标完全一致, 差异仅在中间段数和末段位置 + private static final int[] INDEX_OPEN_SOURCE = {0, 1, 2, 3, 4}; + private static final int[] INDEX_COMMERCIAL = {1, 2, 3, 5, -1}; + /// 国家 private String country; @@ -57,19 +67,35 @@ public class IpRegion { BIG_CHINA.contains(normalizeName(province)); } - /// 官方 v4.xdb 格式: 国家|省份|城市|ISP|国家码 + /// 解析 xdb 定位信息, 默认开源版下标映射(向后兼容) /// /// 防御性取值: 记录段数可能不足(国外/内网等短记录), 逐段判空, 不足不抛异常 public static IpRegion init(List ipInfo){ + return init(ipInfo, Ip2regionDataVersion.OPEN_SOURCE); + } + + /// 按数据版本档次解析 xdb 定位信息 + /// + /// 开源版与商业版字段顺序不同, 由 version 决定下标映射: + /// - OPEN_SOURCE: 国家(0)|省份(1)|城市(2)|ISP(3)|国家码(4) + /// - BASE/HIGH/PRO: 大洲(0)|国家(1)|省份(2)|城市(3)|区县(4)|ISP(5)|...|国家码(末段) + /// + /// 防御性取值: 记录段数可能不足, 逐段判空, 不足不抛异常 + public static IpRegion init(List ipInfo, Ip2regionDataVersion version){ IpRegion ipRegion = new IpRegion(); if (CollUtil.isEmpty(ipInfo)){ return ipRegion; } - ipRegion.country = safeGet(ipInfo, 0); - ipRegion.province = safeGet(ipInfo, 1); - ipRegion.city = safeGet(ipInfo, 2); - ipRegion.isp = safeGet(ipInfo, 3); - ipRegion.countryCode = safeGet(ipInfo, 4); + boolean isOpenSource = version == null || version == Ip2regionDataVersion.OPEN_SOURCE; + int[] map = isOpenSource ? INDEX_OPEN_SOURCE : INDEX_COMMERCIAL; + + ipRegion.country = safeGet(ipInfo, map[0]); + ipRegion.province = safeGet(ipInfo, map[1]); + ipRegion.city = safeGet(ipInfo, map[2]); + ipRegion.isp = safeGet(ipInfo, map[3]); + // 国家码: 开源版固定下标4, 商业版末段(size-1, 自动适配16/17/18段) + int ccIdx = map[4] == -1 ? ipInfo.size() - 1 : map[4]; + ipRegion.countryCode = safeGet(ipInfo, ccIdx); return ipRegion; diff --git a/daxpay-platform/daxpay-platform-capability/capability-audit-log/src/main/java/cn/daxpay/open/platform/capability/audit/log/service/ip2region/IpToRegionService.java b/daxpay-platform/daxpay-platform-capability/capability-audit-log/src/main/java/cn/daxpay/open/platform/capability/audit/log/service/ip2region/IpToRegionService.java index 48cd28def..bb5c45ba4 100644 --- a/daxpay-platform/daxpay-platform-capability/capability-audit-log/src/main/java/cn/daxpay/open/platform/capability/audit/log/service/ip2region/IpToRegionService.java +++ b/daxpay-platform/daxpay-platform-capability/capability-audit-log/src/main/java/cn/daxpay/open/platform/capability/audit/log/service/ip2region/IpToRegionService.java @@ -7,6 +7,7 @@ import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.lionsoul.ip2region.xdb.IPv4; +import org.lionsoul.ip2region.xdb.IPv6; import org.lionsoul.ip2region.xdb.LongByteArray; import org.lionsoul.ip2region.xdb.Searcher; import org.springframework.stereotype.Service; @@ -17,58 +18,111 @@ import java.util.regex.Matcher; /// # IP归属地查询服务 /// +/// 同时支持 IPv4 与 IPv6 查询。v4/v6 各自独立的 xdb 数据文件与 Searcher 缓存, +/// 根据 IP 格式分发到对应版本。v4/v6 xdb 字段格式完全一致(国家|省份|城市|ISP|国家码), +/// 解析结果统一由 [IpRegion] 承载。 +/// @Slf4j @Service @RequiredArgsConstructor public class IpToRegionService { - /// VectorIndex 缓存 - private static byte[] XDB_INDEX; - /// 整个 xdb 文件缓存 - private static LongByteArray XDB_BUFF; + // ---- IPv4 缓存 ---- + /// IPv4 VectorIndex 缓存 + private static byte[] XDB_V4_INDEX; + /// IPv4 整个 xdb 文件缓存 + private static LongByteArray XDB_V4_BUFF; + + // ---- IPv6 缓存 ---- + /// IPv6 VectorIndex 缓存 + private static byte[] XDB_V6_INDEX; + /// IPv6 整个 xdb 文件缓存 + private static LongByteArray XDB_V6_BUFF; + /// 参数配置 private final PlatformStarterProperties platformStarterProperties; - /// VectorIndex 索引 - private Searcher getSearcherByIndex() { + // ==================== IPv4 Searcher ==================== + + /// IPv4 VectorIndex 索引 + private Searcher getV4SearcherByIndex() { + String filePath = getV4FilePath(); + if (StrUtil.isBlank(filePath)) { + return null; + } try { - String filePath = platformStarterProperties.getAuditLog().getIp2region().getFilePath(); - if (StrUtil.isBlank(filePath)) { - log.warn("IP归属地查询: 文件路径未配置"); - return null; + if (Objects.isNull(XDB_V4_INDEX)) { + XDB_V4_INDEX = Searcher.loadVectorIndexFromFile(filePath); + log.info("IP归属地查询: IPv4 VectorIndex 缓存加载成功"); } - if (Objects.isNull(XDB_INDEX)) { - XDB_INDEX = Searcher.loadVectorIndexFromFile(filePath); - log.info("IP归属地查询: VectorIndex 缓存加载成功"); - } - return Searcher.newWithVectorIndex(new IPv4(), filePath, XDB_INDEX); + return Searcher.newWithVectorIndex(new IPv4(), filePath, XDB_V4_INDEX); } catch (Exception e) { - log.warn("IP归属地查询: 创建VectorIndex Searcher失败, error={}", e.getMessage()); + log.debug("IP归属地查询: 创建IPv4 VectorIndex Searcher失败, error={}", e.getMessage()); return null; } } - /// 缓存整个 xdb 数据 - private Searcher getSearcherByCache() { + /// IPv4 缓存整个 xdb 数据 + private Searcher getV4SearcherByCache() { + String filePath = getV4FilePath(); + if (StrUtil.isBlank(filePath)) { + return null; + } try { - String filePath = platformStarterProperties.getAuditLog().getIp2region().getFilePath(); - if (StrUtil.isBlank(filePath)) { - log.warn("IP归属地查询: 文件路径未配置"); - return null; + if (Objects.isNull(XDB_V4_BUFF)) { + XDB_V4_BUFF = Searcher.loadContentFromFile(filePath); + log.info("IP归属地查询: IPv4 xdb数据缓存加载成功"); } - if (Objects.isNull(XDB_BUFF)) { - XDB_BUFF = Searcher.loadContentFromFile(filePath); - log.info("IP归属地查询: xdb数据缓存加载成功"); - } - return Searcher.newWithBuffer(new IPv4(), XDB_BUFF); + return Searcher.newWithBuffer(new IPv4(), XDB_V4_BUFF); } catch (Exception e) { - log.warn("IP归属地查询: 创建缓存Searcher失败, error={}", e.getMessage()); + log.debug("IP归属地查询: 创建IPv4缓存Searcher失败, error={}", e.getMessage()); return null; } } + // ==================== IPv6 Searcher ==================== + + /// IPv6 VectorIndex 索引 + private Searcher getV6SearcherByIndex() { + String filePath = getV6FilePath(); + if (StrUtil.isBlank(filePath)) { + return null; + } + try { + if (Objects.isNull(XDB_V6_INDEX)) { + XDB_V6_INDEX = Searcher.loadVectorIndexFromFile(filePath); + log.info("IP归属地查询: IPv6 VectorIndex 缓存加载成功"); + } + return Searcher.newWithVectorIndex(new IPv6(), filePath, XDB_V6_INDEX); + } catch (Exception e) { + log.debug("IP归属地查询: 创建IPv6 VectorIndex Searcher失败, error={}", e.getMessage()); + return null; + } + } + + /// IPv6 缓存整个 xdb 数据 + private Searcher getV6SearcherByCache() { + String filePath = getV6FilePath(); + if (StrUtil.isBlank(filePath)) { + return null; + } + try { + if (Objects.isNull(XDB_V6_BUFF)) { + XDB_V6_BUFF = Searcher.loadContentFromFile(filePath); + log.info("IP归属地查询: IPv6 xdb数据缓存加载成功"); + } + return Searcher.newWithBuffer(new IPv6(), XDB_V6_BUFF); + } catch (Exception e) { + log.debug("IP归属地查询: 创建IPv6缓存Searcher失败, error={}", e.getMessage()); + return null; + } + } + + // ==================== 查询入口 ==================== + /// 根据IP获得地址信息 - /// 查询失败时返回 null,不抛出异常,保证主流程继续 + /// 查询失败时返回 null,不抛出异常,保证主流程继续。 + /// IPv6 文件未配置或加载失败时静默返回 null(与旧版 IPv6 跳过行为一致)。 public IpRegion getRegionByIp(String ip) { // 判断IP是否合法 if (StrUtil.isBlank(ip)) { @@ -76,34 +130,47 @@ public class IpToRegionService { return null; } - Matcher matcher = PatternPool.IPV4.matcher(ip); - Matcher ipV6Matcher = PatternPool.IPV6.matcher(ip); - if (!matcher.matches()) { - if (ipV6Matcher.matches()) { - log.debug("IP归属地查询: IPv6地址跳过, ip={}", ip); - } else { - log.debug("IP归属地查询: 非法IPv4地址, ip={}", ip); - } + // 按 IP 格式分发到对应版本 Searcher + Matcher ipv4Matcher = PatternPool.IPV4.matcher(ip); + Matcher ipv6Matcher = PatternPool.IPV6.matcher(ip); + boolean isV4 = ipv4Matcher.matches(); + boolean isV6 = ipv6Matcher.matches(); + if (!isV4 && !isV6) { + log.debug("IP归属地查询: 非法IP地址, ip={}", ip); return null; } - // 根据类型获取 Searcher 对象 - Searcher searcher = switch (platformStarterProperties.getAuditLog().getIp2region().getSearchType()) { - case VECTOR_INDEX -> getSearcherByIndex(); - case CACHE -> getSearcherByCache(); - }; + // 根据类型获取对应版本的 Searcher 对象 + Searcher searcher; + String label; + if (isV4) { + label = "IPv4"; + searcher = switch (platformStarterProperties.getAuditLog().getIp2region().getSearchType()) { + case VECTOR_INDEX -> getV4SearcherByIndex(); + case CACHE -> getV4SearcherByCache(); + }; + } else { + label = "IPv6"; + searcher = switch (platformStarterProperties.getAuditLog().getIp2region().getSearchType()) { + case VECTOR_INDEX -> getV6SearcherByIndex(); + case CACHE -> getV6SearcherByCache(); + }; + } - // 无法进行查询 + // 无法进行查询(文件未配置或加载失败) if (Objects.isNull(searcher)) { - log.warn("IP归属地查询: Searcher创建失败,无法进行查询, ip={}", ip); + log.debug("IP归属地查询: {} Searcher创建失败,无法进行查询, ip={}", label, ip); return null; } try { - // 官方 v4.xdb 格式: 国家|省份|城市|ISP|国家码(iso-alpha2) + // xdb 定位信息字段格式由数据版本决定(开源版5段 / 商业版16-18段) String search = searcher.search(ip); List ipInfo = StrUtil.split(search, "|"); - return IpRegion.init(ipInfo); + // v4/v6 可各自配置不同数据版本档次 + var ip2regionConfig = platformStarterProperties.getAuditLog().getIp2region(); + var dataVersion = isV4 ? ip2regionConfig.getDataVersion() : ip2regionConfig.getIpv6DataVersion(); + return IpRegion.init(ipInfo, dataVersion); } catch (Exception e) { // 查询异常不抛出不阻塞主流程,仅记录日志 log.warn("IP归属地查询异常: ip={}, error={}", ip, e.getMessage()); @@ -149,5 +216,14 @@ public class IpToRegionService { return StrUtil.format("{}/{}", region.getCountry(), region.getIsp()); } } -} + /// 获取 IPv4 xdb 文件路径 + private String getV4FilePath() { + return platformStarterProperties.getAuditLog().getIp2region().getFilePath(); + } + + /// 获取 IPv6 xdb 文件路径 + private String getV6FilePath() { + return platformStarterProperties.getAuditLog().getIp2region().getIpv6FilePath(); + } +} diff --git a/daxpay-platform/daxpay-platform-capability/capability-audit-log/src/test/java/cn/daxpay/open/platform/capability/audit/log/service/ip2region/IpRegionTest.java b/daxpay-platform/daxpay-platform-capability/capability-audit-log/src/test/java/cn/daxpay/open/platform/capability/audit/log/service/ip2region/IpRegionTest.java index bbc417ad9..aec5ecc30 100644 --- a/daxpay-platform/daxpay-platform-capability/capability-audit-log/src/test/java/cn/daxpay/open/platform/capability/audit/log/service/ip2region/IpRegionTest.java +++ b/daxpay-platform/daxpay-platform-capability/capability-audit-log/src/test/java/cn/daxpay/open/platform/capability/audit/log/service/ip2region/IpRegionTest.java @@ -1,5 +1,7 @@ package cn.daxpay.open.platform.capability.audit.log.service.ip2region; +import cn.daxpay.open.platform.common.config.properties.PlatformStarterProperties.AuditLog.Ip2regionDataVersion; +import cn.hutool.core.util.StrUtil; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -106,4 +108,94 @@ class IpRegionTest { assertTrue(region.isBigChina()); } + + // ==================== 商业版(付费数据)下标映射 ==================== + + @Test + @DisplayName("商业基础版(16段): 下标映射正确, 不取到大洲/区县") + void init_baseVersion_shouldMapFields() { + // 基础版16段: 大洲|国家|省份|城市|区县|ISP|经度|纬度|行政区码|电话区号|邮编|时区|货币|海拔|气象站|国家码 + String record = "亚洲|中国|广东省|深圳市|宝安区|电信|113.88|22.55|440306|0755|518100|Asia/Shanghai|CNY|11|CHXX0120|CN"; + List ipInfo = StrUtil.split(record, "|"); + IpRegion region = IpRegion.init(ipInfo, Ip2regionDataVersion.BASE); + + assertEquals("中国", region.getCountry()); + assertEquals("广东省", region.getProvince()); + assertEquals("深圳市", region.getCity()); + assertEquals("电信", region.getIsp()); + assertEquals("CN", region.getCountryCode()); + } + + @Test + @DisplayName("商业高级版(17段): 基础版+ASN, 下标映射不变") + void init_highVersion_shouldMapFields() { + // 高级版17段: 基础版在货币(12)后插入ASN(13), 国家码仍在末段 + String record = "亚洲|中国|广东省|深圳市|宝安区|电信|113.88|22.55|440306|0755|518100|Asia/Shanghai|CNY|AS4134|11|CHXX0120|CN"; + List ipInfo = StrUtil.split(record, "|"); + IpRegion region = IpRegion.init(ipInfo, Ip2regionDataVersion.HIGH); + + assertEquals("中国", region.getCountry()); + assertEquals("广东省", region.getProvince()); + assertEquals("深圳市", region.getCity()); + assertEquals("电信", region.getIsp()); + assertEquals("CN", region.getCountryCode()); + } + + @Test + @DisplayName("商业专业版(18段): 高级版+应用场景, 下标映射不变") + void init_proVersion_shouldMapFields() { + // 专业版18段: 高级版在ASN后插入应用场景, 国家码仍在末段 + String record = "亚洲|中国|广东省|深圳市|宝安区|电信|113.88|22.55|440306|0755|518100|Asia/Shanghai|CNY|AS4134|MOB|11|CHXX0120|CN"; + List ipInfo = StrUtil.split(record, "|"); + IpRegion region = IpRegion.init(ipInfo, Ip2regionDataVersion.PRO); + + assertEquals("中国", region.getCountry()); + assertEquals("广东省", region.getProvince()); + assertEquals("深圳市", region.getCity()); + assertEquals("电信", region.getIsp()); + assertEquals("CN", region.getCountryCode()); + } + + @Test + @DisplayName("商业版国外记录(含缺失空段): 下标映射正确") + void init_proVersion_overseasWithMissingFields() { + // 专业版国外示例: 缺失字段用空字符串占位 + String record = "北美洲|美国|California|Los Angeles||美国电话电报公司|-118.24|34.05|||90009|America/Los_Angeles|USD|||US"; + List ipInfo = StrUtil.split(record, "|"); + IpRegion region = IpRegion.init(ipInfo, Ip2regionDataVersion.PRO); + + assertEquals("美国", region.getCountry()); + assertEquals("California", region.getProvince()); + assertEquals("Los Angeles", region.getCity()); + assertEquals("美国电话电报公司", region.getIsp()); + assertEquals("US", region.getCountryCode()); + // 国外地址判定方法正常 + assertFalse(region.isChinaIp()); + } + + @Test + @DisplayName("商业版仍能正确识别国内地址判定(isChinaIp/isProvinceLevel)") + void init_baseVersion_chinaDetection() { + // 北京直辖市: 商业基础版格式 + String record = "亚洲|中国|北京|北京市|朝阳区|电信|116.4|39.9|110105|010|100020|Asia/Shanghai|CNY|11|CHXX0099|CN"; + List ipInfo = StrUtil.split(record, "|"); + IpRegion region = IpRegion.init(ipInfo, Ip2regionDataVersion.BASE); + + assertTrue(region.isChinaIp()); + assertTrue(region.isProvinceLevel()); + } + + @Test + @DisplayName("开源版默认重载与显式传 OPEN_SOURCE 结果一致") + void init_defaultOverload_equalsExplicitOpenSource() { + List ipInfo = List.of("中国", "山东省", "济南市", "电信", "CN"); + IpRegion byDefault = IpRegion.init(ipInfo); + IpRegion explicit = IpRegion.init(ipInfo, Ip2regionDataVersion.OPEN_SOURCE); + + assertEquals(explicit.getCountry(), byDefault.getCountry()); + assertEquals(explicit.getProvince(), byDefault.getProvince()); + assertEquals(explicit.getCity(), byDefault.getCity()); + assertEquals(explicit.getIsp(), byDefault.getIsp()); + assertEquals(explicit.getCountryCode(), byDefault.getCountryCode()); + } } diff --git a/daxpay-platform/daxpay-platform-common/common-config/src/main/java/cn/daxpay/open/platform/common/config/properties/PlatformStarterProperties.java b/daxpay-platform/daxpay-platform-common/common-config/src/main/java/cn/daxpay/open/platform/common/config/properties/PlatformStarterProperties.java index f01042728..a76328b3a 100644 --- a/daxpay-platform/daxpay-platform-common/common-config/src/main/java/cn/daxpay/open/platform/common/config/properties/PlatformStarterProperties.java +++ b/daxpay-platform/daxpay-platform-common/common-config/src/main/java/cn/daxpay/open/platform/common/config/properties/PlatformStarterProperties.java @@ -29,8 +29,14 @@ public class PlatformStarterProperties { @Getter @Setter public static class Ip2region { - /// ip2region.xdb 数据文件所在路径 + /// IPv4 xdb 数据文件所在路径 private String filePath; + /// IPv6 xdb 数据文件路径(可选, 未配置时 IPv6 地址查询返回 null) + private String ipv6FilePath; + /// IPv4 数据版本档次(开源版/商业基础版/高级版/专业版),默认开源版 + private Ip2regionDataVersion dataVersion = Ip2regionDataVersion.OPEN_SOURCE; + /// IPv6 数据版本档次(可与 v4 不同),默认开源版 + private Ip2regionDataVersion ipv6DataVersion = Ip2regionDataVersion.OPEN_SOURCE; /// 查询模式, 默认为缓存 VectorIndex 索引 private Ip2regionSearch searchType = Ip2regionSearch.VECTOR_INDEX; } @@ -48,6 +54,26 @@ public class PlatformStarterProperties { /// 缓存整个 xdb 数据 CACHE } + /// # ip2region 数据版本档次 + /// + /// 决定 xdb 定位信息的字段下标映射。开源版与商业版字段顺序不同, + /// 切换数据文件时必须同步配置此项, 否则解析结果错位。 + /// + /// 各版本字段格式(| 分隔, 下标从 0 起): + /// - OPEN_SOURCE: 国家|省份|城市|ISP|国家码(5段) + /// - BASE/HIGH/PRO: 大洲|国家|省份|城市|区县|ISP|...|国家码(16-18段, 国家码恒在末段) + /// + /// 商业三档的 country/province/city/isp 下标完全一致(1/2/3/5), 差异仅在中间段数。 + public enum Ip2regionDataVersion { + /// 开源版:国家|省份|城市|ISP|国家码(5段) + OPEN_SOURCE, + /// 商业基础版:大洲|国家|省份|城市|区县|ISP|...|国家码(16段) + BASE, + /// 商业高级版:基础版+ASN(17段) + HIGH, + /// 商业专业版:高级版+应用场景(18段) + PRO + } } /// # 认证配置 /// diff --git a/daxpay-start/src/main/resources/application-dev.yml b/daxpay-start/src/main/resources/application-dev.yml index ed433edf1..578613a90 100644 --- a/daxpay-start/src/main/resources/application-dev.yml +++ b/daxpay-start/src/main/resources/application-dev.yml @@ -103,8 +103,14 @@ daxpay: # 审计日志配置 audit-log: ip2region: - # ip2region.xdb 文件路径 + # IPv4 xdb 文件路径 file-path: E:/data/ip2region/ip2region_v4.xdb + # IPv6 xdb 文件路径(可选, 未配置时 IPv6 查询返回 null) + ipv6-file-path: E:/data/ip2region/ip2region_v6.xdb + # IPv4 数据版本: OPEN_SOURCE(开源5段) | BASE(商业基础版) | HIGH(高级版) | PRO(专业版) + data-version: open_source + # IPv6 数据版本(可与 v4 不同) + ipv6-data-version: open_source # 查询模式: VECTOR_INDEX(推荐) 或 CACHE search-type: VECTOR_INDEX config: diff --git a/daxpay-start/src/main/resources/application-prod.yml b/daxpay-start/src/main/resources/application-prod.yml index 6ae4ffc53..15fa4b6f3 100644 --- a/daxpay-start/src/main/resources/application-prod.yml +++ b/daxpay-start/src/main/resources/application-prod.yml @@ -125,6 +125,10 @@ daxpay: audit-log: ip2region: file-path: ${IP2REGION_FILE_PATH:/data/ip/ip2region_v4.xdb} + ipv6-file-path: ${IP2REGION_V6_FILE_PATH:/data/ip/ip2region_v6.xdb} + # 数据版本: open_source | base | high | pro(须与实际 xdb 文件档次一致) + data-version: ${IP2REGION_DATA_VERSION:open_source} + ipv6-data-version: ${IP2REGION_V6_DATA_VERSION:open_source} search-type: VECTOR_INDEX config: # 沙箱环境(必须关闭; 不写时 Java 默认 true 会被 DeploymentModeEnforcer 拦截)