committed by
GitHub
34 changed files with 684618 additions and 16 deletions
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,65 @@ |
|||||
|
// 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); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
// 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 IP2Region.Net.Internal.Abstractions; |
||||
|
using IP2Region.Net.XDB; |
||||
|
using System; |
||||
|
using System.IO; |
||||
|
|
||||
|
namespace IP2Region.Net.Internal; |
||||
|
|
||||
|
internal class CacheStrategyFactory |
||||
|
{ |
||||
|
private readonly Stream _xdbStream; |
||||
|
|
||||
|
public CacheStrategyFactory(Stream xdbStream) |
||||
|
{ |
||||
|
_xdbStream = xdbStream; |
||||
|
} |
||||
|
|
||||
|
public AbstractCacheStrategy CreateCacheStrategy(CachePolicy cachePolicy) |
||||
|
{ |
||||
|
return cachePolicy switch |
||||
|
{ |
||||
|
CachePolicy.Content => new ContentCacheStrategy(_xdbStream), |
||||
|
CachePolicy.VectorIndex => new VectorIndexCacheStrategy(_xdbStream), |
||||
|
CachePolicy.File => new FileCacheStrategy(_xdbStream), |
||||
|
_ => throw new ArgumentException(nameof(cachePolicy)) |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
// 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 IP2Region.Net.Internal.Abstractions; |
||||
|
using System; |
||||
|
using System.IO; |
||||
|
|
||||
|
namespace IP2Region.Net.Internal; |
||||
|
|
||||
|
internal class ContentCacheStrategy : AbstractCacheStrategy |
||||
|
{ |
||||
|
private readonly ReadOnlyMemory<byte> _cacheData; |
||||
|
|
||||
|
public ContentCacheStrategy(Stream xdbStream) : base(xdbStream) |
||||
|
{ |
||||
|
_cacheData = base.GetData(0, (int)XdbStream.Length); |
||||
|
XdbStream.Close(); |
||||
|
XdbStream.Dispose(); |
||||
|
} |
||||
|
|
||||
|
internal override ReadOnlyMemory<byte> GetVectorIndex(uint ip) |
||||
|
{ |
||||
|
int idx = GetVectorIndexStartPos(ip); |
||||
|
return _cacheData.Slice(HeaderInfoLength + idx, VectorIndexSize); |
||||
|
} |
||||
|
|
||||
|
internal override ReadOnlyMemory<byte> GetData(int offset, int length) |
||||
|
{ |
||||
|
return _cacheData.Slice(offset, length); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
// 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 IP2Region.Net.Internal.Abstractions; |
||||
|
using System; |
||||
|
using System.IO; |
||||
|
|
||||
|
namespace IP2Region.Net.Internal; |
||||
|
|
||||
|
internal class FileCacheStrategy : AbstractCacheStrategy |
||||
|
{ |
||||
|
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,28 @@ |
|||||
|
// 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 IP2Region.Net.Internal.Abstractions; |
||||
|
using System; |
||||
|
using System.IO; |
||||
|
|
||||
|
namespace IP2Region.Net.Internal; |
||||
|
|
||||
|
internal class VectorIndexCacheStrategy : AbstractCacheStrategy |
||||
|
{ |
||||
|
private readonly ReadOnlyMemory<byte> _vectorIndex; |
||||
|
|
||||
|
public VectorIndexCacheStrategy(Stream xdbStream) : base(xdbStream) |
||||
|
{ |
||||
|
var vectorLength = VectorIndexRows * VectorIndexCols * VectorIndexSize; |
||||
|
_vectorIndex = base.GetData(HeaderInfoLength, vectorLength); |
||||
|
} |
||||
|
|
||||
|
internal override ReadOnlyMemory<byte> GetVectorIndex(uint ip) |
||||
|
{ |
||||
|
var idx = GetVectorIndexStartPos(ip); |
||||
|
return _vectorIndex.Slice(idx, VectorIndexSize); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0</TargetFrameworks> |
||||
|
<AssemblyName>LINGYUN.Abp.IP2Region</AssemblyName> |
||||
|
<PackageId>LINGYUN.Abp.IP2Region</PackageId> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<None Remove="LINGYUN\Abp\IP2Region\Resources\ip2region.xdb" /> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\IP2Region\Resources\ip2region.xdb" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.VirtualFileSystem" /> |
||||
|
<PackageReference Include="IP2Region.Net" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,28 @@ |
|||||
|
using IP2Region.Net.Abstractions; |
||||
|
using IP2Region.Net.XDB; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace LINGYUN.Abp.IP2Region; |
||||
|
|
||||
|
[DependsOn(typeof(AbpVirtualFileSystemModule))] |
||||
|
public class AbpIP2RegionModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpIP2RegionModule>(); |
||||
|
}); |
||||
|
|
||||
|
context.Services.AddSingleton<ISearcher, AbpSearcher>((serviceProvider) => |
||||
|
{ |
||||
|
var virtualFileProvider = serviceProvider.GetRequiredService<IVirtualFileProvider>(); |
||||
|
var xdbFile = virtualFileProvider.GetFileInfo("/LINGYUN/Abp/IP2Region/Resources/ip2region.xdb"); |
||||
|
var searcher = new AbpSearcher(CachePolicy.File, xdbFile.CreateReadStream()); |
||||
|
|
||||
|
return searcher; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
using IP2Region.Net.Abstractions; |
||||
|
using IP2Region.Net.Internal; |
||||
|
using IP2Region.Net.Internal.Abstractions; |
||||
|
using IP2Region.Net.XDB; |
||||
|
using System.IO; |
||||
|
using System.Net; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace LINGYUN.Abp.IP2Region; |
||||
|
public class AbpSearcher : ISearcher |
||||
|
{ |
||||
|
const int SegmentIndexSize = 14; |
||||
|
|
||||
|
private readonly AbstractCacheStrategy _cacheStrategy; |
||||
|
public int IoCount => _cacheStrategy.IoCount; |
||||
|
|
||||
|
public AbpSearcher(CachePolicy cachePolicy, Stream xdbStream) |
||||
|
{ |
||||
|
var factory = new CacheStrategyFactory(xdbStream); |
||||
|
_cacheStrategy = factory.CreateCacheStrategy(cachePolicy); |
||||
|
} |
||||
|
|
||||
|
public string? Search(string ipStr) |
||||
|
{ |
||||
|
var ip = Util.IpAddressToUInt32(ipStr); |
||||
|
return Search(ip); |
||||
|
} |
||||
|
|
||||
|
public string? Search(IPAddress ipAddress) |
||||
|
{ |
||||
|
var ip = Util.IpAddressToUInt32(ipAddress); |
||||
|
return Search(ip); |
||||
|
} |
||||
|
|
||||
|
public string? Search(uint ip) |
||||
|
{ |
||||
|
var index = _cacheStrategy.GetVectorIndex(ip); |
||||
|
uint sPtr = MemoryMarshal.Read<uint>(index.Span); |
||||
|
uint ePtr = MemoryMarshal.Read<uint>(index.Span.Slice(4)); |
||||
|
|
||||
|
var dataLen = 0; |
||||
|
uint dataPtr = 0; |
||||
|
uint l = 0; |
||||
|
uint h = (ePtr - sPtr) / SegmentIndexSize; |
||||
|
|
||||
|
while (l <= h) |
||||
|
{ |
||||
|
var mid = Util.GetMidIp(l, h); |
||||
|
var pos = sPtr + mid * SegmentIndexSize; |
||||
|
|
||||
|
var buffer = _cacheStrategy.GetData((int)pos, SegmentIndexSize); |
||||
|
uint sip = MemoryMarshal.Read<uint>(buffer.Span); |
||||
|
uint eip = MemoryMarshal.Read<uint>(buffer.Span.Slice(4)); |
||||
|
|
||||
|
if (ip < sip) |
||||
|
{ |
||||
|
h = mid - 1; |
||||
|
} |
||||
|
else if (ip > eip) |
||||
|
{ |
||||
|
l = mid + 1; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
dataLen = MemoryMarshal.Read<ushort>(buffer.Span.Slice(8)); |
||||
|
dataPtr = MemoryMarshal.Read<uint>(buffer.Span.Slice(10)); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (dataLen == 0) |
||||
|
{ |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
var regionBuff = _cacheStrategy.GetData((int)dataPtr, dataLen); |
||||
|
return Encoding.UTF8.GetString(regionBuff.Span.ToArray()); |
||||
|
} |
||||
|
} |
||||
Binary file not shown.
@ -0,0 +1,62 @@ |
|||||
|
# IP2Region.Net |
||||
|
|
||||
|
.NET client library for IP2Region |
||||
|
|
||||
|
## Installation |
||||
|
|
||||
|
Install the package with [NuGet](https://www.nuget.org/packages/IP2Region.Net) |
||||
|
|
||||
|
```bash |
||||
|
Install-Package IP2Region.Net |
||||
|
``` |
||||
|
|
||||
|
## Usage |
||||
|
|
||||
|
```csharp |
||||
|
using IP2Region.Net.Abstractions; |
||||
|
using IP2Region.Net.XDB; |
||||
|
|
||||
|
ISearcher searcher = new Searcher(CachePolicy , "your xdb file path"); |
||||
|
``` |
||||
|
### Cache Policy Description |
||||
|
| Cache Policy | Description | Thread Safe | |
||||
|
|-------------------------|------------------------------------------------------------------------------------------------------------|-------------| |
||||
|
| CachePolicy.Content | Cache the entire `xdb` data. | Yes | |
||||
|
| CachePolicy.VectorIndex | Cache `vecotorIndex` to speed up queries and reduce system io pressure by reducing one fixed IO operation. | Yes | |
||||
|
| CachePolicy.File | Completely file-based queries | Yes | |
||||
|
### XDB File Description |
||||
|
Generate using [maker](https://github.com/lionsoul2014/ip2region/tree/master/maker/csharp), or [download](https://github.com/lionsoul2014/ip2region/blob/master/data/ip2region.xdb) pre-generated xdb files |
||||
|
|
||||
|
## ASP.NET Core Usage |
||||
|
|
||||
|
```csharp |
||||
|
services.AddSingleton<ISearcher>(new Searcher(CachePolicy , "your xdb file path")); |
||||
|
``` |
||||
|
|
||||
|
## Performance |
||||
|
|
||||
|
``` ini |
||||
|
|
||||
|
BenchmarkDotNet=v0.13.2, OS=macOS 13.4.1 (c) (22F770820d) [Darwin 22.5.0] |
||||
|
Apple M1, 1 CPU, 8 logical and 8 physical cores |
||||
|
.NET SDK=7.0.306 |
||||
|
[Host] : .NET 6.0.20 (6.0.2023.32017), Arm64 RyuJIT AdvSIMD |
||||
|
DefaultJob : .NET 6.0.20 (6.0.2023.32017), Arm64 RyuJIT AdvSIMD |
||||
|
|
||||
|
|
||||
|
``` |
||||
|
| Method | Mean | Error | StdDev | |
||||
|
|-------------------------|-----------:|---------:|---------:| |
||||
|
| CachePolicy_Content | 155.7 ns | 0.46 ns | 0.39 ns | |
||||
|
| CachePolicy_File | 2,186.8 ns | 34.27 ns | 32.06 ns | |
||||
|
| CachePolicy_VectorIndex | 1,570.3 ns | 27.53 ns | 22.99 ns | |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Contributing |
||||
|
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. |
||||
|
|
||||
|
Please make sure to update tests as appropriate. |
||||
|
|
||||
|
## License |
||||
|
[Apache License 2.0](https://github.com/lionsoul2014/ip2region/blob/master/LICENSE.md) |
||||
@ -0,0 +1,48 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Identity.Session.AspNetCore; |
||||
|
public class AbpIdentitySessionAspNetCoreOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 是否解析IP地理信息
|
||||
|
/// </summary>
|
||||
|
public bool IsParseIpLocation { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 不做处理的省份
|
||||
|
/// </summary>
|
||||
|
public IList<string> IgnoreProvinces { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 地理信息解析
|
||||
|
/// </summary>
|
||||
|
public Func<LocationInfo, string> LocationParser { get; set; } |
||||
|
|
||||
|
public AbpIdentitySessionAspNetCoreOptions() |
||||
|
{ |
||||
|
IsParseIpLocation = false; |
||||
|
IgnoreProvinces = new List<string> |
||||
|
{ |
||||
|
// 中国直辖市不显示省份数据
|
||||
|
"北京", "上海", "天津", "重庆" |
||||
|
}; |
||||
|
LocationParser = (locationInfo) => |
||||
|
{ |
||||
|
var location = ""; |
||||
|
|
||||
|
if (!locationInfo.Province.IsNullOrWhiteSpace() && |
||||
|
!IgnoreProvinces.Contains(locationInfo.Province)) |
||||
|
{ |
||||
|
location += locationInfo.Province + " "; |
||||
|
} |
||||
|
if (!locationInfo.City.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
location += locationInfo.City; |
||||
|
} |
||||
|
if (location.IsNullOrWhiteSpace() && !locationInfo.Country.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
location = locationInfo.Country; |
||||
|
} |
||||
|
return location; |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
using IP2Region.Net.Abstractions; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Identity.Session.AspNetCore; |
||||
|
public class IP2RegionLocationInfoProvider : IIpLocationInfoProvider, ISingletonDependency |
||||
|
{ |
||||
|
protected static readonly LocationInfo _nullCache = null; |
||||
|
|
||||
|
protected ISearcher Searcher { get; } |
||||
|
public IP2RegionLocationInfoProvider(ISearcher searcher) |
||||
|
{ |
||||
|
Searcher = searcher; |
||||
|
} |
||||
|
|
||||
|
public virtual Task<LocationInfo> GetLocationInfoAsync(string ipAddress) |
||||
|
{ |
||||
|
var region = Searcher.Search(ipAddress); |
||||
|
if (region.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
return Task.FromResult(_nullCache); |
||||
|
} |
||||
|
|
||||
|
var regions = region.Split('|'); |
||||
|
// | 0 | 1 | 2 |3| 4 | 5 | 6 |
|
||||
|
// 39.128.0.0|39.128.31.255|中国|0|云南省|昆明市|移动
|
||||
|
var locationInfo = new LocationInfo |
||||
|
{ |
||||
|
Country = regions.Length >= 3 && !string.Equals(regions[2], "0") ? regions[2] : null, |
||||
|
Province = regions.Length >= 5 && !string.Equals(regions[4], "0") ? regions[4] : null, |
||||
|
City = regions.Length >= 6 && !string.Equals(regions[5], "0") ? regions[5] : null, |
||||
|
}; |
||||
|
return Task.FromResult(locationInfo); |
||||
|
} |
||||
|
} |
||||
@ -1,7 +1,9 @@ |
|||||
namespace LINGYUN.Abp.Identity.Session; |
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Identity.Session; |
||||
public interface IDeviceInfoProvider |
public interface IDeviceInfoProvider |
||||
{ |
{ |
||||
DeviceInfo DeviceInfo { get; } |
Task<DeviceInfo> GetDeviceInfoAsync(); |
||||
|
|
||||
string ClientIpAddress { get; } |
string ClientIpAddress { get; } |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,14 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Identity.Session; |
||||
|
public interface IIpLocationInfoProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 通过ip地址获取地理信息
|
||||
|
/// </summary>
|
||||
|
/// <param name="ipAddress">ip地址</param>
|
||||
|
/// <returns>
|
||||
|
/// 如果解析成功返回地理信息,否则返回null
|
||||
|
/// </returns>
|
||||
|
Task<LocationInfo> GetLocationInfoAsync(string ipAddress); |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace LINGYUN.Abp.Identity.Session; |
||||
|
public class LocationInfo |
||||
|
{ |
||||
|
public string Country { get; set; } |
||||
|
public string Province { get; set; } |
||||
|
public string City { get; set; } |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Identity.Session; |
||||
|
|
||||
|
[Dependency(ServiceLifetime.Singleton, TryRegister = true)] |
||||
|
public class NoneIpLocationInfoProvider : IIpLocationInfoProvider |
||||
|
{ |
||||
|
protected static readonly LocationInfo _nullCache = null; |
||||
|
public Task<LocationInfo> GetLocationInfoAsync(string ipAddress) |
||||
|
{ |
||||
|
return Task.FromResult(_nullCache); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net8.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<IsPackable>false</IsPackable> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.IP2Region\LINGYUN.Abp.IP2Region.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<None Update="LINGYUN\Abp\IP2Region\ip.merge.txt"> |
||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> |
||||
|
</None> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,6 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
|
||||
|
namespace LINGYUN.Abp.IP2Region; |
||||
|
public class AbpIP2RegionTestBase : AbpTestsBase<AbpIP2RegionTestModule> |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.IP2Region; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpIP2RegionModule), |
||||
|
typeof(AbpTestsBaseModule))] |
||||
|
public class AbpIP2RegionTestModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,93 @@ |
|||||
|
using IP2Region.Net.XDB; |
||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace LINGYUN.Abp.IP2Region; |
||||
|
public class SearcherTest : AbpIP2RegionTestBase |
||||
|
{ |
||||
|
private readonly Stream _xdbStream; |
||||
|
|
||||
|
public SearcherTest() |
||||
|
{ |
||||
|
var virtualFileProvider = GetRequiredService<IVirtualFileProvider>(); |
||||
|
_xdbStream = virtualFileProvider.GetFileInfo("/LINGYUN/Abp/IP2Region/Resources/ip2region.xdb").CreateReadStream(); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData("114.114.114.114")] |
||||
|
[InlineData("119.29.29.29")] |
||||
|
[InlineData("223.5.5.5")] |
||||
|
[InlineData("180.76.76.76")] |
||||
|
[InlineData("8.8.8.8")] |
||||
|
public void TestSearchCacheContent(string ip) |
||||
|
{ |
||||
|
var contentSearcher = new AbpSearcher(CachePolicy.Content, _xdbStream); |
||||
|
var region = contentSearcher.Search(ip); |
||||
|
Console.WriteLine(region); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData("114.114.114.114")] |
||||
|
[InlineData("119.29.29.29")] |
||||
|
[InlineData("223.5.5.5")] |
||||
|
[InlineData("180.76.76.76")] |
||||
|
[InlineData("8.8.8.8")] |
||||
|
public void TestSearchCacheVector(string ip) |
||||
|
{ |
||||
|
var vectorSearcher = new AbpSearcher(CachePolicy.VectorIndex, _xdbStream); |
||||
|
var region = vectorSearcher.Search(ip); |
||||
|
Console.WriteLine(region); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData("114.114.114.114")] |
||||
|
[InlineData("119.29.29.29")] |
||||
|
[InlineData("223.5.5.5")] |
||||
|
[InlineData("180.76.76.76")] |
||||
|
[InlineData("8.8.8.8")] |
||||
|
public void TestSearchCacheFile(string ip) |
||||
|
{ |
||||
|
var fileSearcher = new AbpSearcher(CachePolicy.File, _xdbStream); |
||||
|
var region = fileSearcher.Search(ip); |
||||
|
Console.WriteLine(region); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData(CachePolicy.Content)] |
||||
|
[InlineData(CachePolicy.VectorIndex)] |
||||
|
[InlineData(CachePolicy.File)] |
||||
|
public void TestBenchSearch(CachePolicy cachePolicy) |
||||
|
{ |
||||
|
var searcher = new AbpSearcher(cachePolicy, _xdbStream); |
||||
|
var srcPath = Path.Combine(AppContext.BaseDirectory, "ip.merge.txt"); |
||||
|
|
||||
|
foreach (var line in File.ReadLines(srcPath)) |
||||
|
{ |
||||
|
var ps = line.Trim().Split("|", 3); |
||||
|
|
||||
|
if (ps.Length != 3) |
||||
|
{ |
||||
|
throw new ArgumentException($"invalid ip segment line {line}", nameof(line)); |
||||
|
} |
||||
|
|
||||
|
var sip = Util.IpAddressToUInt32(ps[0]); |
||||
|
var eip = Util.IpAddressToUInt32(ps[1]); |
||||
|
var mip = Util.GetMidIp(sip, eip); |
||||
|
|
||||
|
uint[] temp = { sip, Util.GetMidIp(sip, mip), mip, Util.GetMidIp(mip, eip), eip }; |
||||
|
|
||||
|
foreach (var ip in temp) |
||||
|
{ |
||||
|
var region = searcher.Search(ip); |
||||
|
|
||||
|
if (region != ps[2]) |
||||
|
{ |
||||
|
throw new Exception($"failed search {ip} with ({region}!={ps[2]})"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
Loading…
Reference in new issue