mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-12 07:25:39 +08:00
feat(ip-region): ip2region 支持 IPv6 双栈查询与商业版数据版本兼容
- 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 个商业版下标映射用例(基础/高级/专业/国外/直辖市/重载一致性)
This commit is contained in:
@@ -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<String> BIG_CHINA = List.of("香港","澳门","台湾");
|
||||
private static final List<String> 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<String> 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<String> 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;
|
||||
|
||||
|
||||
@@ -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<String> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
/// # 认证配置
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user