1
0
懒得勤快 2 жил өмнө
parent
commit
fffe4e84b3

BIN
src/Masuit.MyBlogs.Core/App_Data/GeoLite2-Country.mmdb


BIN
src/Masuit.MyBlogs.Core/App_Data/ip2region.db → src/Masuit.MyBlogs.Core/App_Data/qqwry.dat


+ 36 - 30
src/Masuit.MyBlogs.Core/Common/CommonHelper.cs

@@ -4,7 +4,6 @@ using AngleSharp.Dom;
 using AutoMapper;
 using FreeRedis;
 using Hangfire;
-using IP2Region;
 using Masuit.MyBlogs.Core.Common.Mails;
 using Masuit.Tools;
 using Masuit.Tools.Media;
@@ -126,9 +125,10 @@ namespace Masuit.MyBlogs.Core.Common
 			return DenyIP.Contains(ip) || DenyIPRange.AsParallel().Any(kv => kv.Key.StartsWith(ip.Split('.')[0]) && ip.IpAddressInRange(kv.Key, kv.Value));
 		}
 
-		private static readonly DbSearcher IPSearcher = new(Path.Combine(AppContext.BaseDirectory + "App_Data", "ip2region.db"));
+		private static readonly QQWrySearcher IPSearcher = new(Path.Combine(AppContext.BaseDirectory + "App_Data", "qqwry.dat"));
 		public static readonly DatabaseReader MaxmindReader = new(Path.Combine(AppContext.BaseDirectory + "App_Data", "GeoLite2-City.mmdb"));
 		private static readonly DatabaseReader MaxmindAsnReader = new(Path.Combine(AppContext.BaseDirectory + "App_Data", "GeoLite2-ASN.mmdb"));
+		private static readonly DatabaseReader MaxmindCountryReader = new(Path.Combine(AppContext.BaseDirectory + "App_Data", "GeoLite2-Country.mmdb"));
 
 		/// <summary>
 		/// 是否是代理ip
@@ -170,6 +170,11 @@ namespace Masuit.MyBlogs.Core.Common
 			return Policy<CityResponse>.Handle<AddressNotFoundException>().Fallback(new CityResponse()).Execute(() => MaxmindReader.City(ip));
 		}
 
+		private static CountryResponse GetCountryResp(IPAddress ip)
+		{
+			return Policy<CountryResponse>.Handle<AddressNotFoundException>().Fallback(new CountryResponse()).Execute(() => MaxmindCountryReader.Country(ip));
+		}
+
 		public static IPLocation GetIPLocation(this string ip)
 		{
 			var b = IPAddress.TryParse(ip, out var ipAddress);
@@ -185,37 +190,38 @@ namespace Masuit.MyBlogs.Core.Common
 		{
 			if (ip.IsPrivateIP())
 			{
-				return new IPLocation("内网", null, null, "内网IP", null);
+				return new IPLocation("内网", null, "内网IP", null);
 			}
 
-			var city = GetCityResp(ip);
-			var asn = GetIPAsn(ip);
-			var countryName = city.Country.Names.GetValueOrDefault("zh-CN") ?? city.Country.Name;
+			var cityTask = Task.Run(() => GetCityResp(ip));
+			var asnTask = Task.Run(() => GetIPAsn(ip));
+			var countryTask = Task.Run(() => GetCountryResp(ip));
+			Task.WaitAll(cityTask, countryTask, asnTask);
+			var city = cityTask.Result;
+			var country = countryTask.Result;
+			var asn = asnTask.Result;
+			var countryName = country.Country.Names.GetValueOrDefault("zh-CN") ?? country.Country.Name;
 			var cityName = city.City.Names.GetValueOrDefault("zh-CN") ?? city.City.Name;
+			var continent = country.Continent.Names.GetValueOrDefault("zh-CN") ?? country.Continent.Name;
 			switch (ip.AddressFamily)
 			{
 				case AddressFamily.InterNetworkV6 when ip.IsIPv4MappedToIPv6:
 					ip = ip.MapToIPv4();
 					goto case AddressFamily.InterNetwork;
 				case AddressFamily.InterNetwork:
-					var parts = IPSearcher.MemorySearch(ip.ToString())?.Region.Split('|');
-					if (parts != null)
+					var location = IPSearcher.GetIpLocation(ip);
+					var network = location.Network + "/" + asn.AutonomousSystemOrganization;
+					return new IPLocation(countryName, location.City, network.Trim('/'), asn.AutonomousSystemNumber)
 					{
-						var network = parts[^1] == "0" ? asn.AutonomousSystemOrganization : parts[^1] + "/" + asn.AutonomousSystemOrganization;
-						parts[0] = parts[0] != "0" ? parts[0] : countryName;
-						parts[3] = parts[3] != "0" ? parts[3] : cityName;
-						return new IPLocation(parts[0], parts[2], parts[3], network?.Trim('/'), asn.AutonomousSystemNumber)
-						{
-							Address2 = new IPLocation.CountryCity(countryName, cityName),
-							Coodinate = city.Location
-						};
-					}
-
-					goto default;
+						Address2 = new IPLocation.CountryCity(continent, countryName, cityName),
+						Coodinate = city.Location,
+						Continent = continent
+					};
 				default:
-					return new IPLocation(countryName, null, cityName, asn.AutonomousSystemOrganization, asn.AutonomousSystemNumber)
+					return new IPLocation(countryName, cityName, asn.AutonomousSystemOrganization, asn.AutonomousSystemNumber)
 					{
-						Coodinate = city.Location
+						Coodinate = city.Location,
+						Continent = continent
 					};
 			}
 		}
