Browse Source

Merge pull request #998 from colinin/ip-location-session

feat(session): add ip geolocation resolution to user sessions
pull/1010/head
yx lin 1 year ago
committed by GitHub
parent
commit
b83cb49d98
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      Directory.Packages.props
  2. 28
      aspnet-core/LINGYUN.MicroService.SingleProject.sln
  3. 3
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/FodyWeavers.xml
  4. 30
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/FodyWeavers.xsd
  5. 65
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/Abstractions/AbstractCacheStrategy.cs
  6. 33
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/CacheStrategyFactory.cs
  7. 34
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/ContentCacheStrategy.cs
  8. 24
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/FileCacheStrategy.cs
  9. 28
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/IP2Region/Net/Internal/VectorIndexCacheStrategy.cs
  10. 27
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN.Abp.IP2Region.csproj
  11. 28
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/AbpIP2RegionModule.cs
  12. 80
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/AbpSearcher.cs
  13. BIN
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/Resources/ip2region.xdb
  14. 62
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/README.md
  15. 5
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionManager.cs
  16. 1
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session.AspNetCore/LINGYUN.Abp.Identity.Session.AspNetCore.csproj
  17. 4
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session.AspNetCore/LINGYUN/Abp/Identity/Session/AspNetCore/AbpIdentitySessionAspNetCoreModule.cs
  18. 48
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session.AspNetCore/LINGYUN/Abp/Identity/Session/AspNetCore/AbpIdentitySessionAspNetCoreOptions.cs
  19. 46
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session.AspNetCore/LINGYUN/Abp/Identity/Session/AspNetCore/HttpContextDeviceInfoProvider.cs
  20. 36
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session.AspNetCore/LINGYUN/Abp/Identity/Session/AspNetCore/IP2RegionLocationInfoProvider.cs
  21. 11
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session.AspNetCore/README.md
  22. 4
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session/LINGYUN/Abp/Identity/Session/DeviceInfo.cs
  23. 6
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session/LINGYUN/Abp/Identity/Session/IDeviceInfoProvider.cs
  24. 14
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session/LINGYUN/Abp/Identity/Session/IIpLocationInfoProvider.cs
  25. 5
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session/LINGYUN/Abp/Identity/Session/IdentitySessionCacheItem.cs
  26. 7
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session/LINGYUN/Abp/Identity/Session/LocationInfo.cs
  27. 8
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session/LINGYUN/Abp/Identity/Session/NoneDeviceInfoProvider.cs
  28. 15
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session/LINGYUN/Abp/Identity/Session/NoneIpLocationInfoProvider.cs
  29. 2
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session/README.md
  30. 24
      aspnet-core/tests/LINGYUN.Abp.IP2Region.Tests/LINGYUN.Abp.IP2Region.Tests.csproj
  31. 6
      aspnet-core/tests/LINGYUN.Abp.IP2Region.Tests/LINGYUN/Abp/IP2Region/AbpIP2RegionTestBase.cs
  32. 11
      aspnet-core/tests/LINGYUN.Abp.IP2Region.Tests/LINGYUN/Abp/IP2Region/AbpIP2RegionTestModule.cs
  33. 93
      aspnet-core/tests/LINGYUN.Abp.IP2Region.Tests/LINGYUN/Abp/IP2Region/SearcherTest.cs
  34. 683844
      aspnet-core/tests/LINGYUN.Abp.IP2Region.Tests/LINGYUN/Abp/IP2Region/ip.merge.txt

2
Directory.Packages.props

@ -131,6 +131,7 @@
<PackageVersion Include="Volo.Abp.Users.Abstractions" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Users.Domain.Shared" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Validation" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.VirtualFileSystem" Version="$(VoloAbpPackageVersion)" />
</ItemGroup>
<!-- .NET -->
<ItemGroup>
@ -222,6 +223,7 @@
<PackageVersion Include="Hangfire.MySqlStorage" Version="2.0.3" />
<PackageVersion Include="HangFire.SqlServer" Version="1.8.14" />
<PackageVersion Include="IdentityModel" Version="6.2.0" />
<PackageVersion Include="IP2Region.Net" Version="2.0.2" />
<PackageVersion Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageVersion Include="Markdig" Version="0.34.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />

28
aspnet-core/LINGYUN.MicroService.SingleProject.sln

