using AutoMapper; using Hangfire; using HtmlAgilityPack; using IP2Region; using Masuit.MyBlogs.Core.Common.Mails; using Masuit.Tools; using Masuit.Tools.Media; using MaxMind.GeoIP2; using MaxMind.GeoIP2.Exceptions; using MaxMind.GeoIP2.Responses; using Microsoft.Extensions.DependencyInjection; using Polly; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using TimeZoneConverter; namespace Masuit.MyBlogs.Core.Common { /// /// 公共类库 /// public static class CommonHelper { static CommonHelper() { ThreadPool.QueueUserWorkItem(_ => { while (true) { BanRegex = File.ReadAllText(Path.Combine(AppContext.BaseDirectory + "App_Data", "ban.txt"), Encoding.UTF8); ModRegex = File.ReadAllText(Path.Combine(AppContext.BaseDirectory + "App_Data", "mod.txt"), Encoding.UTF8); DenyIP = File.ReadAllText(Path.Combine(AppContext.BaseDirectory + "App_Data", "denyip.txt"), Encoding.UTF8); string[] lines = File.ReadAllLines(Path.Combine(AppContext.BaseDirectory + "App_Data", "DenyIPRange.txt"), Encoding.UTF8); DenyIPRange = new Dictionary(); foreach (string line in lines) { try { var strs = line.Split(' '); DenyIPRange[strs[0]] = strs[1]; } catch (IndexOutOfRangeException) { } } IPWhiteList = File.ReadAllText(Path.Combine(AppContext.BaseDirectory + "App_Data", "whitelist.txt")).Split(',', ',').ToList(); Console.WriteLine("刷新公共数据..."); Thread.Sleep(TimeSpan.FromMinutes(10)); } }); } /// /// 敏感词 /// public static string BanRegex { get; set; } /// /// 审核词 /// public static string ModRegex { get; set; } /// /// 全局禁止IP /// public static string DenyIP { get; set; } /// /// ip白名单 /// public static List IPWhiteList { get; set; } /// /// 每IP错误的次数统计 /// public static ConcurrentDictionary IPErrorTimes { get; set; } = new(); /// /// 系统设定 /// public static ConcurrentDictionary SystemSettings { get; set; } = new(); /// /// 网站启动时间 /// public static DateTime StartupTime { get; set; } = DateTime.Now; /// /// IP黑名单地址段 /// public static Dictionary DenyIPRange { get; set; } /// /// 判断IP地址是否被黑名单 /// /// /// public static bool IsDenyIpAddress(this string ip) { if (IPWhiteList.Contains(ip)) { return false; } return DenyIP.Contains(ip) || DenyIPRange.AsParallel().Any(kv => kv.Key.StartsWith(ip.Split('.')[0]) && ip.IpAddressInRange(kv.Key, kv.Value)); } /// /// 是否是禁区 /// /// /// public static bool IsInDenyArea(this string ips) { var denyAreas = SystemSettings.GetOrAdd("DenyArea", "").Split(new[] { ',', ',' }, StringSplitOptions.RemoveEmptyEntries); if (denyAreas.Any()) { foreach (var item in ips.Split(',')) { var pos = GetIPLocation(item); return pos.Contains(denyAreas) || denyAreas.Intersect(pos.Split("|")).Any(); } } return false; } private static readonly DbSearcher IPSearcher = new(Path.Combine(AppContext.BaseDirectory + "App_Data", "ip2region.db")); 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")); public static AsnResponse GetIPAsn(this string ip) { if (ip.IsPrivateIP()) { return new AsnResponse(); } return Policy.Handle().Fallback(new AsnResponse()).Execute(() => MaxmindAsnReader.Asn(ip)); } public static AsnResponse GetIPAsn(this IPAddress ip) { return Policy.Handle().Fallback(new AsnResponse()).Execute(() => MaxmindAsnReader.Asn(ip)); } public static string GetIPLocation(this string ips) { var (location, network) = GetIPLocation(IPAddress.Parse(ips)); return location + "|" + network; } public static (string location, string network) GetIPLocation(this IPAddress ip) { switch (ip.AddressFamily) { case AddressFamily.InterNetwork when ip.IsPrivateIP(): case AddressFamily.InterNetworkV6 when ip.IsPrivateIP(): return ("内网", "内网IP"); 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 asn = GetIPAsn(ip); var network = parts[^1] == "0" ? asn.AutonomousSystemOrganization : parts[^1]; var location = parts[..^1].Where(s => s != "0").Distinct().Join(""); return (location, network + $"(AS{asn.AutonomousSystemNumber})"); } goto default; default: var cityResp = Policy.Handle().Fallback(new CityResponse()).Execute(() => MaxmindReader.City(ip)); var asnResp = GetIPAsn(ip); return (cityResp.Country.Names.GetValueOrDefault("zh-CN") + cityResp.City.Names.GetValueOrDefault("zh-CN"), asnResp.AutonomousSystemOrganization + $"(AS{asnResp.AutonomousSystemNumber})"); } } /// /// 获取ip所在时区 /// /// /// public static string GetClientTimeZone(this IPAddress ip) { switch (ip.AddressFamily) { case AddressFamily.InterNetwork when ip.IsPrivateIP(): case AddressFamily.InterNetworkV6 when ip.IsPrivateIP(): return "Asia/Shanghai"; default: var resp = Policy.Handle().Fallback(new CityResponse()).Execute(() => MaxmindReader.City(ip)); return resp.Location.TimeZone ?? "Asia/Shanghai"; } } /// /// 类型映射 /// /// /// /// public static T Mapper(this object source) where T : class => Startup.ServiceProvider.GetRequiredService().Map(source); /// /// 发送邮件 /// /// 标题 /// 内容 /// 收件人 /// [AutomaticRetry(Attempts = 1, OnAttemptsExceeded = AttemptsExceededAction.Delete)] public static void SendMail(string title, string content, string tos, string clientip) { Startup.ServiceProvider.GetRequiredService().Send(title, content, tos); RedisHelper.SAdd($"Email:{DateTime.Now:yyyyMMdd}", new { title, content, tos, time = DateTime.Now, clientip }); RedisHelper.Expire($"Email:{DateTime.Now:yyyyMMdd}", 86400); } /// /// 清理html的img标签的除src之外的其他属性 /// /// /// public static string ClearImgAttributes(this string html) { var doc = new HtmlDocument(); doc.LoadHtml(html); var nodes = doc.DocumentNode.Descendants("img"); foreach (var node in nodes) { node.Attributes.RemoveWhere(a => !new[] { "src", "data-original", "width", "style", "class" }.Contains(a.Name)); } return doc.DocumentNode.OuterHtml; } /// /// 将html的img标签的src属性名替换成data-original /// /// /// /// public static string ReplaceImgAttribute(this string html, string title) { var doc = new HtmlDocument(); doc.LoadHtml(html); var nodes = doc.DocumentNode.Descendants("img"); foreach (var node in nodes) { if (node.Attributes.Contains("src")) { string src = node.Attributes["src"].Value; node.Attributes.Remove("src"); node.Attributes.Add("data-original", src); node.Attributes.Add("alt", SystemSettings["Title"]); node.Attributes.Add("title", title); } } return doc.DocumentNode.OuterHtml; } /// /// 获取文章摘要 /// /// /// 截取长度 /// 摘要最少字数 /// public static string GetSummary(this string html, int length = 150, int min = 10) { var doc = new HtmlDocument(); doc.LoadHtml(html); var summary = doc.DocumentNode.Descendants("p").FirstOrDefault(n => n.InnerText.Length > min)?.InnerText ?? "没有摘要"; if (summary.Length > length) { return summary.Substring(0, length) + "..."; } return summary; } public static string TrimQuery(this string path) { return path.Split('&').Where(s => s.Split('=', StringSplitOptions.RemoveEmptyEntries).Length == 2).Join("&"); } /// /// 添加水印 /// /// /// public static Stream AddWatermark(this Stream stream) { if (!string.IsNullOrEmpty(SystemSettings.GetOrAdd("Watermark", string.Empty))) { try { var watermarker = new ImageWatermarker(stream) { SkipWatermarkForSmallImages = true, SmallImagePixelsThreshold = 90000 }; return watermarker.AddWatermark(SystemSettings["Watermark"], Color.LightGray, WatermarkPosition.BottomRight, 30); } catch { // } } return stream; } /// /// 转换时区 /// /// UTC时间 /// 时区id /// public static DateTime ToTimeZone(this in DateTime time, string zone) { return TimeZoneInfo.ConvertTime(time, TZConvert.GetTimeZoneInfo(zone)); } /// /// 转换时区 /// /// UTC时间 /// 时区id /// 时间格式字符串 /// public static string ToTimeZoneF(this in DateTime time, string zone, string format = "yyyy-MM-dd HH:mm:ss") { return ToTimeZone(time, zone).ToString(format); } } }