Browse Source

feat(ip2region): sync from ip2region

- sync from [ip2region](https://github.com/lionsoul2014/ip2region)
pull/1416/head
colin 2 months ago
parent
commit
95ad055f02
  1. 65
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/Abstractions/AbstractCacheStrategy.cs
  2. 26
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/CacheStrategyFactory.cs
  3. 33
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/ContentCacheStrategy.cs
  4. 15
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/FileCacheStrategy.cs
  5. 21
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/ICacheStrategy.cs
  6. 77
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/StreamCacheStrategy.cs
  7. 22
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/VectorIndexCacheStrategy.cs
  8. 4
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN.Abp.IP2Region.csproj
  9. 14
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/AbpIP2RegionLocationResolveOptions.cs
  10. 3
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/AbpIP2RegionModule.cs
  11. 153
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/AbpSearcher.cs
  12. 30
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/IP2RegionIPLocationResolveContributor.cs
  13. BIN
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/Resources/ip2region_v4.xdb
  14. 16
      aspnet-core/tests/LINGYUN.Abp.IP2Region.Tests/LINGYUN/Abp/IP2Region/AbpIP2RegionTestModule.cs
  15. 53
      aspnet-core/tests/LINGYUN.Abp.IP2Region.Tests/LINGYUN/Abp/IP2Region/SearcherTest.cs
  16. 1367687
      aspnet-core/tests/LINGYUN.Abp.IP2Region.Tests/LINGYUN/Abp/IP2Region/ipv4_source.txt

65
aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/Abstractions/AbstractCacheStrategy.cs

@ -1,65 +0,0 @@
// Copyright 2023 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25
using System;
using System.Buffers;
using System.IO;
namespace IP2Region.Net.Internal.Abstractions;
internal abstract class AbstractCacheStrategy
{
protected const int HeaderInfoLength = 256;
protected const int VectorIndexRows = 256;
protected const int VectorIndexCols = 256;
protected const int VectorIndexSize = 8;
protected readonly Stream XdbStream;
private const int BufferSize = 4096;
internal int IoCount { get; private set; }
protected AbstractCacheStrategy(Stream xdbStream)
{
XdbStream = xdbStream;
}
protected int GetVectorIndexStartPos(uint ip)
{
var il0 = ip >> 24 & 0xFF;
var il1 = ip >> 16 & 0xFF;
var idx = il0 * VectorIndexCols * VectorIndexSize + il1 * VectorIndexSize;
return (int)idx;
}
internal abstract ReadOnlyMemory<byte> GetVectorIndex(uint ip);
internal virtual ReadOnlyMemory<byte> GetData(int offset, int length)
{
byte[] buffer = ArrayPool<byte>.Shared.Rent(length);
int totalBytesRead = 0;
try
{
XdbStream.Seek(offset, SeekOrigin.Begin);
int bytesRead;
do
{
int bytesToRead = Math.Min(BufferSize, length - totalBytesRead);
bytesRead = XdbStream.Read(buffer, totalBytesRead, bytesToRead);
totalBytesRead += bytesRead;
IoCount++;
} while (bytesRead > 0 && totalBytesRead < length);
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
return new ReadOnlyMemory<byte>(buffer, 0, totalBytesRead);
}
}

26
aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/CacheStrategyFactory.cs

@ -1,33 +1,21 @@
// Copyright 2023 The Ip2Region Authors. All rights reserved. // Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style // Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com> // @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25 // @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
using IP2Region.Net.Internal.Abstractions;
using IP2Region.Net.XDB; using IP2Region.Net.XDB;
using System;
using System.IO; using System.IO;
namespace IP2Region.Net.Internal; namespace IP2Region.Net.Internal;
internal class CacheStrategyFactory static class CacheStrategyFactory
{ {
private readonly Stream _xdbStream; public static ICacheStrategy CreateCacheStrategy(CachePolicy cachePolicy, Stream xdbStream) => cachePolicy switch
public CacheStrategyFactory(Stream xdbStream)
{
_xdbStream = xdbStream;
}
public AbstractCacheStrategy CreateCacheStrategy(CachePolicy cachePolicy)
{
return cachePolicy switch
{ {
CachePolicy.Content => new ContentCacheStrategy(_xdbStream), CachePolicy.Content => new ContentCacheStrategy(xdbStream),
CachePolicy.VectorIndex => new VectorIndexCacheStrategy(_xdbStream), CachePolicy.VectorIndex => new VectorIndexCacheStrategy(xdbStream),
CachePolicy.File => new FileCacheStrategy(_xdbStream), _ => new StreamCacheStrategy(xdbStream),
_ => throw new ArgumentException(nameof(cachePolicy))
}; };
} }
}

33
aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/ContentCacheStrategy.cs

@ -1,34 +1,37 @@
// Copyright 2023 The Ip2Region Authors. All rights reserved. // Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style // Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com> // @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25 // @Date 2023/07/25
// Updated by Wong <vcd.hai@outlook.com> at 2025/12/31
using IP2Region.Net.Internal.Abstractions;
using System; using System;
using System.IO; using System.IO;
namespace IP2Region.Net.Internal; namespace IP2Region.Net.Internal;
internal class ContentCacheStrategy : AbstractCacheStrategy class ContentCacheStrategy(Stream _xdbStream) : ICacheStrategy
{ {
private readonly ReadOnlyMemory<byte> _cacheData; // TODO: these constants can be moved to the interface as defaults when using .NET 10
private const int HeaderInfoLength = 256;
private const int VectorIndexSize = 8;
public ContentCacheStrategy(Stream xdbStream) : base(xdbStream) private readonly ReadOnlyMemory<byte> _cacheData = _xdbStream.GetAllBytes();
{
_cacheData = base.GetData(0, (int)XdbStream.Length); public int IoCount => 0;
XdbStream.Close();
XdbStream.Dispose();
}
internal override ReadOnlyMemory<byte> GetVectorIndex(uint ip) public void ResetIoCount()
{ {
int idx = GetVectorIndexStartPos(ip); // Do nothing
return _cacheData.Slice(HeaderInfoLength + idx, VectorIndexSize);
} }
internal override ReadOnlyMemory<byte> GetData(int offset, int length) public ReadOnlyMemory<byte> GetVectorIndex(int offset)
=> _cacheData.Slice(HeaderInfoLength + offset, VectorIndexSize);
public ReadOnlyMemory<byte> GetData(long offset, int length) => _cacheData.Slice((int)offset, length);
public void Dispose()
{ {
return _cacheData.Slice(offset, length); // Do nothing
} }
} }

15
aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/FileCacheStrategy.cs

@ -1,24 +1,15 @@
// Copyright 2023 The Ip2Region Authors. All rights reserved. // Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style // Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com> // @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25 // @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
using IP2Region.Net.Internal.Abstractions;
using System;
using System.IO; using System.IO;
namespace IP2Region.Net.Internal; namespace IP2Region.Net.Internal;
internal class FileCacheStrategy : AbstractCacheStrategy class FileCacheStrategy(Stream xdbFileStream) : StreamCacheStrategy(xdbFileStream)
{ {
public FileCacheStrategy(Stream xdbStream) : base(xdbStream)
{
}
internal override ReadOnlyMemory<byte> GetVectorIndex(uint ip)
{
var idx = GetVectorIndexStartPos(ip);
return GetData(HeaderInfoLength + idx, VectorIndexSize);
}
} }

21
aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/ICacheStrategy.cs

@ -0,0 +1,21 @@
// Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
using System;
namespace IP2Region.Net.Internal;
internal interface ICacheStrategy : IDisposable
{
int IoCount { get; }
void ResetIoCount();
ReadOnlyMemory<byte> GetVectorIndex(int offset);
ReadOnlyMemory<byte> GetData(long offset, int length);
}

77
aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/StreamCacheStrategy.cs

@ -0,0 +1,77 @@
using System;
using System.Buffers;
using System.IO;
namespace IP2Region.Net.Internal;
internal class StreamCacheStrategy(Stream _xdbStream) : ICacheStrategy
{
protected const int HeaderInfoLength = 256;
protected const int VectorIndexSize = 8;
protected const int BufferSize = 64 * 1024;
public int IoCount { get; set; }
public void ResetIoCount()
{
IoCount = 0;
}
public virtual ReadOnlyMemory<byte> GetVectorIndex(int offset) => GetData(HeaderInfoLength + offset, VectorIndexSize);
public virtual ReadOnlyMemory<byte> GetData(long offset, int length)
{
var buffer = ArrayPool<byte>.Shared.Rent(length);
try
{
int totalBytesRead = 0;
_xdbStream.Seek(offset, SeekOrigin.Begin);
int bytesRead;
while (totalBytesRead < length)
{
bytesRead = _xdbStream.Read(buffer, totalBytesRead, length - totalBytesRead);
if (bytesRead == 0)
{
break;
}
totalBytesRead += bytesRead;
IoCount++;
}
var ret = new byte[totalBytesRead];
if (totalBytesRead > 0)
{
Array.Copy(buffer, 0, ret, 0, totalBytesRead);
}
return ret;
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
/// <summary>
/// 释放文件句柄
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_xdbStream.Close();
_xdbStream.Dispose();
}
}
/// <summary>
/// <inheritdoc/>
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}

22
aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/VectorIndexCacheStrategy.cs

@ -1,28 +1,26 @@
// Copyright 2023 The Ip2Region Authors. All rights reserved. // Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style // Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com> // @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25 // @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
using IP2Region.Net.Internal.Abstractions;
using System; using System;
using System.IO; using System.IO;
namespace IP2Region.Net.Internal; namespace IP2Region.Net.Internal;
internal class VectorIndexCacheStrategy : AbstractCacheStrategy class VectorIndexCacheStrategy : StreamCacheStrategy
{ {
private readonly ReadOnlyMemory<byte> _vectorIndex; private const int VectorIndexRows = 256;
private const int VectorIndexCols = 256;
public VectorIndexCacheStrategy(Stream xdbStream) : base(xdbStream) private readonly ReadOnlyMemory<byte> _vectorCache;
{
var vectorLength = VectorIndexRows * VectorIndexCols * VectorIndexSize;
_vectorIndex = base.GetData(HeaderInfoLength, vectorLength);
}
internal override ReadOnlyMemory<byte> GetVectorIndex(uint ip) public VectorIndexCacheStrategy(Stream _xdbStream) : base(_xdbStream)
{ {
var idx = GetVectorIndexStartPos(ip); _vectorCache = GetData(HeaderInfoLength, VectorIndexRows * VectorIndexCols * VectorIndexSize);
return _vectorIndex.Slice(idx, VectorIndexSize);
} }
public override ReadOnlyMemory<byte> GetVectorIndex(int offset) => _vectorCache.Slice(offset, VectorIndexSize);
} }

4
aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN.Abp.IP2Region.csproj

@ -15,8 +15,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="LINGYUN\Abp\IP2Region\Resources\ip2region.xdb" /> <None Remove="LINGYUN\Abp\IP2Region\Resources\ip2region_v4.xdb" />
<EmbeddedResource Include="LINGYUN\Abp\IP2Region\Resources\ip2region.xdb" /> <EmbeddedResource Include="LINGYUN\Abp\IP2Region\Resources\ip2region_v4.xdb" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

14
aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/AbpIP2RegionLocationResolveOptions.cs

@ -0,0 +1,14 @@
using LINGYUN.Abp.IP.Location;
using System;
namespace LINGYUN.Abp.IP2Region;
public class AbpIP2RegionLocationResolveOptions
{
public Func<IPLocation, bool> UseCountry { get; set; }
public Func<IPLocation, bool> UseProvince { get; set; }
public AbpIP2RegionLocationResolveOptions()
{
UseCountry = _ => true;
UseProvince = _ => true;
}
}

3
aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/AbpIP2RegionModule.cs

@ -18,10 +18,11 @@ public class AbpIP2RegionModule : AbpModule
options.FileSets.AddEmbedded<AbpIP2RegionModule>(); options.FileSets.AddEmbedded<AbpIP2RegionModule>();
}); });
// TODO: ipv6 support?
context.Services.AddSingleton<ISearcher, AbpSearcher>((serviceProvider) => context.Services.AddSingleton<ISearcher, AbpSearcher>((serviceProvider) =>
{ {
var virtualFileProvider = serviceProvider.GetRequiredService<IVirtualFileProvider>(); var virtualFileProvider = serviceProvider.GetRequiredService<IVirtualFileProvider>();
var xdbFile = virtualFileProvider.GetFileInfo("/LINGYUN/Abp/IP2Region/Resources/ip2region.xdb"); var xdbFile = virtualFileProvider.GetFileInfo("/LINGYUN/Abp/IP2Region/Resources/ip2region_v4.xdb");
var searcher = new AbpSearcher(CachePolicy.File, xdbFile.CreateReadStream()); var searcher = new AbpSearcher(CachePolicy.File, xdbFile.CreateReadStream());
return searcher; return searcher;

153
aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/AbpSearcher.cs

@ -1,80 +1,155 @@
using IP2Region.Net.Abstractions; // Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
using IP2Region.Net.Abstractions;
using IP2Region.Net.Internal; using IP2Region.Net.Internal;
using IP2Region.Net.Internal.Abstractions;
using IP2Region.Net.XDB; using IP2Region.Net.XDB;
using System;
using System.Buffers.Binary;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
namespace LINGYUN.Abp.IP2Region; namespace LINGYUN.Abp.IP2Region;
public class AbpSearcher : ISearcher public class AbpSearcher(CachePolicy cachePolicy, Stream xdbStream) : ISearcher
{ {
const int SegmentIndexSize = 14; private readonly ICacheStrategy _cacheStrategy = CacheStrategyFactory.CreateCacheStrategy(cachePolicy, xdbStream);
private readonly AbstractCacheStrategy _cacheStrategy; /// <summary>
/// <inheritdoc/>
/// </summary>
public int IoCount => _cacheStrategy.IoCount; public int IoCount => _cacheStrategy.IoCount;
public AbpSearcher(CachePolicy cachePolicy, Stream xdbStream) /// <summary>
{ /// <inheritdoc/>
var factory = new CacheStrategyFactory(xdbStream); /// </summary>
_cacheStrategy = factory.CreateCacheStrategy(cachePolicy);
}
public string? Search(string ipStr) public string? Search(string ipStr)
{ {
var ip = Util.IpAddressToUInt32(ipStr); var ipAddress = IPAddress.Parse(ipStr);
return Search(ip); return SearchCore(ipAddress.GetAddressBytes());
} }
public string? Search(IPAddress ipAddress) /// <summary>
/// <inheritdoc/>
/// </summary>
public string? Search(IPAddress ipAddress) => SearchCore(ipAddress.GetAddressBytes());
/// <summary>
/// <inheritdoc/>
/// </summary>
[Obsolete("已弃用,请改用其他方法;Deprecated; please use Search(string) or Search(IPAddress) method.")]
[ExcludeFromCodeCoverage]
public string? Search(uint ipAddress)
{ {
var ip = Util.IpAddressToUInt32(ipAddress); var bytes = BitConverter.GetBytes(ipAddress);
return Search(ip); Array.Reverse(bytes);
return SearchCore(bytes);
} }
public string? Search(uint ip) string? SearchCore(byte[] ipBytes)
{ {
var index = _cacheStrategy.GetVectorIndex(ip); // 重置 IO 计数器
uint sPtr = MemoryMarshal.Read<uint>(index.Span); _cacheStrategy.ResetIoCount();
uint ePtr = MemoryMarshal.Read<uint>(index.Span.Slice(4));
// 每个 vector 索引项的字节数
var vectorIndexSize = 8;
// vector 索引的列数
var vectorIndexCols = 256;
// 计算得到 vector 索引项的开始地址。
var il0 = ipBytes[0];
var il1 = ipBytes[1];
var idx = il0 * vectorIndexCols * vectorIndexSize + il1 * vectorIndexSize;
var vector = _cacheStrategy.GetVectorIndex(idx);
var sPtr = BinaryPrimitives.ReadUInt32LittleEndian(vector.Span);
var ePtr = BinaryPrimitives.ReadUInt32LittleEndian(vector.Span.Slice(4));
var length = ipBytes.Length;
var indexSize = length * 2 + 6;
var l = 0;
var h = (ePtr - sPtr) / indexSize;
var dataLen = 0; var dataLen = 0;
uint dataPtr = 0; long dataPtr = 0;
uint l = 0;
uint h = (ePtr - sPtr) / SegmentIndexSize;
while (l <= h) while (l <= h)
{ {
var mid = Util.GetMidIp(l, h); int m = (int)(l + h) >> 1;
var pos = sPtr + mid * SegmentIndexSize;
var buffer = _cacheStrategy.GetData((int)pos, SegmentIndexSize); var p = sPtr + m * indexSize;
uint sip = MemoryMarshal.Read<uint>(buffer.Span); var buff = _cacheStrategy.GetData(p, indexSize);
uint eip = MemoryMarshal.Read<uint>(buffer.Span.Slice(4));
if (ip < sip) var s = buff.Span.Slice(0, length);
var e = buff.Span.Slice(length, length);
if (ByteCompare(ipBytes, s) < 0)
{ {
h = mid - 1; h = m - 1;
} }
else if (ip > eip) else if (ByteCompare(ipBytes, e) > 0)
{ {
l = mid + 1; l = m + 1;
} }
else else
{ {
dataLen = MemoryMarshal.Read<ushort>(buffer.Span.Slice(8)); dataLen = BinaryPrimitives.ReadUInt16LittleEndian(buff.Span.Slice(length * 2, 2));
dataPtr = MemoryMarshal.Read<uint>(buffer.Span.Slice(10)); dataPtr = BinaryPrimitives.ReadUInt32LittleEndian(buff.Span.Slice(length * 2 + 2, 4));
break; break;
} }
} }
if (dataLen == 0) var regionBuff = _cacheStrategy.GetData(dataPtr, dataLen);
return Encoding.UTF8.GetString(regionBuff.Span.ToArray());
}
static int ByteCompare(byte[] ip1, ReadOnlySpan<byte> ip2) => ip1.Length == 4 ? IPv4Compare(ip1, ip2) : IPv6Compare(ip1, ip2);
static int IPv4Compare(byte[] ip1, ReadOnlySpan<byte> ip2)
{
var ret = 0;
for (int i = 0; i < ip1.Length; i++)
{
var ip2Index = ip1.Length - 1 - i;
if (ip1[i] < ip2[ip2Index])
{
return -1;
}
else if (ip1[i] > ip2[ip2Index])
{
return 1;
}
}
return ret;
}
static int IPv6Compare(byte[] ip1, ReadOnlySpan<byte> ip2)
{
var ret = 0;
for (int i = 0; i < ip1.Length; i++)
{ {
return default; if (ip1[i] < ip2[i])
{
return -1;
}
else if (ip1[i] > ip2[i])
{
return 1;
}
}
return ret;
} }
var regionBuff = _cacheStrategy.GetData((int)dataPtr, dataLen); /// <summary>
return Encoding.UTF8.GetString(regionBuff.Span.ToArray()); /// <inheritdoc/>
/// </summary>
public void Dispose()
{
_cacheStrategy.Dispose();
GC.SuppressFinalize(this);
} }
} }

30
aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/IP2RegionIPLocationResolveContributor.cs

@ -1,6 +1,7 @@
using IP2Region.Net.Abstractions; using IP2Region.Net.Abstractions;
using LINGYUN.Abp.IP.Location; using LINGYUN.Abp.IP.Location;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -13,6 +14,7 @@ public class IP2RegionIPLocationResolveContributor : IPLocationResolveContributo
public override Task ResolveAsync(IIPLocationResolveContext context) public override Task ResolveAsync(IIPLocationResolveContext context)
{ {
var searcher = context.ServiceProvider.GetRequiredService<ISearcher>(); var searcher = context.ServiceProvider.GetRequiredService<ISearcher>();
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpIP2RegionLocationResolveOptions>>();
var region = searcher.Search(context.IpAddress); var region = searcher.Search(context.IpAddress);
@ -22,39 +24,43 @@ public class IP2RegionIPLocationResolveContributor : IPLocationResolveContributo
} }
var regions = region!.Split('|'); var regions = region!.Split('|');
// | 0 | 1 | 2 |3| 4 | 5 | 6 | // | 0 | 1 | 2 | 3 | 4 | 5 |
// 39.128.0.0|39.128.31.255|中国|0|云南省|昆明市|移动 // 39.128.0.0|39.128.31.255|中国|云南省|昆明市|移动
// regions: // regions:
// 中国 0 云南省 昆明市 移动 // 中国|云南省|昆明市|移动
var ipLocation = new IPLocation( var ipLocation = new IPLocation(
regions.Length >= 1 && !string.Equals(regions[0], "0") ? regions[0] : null, regions.Length >= 1 && !string.Equals(regions[0], "0") ? regions[0] : null,
regions.Length >= 3 && !string.Equals(regions[2], "0") ? regions[2] : null, regions.Length >= 2 && !string.Equals(regions[1], "0") ? regions[1] : null,
regions.Length >= 4 && !string.Equals(regions[3], "0") ? regions[3] : null); regions.Length >= 3 && !string.Equals(regions[2], "0") ? regions[2] : null);
// 36.133.108.0|36.133.119.255|中国|0|重庆|重庆市|移动 // 36.133.232.0|36.133.239.255|中国|重庆|重庆市|移动
if (!ipLocation.Province.IsNullOrWhiteSpace() && !ipLocation.City.IsNullOrWhiteSpace()) if (!options.Value.UseCountry(ipLocation) &&
!ipLocation.Province.IsNullOrWhiteSpace() &&
!ipLocation.City.IsNullOrWhiteSpace())
{ {
if (ipLocation.Province.Length < ipLocation.City.Length && if (ipLocation.Province.Length <= ipLocation.City.Length &&
ipLocation.City.StartsWith(ipLocation.Province, StringComparison.InvariantCultureIgnoreCase)) ipLocation.City.StartsWith(ipLocation.Province, StringComparison.InvariantCultureIgnoreCase))
{ {
// 重庆市 // 重庆市
ipLocation.Remarks = $"{ipLocation.City}"; ipLocation.Remarks = $"{ipLocation.City}";
} }
// 111.26.31.0|111.26.31.127|中国|0|吉林省|吉林市|移动 // 111.26.31.0|111.26.31.127|中国|吉林省|吉林市|移动
else else
{ {
// 吉林省吉林市 // 吉林省吉林市
ipLocation.Remarks = $"{ipLocation.Province}{ipLocation.City}"; ipLocation.Remarks = $"{ipLocation.Province}{ipLocation.City}";
} }
} }
// 220.246.0.0|220.246.255.255|中国|0|香港|0|电讯盈科 // 220.246.0.0|220.246.255.255|中国|香港|0|电讯盈科
else if (!ipLocation.Country.IsNullOrWhiteSpace() && !ipLocation.Province.IsNullOrWhiteSpace()) else if (options.Value.UseProvince(ipLocation) &&
!ipLocation.Country.IsNullOrWhiteSpace() &&
!ipLocation.Province.IsNullOrWhiteSpace())
{ {
// 中国香港 // 中国香港
ipLocation.Remarks = $"{ipLocation.Country}{ipLocation.Province}"; ipLocation.Remarks = $"{ipLocation.Country}{ipLocation.Province}";
} }
// 220.247.4.0|220.247.31.255|日本|0|0|0|0 // 103.151.173.0|103.151.173.255|日本|东京|东京|IKUUU网络
else else
{ {
// 日本 // 日本

BIN
aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/Resources/ip2region.xdb → aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/Resources/ip2region_v4.xdb

Binary file not shown.

16
aspnet-core/tests/LINGYUN.Abp.IP2Region.Tests/LINGYUN/Abp/IP2Region/AbpIP2RegionTestModule.cs

@ -8,4 +8,20 @@ namespace LINGYUN.Abp.IP2Region;
typeof(AbpTestsBaseModule))] typeof(AbpTestsBaseModule))]
public class AbpIP2RegionTestModule : AbpModule public class AbpIP2RegionTestModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpIP2RegionLocationResolveOptions>(options =>
{
// 仅中国IP不显示国家
options.UseCountry = (localtion) =>
{
return !string.Equals("中国", localtion.Country);
};
// 仅中国IP显示省份
options.UseProvince = (localtion) =>
{
return string.Equals("中国", localtion.Country);
};
});
}
} }