@ -539,6 +539,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.WeChat.Work.Han
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.MultiTenancy.Saas", "modules\saas\LINGYUN.Abp.MultiTenancy.Saas\LINGYUN.Abp.MultiTenancy.Saas.csproj", "{E3C07A77-EAF9-4A3F-8814-7D2F116C8E26}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.IP2Region", "framework\common\LINGYUN.Abp.IP2Region\LINGYUN.Abp.IP2Region.csproj", "{DA54E88E-C43E-4E31-92D0-08A753F2C08D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.TestsBase", "tests\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj", "{A7417E7F-DB97-48DB-B849-AFC15854A6E7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Aliyun.Tests", "tests\LINGYUN.Abp.Aliyun.Tests\LINGYUN.Abp.Aliyun.Tests.csproj", "{8DDE4291-BC57-4C56-9871-B982462F4C3D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.IP2Region.Tests", "tests\LINGYUN.Abp.IP2Region.Tests\LINGYUN.Abp.IP2Region.Tests.csproj", "{C5D64A2B-7E7E-4509-9F31-425D59712E6F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -1421,6 +1429,22 @@ Global
{E3C07A77-EAF9-4A3F-8814-7D2F116C8E26}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E3C07A77-EAF9-4A3F-8814-7D2F116C8E26}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E3C07A77-EAF9-4A3F-8814-7D2F116C8E26}.Release|Any CPU.Build.0 = Release|Any CPU
{DA54E88E-C43E-4E31-92D0-08A753F2C08D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DA54E88E-C43E-4E31-92D0-08A753F2C08D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DA54E88E-C43E-4E31-92D0-08A753F2C08D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DA54E88E-C43E-4E31-92D0-08A753F2C08D}.Release|Any CPU.Build.0 = Release|Any CPU
{A7417E7F-DB97-48DB-B849-AFC15854A6E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A7417E7F-DB97-48DB-B849-AFC15854A6E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A7417E7F-DB97-48DB-B849-AFC15854A6E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A7417E7F-DB97-48DB-B849-AFC15854A6E7}.Release|Any CPU.Build.0 = Release|Any CPU
{8DDE4291-BC57-4C56-9871-B982462F4C3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8DDE4291-BC57-4C56-9871-B982462F4C3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8DDE4291-BC57-4C56-9871-B982462F4C3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8DDE4291-BC57-4C56-9871-B982462F4C3D}.Release|Any CPU.Build.0 = Release|Any CPU
{C5D64A2B-7E7E-4509-9F31-425D59712E6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C5D64A2B-7E7E-4509-9F31-425D59712E6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C5D64A2B-7E7E-4509-9F31-425D59712E6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C5D64A2B-7E7E-4509-9F31-425D59712E6F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1684,6 +1708,10 @@ Global
{BB2DF96A-6ED8-4F47-948C-230EA2065C4C} = {91867618-0D86-4410-91C6-B1166A9ACDF9}
{DB80C55F-8B70-4840-942A-ED021ED88BD6} = {91867618-0D86-4410-91C6-B1166A9ACDF9}
{E3C07A77-EAF9-4A3F-8814-7D2F116C8E26} = {ECE6E6D7-A4F6-4F50-BC21-AE2EB14A3129}
{DA54E88E-C43E-4E31-92D0-08A753F2C08D} = {99B7CBDE-A251-4738-97F0-DB1DB484BEE1}
{A7417E7F-DB97-48DB-B849-AFC15854A6E7} = {37E2CAFE-0024-452E-BF81-E15F937E08EF}
{8DDE4291-BC57-4C56-9871-B982462F4C3D} = {37E2CAFE-0024-452E-BF81-E15F937E08EF}
{C5D64A2B-7E7E-4509-9F31-425D59712E6F} = {37E2CAFE-0024-452E-BF81-E15F937E08EF}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {711A43C0-A2F8-4E5C-9B9F-F2551E4B3FF1}

3
aspnet-core/framework/common/LINGYUN.Abp.IP2Region/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
aspnet-core/framework/common/LINGYUN.Abp.IP2Region/FodyWeavers.xsd

@ -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>

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

@ -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);
}
}

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

@ -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))
};
}
}

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

@ -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);
}
}

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

@ -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);
}
}

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

@ -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);
}
}

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

@ -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>

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

@ -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;
});
}
}

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

@ -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());
}
}

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

Binary file not shown.

62
aspnet-core/framework/common/LINGYUN.Abp.IP2Region/README.md

@ -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)

5
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionManager.cs

@ -50,7 +50,7 @@ public class IdentitySessionManager : DomainService, IIdentitySessionManager
{
return;
}
var deviceInfo = DeviceInfoProvider.DeviceInfo;
var deviceInfo = await DeviceInfoProvider.GetDeviceInfoAsync();
var device = deviceInfo.Device ?? IdentitySessionDevices.OAuth;
var deviceDesc = deviceInfo.Description;
@ -85,7 +85,8 @@ public class IdentitySessionManager : DomainService, IIdentitySessionManager
clientId,
clientIpAddress,
Clock.Now,
Clock.Now));
Clock.Now,
deviceInfo.IpRegion));
}
}
}

