懒得勤快 2 년 전
부모
커밋
d87170497c

+ 383 - 383
src/Masuit.MyBlogs.Core/Controllers/SystemController.cs

@@ -25,386 +25,386 @@ namespace Masuit.MyBlogs.Core.Controllers;
 /// </summary>
 public sealed class SystemController : AdminController
 {
-	/// <summary>
-	/// 系统设置
-	/// </summary>
-	public ISystemSettingService SystemSettingService { get; set; }
-
-	public IPerfCounter PerfCounter { get; set; }
-
-	public ActionResult GetServers()
-	{
-		var servers = PerfCounter.CreateDataSource().Select(c => c.ServerIP).Distinct().ToArray();
-		return Ok(servers);
-	}
-
-	/// <summary>
-	/// 获取历史性能计数器
-	/// </summary>
-	/// <returns></returns>
-	public IActionResult GetCounterHistory(string ip = null)
-	{
-		ip = ip.IfNullOrEmpty(() => SystemInfo.GetLocalUsedIP(AddressFamily.InterNetwork).ToString());
-		var time = DateTime.Now.AddDays(-15).GetTotalMilliseconds();
-		var counters = PerfCounter.CreateDataSource().Where(c => c.ServerIP == ip && c.Time >= time);
-		var count = counters.Count();
-		var ticks = count switch
-		{
-			<= 5000 => count,
-			> 5000 and <= 10000 => 3,
-			> 10000 and <= 20000 => 6,
-			> 20000 and <= 50000 => 12,
-			> 50000 and <= 100000 => 24,
-			> 100000 and <= 200000 => 48,
-			_ => 72
-		} * 10000;
-
-		var list = count < 5000 ? counters.OrderBy(c => c.Time).ToPooledListScope() : counters.GroupBy(c => c.Time / ticks).Select(g => new PerformanceCounter
-		{
-			Time = g.Key * ticks,
-			CpuLoad = g.Average(c => c.CpuLoad),
-			DiskRead = g.Average(c => c.DiskRead),
-			DiskWrite = g.Average(c => c.DiskWrite),
-			Download = g.Average(c => c.Download),
-			Upload = g.Average(c => c.Upload),
-			MemoryUsage = g.Average(c => c.MemoryUsage)
-		}).OrderBy(c => c.Time).ToPooledListScope();
-		return Ok(new
-		{
-			cpu = list.Select(c => new[]
-			{
-				c.Time,
-				c.CpuLoad.ToDecimal(2)
-			}),
-			mem = list.Select(c => new[]
-			{
-				c.Time,
-				c.MemoryUsage.ToDecimal(2)
-			}),
-			read = list.Select(c => new[]
-			{
-				c.Time,
-				c.DiskRead.ToDecimal(2)
-			}),
-			write = list.Select(c => new[]
-			{
-				c.Time,
-				c.DiskWrite.ToDecimal(2)
-			}),
-			down = list.Select(c => new[]
-			{
-				c.Time,
-				c.Download.ToDecimal(2)
-			}),
-			up = list.Select(c => new[]
-			{
-				c.Time,
-				c.Upload.ToDecimal(2)
-			})
-		});
-	}
-
-	/// <summary>
-	/// 获取设置信息
-	/// </summary>
-	/// <returns></returns>
-	public ActionResult GetSettings()
-	{
-		var list = SystemSettingService.GetAll().Select(s => new
-		{
-			s.Name,
-			s.Value
-		}).ToPooledListScope();
-		return ResultData(list);
-	}
-
-	/// <summary>
-	/// 保存设置
-	/// </summary>
-	/// <param name="settings"></param>
-	/// <returns></returns>
-	public async Task<ActionResult> Save([FromBody] List<SystemSetting> settings)
-	{
-		var b = await SystemSettingService.AddOrUpdateSavedAsync(s => s.Name, settings) > 0;
-		var dic = settings.ToDictionary(s => s.Name, s => s.Value); //同步设置
-		foreach (var (key, value) in dic)
-		{
-			CommonHelper.SystemSettings[key] = value;
-		}
-
-		return ResultData(null, b, b ? "设置保存成功!" : "设置保存失败!");
-	}
-
-	/// <summary>
-	/// 获取状态
-	/// </summary>
-	/// <returns></returns>
-	public ActionResult GetStatus()
-	{
-		Array array = Enum.GetValues(typeof(Status));
-		var list = new List<object>();
-		foreach (Enum e in array)
-		{
-			list.Add(new
-			{
-				e,
-				name = e.GetDisplay()
-			});
-		}
-
-		return ResultData(list);
-	}
-
-	/// <summary>
-	/// 邮件测试
-	/// </summary>
-	/// <param name="smtp"></param>
-	/// <param name="user"></param>
-	/// <param name="pwd"></param>
-	/// <param name="port"></param>
-	/// <param name="to"></param>
-	/// <returns></returns>
-	public ActionResult MailTest([FromBodyOrDefault] string smtp, [FromBodyOrDefault] string user, [FromBodyOrDefault] string pwd, [FromBodyOrDefault] int port, [FromBodyOrDefault] string to, [FromBodyOrDefault] bool ssl)
-	{
-		try
-		{
-			new Email()
-			{
-				EnableSsl = ssl,
-				Body = "发送成功,网站邮件配置正确!",
-				SmtpServer = smtp,
-				Username = user,
-				Password = pwd,
-				SmtpPort = port,
-				Subject = "网站测试邮件",
-				Tos = to
-			}.Send();
-			return ResultData(null, true, "测试邮件发送成功,网站邮件配置正确!");
-		}
-		catch (Exception e)
-		{
-			return ResultData(null, false, "邮件配置测试失败!错误信息:\r\n" + e.Message + "\r\n\r\n详细堆栈跟踪:\r\n" + e.StackTrace);
-		}
-	}
-
-	/// <summary>
-	/// 发送一封系统邮件
-	/// </summary>
-	/// <param name="tos"></param>
-	/// <param name="title"></param>
-	/// <param name="content"></param>
-	/// <returns></returns>
-	public ActionResult SendMail([Required(ErrorMessage = "收件人不能为空"), FromBodyOrDefault] string tos, [Required(ErrorMessage = "邮件标题不能为空"), FromBodyOrDefault] string title, [Required(ErrorMessage = "邮件内容不能为空"), FromBodyOrDefault] string content)
-	{
-		BackgroundJob.Enqueue<IMailSender>(sender => sender.Send(title, content + "<p style=\"color: red\">本邮件由系统自动发出,请勿回复本邮件!</p>", tos, "127.0.0.1"));
-		return Ok();
-	}
-
-	/// <summary>
-	/// 路径测试
-	/// </summary>
-	/// <param name="path"></param>
-	/// <returns></returns>
-	public ActionResult PathTest([FromBodyOrDefault] string path)
-	{
-		if (!(path.EndsWith("/") || path.EndsWith("\\")))
-		{
-			return ResultData(null, false, "路径不存在");
-		}
-
-		if (path.Equals("/") || path.Equals("\\"))
-		{
-			return ResultData(null, true, "根路径正确");
-		}
-
-		try
-		{
-			bool b = Directory.Exists(path);
-			return ResultData(null, b, b ? "根路径正确" : "路径不存在");
-		}
-		catch (Exception e)
-		{
-			LogManager.Error(GetType(), e.Demystify());
-			return ResultData(null, false, "路径格式不正确!错误信息:\r\n" + e.Message + "\r\n\r\n详细堆栈跟踪:\r\n" + e.StackTrace);
-		}
-	}
-
-	/// <summary>
-	/// 发件箱记录
-	/// </summary>
-	/// <returns></returns>
-	public ActionResult<PooledList<JObject>> SendBox()
-	{
-		return RedisHelper.SUnion(RedisHelper.Keys("Email:*")).Select(JObject.Parse).OrderByDescending(o => o["time"]).ToPooledListScope();
-	}
-
-	public ActionResult BounceEmail([FromServices] IMailSender mailSender, [FromBodyOrDefault] string email)
-	{
-		var msg = mailSender.AddRecipient(email);
-		return Ok(new
-		{
-			msg
-		});
-	}
-
-	#region 网站防火墙
-
-	/// <summary>
-	/// 获取全局IP黑名单
-	/// </summary>
-	/// <returns></returns>
-	public ActionResult IpBlackList()
-	{
-		return ResultData(CommonHelper.DenyIP);
-	}
-
-	/// <summary>
-	/// 获取IP地址段黑名单
-	/// </summary>
-	/// <returns></returns>
-	public ActionResult GetIPRangeBlackList()
-	{
-		return ResultData(new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "DenyIPRange.txt")).ShareReadWrite().ReadAllText(Encoding.UTF8));
-	}
-
-	/// <summary>
-	/// 设置IP地址段黑名单
-	/// </summary>
-	/// <returns></returns>
-	public async Task<ActionResult> SetIPRangeBlackList([FromBodyOrDefault] string content)
-	{
-		var file = new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "DenyIPRange.txt")).ShareReadWrite();
-		await file.WriteAllTextAsync(content, Encoding.UTF8, false);
-		CommonHelper.DenyIPRange.Clear();
-		var lines = (await file.ReadAllLinesAsync(Encoding.UTF8)).Where(s => s.Split(' ').Length > 2);
-		foreach (var line in lines)
-		{
-			try
-			{
-				var strs = line.Split(' ');
-				CommonHelper.DenyIPRange[strs[0]] = strs[1];
-			}
-			catch (IndexOutOfRangeException)
-			{
-			}
-		}
-
-		return ResultData(null);
-	}
-
-	/// <summary>
-	/// 全局IP白名单
-	/// </summary>
-	/// <returns></returns>
-	public ActionResult IpWhiteList()
-	{
-		return ResultData(new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "whitelist.txt")).ShareReadWrite().ReadAllText(Encoding.UTF8));
-	}
-
-	/// <summary>
-	/// 设置IP黑名单
-	/// </summary>
-	/// <param name="content"></param>
-	/// <returns></returns>
-	public async Task<ActionResult> SetIpBlackList([FromBodyOrDefault] string content)
-	{
-		CommonHelper.DenyIP = content + "";
-		await new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "denyip.txt")).ShareReadWrite().WriteAllTextAsync(CommonHelper.DenyIP, Encoding.UTF8);
-		return ResultData(null);
-	}
-
-	/// <summary>
-	/// 设置IP白名单
-	/// </summary>
-	/// <param name="content"></param>
-	/// <returns></returns>
-	public async Task<ActionResult> SetIpWhiteList([FromBodyOrDefault] string content)
-	{
-		await new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "whitelist.txt")).ShareReadWrite().WriteAllTextAsync(content, Encoding.UTF8);
-		CommonHelper.IPWhiteList.Add(content);
-		return ResultData(null);
-	}
-
-	/// <summary>
-	/// 获取拦截日志
-	/// </summary>
-	/// <returns></returns>
-	public ActionResult InterceptLog()
-	{
-		var list = RedisHelper.LRange<IpIntercepter>("intercept", 0, -1);
-		return ResultData(new
-		{
-			interceptCount = RedisHelper.Get("interceptCount"),
-			list,
-			ranking = list.GroupBy(i => i.IP).Where(g => g.Count() > 1).Select(g =>
-			{
-				var start = g.Min(t => t.Time);
-				var end = g.Max(t => t.Time);
-				return new
-				{
-					g.Key,
-					g.First().Address,
-					Start = start,
-					End = end,
-					Continue = start.GetDiffTime(end),
-					Count = g.Count()
-				};
-			}).OrderByDescending(a => a.Count).Take(30)
-		});
-	}
-
-	/// <summary>
-	/// 清除拦截日志
-	/// </summary>
-	/// <returns></returns>
-	public ActionResult ClearInterceptLog()
-	{
-		bool b = RedisHelper.Del("intercept") > 0;
-		return ResultData(null, b, b ? "拦截日志清除成功!" : "拦截日志清除失败!");
-	}
-
-	/// <summary>
-	/// 将IP添加到白名单
-	/// </summary>
-	/// <param name="ip"></param>
-	/// <returns></returns>
-	public async Task<ActionResult> AddToWhiteList([FromBodyOrDefault] string ip)
-	{
-		if (!ip.MatchInetAddress())
-		{
-			return ResultData(null, false);
-		}
-
-		var fs = new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "whitelist.txt")).ShareReadWrite();
-		string ips = await fs.ReadAllTextAsync(Encoding.UTF8, false);
-		var list = ips.Split(',').Where(s => !string.IsNullOrEmpty(s)).ToList();
-		list.Add(ip);
-		await fs.WriteAllTextAsync(string.Join(",", list.Distinct()), Encoding.UTF8);
-		CommonHelper.IPWhiteList = list;
-		return ResultData(null);
-	}
-
-	/// <summary>
-	/// 将IP添加到黑名单
-	/// </summary>
-	/// <param name="firewallRepoter"></param>
-	/// <param name="ip"></param>
-	/// <returns></returns>
-	public async Task<ActionResult> AddToBlackList([FromServices] IFirewallRepoter firewallRepoter, [FromBodyOrDefault] string ip)
-	{
-		if (!ip.MatchInetAddress())
-		{
-			return ResultData(null, false);
-		}
-
-		CommonHelper.DenyIP += "," + ip;
-		var basedir = AppDomain.CurrentDomain.BaseDirectory;
-		await new FileInfo(Path.Combine(basedir, "App_Data", "denyip.txt")).ShareReadWrite().WriteAllTextAsync(CommonHelper.DenyIP, Encoding.UTF8);
-		CommonHelper.IPWhiteList.Remove(ip);
-		await new FileInfo(Path.Combine(basedir, "App_Data", "whitelist.txt")).ShareReadWrite().WriteAllTextAsync(string.Join(",", CommonHelper.IPWhiteList.Distinct()), Encoding.UTF8);
-		await firewallRepoter.ReportAsync(IPAddress.Parse(ip));
-		return ResultData(null);
-	}
-
-	#endregion 网站防火墙
-}
+    /// <summary>
+    /// 系统设置
+    /// </summary>
+    public ISystemSettingService SystemSettingService { get; set; }
+
+    public IPerfCounter PerfCounter { get; set; }
+
+    public ActionResult GetServers()
+    {
+        var servers = PerfCounter.CreateDataSource().Select(c => c.ServerIP).Distinct().ToArray();
+        return Ok(servers);
+    }
+
+    /// <summary>
+    /// 获取历史性能计数器
+    /// </summary>
+    /// <returns></returns>
+    public IActionResult GetCounterHistory(string ip = null)
+    {
+        ip = ip.IfNullOrEmpty(() => SystemInfo.GetLocalUsedIP(AddressFamily.InterNetwork).ToString());
+        var time = DateTime.Now.AddDays(-15).GetTotalMilliseconds();
+        var counters = PerfCounter.CreateDataSource().Where(c => c.ServerIP == ip && c.Time >= time);
+        var count = counters.Count();
+        var ticks = count switch
+        {
+            <= 5000 => count,
+            > 5000 and <= 10000 => 3,
+            > 10000 and <= 20000 => 6,
+            > 20000 and <= 50000 => 12,
+            > 50000 and <= 100000 => 24,
+            > 100000 and <= 200000 => 48,
+            _ => 72
+        } * 10000;
+
+        var list = count < 5000 ? counters.OrderBy(c => c.Time).ToPooledListScope() : counters.GroupBy(c => c.Time / ticks).Select(g => new PerformanceCounter
+        {
+            Time = g.Key * ticks,
+            CpuLoad = g.Average(c => c.CpuLoad),
+            DiskRead = g.Average(c => c.DiskRead),
+            DiskWrite = g.Average(c => c.DiskWrite),
+            Download = g.Average(c => c.Download),
+            Upload = g.Average(c => c.Upload),
+            MemoryUsage = g.Average(c => c.MemoryUsage)
+        }).OrderBy(c => c.Time).ToPooledListScope();
+        return Ok(new
+        {
+            cpu = list.Select(c => new[]
+            {
+                c.Time,
+                c.CpuLoad.ToDecimal(2)
+            }),
+            mem = list.Select(c => new[]
+            {
+                c.Time,
+                c.MemoryUsage.ToDecimal(2)
+            }),
+            read = list.Select(c => new[]
+            {
+                c.Time,
+                c.DiskRead.ToDecimal(2)
+            }),
+            write = list.Select(c => new[]
+            {
+                c.Time,
+                c.DiskWrite.ToDecimal(2)
+            }),
+            down = list.Select(c => new[]
+            {
+                c.Time,
+                c.Download.ToDecimal(2)
+            }),
+            up = list.Select(c => new[]
+            {
+                c.Time,
+                c.Upload.ToDecimal(2)
+            })
+        });
+    }
+
+    /// <summary>
+    /// 获取设置信息
+    /// </summary>
+    /// <returns></returns>
+    public ActionResult GetSettings()
+    {
+        var list = SystemSettingService.GetAll().Select(s => new
+        {
+            s.Name,
+            s.Value
+        }).ToPooledListScope();
+        return ResultData(list);
+    }
+
+    /// <summary>
+    /// 保存设置
+    /// </summary>
+    /// <param name="settings"></param>
+    /// <returns></returns>
+    public async Task<ActionResult> Save([FromBody] List<SystemSetting> settings)
+    {
+        var b = await SystemSettingService.AddOrUpdateSavedAsync(s => s.Name, settings) > 0;
+        var dic = settings.ToDictionary(s => s.Name, s => s.Value); //同步设置
+        foreach (var (key, value) in dic)
+        {
+            CommonHelper.SystemSettings[key] = value;
+        }
+
+        return ResultData(null, b, b ? "设置保存成功!" : "设置保存失败!");
+    }
+
+    /// <summary>
+    /// 获取状态
+    /// </summary>
+    /// <returns></returns>
+    public ActionResult GetStatus()
+    {
+        Array array = Enum.GetValues(typeof(Status));
+        var list = new List<object>();
+        foreach (Enum e in array)
+        {
+            list.Add(new
+            {
+                e,
+                name = e.GetDisplay()
+            });
+        }
+
+        return ResultData(list);
+    }
+
+    /// <summary>
+    /// 邮件测试
+    /// </summary>
+    /// <param name="smtp"></param>
+    /// <param name="user"></param>
+    /// <param name="pwd"></param>
+    /// <param name="port"></param>
+    /// <param name="to"></param>
+    /// <returns></returns>
+    public ActionResult MailTest([FromBodyOrDefault] string smtp, [FromBodyOrDefault] string user, [FromBodyOrDefault] string pwd, [FromBodyOrDefault] int port, [FromBodyOrDefault] string to, [FromBodyOrDefault] bool ssl)
+    {
+        try
+        {
+            new Email()
+            {
+                EnableSsl = ssl,
+                Body = "发送成功,网站邮件配置正确!",
+                SmtpServer = smtp,
+                Username = user,
+                Password = pwd,
+                SmtpPort = port,
+                Subject = "网站测试邮件",
+                Tos = to
+            }.Send();
+            return ResultData(null, true, "测试邮件发送成功,网站邮件配置正确!");
+        }
+        catch (Exception e)
+        {
+            return ResultData(null, false, "邮件配置测试失败!错误信息:\r\n" + e.Message + "\r\n\r\n详细堆栈跟踪:\r\n" + e.StackTrace);
+        }
+    }
+
+    /// <summary>
+    /// 发送一封系统邮件
+    /// </summary>
+    /// <param name="tos"></param>
+    /// <param name="title"></param>
+    /// <param name="content"></param>
+    /// <returns></returns>
+    public ActionResult SendMail([Required(ErrorMessage = "收件人不能为空"), FromBodyOrDefault] string tos, [Required(ErrorMessage = "邮件标题不能为空"), FromBodyOrDefault] string title, [Required(ErrorMessage = "邮件内容不能为空"), FromBodyOrDefault] string content)
+    {
+        BackgroundJob.Enqueue<IMailSender>(sender => sender.Send(title, content + "<p style=\"color: red\">本邮件由系统自动发出,请勿回复本邮件!</p>", tos, "127.0.0.1"));
+        return Ok();
+    }
+
+    /// <summary>
+    /// 路径测试
+    /// </summary>
+    /// <param name="path"></param>
+    /// <returns></returns>
+    public ActionResult PathTest([FromBodyOrDefault] string path)
+    {
+        if (!(path.EndsWith("/") || path.EndsWith("\\")))
+        {
+            return ResultData(null, false, "路径不存在");
+        }
+
+        if (path.Equals("/") || path.Equals("\\"))
+        {
+            return ResultData(null, true, "根路径正确");
+        }
+
+        try
+        {
+            bool b = Directory.Exists(path);
+            return ResultData(null, b, b ? "根路径正确" : "路径不存在");
+        }
+        catch (Exception e)
+        {
+            LogManager.Error(GetType(), e.Demystify());
+            return ResultData(null, false, "路径格式不正确!错误信息:\r\n" + e.Message + "\r\n\r\n详细堆栈跟踪:\r\n" + e.StackTrace);
+        }
+    }
+
+    /// <summary>
+    /// 发件箱记录
+    /// </summary>
+    /// <returns></returns>
+    public ActionResult<PooledList<JObject>> SendBox()
+    {
+        return RedisHelper.SUnion(RedisHelper.Keys("Email:*")).Select(JObject.Parse).OrderByDescending(o => o["time"]).ToPooledListScope();
+    }
+
+    public ActionResult BounceEmail([FromServices] IMailSender mailSender, [FromBodyOrDefault] string email)
+    {
+        var msg = mailSender.AddRecipient(email);
+        return Ok(new
+        {
+            msg
+        });
+    }
+
+    #region 网站防火墙
+
+    /// <summary>
+    /// 获取全局IP黑名单
+    /// </summary>
+    /// <returns></returns>
+    public ActionResult IpBlackList()
+    {
+        return ResultData(CommonHelper.DenyIP);
+    }
+
+    /// <summary>
+    /// 获取IP地址段黑名单
+    /// </summary>
+    /// <returns></returns>
+    public ActionResult GetIPRangeBlackList()
+    {
+        return ResultData(new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "DenyIPRange.txt")).ShareReadWrite().ReadAllText(Encoding.UTF8));
+    }
+
+    /// <summary>
+    /// 设置IP地址段黑名单
+    /// </summary>
+    /// <returns></returns>
+    public async Task<ActionResult> SetIPRangeBlackList([FromBodyOrDefault] string content)
+    {
+        var file = new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "DenyIPRange.txt")).ShareReadWrite();
+        await file.WriteAllTextAsync(content, Encoding.UTF8, false);
+        CommonHelper.DenyIPRange.Clear();
+        var lines = (await file.ReadAllLinesAsync(Encoding.UTF8)).Where(s => s.Split(' ').Length > 2);
+        foreach (var line in lines)
+        {
+            try
+            {
+                var strs = line.Split(' ');
+                CommonHelper.DenyIPRange[strs[0]] = strs[1];
+            }
+            catch (IndexOutOfRangeException)
+            {
+            }
+        }
+
+        return ResultData(null);
+    }
+
+    /// <summary>
+    /// 全局IP白名单
+    /// </summary>
+    /// <returns></returns>
+    public ActionResult IpWhiteList()
+    {
+        return ResultData(new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "whitelist.txt")).ShareReadWrite().ReadAllText(Encoding.UTF8));
+    }
+
+    /// <summary>
+    /// 设置IP黑名单
+    /// </summary>
+    /// <param name="content"></param>
+    /// <returns></returns>
+    public async Task<ActionResult> SetIpBlackList([FromBodyOrDefault] string content)
+    {
+        CommonHelper.DenyIP = content + "";
+        await new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "denyip.txt")).ShareReadWrite().WriteAllTextAsync(CommonHelper.DenyIP, Encoding.UTF8);
+        return ResultData(null);
+    }
+
+    /// <summary>
+    /// 设置IP白名单
+    /// </summary>
+    /// <param name="content"></param>
+    /// <returns></returns>
+    public async Task<ActionResult> SetIpWhiteList([FromBodyOrDefault] string content)
+    {
+        await new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "whitelist.txt")).ShareReadWrite().WriteAllTextAsync(content, Encoding.UTF8);
+        CommonHelper.IPWhiteList.Add(content);
+        return ResultData(null);
+    }
+
+    /// <summary>
+    /// 获取拦截日志
+    /// </summary>
+    /// <returns></returns>
+    public ActionResult InterceptLog()
+    {
+        var list = RedisHelper.LRange<IpIntercepter>("intercept", 0, -1);
+        return ResultData(new
+        {
+            interceptCount = RedisHelper.Get("interceptCount"),
+            list,
+            ranking = list.GroupBy(i => i.IP).Where(g => g.Count() > 1).Select(g =>
+            {
+                var start = g.Min(t => t.Time);
+                var end = g.Max(t => t.Time);
+                return new
+                {
+                    g.Key,
+                    g.First().Address,
+                    Start = start,
+                    End = end,
+                    Continue = start.GetDiffTime(end),
+                    Count = g.Count()
+                };
+            }).OrderByDescending(a => a.Count).Take(30)
+        });
+    }
+
+    /// <summary>
+    /// 清除拦截日志
+    /// </summary>
+    /// <returns></returns>
+    public ActionResult ClearInterceptLog()
+    {
+        bool b = RedisHelper.Del("intercept") > 0;
+        return ResultData(null, b, b ? "拦截日志清除成功!" : "拦截日志清除失败!");
+    }
+
+    /// <summary>
+    /// 将IP添加到白名单
+    /// </summary>
+    /// <param name="ip"></param>
+    /// <returns></returns>
+    public async Task<ActionResult> AddToWhiteList([FromBodyOrDefault] string ip)
+    {
+        if (!ip.MatchInetAddress())
+        {
+            return ResultData(null, false);
+        }
+
+        var fs = new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "whitelist.txt")).ShareReadWrite();
+        string ips = await fs.ReadAllTextAsync(Encoding.UTF8, false);
+        var list = ips.Split(',').Where(s => !string.IsNullOrEmpty(s)).ToList();
+        list.Add(ip);
+        await fs.WriteAllTextAsync(string.Join(",", list.Distinct()), Encoding.UTF8);
+        CommonHelper.IPWhiteList = list;
+        return ResultData(null);
+    }
+
+    /// <summary>
+    /// 将IP添加到黑名单
+    /// </summary>
+    /// <param name="firewallRepoter"></param>
+    /// <param name="ip"></param>
+    /// <returns></returns>
+    public async Task<ActionResult> AddToBlackList([FromServices] IFirewallRepoter firewallRepoter, [FromBodyOrDefault] string ip)
+    {
+        if (!ip.MatchInetAddress())
+        {
+            return ResultData(null, false);
+        }
+
+        CommonHelper.DenyIP += "," + ip;
+        var basedir = AppDomain.CurrentDomain.BaseDirectory;
+        await new FileInfo(Path.Combine(basedir, "App_Data", "denyip.txt")).ShareReadWrite().WriteAllTextAsync(CommonHelper.DenyIP, Encoding.UTF8);
+        CommonHelper.IPWhiteList.Remove(ip);
+        await new FileInfo(Path.Combine(basedir, "App_Data", "whitelist.txt")).ShareReadWrite().WriteAllTextAsync(string.Join(",", CommonHelper.IPWhiteList.Distinct()), Encoding.UTF8);
+        await firewallRepoter.ReportAsync(IPAddress.Parse(ip));
+        return ResultData(null);
+    }
+
+    #endregion 网站防火墙
+}

