16 changed files with 684175 additions and 684048 deletions
@ -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); |
|||
} |
|||
} |
|||
@ -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
|
|||
// 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.Internal.Abstractions; |
|||
using IP2Region.Net.XDB; |
|||
using System; |
|||
using System.IO; |
|||
|
|||
namespace IP2Region.Net.Internal; |
|||
|
|||
internal class CacheStrategyFactory |
|||
static class CacheStrategyFactory |
|||
{ |
|||
private readonly Stream _xdbStream; |
|||
|
|||
public CacheStrategyFactory(Stream xdbStream) |
|||
{ |
|||
_xdbStream = xdbStream; |
|||
} |
|||
|
|||
public AbstractCacheStrategy CreateCacheStrategy(CachePolicy cachePolicy) |
|||
public static ICacheStrategy CreateCacheStrategy(CachePolicy cachePolicy, Stream xdbStream) => cachePolicy switch |
|||
{ |
|||
return cachePolicy switch |
|||
{ |
|||
CachePolicy.Content => new ContentCacheStrategy(_xdbStream), |
|||
CachePolicy.VectorIndex => new VectorIndexCacheStrategy(_xdbStream), |
|||
CachePolicy.File => new FileCacheStrategy(_xdbStream), |
|||
_ => throw new ArgumentException(nameof(cachePolicy)) |
|||
}; |
|||
} |
|||
CachePolicy.Content => new ContentCacheStrategy(xdbStream), |
|||
CachePolicy.VectorIndex => new VectorIndexCacheStrategy(xdbStream), |
|||
_ => new StreamCacheStrategy(xdbStream), |
|||
}; |
|||
} |
|||
@ -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
|
|||
// license that can be found in the LICENSE file.
|
|||
// @Author Alan <lzh.shap@gmail.com>
|
|||
// @Date 2023/07/25
|
|||
// Updated by Wong <vcd.hai@outlook.com> at 2025/12/31
|
|||
|
|||
using IP2Region.Net.Internal.Abstractions; |
|||
using System; |
|||
using System.IO; |
|||
|
|||
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) |
|||
{ |
|||
_cacheData = base.GetData(0, (int)XdbStream.Length); |
|||
XdbStream.Close(); |
|||
XdbStream.Dispose(); |
|||
} |
|||
private readonly ReadOnlyMemory<byte> _cacheData = _xdbStream.GetAllBytes(); |
|||
|
|||
public int IoCount => 0; |
|||
|
|||
internal override ReadOnlyMemory<byte> GetVectorIndex(uint ip) |
|||
public void ResetIoCount() |
|||
{ |
|||
int idx = GetVectorIndexStartPos(ip); |
|||
return _cacheData.Slice(HeaderInfoLength + idx, VectorIndexSize); |
|||
// Do nothing
|
|||
} |
|||
|
|||
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
|
|||
} |
|||
} |
|||
@ -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
|
|||
// 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.Internal.Abstractions; |
|||
using System; |
|||
using System.IO; |
|||
|
|||
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); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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
|
|||
// 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.Internal.Abstractions; |
|||
using System; |
|||
using System.IO; |
|||
|
|||
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) |
|||
{ |
|||
var vectorLength = VectorIndexRows * VectorIndexCols * VectorIndexSize; |
|||
_vectorIndex = base.GetData(HeaderInfoLength, vectorLength); |
|||
} |
|||
private readonly ReadOnlyMemory<byte> _vectorCache; |
|||
|
|||
internal override ReadOnlyMemory<byte> GetVectorIndex(uint ip) |
|||
public VectorIndexCacheStrategy(Stream _xdbStream) : base(_xdbStream) |
|||
{ |
|||
var idx = GetVectorIndexStartPos(ip); |
|||
return _vectorIndex.Slice(idx, VectorIndexSize); |
|||
_vectorCache = GetData(HeaderInfoLength, VectorIndexRows * VectorIndexCols * VectorIndexSize); |
|||
} |
|||
|
|||
public override ReadOnlyMemory<byte> GetVectorIndex(int offset) => _vectorCache.Slice(offset, VectorIndexSize); |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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.Abstractions; |
|||
using IP2Region.Net.XDB; |
|||
using System; |
|||
using System.Buffers.Binary; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.IO; |
|||
using System.Net; |
|||
using System.Runtime.InteropServices; |
|||
using System.Text; |
|||
|
|||
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 AbpSearcher(CachePolicy cachePolicy, Stream xdbStream) |
|||
{ |
|||
var factory = new CacheStrategyFactory(xdbStream); |
|||
_cacheStrategy = factory.CreateCacheStrategy(cachePolicy); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// <inheritdoc/>
|
|||
/// </summary>
|
|||
public string? Search(string ipStr) |
|||
{ |
|||
var ip = Util.IpAddressToUInt32(ipStr); |
|||
return Search(ip); |
|||
var ipAddress = IPAddress.Parse(ipStr); |
|||
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); |
|||
return Search(ip); |
|||
var bytes = BitConverter.GetBytes(ipAddress); |
|||
Array.Reverse(bytes); |
|||
return SearchCore(bytes); |
|||
} |
|||
|
|||
public string? Search(uint ip) |
|||
string? SearchCore(byte[] ipBytes) |
|||
{ |
|||
var index = _cacheStrategy.GetVectorIndex(ip); |
|||
uint sPtr = MemoryMarshal.Read<uint>(index.Span); |
|||
uint ePtr = MemoryMarshal.Read<uint>(index.Span.Slice(4)); |
|||
// 重置 IO 计数器
|
|||
_cacheStrategy.ResetIoCount(); |
|||
|
|||
// 每个 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; |
|||
uint dataPtr = 0; |
|||
uint l = 0; |
|||
uint h = (ePtr - sPtr) / SegmentIndexSize; |
|||
long dataPtr = 0; |
|||
|
|||
while (l <= h) |
|||
{ |
|||
var mid = Util.GetMidIp(l, h); |
|||
var pos = sPtr + mid * SegmentIndexSize; |
|||
int m = (int)(l + h) >> 1; |
|||
|
|||
var buffer = _cacheStrategy.GetData((int)pos, SegmentIndexSize); |
|||
uint sip = MemoryMarshal.Read<uint>(buffer.Span); |
|||
uint eip = MemoryMarshal.Read<uint>(buffer.Span.Slice(4)); |
|||
var p = sPtr + m * indexSize; |
|||
var buff = _cacheStrategy.GetData(p, indexSize); |
|||
|
|||
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 |
|||
{ |
|||
dataLen = MemoryMarshal.Read<ushort>(buffer.Span.Slice(8)); |
|||
dataPtr = MemoryMarshal.Read<uint>(buffer.Span.Slice(10)); |
|||
dataLen = BinaryPrimitives.ReadUInt16LittleEndian(buff.Span.Slice(length * 2, 2)); |
|||
dataPtr = BinaryPrimitives.ReadUInt32LittleEndian(buff.Span.Slice(length * 2 + 2, 4)); |
|||
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); |
|||
return Encoding.UTF8.GetString(regionBuff.Span.ToArray()); |
|||
/// <summary>
|
|||
/// <inheritdoc/>
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
_cacheStrategy.Dispose(); |
|||
GC.SuppressFinalize(this); |
|||
} |
|||
} |
|||
|
|||
Binary file not shown.
File diff suppressed because it is too large
Loading…
Reference in new issue