1
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session.AspNetCore/LINGYUN.Abp.Identity.Session.AspNetCore.csproj

@ -18,6 +18,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\framework\common\LINGYUN.Abp.IP2Region\LINGYUN.Abp.IP2Region.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.Identity.Session\LINGYUN.Abp.Identity.Session.csproj" />
</ItemGroup>

4
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session.AspNetCore/LINGYUN/Abp/Identity/Session/AspNetCore/AbpIdentitySessionAspNetCoreModule.cs

@ -1,9 +1,11 @@
using Volo.Abp.AspNetCore;
using LINGYUN.Abp.IP2Region;
using Volo.Abp.AspNetCore;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.Identity.Session.AspNetCore;
[DependsOn(typeof(AbpAspNetCoreModule))]
[DependsOn(typeof(AbpIP2RegionModule))]
[DependsOn(typeof(AbpIdentitySessionModule))]
public class AbpIdentitySessionAspNetCoreModule : AbpModule
{

48
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session.AspNetCore/LINGYUN/Abp/Identity/Session/AspNetCore/AbpIdentitySessionAspNetCoreOptions.cs

@ -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;
};
}
}

46
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session.AspNetCore/LINGYUN/Abp/Identity/Session/AspNetCore/HttpContextDeviceInfoProvider.cs

@ -1,31 +1,67 @@
using DeviceDetectorNET;
using Microsoft.Extensions.Options;
using System;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.WebClientInfo;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.Identity.Session.AspNetCore;
public class HttpContextDeviceInfoProvider : IDeviceInfoProvider, ITransientDependency
{
protected IIpLocationInfoProvider IpLocationInfoProvider { get; }
protected IWebClientInfoProvider WebClientInfoProvider { get; }
protected AbpIdentitySessionAspNetCoreOptions Options { get; }
public HttpContextDeviceInfoProvider(
IWebClientInfoProvider webClientInfoProvider)
IIpLocationInfoProvider ipLocationInfoProvider,
IWebClientInfoProvider webClientInfoProvider,
IOptions<AbpIdentitySessionAspNetCoreOptions> options)
{
IpLocationInfoProvider = ipLocationInfoProvider;
WebClientInfoProvider = webClientInfoProvider;
Options = options.Value;
}
public DeviceInfo DeviceInfo => GetDeviceInfo();
public string ClientIpAddress => WebClientInfoProvider.ClientIpAddress;
protected virtual DeviceInfo GetDeviceInfo()
public async virtual Task<DeviceInfo> GetDeviceInfoAsync()
{
var deviceInfo = BrowserDeviceInfo.Parse(WebClientInfoProvider.BrowserInfo);
var ipAddress = WebClientInfoProvider.ClientIpAddress;
var ipRegion = "";
if (!ipAddress.IsNullOrWhiteSpace())
{
if (Options.IsParseIpLocation)
{
var region = await GetRegion(ipAddress);
if (!region.IsNullOrWhiteSpace())
{
ipRegion = region;
}
}
}
return new DeviceInfo(
deviceInfo.Device,
deviceInfo.Description,
WebClientInfoProvider.ClientIpAddress);
ipAddress,
ipRegion);
}
protected async virtual Task<string> GetRegion(string ipAddress)
{
var locationInfo = await IpLocationInfoProvider.GetLocationInfoAsync(ipAddress);
if (locationInfo == null)
{
return null;
}
if (Options.LocationParser != null)
{
return Options.LocationParser(locationInfo);
}
return null;
}
private class BrowserDeviceInfo

36
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session.AspNetCore/LINGYUN/Abp/Identity/Session/AspNetCore/IP2RegionLocationInfoProvider.cs

@ -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);
}
}

11
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session.AspNetCore/README.md

@ -5,12 +5,19 @@
## 接口描述
### AbpSessionMiddleware 在请求管道中从用户令牌提取 *sessionId* 作为全局会话标识, 可用于注销会话
注意: 当匿名用户访问时, 以请求 *CorrelationId* 作为标识;
> 注意: 当匿名用户访问时, 以请求 *CorrelationId* 作为标识;
*CorrelationId* 不存在时, 使用随机 *Guid.NewGuid()*.
### HttpContextDeviceInfoProvider 从请求参数中提取设备标识
出于模块职责分离原则, 请勿与 *LINGYUN.Abp.Identity.AspNetCore.Session* 模块混淆
> 出于模块职责分离原则, 请勿与 *LINGYUN.Abp.Identity.AspNetCore.Session* 模块混淆
### HttpContextDeviceInfoProvider 用于处理会话IP的地址位置解析
> IsParseIpLocation 启用后将对会话IP进行地理位置解析
> IgnoreProvinces 解析地理位置时需要忽略的省份,通常情况下中国的直辖市需要
> LocationParser 自定义需要处理的地理位置数据
## 配置使用