+ 38 - 38
src/Masuit.MyBlogs.Core/Program.cs

@@ -10,64 +10,64 @@ using Z.EntityFramework.Plus;
 
 QueryCacheManager.DefaultMemoryCacheEntryOptions = new MemoryCacheEntryOptions()
 {
-	AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
+    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
 };
 AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
 
 if (Environment.OSVersion.Platform is not (PlatformID.MacOSX or PlatformID.Unix))
 {
-	// 设置相关进程优先级为高于正常,防止其他进程影响应用程序的运行性能
-	Process.GetProcessesByName("mysqld").ForEach(p => p.PriorityClass = ProcessPriorityClass.AboveNormal);
-	Process.GetProcessesByName("pg_ctl").ForEach(p => p.PriorityClass = ProcessPriorityClass.AboveNormal);
-	Process.GetProcessesByName("postgres").ForEach(p => p.PriorityClass = ProcessPriorityClass.AboveNormal);
-	Process.GetProcessesByName("redis-server").ForEach(p => p.PriorityClass = ProcessPriorityClass.AboveNormal);
-	Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.AboveNormal;
+    // 设置相关进程优先级为高于正常,防止其他进程影响应用程序的运行性能
+    Process.GetProcessesByName("mysqld").ForEach(p => p.PriorityClass = ProcessPriorityClass.AboveNormal);
+    Process.GetProcessesByName("pg_ctl").ForEach(p => p.PriorityClass = ProcessPriorityClass.AboveNormal);
+    Process.GetProcessesByName("postgres").ForEach(p => p.PriorityClass = ProcessPriorityClass.AboveNormal);
+    Process.GetProcessesByName("redis-server").ForEach(p => p.PriorityClass = ProcessPriorityClass.AboveNormal);
+    Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.AboveNormal;
 }
 
 // 确保IP数据库正常
 if (!"223.5.5.5".GetIPLocation().Contains("阿里"))
 {
-	throw new Exception("IP地址库初始化失败,请重启应用!");
+    throw new Exception("IP地址库初始化失败,请重启应用!");
 }
 
 InitOneDrive(); // 初始化Onedrive程序
 Host.CreateDefaultBuilder(args).UseServiceProviderFactory(new AutofacServiceProviderFactory()).ConfigureWebHostDefaults(hostBuilder => hostBuilder.UseKestrel(opt =>
 {
-	var config = opt.ApplicationServices.GetService<IConfiguration>();
-	var port = config["Port"] ?? "5000";
-	var sslport = config["Https:Port"] ?? "5001";
-	opt.ListenAnyIP(port.ToInt32(), options => options.Protocols = HttpProtocols.Http1AndHttp2AndHttp3);
-	if (bool.Parse(config["Https:Enabled"]))
-	{
-		opt.ListenAnyIP(sslport.ToInt32(), s =>
-		{
-			if (Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Major >= 10)
-			{
-				s.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
-			}
+    var config = opt.ApplicationServices.GetService<IConfiguration>();
+    var port = config["Port"] ?? "5000";
+    var sslport = config["Https:Port"] ?? "5001";
+    opt.ListenAnyIP(port.ToInt32(), options => options.Protocols = HttpProtocols.Http1AndHttp2AndHttp3);
+    if (bool.Parse(config["Https:Enabled"]))
+    {
+        opt.ListenAnyIP(sslport.ToInt32(), s =>
+        {
+            if (Environment.OSVersion is { Platform: PlatformID.Win32NT, Version.Major: >= 10 })
+            {
+                s.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
+            }
 
-			s.UseHttps(AppContext.BaseDirectory + config["Https:CertPath"], config["Https:CertPassword"]);
-		});
-	}
+            s.UseHttps(AppContext.BaseDirectory + config["Https:CertPath"], config["Https:CertPassword"]);
+        });
+    }
 
-	opt.Limits.MaxRequestBodySize = null;
-	Console.WriteLine($"应用程序监听端口:http:{port},https:{sslport}");
+    opt.Limits.MaxRequestBodySize = null;
+    Console.WriteLine($"应用程序监听端口:http:{port},https:{sslport}");
 }).UseStartup<Startup>()).Build().Run();
 
 static void InitOneDrive()
 {
-	//初始化
-	if (!File.Exists(Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "OneDrive.db")))
-	{
-		File.Copy(Path.Combine("App_Data", "OneDrive.template.db"), Path.Combine("App_Data", "OneDrive.db"));
-		Console.WriteLine("数据库创建成功");
-	}
+    //初始化
+    if (!File.Exists(Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "OneDrive.db")))
+    {
+        File.Copy(Path.Combine("App_Data", "OneDrive.template.db"), Path.Combine("App_Data", "OneDrive.db"));
+        Console.WriteLine("数据库创建成功");
+    }
 