53
aspnet-core/tests/LINGYUN.Abp.IP2Region.Tests/LINGYUN/Abp/IP2Region/SearcherTest.cs

@ -4,6 +4,7 @@ using Shouldly;
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.VirtualFileSystem; using Volo.Abp.VirtualFileSystem;
using Xunit; using Xunit;
@ -15,7 +16,7 @@ public class SearcherTest : AbpIP2RegionTestBase
public SearcherTest() public SearcherTest()
{ {
var virtualFileProvider = GetRequiredService<IVirtualFileProvider>(); var virtualFileProvider = GetRequiredService<IVirtualFileProvider>();
_xdbStream = virtualFileProvider.GetFileInfo("/LINGYUN/Abp/IP2Region/Resources/ip2region.xdb").CreateReadStream(); _xdbStream = virtualFileProvider.GetFileInfo("/LINGYUN/Abp/IP2Region/Resources/ip2region_v4.xdb").CreateReadStream();
} }
[Theory] [Theory]
@ -23,50 +24,52 @@ public class SearcherTest : AbpIP2RegionTestBase
[InlineData("36.133.108.1", "重庆市")] [InlineData("36.133.108.1", "重庆市")]
[InlineData("111.26.31.1", "吉林省吉林市")] [InlineData("111.26.31.1", "吉林省吉林市")]
[InlineData("220.246.0.1", "中国香港")] [InlineData("220.246.0.1", "中国香港")]
public async void TestSearchLocation(string ip, string shouleBeRemarks) [InlineData("103.151.173.211", "日本")]
[InlineData("103.151.191.5", "印度尼西亚")]
public async Task TestSearchLocation(string ip, string shouldBeRemarks)
{ {
var resolver = GetRequiredService<IIPLocationResolver>(); var resolver = GetRequiredService<IIPLocationResolver>();
var result = await resolver.ResolveAsync(ip); var result = await resolver.ResolveAsync(ip);
result.Location.Remarks.ShouldBe(shouleBeRemarks); result.Location.Remarks.ShouldBe(shouldBeRemarks);
} }
[Theory] [Theory]
[InlineData("114.114.114.114")] [InlineData("114.114.114.114", "中国|江苏省|南京市|0")]
[InlineData("119.29.29.29")] [InlineData("119.29.29.29", "中国|北京|北京市|腾讯")]
[InlineData("223.5.5.5")] [InlineData("223.5.5.5", "中国|浙江省|杭州市|阿里")]
[InlineData("180.76.76.76")] [InlineData("180.76.76.76", "中国|北京|北京市|百度")]
[InlineData("8.8.8.8")] [InlineData("8.8.8.8", "美国|0|0|谷歌")]
public void TestSearchCacheContent(string ip) public void TestSearchCacheContent(string ip, string shouldBeRegion)
{ {
var contentSearcher = new AbpSearcher(CachePolicy.Content, _xdbStream); var contentSearcher = new AbpSearcher(CachePolicy.Content, _xdbStream);
var region = contentSearcher.Search(ip); var region = contentSearcher.Search(ip);
Console.WriteLine(region); region.ShouldBe(shouldBeRegion);
} }
[Theory] [Theory]
[InlineData("114.114.114.114")] [InlineData("114.114.114.114", "中国|江苏省|南京市|0")]
[InlineData("119.29.29.29")] [InlineData("119.29.29.29", "中国|北京|北京市|腾讯")]
[InlineData("223.5.5.5")] [InlineData("223.5.5.5", "中国|浙江省|杭州市|阿里")]
[InlineData("180.76.76.76")] [InlineData("180.76.76.76", "中国|北京|北京市|百度")]
[InlineData("8.8.8.8")] [InlineData("8.8.8.8", "美国|0|0|谷歌")]
public void TestSearchCacheVector(string ip) public void TestSearchCacheVector(string ip, string shouldBeRegion)
{ {
var vectorSearcher = new AbpSearcher(CachePolicy.VectorIndex, _xdbStream); var vectorSearcher = new AbpSearcher(CachePolicy.VectorIndex, _xdbStream);
var region = vectorSearcher.Search(ip); var region = vectorSearcher.Search(ip);
Console.WriteLine(region); region.ShouldBe(shouldBeRegion);
} }
[Theory] [Theory]
[InlineData("114.114.114.114")] [InlineData("114.114.114.114", "中国|江苏省|南京市|0")]
[InlineData("119.29.29.29")] [InlineData("119.29.29.29", "中国|北京|北京市|腾讯")]
[InlineData("223.5.5.5")] [InlineData("223.5.5.5", "中国|浙江省|杭州市|阿里")]
[InlineData("180.76.76.76")] [InlineData("180.76.76.76", "中国|北京|北京市|百度")]
[InlineData("8.8.8.8")] [InlineData("8.8.8.8", "美国|0|0|谷歌")]
public void TestSearchCacheFile(string ip) public void TestSearchCacheFile(string ip, string shouldBeRegion)
{ {
var fileSearcher = new AbpSearcher(CachePolicy.File, _xdbStream); var fileSearcher = new AbpSearcher(CachePolicy.File, _xdbStream);
var region = fileSearcher.Search(ip); var region = fileSearcher.Search(ip);
Console.WriteLine(region); region.ShouldBe(shouldBeRegion);
} }
[Theory] [Theory]
@ -76,7 +79,7 @@ public class SearcherTest : AbpIP2RegionTestBase
public void TestBenchSearch(CachePolicy cachePolicy) public void TestBenchSearch(CachePolicy cachePolicy)
{ {
var searcher = new AbpSearcher(cachePolicy, _xdbStream); var searcher = new AbpSearcher(cachePolicy, _xdbStream);
var srcPath = Path.Combine(AppContext.BaseDirectory, "ip.merge.txt"); var srcPath = Path.Combine(AppContext.BaseDirectory, "ipv4_source.txt");
foreach (var line in File.ReadLines(srcPath)) foreach (var line in File.ReadLines(srcPath))
{ {

1367687
aspnet-core/tests/LINGYUN.Abp.IP2Region.Tests/LINGYUN/Abp/IP2Region/ip.merge.txt → aspnet-core/tests/LINGYUN.Abp.IP2Region.Tests/LINGYUN/Abp/IP2Region/ipv4_source.txt

File diff suppressed because it is too large
Loading…
Cancel
Save