4
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session/LINGYUN/Abp/Identity/Session/DeviceInfo.cs

@ -4,10 +4,12 @@ public class DeviceInfo
public string Device { get; }
public string Description { get; }
public string ClientIpAddress { get; }
public DeviceInfo(string device, string description, string clientIpAddress)
public string IpRegion { get; }
public DeviceInfo(string device, string description, string clientIpAddress, string ipRegion)
{
Device = device;
Description = description;
ClientIpAddress = clientIpAddress;
IpRegion = ipRegion;
}
}

6
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session/LINGYUN/Abp/Identity/Session/IDeviceInfoProvider.cs

@ -1,7 +1,9 @@
namespace LINGYUN.Abp.Identity.Session;
using System.Threading.Tasks;
namespace LINGYUN.Abp.Identity.Session;
public interface IDeviceInfoProvider
{
DeviceInfo DeviceInfo { get; }
Task<DeviceInfo> GetDeviceInfoAsync();
string ClientIpAddress { get; }
}

14
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session/LINGYUN/Abp/Identity/Session/IIpLocationInfoProvider.cs

@ -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);
}

5
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session/LINGYUN/Abp/Identity/Session/IdentitySessionCacheItem.cs

@ -18,6 +18,7 @@ public class IdentitySessionCacheItem
public string ClientId { get; set; }
public string IpAddresses { get; set; }
public string IpRegion { get; set; }
public DateTime SignedIn { get; set; }
@ -35,7 +36,8 @@ public class IdentitySessionCacheItem
string clientId,
string ipAddresses,
DateTime signedIn,
DateTime? lastAccessed = null)
DateTime? lastAccessed = null,
string ipRegion = null)
{
Device = device;
DeviceInfo = deviceInfo;
@ -43,6 +45,7 @@ public class IdentitySessionCacheItem
SessionId = sessionId;
ClientId = clientId;
IpAddresses = ipAddresses;
IpRegion = ipRegion;
SignedIn = signedIn;
LastAccessed = lastAccessed;
}

7
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session/LINGYUN/Abp/Identity/Session/LocationInfo.cs

@ -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; }
}

8
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session/LINGYUN/Abp/Identity/Session/NoneDeviceInfoProvider.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.Identity.Session;
@ -6,7 +7,12 @@ namespace LINGYUN.Abp.Identity.Session;
[Dependency(ServiceLifetime.Singleton, TryRegister = true)]
public class NoneDeviceInfoProvider : IDeviceInfoProvider
{
public DeviceInfo DeviceInfo => new DeviceInfo("unknown", "unknown", "unknown");
public static DeviceInfo DeviceInfo => new DeviceInfo("unknown", "unknown", "unknown", "unknown");
public virtual Task<DeviceInfo> GetDeviceInfoAsync()
{
return Task.FromResult(DeviceInfo);
}
public string ClientIpAddress => "::1";
}

15
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session/LINGYUN/Abp/Identity/Session/NoneIpLocationInfoProvider.cs

@ -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);
}
}

2
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Session/README.md

@ -13,7 +13,7 @@ public class YouProjectModule : AbpModule
{
Configure<IdentitySessionCheckOptions>(options =>
{
// 设置会话缓存与数据库刷新间隔为10分钟
// 设置会话缓存与持久化设施刷新间隔为10分钟
options.KeepAccessTimeSpan = TimeSpan.FromMinutes(10);
});
}

24
aspnet-core/tests/LINGYUN.Abp.IP2Region.Tests/LINGYUN.Abp.IP2Region.Tests.csproj

@ -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>

6
aspnet-core/tests/LINGYUN.Abp.IP2Region.Tests/LINGYUN/Abp/IP2Region/AbpIP2RegionTestBase.cs

@ -0,0 +1,6 @@
using LINGYUN.Abp.Tests;
namespace LINGYUN.Abp.IP2Region;
public class AbpIP2RegionTestBase : AbpTestsBase<AbpIP2RegionTestModule>
{
}

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

@ -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
{
}

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

@ -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]})");
}
}
}
}
}

683844
aspnet-core/tests/LINGYUN.Abp.IP2Region.Tests/LINGYUN/Abp/IP2Region/ip.merge.txt

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