@@ -438,18 +444,17 @@ namespace Masuit.MyBlogs.Core.Common
 
 	public class IPLocation
 	{
-		public IPLocation(string country, string province, string city, string isp, long? asn)
+		public IPLocation(string country, string city, string isp, long? asn)
 		{
 			Country = country?.Trim('0');
-			Province = province?.Trim('0');
 			City = city?.Trim('0');
 			ISP = isp;
 			ASN = asn;
 		}
 
-		public string Country { get; set; }
+		public string Continent { get; set; }
 
-		public string Province { get; set; }
+		public string Country { get; set; }
 
 		public string City { get; set; }
 
@@ -457,9 +462,9 @@ namespace Masuit.MyBlogs.Core.Common
 
 		public long? ASN { get; set; }
 
-		public string Address => new[] { Country, Province, City }.Where(s => !string.IsNullOrEmpty(s)).Distinct().Join("");
+		public string Address => new[] { Continent, Country, City }.Where(s => !string.IsNullOrEmpty(s)).Distinct().Join("");
 
-		public CountryCity Address2 { get; set; } = new("", "");
+		public CountryCity Address2 { get; set; } = new("", "", "");
 
 		public string Network => ASN.HasValue ? ISP + "(AS" + ASN + ")" : ISP;
 
@@ -504,12 +509,13 @@ namespace Masuit.MyBlogs.Core.Common
 			return ToString().Contains(s);
 		}
 
-		public record CountryCity(string Country, string City)
+		public record CountryCity(string Continent, string Country, string City)
 		{
-			public void Deconstruct(out string country, out string city)
+			public void Deconstruct(out string continent, out string country, out string city)
 			{
 				country = Country;
 				city = City;
+				continent = Continent;
 			}
 
 			public static implicit operator string(CountryCity entry)
@@ -519,7 +525,7 @@ namespace Masuit.MyBlogs.Core.Common
 
 			public override string ToString()
 			{
-				return Country + City;
+				return Continent + Country + City;
 			}
 		}
 	}

+ 342 - 0
src/Masuit.MyBlogs.Core/Common/QQWrySearcher.cs