-	using var settingService = new SettingService(new DriveContext());
-	if (settingService.Get("IsInit") != "true")
-	{
-		settingService.Set("IsInit", "true").Wait();
-		Console.WriteLine("数据初始化成功");
-		Console.WriteLine($"请登录 {OneDriveConfiguration.BaseUri}/#/admin 进行身份及其他配置");
-	}
+    using var settingService = new SettingService(new DriveContext());
+    if (settingService.Get("IsInit") != "true")
+    {
+        settingService.Set("IsInit", "true").Wait();
+        Console.WriteLine("数据初始化成功");
+        Console.WriteLine($"请登录 {OneDriveConfiguration.BaseUri}/#/admin 进行身份及其他配置");
+    }
 }

+ 133 - 134
src/Masuit.MyBlogs.Core/Startup.cs

@@ -22,150 +22,149 @@ using Newtonsoft.Json;
 using SixLabors.ImageSharp.Web.DependencyInjection;
 using System.Text.RegularExpressions;
 
-namespace Masuit.MyBlogs.Core
+namespace Masuit.MyBlogs.Core;
+
+/// <summary>
+/// asp.net core核心配置
+/// </summary>
+public class Startup
 {
-	/// <summary>
-	/// asp.net core核心配置
-	/// </summary>
-	public class Startup
-	{
-		/// <summary>
-		/// 配置中心
-		/// </summary>
-		public IConfiguration Configuration { get; set; }
+    /// <summary>
+    /// 配置中心
+    /// </summary>
+    public IConfiguration Configuration { get; set; }
 
-		private readonly IWebHostEnvironment _env;
+    private readonly IWebHostEnvironment _env;
 
-		/// <summary>
-		/// asp.net core核心配置
-		/// </summary>
-		/// <param name="configuration"></param>
-		public Startup(IConfiguration configuration, IWebHostEnvironment env)
-		{
-			_env = env;
+    /// <summary>
+    /// asp.net core核心配置
+    /// </summary>
+    /// <param name="configuration"></param>
+    public Startup(IConfiguration configuration, IWebHostEnvironment env)
+    {
+        _env = env;
 
-			void BindConfig()
-			{
-				Configuration = configuration;
-				AppConfig.ConnString = configuration["Database:" + nameof(AppConfig.ConnString)];
-				AppConfig.BaiduAK = configuration[nameof(AppConfig.BaiduAK)];
-				AppConfig.Redis = configuration[nameof(AppConfig.Redis)];
-				AppConfig.TrueClientIPHeader = configuration[nameof(AppConfig.TrueClientIPHeader)] ?? "CF-Connecting-IP";
-				AppConfig.EnableIPDirect = bool.Parse(configuration[nameof(AppConfig.EnableIPDirect)] ?? "false");
-				configuration.Bind("Imgbed:Gitlabs", AppConfig.GitlabConfigs);
-				configuration.AddToMasuitTools();
-			}
+        void BindConfig()
+        {
+            Configuration = configuration;
+            AppConfig.ConnString = configuration["Database:" + nameof(AppConfig.ConnString)];
+            AppConfig.BaiduAK = configuration[nameof(AppConfig.BaiduAK)];
+            AppConfig.Redis = configuration[nameof(AppConfig.Redis)];
+            AppConfig.TrueClientIPHeader = configuration[nameof(AppConfig.TrueClientIPHeader)] ?? "CF-Connecting-IP";
+            AppConfig.EnableIPDirect = bool.Parse(configuration[nameof(AppConfig.EnableIPDirect)] ?? "false");
+            configuration.Bind("Imgbed:Gitlabs", AppConfig.GitlabConfigs);
+            configuration.AddToMasuitTools();
+        }
 
-			ChangeToken.OnChange(configuration.GetReloadToken, BindConfig);
-			BindConfig();
-		}
+        ChangeToken.OnChange(configuration.GetReloadToken, BindConfig);
+        BindConfig();
+    }
 
-		/// <summary>
-		/// ConfigureServices
-		/// </summary>
-		/// <param name="services"></param>
-		/// <returns></returns>
-		public void ConfigureServices(IServiceCollection services)
-		{
-			services.AddDbContext<DataContext>(opt => opt.UseNpgsql(AppConfig.ConnString, builder => builder.EnableRetryOnFailure(10)).EnableSensitiveDataLogging()); //配置数据库
-			services.AddDbContext<LoggerDbContext>(opt => opt.UseNpgsql(AppConfig.ConnString)); //配置数据库
-			services.ConfigureOptions();
-			services.AddHttpsRedirection(options =>
-			{
-				options.RedirectStatusCode = StatusCodes.Status301MovedPermanently;
-			});
-			services.AddSession().AddAntiforgery(); //注入Session
-			services.AddResponseCache().AddCacheConfig();
-			services.AddHangfireServer().AddHangfire((serviceProvider, configuration) =>
-			{
-				configuration.UseActivator(new HangfireActivator(serviceProvider));
-				configuration.UseFilter(new AutomaticRetryAttribute());
-				configuration.UseMemoryStorage();
-			}); //配置hangfire
+    /// <summary>
+    /// ConfigureServices
+    /// </summary>
+    /// <param name="services"></param>
+    /// <returns></returns>
+    public void ConfigureServices(IServiceCollection services)
+    {
+        services.AddDbContext<DataContext>(opt => opt.UseNpgsql(AppConfig.ConnString, builder => builder.EnableRetryOnFailure(10)).EnableSensitiveDataLogging()); //配置数据库
+        services.AddDbContext<LoggerDbContext>(opt => opt.UseNpgsql(AppConfig.ConnString)); //配置数据库
+        services.ConfigureOptions();
+        services.AddHttpsRedirection(options =>
+        {
+            options.RedirectStatusCode = StatusCodes.Status301MovedPermanently;
+        });
+        services.AddSession().AddAntiforgery(); //注入Session
+        services.AddResponseCache().AddCacheConfig();
+        services.AddHangfireServer().AddHangfire((serviceProvider, configuration) =>
+        {
+            configuration.UseActivator(new HangfireActivator(serviceProvider));
+            configuration.UseFilter(new AutomaticRetryAttribute());
+            configuration.UseMemoryStorage();
+        }); //配置hangfire
 
-			services.AddSevenZipCompressor().AddResumeFileResult().AddSearchEngine<DataContext>(new LuceneIndexerOptions()
-			{
-				Path = "lucene"
-			}); // 配置7z和断点续传和Redis和Lucene搜索引擎
+        services.AddSevenZipCompressor().AddResumeFileResult().AddSearchEngine<DataContext>(new LuceneIndexerOptions()
+        {
+            Path = "lucene"
+        }); // 配置7z和断点续传和Redis和Lucene搜索引擎
 
-			services.SetupHttpClients(Configuration);
-			services.AddMailSender(Configuration).AddFirewallReporter(Configuration).AddRequestLogger(Configuration).AddPerfCounterManager(Configuration);
-			services.AddBundling().UseDefaults(_env).UseNUglify().EnableMinification().EnableChangeDetection().EnableCacheHeader(TimeSpan.FromHours(1));
-			services.AddSingleton<IRedisClient>(new RedisClient(AppConfig.Redis)
-			{
-				Serialize = JsonConvert.SerializeObject,
-				Deserialize = JsonConvert.DeserializeObject
-			});
-			services.SetupMiniProfile();
-			services.AddSingleton<IMimeMapper, MimeMapper>(p => new MimeMapper());
-			services.AddOneDrive();
-			services.AutoRegisterServices();
-			services.AddRazorPages();
-			services.AddServerSideBlazor();
-			services.AddMapper().AddMyMvc().AddHealthChecks();
-			services.SetupImageSharp();
-			services.AddHttpContextAccessor();
-		}
+        services.SetupHttpClients(Configuration);
+        services.AddMailSender(Configuration).AddFirewallReporter(Configuration).AddRequestLogger(Configuration).AddPerfCounterManager(Configuration);
+        services.AddBundling().UseDefaults(_env).UseNUglify().EnableMinification().EnableChangeDetection().EnableCacheHeader(TimeSpan.FromHours(1));
+        services.AddSingleton<IRedisClient>(new RedisClient(AppConfig.Redis)
+        {
+            Serialize = JsonConvert.SerializeObject,
+            Deserialize = JsonConvert.DeserializeObject
+        });
+        services.SetupMiniProfile();
+        services.AddSingleton<IMimeMapper, MimeMapper>(p => new MimeMapper());
+        services.AddOneDrive();
+        services.AutoRegisterServices();
+        services.AddRazorPages();
+        services.AddServerSideBlazor();
+        services.AddMapper().AddMyMvc().AddHealthChecks();
+        services.SetupImageSharp();
+        services.AddHttpContextAccessor();
+    }
 
-		public void ConfigureContainer(ContainerBuilder builder)
-		{
-			builder.RegisterModule(new AutofacModule());
-		}
+    public void ConfigureContainer(ContainerBuilder builder)
+    {
+        builder.RegisterModule(new AutofacModule());
+    }
 
-		/// <summary>
-		/// Configure
-		/// </summary>
-		/// <param name="app"></param>
-		/// <param name="env"></param>
-		/// <param name="hangfire"></param>
-		/// <param name="luceneIndexerOptions"></param>
-		/// <param name="maindb"></param>
-		/// <param name="loggerdb"></param>
-		public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHangfireBackJob hangfire, LuceneIndexerOptions luceneIndexerOptions, DataContext maindb, LoggerDbContext loggerdb)
-		{
-			maindb.Database.EnsureCreated();
-			loggerdb.Database.EnsureCreated();
-			app.InitSettings();
-			app.UseDisposeScope();
-			app.UseLuceneSearch(env, hangfire, luceneIndexerOptions);
-			app.UseForwardedHeaders().UseCertificateForwarding(); // X-Forwarded-For
-			if (env.IsDevelopment())
-			{
-				app.UseDeveloperExceptionPage();
-			}
-			else
-			{
-				app.UseExceptionHandler("/ServiceUnavailable");
-			}
+    /// <summary>
+    /// Configure
+    /// </summary>
+    /// <param name="app"></param>
+    /// <param name="env"></param>
+    /// <param name="hangfire"></param>
+    /// <param name="luceneIndexerOptions"></param>
+    /// <param name="maindb"></param>
+    /// <param name="loggerdb"></param>
+    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHangfireBackJob hangfire, LuceneIndexerOptions luceneIndexerOptions, DataContext maindb, LoggerDbContext loggerdb)
+    {
+        maindb.Database.EnsureCreated();
+        loggerdb.Database.EnsureCreated();
+        app.InitSettings();
+        app.UseDisposeScope();
+        app.UseLuceneSearch(env, hangfire, luceneIndexerOptions);
+        app.UseForwardedHeaders().UseCertificateForwarding(); // X-Forwarded-For
+        if (env.IsDevelopment())
+        {
+            app.UseDeveloperExceptionPage();
+        }
+        else
+        {
+            app.UseExceptionHandler("/ServiceUnavailable");
+        }
 
-			app.UseBundles();
-			app.SetupHttpsRedirection(Configuration);
-			app.UseDefaultFiles().UseWhen(c => Regex.IsMatch(c.Request.Path.Value + "", @"(\.jpg|\.jpeg|\.png|\.bmp|\.webp|\.tiff|\.pbm)$", RegexOptions.IgnoreCase), builder => builder.UseImageSharp()).UseStaticFiles();
-			app.UseSession().UseCookiePolicy(); //注入Session
-			app.UseWhen(c => c.Session.Get<UserInfoDto>(SessionKey.UserInfo)?.IsAdmin == true, builder =>
-			{
-				builder.UseMiniProfiler();
-				builder.UseCLRStatsDashboard();
-			});
-			app.UseWhen(c => !c.Request.Path.StartsWithSegments("/_blazor"), builder => builder.UseMiddleware<RequestInterceptMiddleware>()); //启用网站请求拦截
-			app.SetupHangfire();
-			app.UseResponseCaching().UseResponseCompression(); //启动Response缓存
-			app.UseMiddleware<TranslateMiddleware>();
-			app.UseRouting().UseEndpoints(endpoints =>
-			{
-				endpoints.MapBlazorHub(options =>
-				{
-					options.ApplicationMaxBufferSize = 4194304;
-					options.LongPolling.PollTimeout = TimeSpan.FromSeconds(10);
-					options.TransportMaxBufferSize = 8388608;
-				});
-				endpoints.MapHealthChecks("/health");
-				endpoints.MapControllers(); // 属性路由
-				endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}"); // 默认路由
-				endpoints.MapFallbackToController("Index", "Error");
-			});
+        app.UseBundles();
+        app.SetupHttpsRedirection(Configuration);
+        app.UseDefaultFiles().UseWhen(c => Regex.IsMatch(c.Request.Path.Value + "", @"(\.jpg|\.jpeg|\.png|\.bmp|\.webp|\.tiff|\.pbm)$", RegexOptions.IgnoreCase), builder => builder.UseImageSharp()).UseStaticFiles();
+        app.UseSession().UseCookiePolicy(); //注入Session
+        app.UseWhen(c => c.Session.Get<UserInfoDto>(SessionKey.UserInfo)?.IsAdmin == true, builder =>
+        {
+            builder.UseMiniProfiler();
+            builder.UseCLRStatsDashboard();
+        });
+        app.UseWhen(c => !c.Request.Path.StartsWithSegments("/_blazor"), builder => builder.UseMiddleware<RequestInterceptMiddleware>()); //启用网站请求拦截
+        app.SetupHangfire();
+        app.UseResponseCaching().UseResponseCompression(); //启动Response缓存
+        app.UseMiddleware<TranslateMiddleware>();
+        app.UseRouting().UseEndpoints(endpoints =>
+        {
+            endpoints.MapBlazorHub(options =>
+            {
+                options.ApplicationMaxBufferSize = 4194304;
+                options.LongPolling.PollTimeout = TimeSpan.FromSeconds(10);
+                options.TransportMaxBufferSize = 8388608;
+            });
+            endpoints.MapHealthChecks("/health");
+            endpoints.MapControllers(); // 属性路由
+            endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}"); // 默认路由
+            endpoints.MapFallbackToController("Index", "Error");
+        });
 
-			Console.WriteLine("网站启动完成");
-		}
-	}
+        Console.WriteLine("网站启动完成");
+    }
 }

+ 45 - 38
src/Masuit.MyBlogs.Core/Views/Dashboard/Counter.razor

@@ -4,7 +4,9 @@
 @using Masuit.MyBlogs.Core.Common
 @using Masuit.Tools
 @using System.IO
+@using Masuit.Tools.Core.AspNetCore
 @using Masuit.Tools.Logging
+@using Masuit.Tools.Maths
 @using PerformanceCounter = Masuit.MyBlogs.Core.Common.PerformanceCounter
 @inject IPerfCounter PerfCounter;
 @inject IJSRuntime JS;
@@ -162,72 +164,77 @@
         Windows.ClearMemorySilent();
         JS.InvokeVoidAsync("showSuccess");
     }
-    
+
     public Dictionary<string, dynamic> GetCounterPercent()
     {
         var counters = PerfCounter.CreateDataSource().Where(c => c.ServerIP==ip).OrderByRandom().Take(5000).ToList();
-        var count = counters.Count();
+        var cpuLoads = counters.Select(c => c.CpuLoad).ToList();
+        var memUse = counters.Select(c => c.MemoryUsage).ToList();
+        var reads = counters.Select(c => c.DiskRead).ToList();
+        var writes = counters.Select(c => c.DiskWrite).ToList();
+        var downloads = counters.Select(c => c.Download).ToList();
+        var uploads = counters.Select(c => c.Upload).ToList();
         return new()
         {
             ["CPU使用率(%)"] = new
             {
-                p50 = counters.OrderByDescending(c => c.CpuLoad).Take(count / 2).MinOrDefault(c => c.CpuLoad).ToString("N2"),
-                p75 = counters.OrderByDescending(c => c.CpuLoad).Take(count / 4).MinOrDefault(c => c.CpuLoad).ToString("N2"),
-                p90 = counters.OrderByDescending(c => c.CpuLoad).Take(count / 10).MinOrDefault(c => c.CpuLoad).ToString("N2"),
-                p99 = counters.OrderByDescending(c => c.CpuLoad).Take(count / 100).MinOrDefault(c => c.CpuLoad).ToString("N2"),
-                p999 = counters.OrderByDescending(c => c.CpuLoad).Take(count / 1000).MinOrDefault(c => c.CpuLoad).ToString("N2"),
+                p50 = cpuLoads.Percentile(50).ToString("N2"),
+                p75 = cpuLoads.Percentile(75).ToString("N2"),
+                p90 = cpuLoads.Percentile(90).ToString("N2"),
+                p99 = cpuLoads.Percentile(99).ToString("N2"),
+                p999 = cpuLoads.Percentile(99.9).ToString("N2"),
                 average = counters.Average(c => c.CpuLoad).ToString("N2"),
-                stdev = counters.Select(c => c.CpuLoad).StandardDeviation().ToString("N2"),
+                stdev = cpuLoads.StandardDeviation().ToString("N2"),
             },
             ["内存使用率(%)"] = new
             {
-                p50 = counters.OrderByDescending(c => c.MemoryUsage).Take(count / 2).MinOrDefault(c => c.MemoryUsage).ToString("N2"),
-                p75 = counters.OrderByDescending(c => c.MemoryUsage).Take(count / 4).MinOrDefault(c => c.MemoryUsage).ToString("N2"),
-                p90 = counters.OrderByDescending(c => c.MemoryUsage).Take(count / 10).MinOrDefault(c => c.MemoryUsage).ToString("N2"),
-                p99 = counters.OrderByDescending(c => c.MemoryUsage).Take(count / 100).MinOrDefault(c => c.MemoryUsage).ToString("N2"),
-                p999 = counters.OrderByDescending(c => c.MemoryUsage).Take(count / 1000).MinOrDefault(c => c.MemoryUsage).ToString("N2"),
+                p50 = memUse.Percentile(50).ToString("N2"),
+                p75 = memUse.Percentile(75).ToString("N2"),
+                p90 = memUse.Percentile(90).ToString("N2"),
+                p99 = memUse.Percentile(99).ToString("N2"),
+                p999 = memUse.Percentile(99.9).ToString("N2"),
                 average = counters.Average(c => c.MemoryUsage).ToString("N2"),
-                stdev = counters.Select(c => c.MemoryUsage).StandardDeviation().ToString("N2"),
+                stdev = memUse.StandardDeviation().ToString("N2"),
             },
             ["磁盘读(KBps)"] = new
             {
-                p50 = counters.OrderByDescending(c => c.DiskRead).Take(count / 2).MinOrDefault(c => c.DiskRead).ToString("N2"),
-                p75 = counters.OrderByDescending(c => c.DiskRead).Take(count / 4).MinOrDefault(c => c.DiskRead).ToString("N2"),
-                p90 = counters.OrderByDescending(c => c.DiskRead).Take(count / 10).MinOrDefault(c => c.DiskRead).ToString("N2"),
-                p99 = counters.OrderByDescending(c => c.DiskRead).Take(count / 100).MinOrDefault(c => c.DiskRead).ToString("N2"),
-                p999 = counters.OrderByDescending(c => c.DiskRead).Take(count / 1000).MinOrDefault(c => c.DiskRead).ToString("N2"),
+                p50 = reads.Percentile(50).ToString("N2"),
+                p75 = reads.Percentile(75).ToString("N2"),
+                p90 = reads.Percentile(90).ToString("N2"),
+                p99 = reads.Percentile(99).ToString("N2"),
+                p999 = reads.Percentile(99.9).ToString("N2"),
                 average = counters.Average(c => c.DiskRead).ToString("N2"),
-                stdev = counters.Select(c => c.DiskRead).StandardDeviation().ToString("N2"),
+                stdev = reads.StandardDeviation().ToString("N2"),
             },
             ["磁盘写(KBps)"] = new
             {
-                p50 = counters.OrderByDescending(c => c.DiskWrite).Take(count / 2).MinOrDefault(c => c.DiskWrite).ToString("N2"),
-                p75 = counters.OrderByDescending(c => c.DiskWrite).Take(count / 4).MinOrDefault(c => c.DiskWrite).ToString("N2"),
-                p90 = counters.OrderByDescending(c => c.DiskWrite).Take(count / 10).MinOrDefault(c => c.DiskWrite).ToString("N2"),
-                p99 = counters.OrderByDescending(c => c.DiskWrite).Take(count / 100).MinOrDefault(c => c.DiskWrite).ToString("N2"),
-                p999 = counters.OrderByDescending(c => c.DiskWrite).Take(count / 1000).MinOrDefault(c => c.DiskWrite).ToString("N2"),
+                p50 = writes.Percentile(50).ToString("N2"),
+                p75 = writes.Percentile(75).ToString("N2"),
+                p90 = writes.Percentile(90).ToString("N2"),
+                p99 = writes.Percentile(99).ToString("N2"),
+                p999 = writes.Percentile(99.9).ToString("N2"),
                 average = counters.Average(c => c.DiskWrite).ToString("N2"),
-                stdev = counters.Select(c => c.DiskWrite).StandardDeviation().ToString("N2")
+                stdev = writes.StandardDeviation().ToString("N2")
             },
             ["网络下载(KBps)"] = new
             {
-                p50 = counters.OrderByDescending(c => c.Download).Take(count / 2).MinOrDefault(c => c.Download).ToString("N2"),
-                p75 = counters.OrderByDescending(c => c.Download).Take(count / 4).MinOrDefault(c => c.Download).ToString("N2"),
-                p90 = counters.OrderByDescending(c => c.Download).Take(count / 10).MinOrDefault(c => c.Download).ToString("N2"),
-                p99 = counters.OrderByDescending(c => c.Download).Take(count / 100).MinOrDefault(c => c.Download).ToString("N2"),
-                p999 = counters.OrderByDescending(c => c.Download).Take(count / 1000).MinOrDefault(c => c.Download).ToString("N2"),
+                p50 = downloads.Percentile(50).ToString("N2"),
+                p75 = downloads.Percentile(75).ToString("N2"),
+                p90 = downloads.Percentile(90).ToString("N2"),
+                p99 = downloads.Percentile(99).ToString("N2"),
+                p999 = downloads.Percentile(99.9).ToString("N2"),
                 average = counters.Average(c => c.Download).ToString("N2"),
-                stdev = counters.Select(c => c.Download).StandardDeviation().ToString("N2")
+                stdev = downloads.StandardDeviation().ToString("N2")
             },
             ["网络上行(KBps)"] = new
             {
-                p50 = counters.OrderByDescending(c => c.Upload).Take(count / 2).MinOrDefault(c => c.Upload).ToString("N2"),
-                p75 = counters.OrderByDescending(c => c.Upload).Take(count / 4).MinOrDefault(c => c.Upload).ToString("N2"),
-                p90 = counters.OrderByDescending(c => c.Upload).Take(count / 10).MinOrDefault(c => c.Upload).ToString("N2"),
-                p99 = counters.OrderByDescending(c => c.Upload).Take(count / 100).MinOrDefault(c => c.Upload).ToString("N2"),
-                p999 = counters.OrderByDescending(c => c.Upload).Take(count / 1000).MinOrDefault(c => c.Upload).ToString("N2"),
+                p50 = uploads.Percentile(50).ToString("N2"),
+                p75 = uploads.Percentile(75).ToString("N2"),
+                p90 = uploads.Percentile(90).ToString("N2"),
+                p99 = uploads.Percentile(99).ToString("N2"),
+                p999 = uploads.Percentile(99.9).ToString("N2"),
                 average = counters.Average(c => c.Upload).ToString("N2"),
-                stdev = counters.Select(c => c.Upload).StandardDeviation().ToString("N2")
+                stdev = uploads.StandardDeviation().ToString("N2")
             }
         };
     }