@@ -0,0 +1,342 @@
+using Masuit.Tools;
+using System.Net;
+using System.Text;
+
+namespace Masuit.MyBlogs.Core.Common;
+
+/// <summary>
+/// QQWryIpSearch 请作为单例使用 数据库缓存在内存
+/// </summary>
+public sealed class QQWrySearcher : IDisposable
+{
+    private readonly SemaphoreSlim _initLock = new SemaphoreSlim(initialCount: 1, maxCount: 1);
+
+    private object _versionLock = new object();
+
+    private static readonly Encoding Gb2312Encoding;
+
+    /// <summary>
+    /// 数据库 缓存
+    /// </summary>
+    private byte[] _qqwryDbBytes;
+
+    /// <summary>
+    /// Ip索引 缓存
+    /// </summary>
+    private long[] _ipIndexCache;
+
+    /// <summary>
+    /// 起始定位
+    /// </summary>
+    private long _startPosition;
+
+    /// <summary>
+    /// 是否初始化
+    /// </summary>
+    private bool? _init;
+
+    private readonly string _dbPath;
+
+    private int? _ipCount;
+
+    private string _version;
+
+    /// <summary>
+    /// 记录总数
+    /// </summary>
+    public int IpCount
+    {
+        get
+        {
+            _ipCount ??= _ipIndexCache.Length;
+            return _ipCount.Value;
+        }
+    }
+
+    /// <summary>
+    /// 版本信息
+    /// </summary>
+    public string Version
+    {
+        get
+        {
+            if (!string.IsNullOrWhiteSpace(_version))
+            {
+                return _version;
+            }
+            lock (_versionLock)
+            {
+                if (!string.IsNullOrWhiteSpace(_version))
+                {
+                    return _version;
+                }
+                _version = GetIpLocation(IPAddress.Parse("255.255.255.255")).Network;
+                return _version;
+            }
+        }
+    }
+
+    static QQWrySearcher()
+    {
+#if NET45
+
+#else
+        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+#endif
+        Gb2312Encoding = Encoding.GetEncoding("gb2312");
+    }
+
+    public QQWrySearcher(string dbPath)
+    {
+        _dbPath = dbPath;
+        Init();
+    }
+
+    /// <summary>
+    /// 初始化
+    /// </summary>
+    /// <returns></returns>
+    public bool Init(bool getNewDb = false)
+    {
+        if (_init != null && !getNewDb)
+        {
+            return _init.Value;
+        }
+        _initLock.Wait();
+        try
+        {
+            if (_init != null && !getNewDb)
+            {
+                return _init.Value;
+            }
+
+            _qqwryDbBytes = FileToBytes(_dbPath);
+
+            _ipIndexCache = BlockToArray(ReadIpBlock(_qqwryDbBytes, out _startPosition));
+
+            _ipCount = null;
+            _version = null;
+            _init = true;
+        }
+        finally
+        {
+            _initLock.Release();
+        }
+
+        if (_qqwryDbBytes == null)
+        {
+            throw new InvalidOperationException("无法打开IP数据库" + _dbPath + "!");
+        }
+
+        return true;
+
+    }
+
+    /// <summary>
+    ///  获取指定IP所在地理位置
+    /// </summary>
+    /// <param name="ip">要查询的IP地址</param>
+    /// <returns></returns>
+    public (string City, string Network) GetIpLocation(IPAddress ip)
+    {
+        if (ip.IsPrivateIP())
+        {
+            return ("内网", "内网");
+        }
+        var ipnum = IpToLong(ip);
+        return ReadLocation(ipnum, _startPosition, _ipIndexCache, _qqwryDbBytes);
+    }
+
+    /// <inheritdoc />
+    /// <summary>
+    /// 释放
+    /// </summary>
+    public void Dispose()
+    {
+        _initLock?.Dispose();
+        _versionLock = null;
+        _qqwryDbBytes = null;
+        _qqwryDbBytes = null;
+        _ipIndexCache = null;
+        _init = null;
+    }
+
+    ///<summary>
+    /// 将字符串形式的IP转换位long
+    ///</summary>
+    ///<param name="ip"></param>
+    ///<returns></returns>
+    private static long IpToLong(IPAddress ip)
+    {
+        var bytes = ip.GetAddressBytes();
+        var ipBytes = new byte[8];
+        for (var i = 0; i < 4; i++)
+        {
+            ipBytes[i] = bytes[3 - i];
+        }
+
+        return BitConverter.ToInt64(ipBytes);
+    }
+
+    ///<summary>
+    /// 将索引区字节块中的起始IP转换成Long数组
+    ///</summary>
+    ///<param name="ipBlock"></param>
+    private static long[] BlockToArray(byte[] ipBlock)
+    {
+        var ipArray = new long[ipBlock.Length / 7];
+        var ipIndex = 0;
+        var temp = new byte[8];
+        for (var i = 0; i < ipBlock.Length; i += 7)
+        {
+            Array.Copy(ipBlock, i, temp, 0, 4);
+            ipArray[ipIndex] = BitConverter.ToInt64(temp, 0);
+            ipIndex++;
+        }
+        return ipArray;
+    }
+
+    /// <summary>
+    ///  从IP数组中搜索指定IP并返回其索引
+    /// </summary>
+    /// <param name="ip"></param>
+    /// <param name="ipArray">IP数组</param>
+    /// <param name="start">指定搜索的起始位置</param>
+    /// <param name="end">指定搜索的结束位置</param>
+    /// <returns></returns>
+    private static int SearchIp(long ip, long[] ipArray, int start, int end)
+    {
+        //二分法 https://baike.baidu.com/item/%E4%BA%8C%E5%88%86%E6%B3%95%E6%9F%A5%E6%89%BE
+        while (true)
+        {
+            //计算中间索引
+            var middle = (start + end) / 2;
+            if (middle == start)
+            {
+                return middle;
+            }
+            else if (ip < ipArray[middle])
+            {
+                end = middle;
+            }
+            else
+            {
+                start = middle;
+            }
+        }
+    }
+
+    ///<summary>
+    /// 读取IP文件中索引区块
+    ///</summary>
+    ///<returns></returns>
+    private static byte[] ReadIpBlock(byte[] bytes, out long startPosition)
+    {
+        long offset = 0;
+        startPosition = ReadLongX(bytes, offset, 4);
+        offset += 4;
+        var endPosition = ReadLongX(bytes, offset, 4);
+        offset = startPosition;
+        var count = (endPosition - startPosition) / 7 + 1;//总记录数
+
+        var ipBlock = new byte[count * 7];
+        for (var i = 0; i < ipBlock.Length; i++)
+        {
+            ipBlock[i] = bytes[offset + i];
+        }
+        return ipBlock;
+    }
+
+    /// <summary>
+    ///  从IP文件中读取指定字节并转换位long
+    /// </summary>
+    /// <param name="bytes"></param>
+    /// <param name="bytesCount">需要转换的字节数,主意不要超过8字节</param>
+    /// <returns></returns>
+    private static long ReadLongX(byte[] bytes, long offset, int bytesCount)
+    {
+        var cBytes = new byte[8];
+        for (var i = 0; i < bytesCount; i++)
+        {
+            cBytes[i] = bytes[offset + i];
+        }
+        return BitConverter.ToInt64(cBytes, 0);
+    }
+
+    /// <summary>
+    ///  从IP文件中读取字符串
+    /// </summary>
+    /// <param name="bytes"></param>
+    /// <param name="flag">转向标志</param>
+    /// <param name="offset"></param>
+    /// <returns></returns>
+    private static string ReadString(byte[] bytes, int flag, ref long offset)
+    {
+        if (flag == 1 || flag == 2)//转向标志
+        {
+            offset = ReadLongX(bytes, offset, 3);
+        }
+        else
+        {
+            offset -= 1;
+        }
+        var list = new List<byte>();
+        var b = bytes[offset];
+        offset += 1;
+        while (b > 0)
+        {
+            list.Add(b);
+            b = bytes[offset];
+            offset += 1;
+        }
+        return Gb2312Encoding.GetString(list.ToArray());
+    }
+
+    private static byte[] FileToBytes(string fileName)
+    {
+        using (FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
+        {
+            byte[] bytes = new byte[fileStream.Length];
+
+            fileStream.Read(bytes, 0, bytes.Length);
+
+            fileStream.Close();
+
+            return bytes;
+        }
+    }
+
+    private static (string City, string Network) ReadLocation(long ip, long startPosition, long[] ipIndex, byte[] qqwryDbBytes)
+    {
+        long offset = SearchIp(ip, ipIndex, 0, ipIndex.Length) * 7 + 4;
+
+        //偏移
+        var arrayOffset = startPosition + offset;
+        //跳过结束IP
+        arrayOffset = ReadLongX(qqwryDbBytes, arrayOffset, 3) + 4;
+        //读取标志
+        var flag = qqwryDbBytes[arrayOffset];
+        arrayOffset += 1;
+        //表示国家和地区被转向
+        if (flag == 1)
+        {
+            arrayOffset = ReadLongX(qqwryDbBytes, arrayOffset, 3);
+            //再读标志
+            flag = qqwryDbBytes[arrayOffset];
+            arrayOffset += 1;
+        }
+        var countryOffset = arrayOffset;
+        var city = ReadString(qqwryDbBytes, flag, ref arrayOffset);
+
+        if (flag == 2)
+        {
+            arrayOffset = countryOffset + 3;
+        }
+
+        flag = qqwryDbBytes[arrayOffset];
+        arrayOffset += 1;
+        var network = ReadString(qqwryDbBytes, flag, ref arrayOffset);
+
+        return (city, network);
+    }
+}

+ 0 - 1
src/Masuit.MyBlogs.Core/Masuit.MyBlogs.Core.csproj

@@ -49,7 +49,6 @@
         <PackageReference Include="Hangfire" Version="1.7.32" />
         <PackageReference Include="Hangfire.MemoryStorage" Version="1.7.0" />
         <PackageReference Include="htmldiff.net" Version="1.4.0" />
-        <PackageReference Include="IP2Region" Version="1.2.0" />
         <PackageReference Include="Karambolo.AspNetCore.Bundling.NUglify" Version="3.6.0" />
         <PackageReference Include="Markdig" Version="0.30.4" />
         <PackageReference Include="Masuit.Tools.Excel" Version="1.2.3.6" />