懒得勤快 1 vuosi sitten
vanhempi
sitoutus
e0ba33ba9a

+ 106 - 106
src/Masuit.MyBlogs.Core/Controllers/DashboardController.cs

@@ -12,117 +12,117 @@ namespace Masuit.MyBlogs.Core.Controllers;
 /// </summary>
 public sealed class DashboardController : AdminController
 {
-	/// <summary>
-	/// 控制面板
-	/// </summary>
-	/// <returns></returns>
-	[Route("dashboard"), ResponseCache(Duration = 60, VaryByHeader = "Cookie")]
-	public ActionResult Index()
-	{
-		Response.Cookies.Append("lang", "zh-cn", new CookieOptions()
-		{
-			Expires = DateTime.Now.AddYears(1),
-		});
-		return View();
-	}
+    /// <summary>
+    /// 控制面板
+    /// </summary>
+    /// <returns></returns>
+    [Route("dashboard"), ResponseCache(Duration = 60, VaryByHeader = "Cookie")]
+    public ActionResult Index()
+    {
+        Response.Cookies.Append("lang", "zh-cn", new CookieOptions()
+        {
+            Expires = DateTime.Now.AddYears(1),
+        });
+        return View();
+    }
 
-	[Route("counter")]
-	public ActionResult Counter()
-	{
-		return View();
-	}
+    [Route("counter")]
+    public ActionResult Counter()
+    {
+        return View();
+    }
 
-	/// <summary>
-	/// 获取站内消息
-	/// </summary>
-	/// <returns></returns>
-	public async Task<ActionResult> GetMessages([FromServices] IPostService postService, [FromServices] ILeaveMessageService leaveMessageService, [FromServices] ICommentService commentService, CancellationToken cancellationToken)
-	{
-		Response.ContentType = "text/event-stream";
-		while (true)
-		{
-			if (cancellationToken.IsCancellationRequested)
-			{
-				break;
-			}
-			await Response.WriteAsync($"event: message\n", cancellationToken);
-			var post = postService.GetQuery(p => p.Status == Status.Pending).Select(p => new
-			{
-				p.Id,
-				p.Title,
-				p.PostDate,
-				p.Author
-			}).ToPooledListScope();
-			var msgs = leaveMessageService.GetQuery(m => m.Status == Status.Pending).Select(p => new
-			{
-				p.Id,
-				p.PostDate,
-				p.NickName
-			}).ToPooledListScope();
-			var comments = commentService.GetQuery(c => c.Status == Status.Pending).Select(p => new
-			{
-				p.Id,
-				p.CommentDate,
-				p.PostId,
-				p.NickName
-			}).ToPooledListScope();
-			await Response.WriteAsync("data:" + new
-			{
-				post,
-				msgs,
-				comments
-			}.ToJsonString() + "\r\r");
-			await Response.Body.FlushAsync(cancellationToken);
-			await Task.Delay(5000, cancellationToken);
-		}
+    /// <summary>
+    /// 获取站内消息
+    /// </summary>
+    /// <returns></returns>
+    public async Task<ActionResult> GetMessages([FromServices] IPostService postService, [FromServices] ILeaveMessageService leaveMessageService, [FromServices] ICommentService commentService, CancellationToken cancellationToken)
+    {
+        Response.ContentType = "text/event-stream";
+        while (true)
+        {
+            if (cancellationToken.IsCancellationRequested)
+            {
+                break;
+            }
+            await Response.WriteAsync($"event: message\n", cancellationToken);
+            var post = postService.GetQuery(p => p.Status == Status.Pending).Select(p => new
+            {
+                p.Id,
+                p.Title,
+                p.PostDate,
+                p.Author
+            }).ToPooledListScope();
+            var msgs = leaveMessageService.GetQuery(m => m.Status == Status.Pending).Select(p => new
+            {
+                p.Id,
+                p.PostDate,
+                p.NickName
+            }).ToPooledListScope();
+            var comments = commentService.GetQuery(c => c.Status == Status.Pending).Select(p => new
+            {
+                p.Id,
+                p.CommentDate,
+                p.PostId,
+                p.NickName
+            }).ToPooledListScope();
+            await Response.WriteAsync("data:" + new
+            {
+                post,
+                msgs,
+                comments
+            }.ToJsonString() + "\r\r");
+            await Response.Body.FlushAsync(cancellationToken);
+            await Task.Delay(5000, cancellationToken);
+        }
 
-		Response.Body.Close();
-		return ResultData(null);
-	}
+        Response.Body.Close();
+        return Ok();
+    }
 
-	/// <summary>
-	/// 获取日志文件列表
-	/// </summary>
-	/// <returns></returns>
-	public ActionResult GetLogfiles()
-	{
-		var files = Directory.GetFiles(LogManager.LogDirectory).OrderByDescending(s => s).Select(Path.GetFileName).ToPooledListScope();
-		return ResultData(files);
-	}
+    /// <summary>
+    /// 获取日志文件列表
+    /// </summary>
+    /// <returns></returns>
+    public ActionResult GetLogfiles()
+    {
+        var files = Directory.GetFiles(LogManager.LogDirectory).OrderByDescending(s => s).Select(Path.GetFileName).ToPooledListScope();
+        return ResultData(files);
+    }
 
-	/// <summary>
-	/// 查看日志
-	/// </summary>
-	/// <param name="filename"></param>
-	/// <returns></returns>
-	public ActionResult Catlog([FromBodyOrDefault] string filename)
-	{
-		if (System.IO.File.Exists(Path.Combine(LogManager.LogDirectory, filename)))
-		{
-			string text = new FileInfo(Path.Combine(LogManager.LogDirectory, filename)).ShareReadWrite().ReadAllText(Encoding.UTF8);
-			return ResultData(text);
-		}
-		return ResultData(null, false, "文件不存在!");
-	}
+    /// <summary>
+    /// 查看日志
+    /// </summary>
+    /// <param name="filename"></param>
+    /// <returns></returns>
+    public ActionResult Catlog([FromBodyOrDefault] string filename)
+    {
+        if (System.IO.File.Exists(Path.Combine(LogManager.LogDirectory, filename)))
+        {
+            string text = new FileInfo(Path.Combine(LogManager.LogDirectory, filename)).ShareReadWrite().ReadAllText(Encoding.UTF8);
+            return ResultData(text);
+        }
+        return ResultData(null, false, "文件不存在!");
+    }
 
-	/// <summary>
-	/// 删除文件
-	/// </summary>
-	/// <param name="filename"></param>
-	/// <returns></returns>
-	public ActionResult DeleteFile([FromBodyOrDefault] string filename)
-	{
-		Policy.Handle<IOException>().WaitAndRetry(5, i => TimeSpan.FromSeconds(1)).Execute(() => System.IO.File.Delete(Path.Combine(LogManager.LogDirectory, filename)));
-		return ResultData(null, message: "文件删除成功!");
-	}
+    /// <summary>
+    /// 删除文件
+    /// </summary>
+    /// <param name="filename"></param>
+    /// <returns></returns>
+    public ActionResult DeleteFile([FromBodyOrDefault] string filename)
+    {
+        Policy.Handle<IOException>().WaitAndRetry(5, i => TimeSpan.FromSeconds(1)).Execute(() => System.IO.File.Delete(Path.Combine(LogManager.LogDirectory, filename)));
+        return ResultData(null, message: "文件删除成功!");
+    }
 
-	/// <summary>
-	/// 资源管理器
-	/// </summary>
-	/// <returns></returns>
-	[Route("filemanager"), ResponseCache(Duration = 60, VaryByHeader = "Cookie")]
-	public ActionResult FileManager()
-	{
-		return View();
-	}
+    /// <summary>
+    /// 资源管理器
+    /// </summary>
+    /// <returns></returns>
+    [Route("filemanager"), ResponseCache(Duration = 60, VaryByHeader = "Cookie")]
+    public ActionResult FileManager()
+    {
+        return View();
+    }
 }

+ 222 - 233
src/Masuit.MyBlogs.Core/Controllers/Drive/AdminController.cs

@@ -12,264 +12,253 @@ namespace Masuit.MyBlogs.Core.Controllers.Drive;
 [MyAuthorize]
 [ApiController]
 [Route("api/[controller]")]
-public sealed class AdminController : Controller
+public sealed class AdminController(IDriveAccountService driveAccount, SettingService setting, TokenService tokenService) : Controller
 {
-	private readonly IDriveAccountService _driveAccount;
-	private readonly SettingService _setting;
-	private readonly TokenService _tokenService;
-
-	public AdminController(IDriveAccountService driveAccount, SettingService setting, TokenService tokenService)
-	{
-		_driveAccount = driveAccount;
-		_setting = setting;
-		_tokenService = tokenService;
-	}
-
-	/// <summary>
+    /// <summary>
 	/// 重定向到 M$ 的 Oauth
 	/// </summary>
 	/// <returns></returns>
 	[AllowAnonymous]
-	[HttpGet("bind/url")]
-	public async Task<RedirectResult> RedirectToBinding()
-	{
-		string url = await _driveAccount.GetAuthorizationRequestUrl();
-		var result = new RedirectResult(url);
-		return result;
-	}
+    [HttpGet("bind/url")]
+    public async Task<RedirectResult> RedirectToBinding()
+    {
+        string url = await driveAccount.GetAuthorizationRequestUrl();
+        var result = new RedirectResult(url);
+        return result;
+    }
 
-	/// <summary>
-	/// 从 Oauth 重定向的url
-	/// </summary>
-	/// <returns></returns>
-	[AllowAnonymous]
-	[HttpGet("bind/new")]
-	public async Task<IActionResult> NewBinding(string code)
-	{
-		try
-		{
-			var result = await _tokenService.Authorize(code);
-			if (result.AccessToken != null)
-			{
-				await _setting.Set("AccountStatus", "已认证");
-				return Redirect("/#/admin");
-			}
-		}
-		catch (Exception ex)
-		{
-			return StatusCode(500, new ErrorResponse()
-			{
-				message = ex.Message
-			});
-		}
-		return StatusCode(500, new ErrorResponse()
-		{
-			message = "未知错误"
-		});
-	}
+    /// <summary>
+    /// 从 Oauth 重定向的url
+    /// </summary>
+    /// <returns></returns>
+    [AllowAnonymous]
+    [HttpGet("bind/new")]
+    public async Task<IActionResult> NewBinding(string code)
+    {
+        try
+        {
+            var result = await tokenService.Authorize(code);
+            if (result.AccessToken != null)
+            {
+                await setting.Set("AccountStatus", "已认证");
+                return Redirect("/#/admin");
+            }
+        }
+        catch (Exception ex)
+        {
+            return StatusCode(500, new ErrorResponse()
+            {
+                message = ex.Message
+            });
+        }
+        return StatusCode(500, new ErrorResponse()
+        {
+            message = "未知错误"
+        });
+    }
 
-	/// <summary>
-	/// 添加 SharePoint Site
-	/// </summary>
-	/// <returns></returns>
-	[HttpPost("sites")]
-	public async Task<IActionResult> AddSite(AddSiteModel model)
-	{
-		try
-		{
-			await _driveAccount.AddSiteId(model.siteName, model.nickName);
-		}
-		catch (Exception ex)
-		{
-			return StatusCode(500, new ErrorResponse()
-			{
-				message = ex.Message
-			});
-		}
-		return StatusCode(201);
-	}
+    /// <summary>
+    /// 添加 SharePoint Site
+    /// </summary>
+    /// <returns></returns>
+    [HttpPost("sites")]
+    public async Task<IActionResult> AddSite(AddSiteModel model)
+    {
+        try
+        {
+            await driveAccount.AddSiteId(model.siteName, model.nickName);
+        }
+        catch (Exception ex)
+        {
+            return StatusCode(500, new ErrorResponse()
+            {
+                message = ex.Message
+            });
+        }
+        return StatusCode(201);
+    }
 
-	/// <summary>
-	/// 获取基本内容
-	/// </summary>
-	/// <returns></returns>
-	[HttpGet("info")]
-	public async Task<IActionResult> GetInfo()
-	{
-		try
-		{
-			var driveInfo = new List<DriveAccountService.DriveInfo>();
-			if (_setting.Get("AccountStatus") == "已认证")
-			{
-				driveInfo = await _driveAccount.GetDriveInfo();
-			}
-			return Json(new
-			{
-				officeName = OneDriveConfiguration.AccountName,
-				officeType = Enum.GetName(typeof(OneDriveConfiguration.OfficeType), OneDriveConfiguration.Type),
-				driveInfo,
-				appName = _setting.Get("AppName"),
-				webName = _setting.Get("WebName"),
-				defaultDrive = _setting.Get("DefaultDrive"),
-				accountStatus = _setting.Get("AccountStatus"),
-				readme = _setting.Get("Readme"),
-				footer = _setting.Get("Footer"),
-				allowAnonymouslyUpload = !string.IsNullOrEmpty(_setting.Get("AllowAnonymouslyUpload")) && Convert.ToBoolean(_setting.Get("AllowAnonymouslyUpload")),
-				uploadPassword = _setting.Get("UploadPassword"),
-			}, new JsonSerializerSettings()
-			{
-				ContractResolver = new CamelCasePropertyNamesContractResolver()
-			});
-		}
-		catch (Exception ex)
-		{
-			return StatusCode(500, new ErrorResponse()
-			{
-				message = ex.Message
-			});
-		}
-	}
+    /// <summary>
+    /// 获取基本内容
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("info")]
+    public async Task<IActionResult> GetInfo()
+    {
+        try
+        {
+            var driveInfo = new List<DriveAccountService.DriveInfo>();
+            if (setting.Get("AccountStatus") == "已认证")
+            {
+                driveInfo = await driveAccount.GetDriveInfo();
+            }
+            return Json(new
+            {
+                officeName = OneDriveConfiguration.AccountName,
+                officeType = Enum.GetName(typeof(OneDriveConfiguration.OfficeType), OneDriveConfiguration.Type),
+                driveInfo,
+                appName = setting.Get("AppName"),
+                webName = setting.Get("WebName"),
+                defaultDrive = setting.Get("DefaultDrive"),
+                accountStatus = setting.Get("AccountStatus"),
+                readme = setting.Get("Readme"),
+                footer = setting.Get("Footer"),
+                allowAnonymouslyUpload = !string.IsNullOrEmpty(setting.Get("AllowAnonymouslyUpload")) && Convert.ToBoolean(setting.Get("AllowAnonymouslyUpload")),
+                uploadPassword = setting.Get("UploadPassword"),
+            }, new JsonSerializerSettings()
+            {
+                ContractResolver = new CamelCasePropertyNamesContractResolver()
+            });
+        }
+        catch (Exception ex)
+        {
+            return StatusCode(500, new ErrorResponse()
+            {
+                message = ex.Message
+            });
+        }
+    }
 
-	/// <summary>
-	/// 设置readme
-	/// </summary>
-	/// <param name="model"></param>
-	/// <returns></returns>
-	[HttpPost("readme")]
-	public async Task<IActionResult> UpdateReadme(ReadmeModel model)
-	{
-		try
-		{
-			await _setting.Set("Readme", model.text);
-		}
-		catch (Exception e)
-		{
-			return StatusCode(500, new ErrorResponse()
-			{
-				message = e.Message
-			});
-		};
-		return StatusCode(204);
-	}
+    /// <summary>
+    /// 设置readme
+    /// </summary>
+    /// <param name="model"></param>
+    /// <returns></returns>
+    [HttpPost("readme")]
+    public async Task<IActionResult> UpdateReadme(ReadmeModel model)
+    {
+        try
+        {
+            await setting.Set("Readme", model.text);
+        }
+        catch (Exception e)
+        {
+            return StatusCode(500, new ErrorResponse()
+            {
+                message = e.Message
+            });
+        };
+        return StatusCode(204);
+    }
 
-	/// <summary>
-	/// 更新
-	/// </summary>
-	/// <param name="settings"></param>
-	/// <returns></returns>
-	[HttpPost("settings")]
-	public async Task<IActionResult> UpdateSetting(UpdateSettings toSaveSetting)
-	{
-		try
-		{
-			await _setting.Set("AppName", toSaveSetting.appName);
-			await _setting.Set("WebName", toSaveSetting.webName);
-			await _setting.Set("DefaultDrive", toSaveSetting.defaultDrive);
-			await _setting.Set("Footer", toSaveSetting.footer);
-			await _setting.Set("UploadPassword", toSaveSetting.uploadPassword);
-			await _setting.Set("AllowAnonymouslyUpload", toSaveSetting.allowAnonymouslyUpload.ToString());
-		}
-		catch (Exception e)
-		{
-			return StatusCode(500, new ErrorResponse()
-			{
-				message = e.Message
-			});
-		};
-		return StatusCode(204);
-	}
+    /// <summary>
+    /// 更新
+    /// </summary>
+    /// <param name="settings"></param>
+    /// <returns></returns>
+    [HttpPost("settings")]
+    public async Task<IActionResult> UpdateSetting(UpdateSettings toSaveSetting)
+    {
+        try
+        {
+            await setting.Set("AppName", toSaveSetting.appName);
+            await setting.Set("WebName", toSaveSetting.webName);
+            await setting.Set("DefaultDrive", toSaveSetting.defaultDrive);
+            await setting.Set("Footer", toSaveSetting.footer);
+            await setting.Set("UploadPassword", toSaveSetting.uploadPassword);
+            await setting.Set("AllowAnonymouslyUpload", toSaveSetting.allowAnonymouslyUpload.ToString());
+        }
+        catch (Exception e)
+        {
+            return StatusCode(500, new ErrorResponse()
+            {
+                message = e.Message
+            });
+        };
+        return StatusCode(204);
+    }
 
-	/// <summary>
-	/// 解除绑定
-	/// </summary>
-	/// <param name="nickName"></param>
-	/// <returns></returns>
-	[HttpDelete("sites")]
-	public async Task<IActionResult> Unbind(string nickName)
-	{
-		try
-		{
-			await _driveAccount.Unbind(nickName);
-		}
-		catch (Exception e)
-		{
-			return StatusCode(500, new ErrorResponse()
-			{
-				message = e.Message
-			});
-		};
-		return StatusCode(204);
-	}
+    /// <summary>
+    /// 解除绑定
+    /// </summary>
+    /// <param name="nickName"></param>
+    /// <returns></returns>
+    [HttpDelete("sites")]
+    public async Task<IActionResult> Unbind(string nickName)
+    {
+        try
+        {
+            await driveAccount.Unbind(nickName);
+        }
+        catch (Exception e)
+        {
+            return StatusCode(500, new ErrorResponse()
+            {
+                message = e.Message
+            });
+        };
+        return StatusCode(204);
+    }
 
-	/// <summary>
-	/// 更新站点设置
-	/// </summary>
-	/// <returns></returns>
-	[HttpPost("sites/settings")]
-	public async Task<IActionResult> UpdateSiteSettings(SiteSettingsModel model)
-	{
-		try
-		{
-			var site = _driveAccount.SiteContext.Sites.SingleOrDefault(site => site.Name == model.siteName);
-			if (site != null)
-			{
-				site.NickName = model.nickName;
-				site.HiddenFolders = model.hiddenFolders.Split(',');
-				_driveAccount.SiteContext.Sites.Update(site);
-				await _driveAccount.SiteContext.SaveChangesAsync();
-			}
-		}
-		catch (Exception ex)
-		{
-			return StatusCode(500, new ErrorResponse()
-			{
-				message = ex.Message
-			});
-		}
-		return StatusCode(204);
-	}
+    /// <summary>
+    /// 更新站点设置
+    /// </summary>
+    /// <returns></returns>
+    [HttpPost("sites/settings")]
+    public async Task<IActionResult> UpdateSiteSettings(SiteSettingsModel model)
+    {
+        try
+        {
+            var site = driveAccount.SiteContext.Sites.SingleOrDefault(site => site.Name == model.siteName);
+            if (site != null)
+            {
+                site.NickName = model.nickName;
+                site.HiddenFolders = model.hiddenFolders.Split(',');
+                driveAccount.SiteContext.Sites.Update(site);
+                await driveAccount.SiteContext.SaveChangesAsync();
+            }
+        }
+        catch (Exception ex)
+        {
+            return StatusCode(500, new ErrorResponse()
+            {
+                message = ex.Message
+            });
+        }
+        return StatusCode(204);
+    }
 
-	#region 接收表单模型
+    #region 接收表单模型
 
-	public class UpdateSettings
-	{
-		public string appName { get; set; }
+    public class UpdateSettings
+    {
+        public string appName { get; set; }
 
-		public string webName { get; set; }
+        public string webName { get; set; }
 
-		public string navImg { get; set; }
+        public string navImg { get; set; }
 
-		public string defaultDrive { get; set; }
+        public string defaultDrive { get; set; }
 
-		public string readme { get; set; }
+        public string readme { get; set; }
 
-		public string footer { get; set; }
+        public string footer { get; set; }
 
-		public bool allowAnonymouslyUpload { get; set; }
+        public bool allowAnonymouslyUpload { get; set; }
 
-		public string uploadPassword { get; set; }
-	}
+        public string uploadPassword { get; set; }
+    }
 
-	public class AddSiteModel
-	{
-		public string siteName { get; set; }
+    public class AddSiteModel
+    {
+        public string siteName { get; set; }
 
-		public string nickName { get; set; }
-	}
+        public string nickName { get; set; }
+    }
 
-	public class SiteSettingsModel
-	{
-		public string siteName { get; set; }
+    public class SiteSettingsModel
+    {
+        public string siteName { get; set; }
 
-		public string nickName { get; set; }
+        public string nickName { get; set; }
 
-		public string hiddenFolders { get; set; }
-	}
+        public string hiddenFolders { get; set; }
+    }
 
-	public class ReadmeModel
-	{
-		public string text { get; set; }
-	}
+    public class ReadmeModel
+    {
+        public string text { get; set; }
+    }
 
-	#endregion 接收表单模型
-}
+    #endregion 接收表单模型
+}

+ 374 - 374
src/Masuit.MyBlogs.Core/Controllers/MsgController.cs

@@ -21,378 +21,378 @@ namespace Masuit.MyBlogs.Core.Controllers;
 /// </summary>
 public sealed class MsgController : BaseController
 {
-	/// <summary>
-	/// 留言
-	/// </summary>
-	public ILeaveMessageService LeaveMessageService { get; set; }
-
-	/// <summary>
-	/// 站内信
-	/// </summary>
-	public IInternalMessageService MessageService { get; set; }
-
-	public IWebHostEnvironment HostEnvironment { get; set; }
-
-	/// <summary>
-	/// 留言板
-	/// </summary>
-	/// <returns></returns>
-	[Route("msg"), Route("msg/{cid:int}"), ResponseCache(Duration = 600, VaryByHeader = "Cookie")]
-	public async Task<ActionResult> Index()
-	{
-		ViewBag.TotalCount = LeaveMessageService.Count(m => m.ParentId == null && m.Status == Status.Published);
-		var text = await new FileInfo(Path.Combine(HostEnvironment.WebRootPath, "template", "agreement.html")).ShareReadWrite().ReadAllTextAsync(Encoding.UTF8);
-		return CurrentUser.IsAdmin ? View("Index_Admin", text) : View(model: text);
-	}
-
-	/// <summary>
-	/// 获取留言
-	/// </summary>
-	/// <param name="page"></param>
-	/// <param name="size"></param>
-	/// <param name="cid"></param>
-	/// <returns></returns>
-	public async Task<ActionResult> GetMsgs([Range(1, int.MaxValue, ErrorMessage = "页码必须大于0")] int page = 1, [Range(1, 50, ErrorMessage = "页大小必须在0到50之间")] int size = 15, int? cid = null)
-	{
-		if (cid > 0)
-		{
-			var message = await LeaveMessageService.GetByIdAsync(cid.Value) ?? throw new NotFoundException("留言未找到");
-			var layer = LeaveMessageService.GetQueryNoTracking(e => e.GroupTag == message.GroupTag).ToPooledListScope();
-			foreach (var m in layer)
-			{
-				m.PostDate = m.PostDate.ToTimeZone(HttpContext.Session.Get<string>(SessionKey.TimeZone));
-				if (!CurrentUser.IsAdmin)
-				{
-					m.Email = null;
-					m.IP = null;
-					m.Location = null;
-				}
-			}
-
-			return ResultData(new
-			{
-				total = 1,
-				parentTotal = 1,
-				page,
-				size,
-				rows = Mapper.Map<IList<LeaveMessageViewModel>>(layer.ToTree(e => e.Id, e => e.ParentId))
-			});
-		}
-
-		var parent = await LeaveMessageService.GetPagesAsync(page, size, m => m.ParentId == null && (m.Status == Status.Published || CurrentUser.IsAdmin), m => m.PostDate, false);
-		if (!parent.Data.Any())
-		{
-			return ResultData(null, false, "没有留言");
-		}
-		var total = parent.TotalCount;
-		var tags = parent.Data.Select(c => c.GroupTag).ToArray();
-		var messages = LeaveMessageService.GetQueryNoTracking(c => tags.Contains(c.GroupTag)).ToPooledListScope();
-		messages.ForEach(m =>
-		{
-			m.PostDate = m.PostDate.ToTimeZone(HttpContext.Session.Get<string>(SessionKey.TimeZone));
-			if (!CurrentUser.IsAdmin)
-			{
-				m.Email = null;
-				m.IP = null;
-				m.Location = null;
-			}
-		});
-		if (total > 0)
-		{
-			return ResultData(new
-			{
-				total,
-				parentTotal = total,
-				page,
-				size,
-				rows = Mapper.Map<List<LeaveMessageViewModel>>(messages.OrderByDescending(c => c.PostDate).ToTree(c => c.Id, c => c.ParentId))
-			});
-		}
-
-		return ResultData(null, false, "没有留言");
-	}
-
-	/// <summary>
-	/// 发表留言
-	/// </summary>
-	/// <param name="mailSender"></param>
-	/// <param name="cmd"></param>
-	/// <returns></returns>
-	[HttpPost, ValidateAntiForgeryToken]
-	public async Task<ActionResult> Submit([FromServices] IMailSender mailSender, LeaveMessageCommand cmd)
-	{
-		var match = Regex.Match(cmd.NickName + cmd.Content.RemoveHtmlTag(), CommonHelper.BanRegex);
-		if (match.Success)
-		{
-			LogManager.Info($"提交内容:{cmd.NickName}/{cmd.Content},敏感词:{match.Value}");
-			return ResultData(null, false, "您提交的内容包含敏感词,被禁止发表,请检查您的内容后尝试重新提交!");
-		}
-
-		var error = await ValidateEmailCode(mailSender, cmd.Email, cmd.Code);
-		if (!string.IsNullOrEmpty(error))
-		{
-			return ResultData(null, false, error);
-		}
-
-		if (cmd.ParentId > 0 && DateTime.Now - LeaveMessageService[cmd.ParentId.Value, m => m.PostDate] > TimeSpan.FromDays(180))
-		{
-			return ResultData(null, false, "当前留言过于久远,不再允许回复!");
-		}
-
-		cmd.Content = cmd.Content.Trim().Replace("<p><br></p>", string.Empty);
-		var ip = ClientIP.ToString();
-
-		if (!CurrentUser.IsAdmin)
-		{
-			if (await RedisHelper.SAddAsync("Comments:" + ip, cmd.Content) == 0)
-			{
-				await RedisHelper.ExpireAsync("Comments:" + ip, TimeSpan.FromMinutes(2));
-				return ResultData(null, false, "您已发表了相同的评论内容,请稍后再发表吧!");
-			}
-
-			if (await RedisHelper.SCardAsync("Comments:" + ip) > 2)
-			{
-				await RedisHelper.ExpireAsync("Comments:" + ip, TimeSpan.FromMinutes(2));
-				return ResultData(null, false, "您的发言频率过快,请稍后再发表吧!");
-			}
-		}
-
-		var msg = Mapper.Map<LeaveMessage>(cmd);
-		if (cmd.ParentId > 0)
-		{
-			msg.GroupTag = LeaveMessageService.GetQuery(c => c.Id == cmd.ParentId).Select(c => c.GroupTag).FirstOrDefault();
-			msg.Path = (LeaveMessageService.GetQuery(c => c.Id == cmd.ParentId).Select(c => c.Path).FirstOrDefault() + "," + cmd.ParentId).Trim(',');
-		}
-		else
-		{
-			msg.GroupTag = SnowFlake.NewId;
-			msg.Path = SnowFlake.NewId;
-		}
-
-		if (Regex.Match(cmd.NickName + cmd.Content, CommonHelper.ModRegex).Length <= 0)
-		{
-			msg.Status = Status.Published;
-		}
-
-		msg.PostDate = DateTime.Now;
-		var user = HttpContext.Session.Get<UserInfoDto>(SessionKey.UserInfo);
-		if (user != null)
-		{
-			msg.NickName = user.NickName;
-			msg.Email = user.Email;
-			if (user.IsAdmin)
-			{
-				msg.Status = Status.Published;
-				msg.IsMaster = true;
-			}
-		}
-
-		msg.Content = await cmd.Content.HtmlSanitizerStandard().ClearImgAttributes();
-		msg.Browser = cmd.Browser ?? Request.Headers[HeaderNames.UserAgent];
-		msg.IP = ip;
-		msg.Location = Request.Location();
-		msg = LeaveMessageService.AddEntitySaved(msg);
-		if (msg == null)
-		{
-			return ResultData(null, false, "留言发表失败!");
-		}
-
-		Response.Cookies.Append("NickName", msg.NickName, new CookieOptions()
-		{
-			Expires = DateTimeOffset.Now.AddYears(1),
-			SameSite = SameSiteMode.Lax
-		});
-		WriteEmailKeyCookie(cmd.Email);
-		await RedisHelper.ExpireAsync("Comments:" + ip, TimeSpan.FromMinutes(1));
-		var email = CommonHelper.SystemSettings["ReceiveEmail"];
-		var content = new Template(await new FileInfo(HostEnvironment.WebRootPath + "/template/notify.html").ShareReadWrite().ReadAllTextAsync(Encoding.UTF8)).Set("title", "网站留言板").Set("time", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")).Set("nickname", msg.NickName).Set("content", msg.Content);
-		if (msg.Status == Status.Published)
-		{
-			if (!msg.IsMaster)
-			{
-				await MessageService.AddEntitySavedAsync(new InternalMessage()
-				{
-					Title = $"来自【{msg.NickName}】的新留言",
-					Content = msg.Content,
-					Link = Url.Action("Index", "Msg", new { cid = msg.Id })
-				});
-			}
-			if (msg.ParentId == null)
-			{
-				//新评论,只通知博主
-				BackgroundJob.Enqueue<IMailSender>(sender => sender.Send(Request.Host + "|博客新留言:", content.Set("link", Url.Action("Index", "Msg", new { cid = msg.Id }, Request.Scheme)).Render(false), email, ip));
-			}
-			else
-			{
-				//通知博主和上层所有关联的评论访客
-				var emails = LeaveMessageService.GetQuery(e => e.GroupTag == msg.GroupTag).Select(c => c.Email).Distinct().AsEnumerable().Append(email).Except(new[] { msg.Email }).ToHashSet();
-				string link = Url.Action("Index", "Msg", new { cid = msg.Id }, Request.Scheme);
-				foreach (var s in emails)
-				{
-					BackgroundJob.Enqueue<IMailSender>(sender => sender.Send($"{Request.Host}{CommonHelper.SystemSettings["Title"]} 留言回复:", content.Set("link", link).Render(false), s, ip));
-				}
-			}
-			return ResultData(null, true, "留言发表成功,服务器正在后台处理中,这会有一定的延迟,稍后将会显示到列表中!");
-		}
-
-		BackgroundJob.Enqueue<IMailSender>(sender => sender.Send(Request.Host + "|博客新留言(待审核):", content.Set("link", Url.Action("Index", "Msg", new
-		{
-			cid = msg.Id
-		}, Request.Scheme)).Render(false) + "<p style='color:red;'>(待审核)</p>", email, ip));
-		return ResultData(null, true, "留言发表成功,待站长审核通过以后将显示到列表中!");
-	}
-
-	/// <summary>
-	/// 审核
-	/// </summary>
-	/// <param name="id"></param>
-	/// <returns></returns>
-	[MyAuthorize]
-	public async Task<ActionResult> Pass(int id)
-	{
-		var msg = await LeaveMessageService.GetByIdAsync(id);
-		msg.Status = Status.Published;
-		bool b = await LeaveMessageService.SaveChangesAsync() > 0;
-		if (b)
-		{
-			var content = new Template(await new FileInfo(Path.Combine(HostEnvironment.WebRootPath, "template", "notify.html")).ShareReadWrite().ReadAllTextAsync(Encoding.UTF8)).Set("time", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")).Set("nickname", msg.NickName).Set("content", msg.Content);
-			var emails = LeaveMessageService.GetQuery(m => m.GroupTag == msg.GroupTag).Select(m => m.Email).Distinct().AsEnumerable().Except(new List<string> { msg.Email, CurrentUser.Email }).ToPooledSetScope();
-			var link = Url.Action("Index", "Msg", new { cid = id }, Request.Scheme);
-			foreach (var s in emails)
-			{
-				BackgroundJob.Enqueue<IMailSender>(sender => sender.Send($"{Request.Host}{CommonHelper.SystemSettings["Title"]} 留言回复:", content.Set("link", link).Render(false), s, ClientIP.ToString()));
-			}
-		}
-
-		return ResultData(null, b, b ? "审核通过!" : "审核失败!");
-	}
-
-	/// <summary>
-	/// 删除留言
-	/// </summary>
-	/// <param name="id"></param>
-	/// <returns></returns>
-	[MyAuthorize]
-	public ActionResult Delete(int id)
-	{
-		var b = LeaveMessageService.DeleteById(id);
-		return ResultData(null, b, b ? "删除成功!" : "删除失败!");
-	}
-
-	/// <summary>
-	/// 获取待审核的留言
-	/// </summary>
-	/// <returns></returns>
-	[MyAuthorize]
-	public async Task<ActionResult> GetPendingMsgs([Range(1, int.MaxValue, ErrorMessage = "页码必须大于0")] int page = 1, [Range(1, 50, ErrorMessage = "页大小必须在0到50之间")] int size = 15)
-	{
-		var list = await LeaveMessageService.GetPagesAsync<DateTime, LeaveMessageDto>(page, size, m => m.Status == Status.Pending, l => l.PostDate, false);
-		foreach (var m in list.Data)
-		{
-			m.PostDate = m.PostDate.ToTimeZone(HttpContext.Session.Get<string>(SessionKey.TimeZone));
-		}
-
-		return Ok(list);
-	}
-
-	#region 站内消息
-
-	/// <summary>
-	/// 已读站内信
-	/// </summary>
-	/// <param name="id"></param>
-	/// <returns></returns>
-	[MyAuthorize]
-	public async Task<ActionResult> Read(int id)
-	{
-		await MessageService.GetQuery(m => m.Id == id).ExecuteUpdateAsync(s => s.SetProperty(m => m.Read, true));
-		return Content("ok");
-	}
-
-	/// <summary>
-	/// 标记为未读
-	/// </summary>
-	/// <param name="id"></param>
-	/// <returns></returns>
-	[MyAuthorize]
-	public async Task<ActionResult> Unread(int id)
-	{
-		await MessageService.GetQuery(m => m.Id == id).ExecuteUpdateAsync(s => s.SetProperty(m => m.Read, false));
-		return Content("ok");
-	}
-
-	/// <summary>
-	/// 标记为已读
-	/// </summary>
-	/// <param name="id"></param>
-	/// <returns></returns>
-	[MyAuthorize]
-	public async Task<ActionResult> MarkRead(int id)
-	{
-		await MessageService.GetQuery(m => m.Id <= id).ExecuteUpdateAsync(s => s.SetProperty(m => m.Read, true));
-		return ResultData(null);
-	}
-
-	/// <summary>
-	/// 删除站内信
-	/// </summary>
-	/// <param name="id"></param>
-	/// <returns></returns>
-	[MyAuthorize]
-	public async Task<ActionResult> DeleteMsg(int id)
-	{
-		bool b = await MessageService.DeleteByIdAsync(id) > 0;
-		return ResultData(null, b, b ? "站内消息删除成功!" : "站内消息删除失败!");
-	}
-
-	/// <summary>
-	/// 获取站内信
-	/// </summary>
-	/// <param name="page"></param>
-	/// <param name="size"></param>
-	/// <returns></returns>
-	[MyAuthorize]
-	public ActionResult GetInternalMsgs([Range(1, int.MaxValue, ErrorMessage = "页码必须大于0")] int page = 1, [Range(1, 50, ErrorMessage = "页大小必须在0到50之间")] int size = 15)
-	{
-		var msgs = MessageService.GetPagesNoTracking(page, size, m => true, m => m.Time, false);
-		return Ok(msgs);
-	}
-
-	/// <summary>
-	/// 获取未读消息
-	/// </summary>
-	/// <returns></returns>
-	[MyAuthorize]
-	public async Task<ActionResult> GetUnreadMsgs(CancellationToken cancellationToken)
-	{
-		Response.ContentType = "text/event-stream";
-		while (true)
-		{
-			if (cancellationToken.IsCancellationRequested)
-			{
-				break;
-			}
-			await Response.WriteAsync($"event: message\n", cancellationToken);
-			var msgs = MessageService.GetQueryNoTracking(m => !m.Read, m => m.Time, false).ToPooledListScope();
-			await Response.WriteAsync("data:" + msgs.ToJsonString() + "\r\r");
-			await Response.Body.FlushAsync(cancellationToken);
-			await Task.Delay(5000, cancellationToken);
-		}
-
-		Response.Body.Close();
-		return ResultData(null);
-	}
-
-	/// <summary>
-	/// 清除站内信
-	/// </summary>
-	/// <returns></returns>
-	[MyAuthorize]
-	public async Task<ActionResult> ClearMsgs()
-	{
-		await MessageService.DeleteEntitySavedAsync(m => m.Read);
-		return ResultData(null, true, "站内消息清除成功!");
-	}
-
-	#endregion 站内消息
+    /// <summary>
+    /// 留言
+    /// </summary>
+    public ILeaveMessageService LeaveMessageService { get; set; }
+
+    /// <summary>
+    /// 站内信
+    /// </summary>
+    public IInternalMessageService MessageService { get; set; }
+
+    public IWebHostEnvironment HostEnvironment { get; set; }
+
+    /// <summary>
+    /// 留言板
+    /// </summary>
+    /// <returns></returns>
+    [Route("msg"), Route("msg/{cid:int}"), ResponseCache(Duration = 600, VaryByHeader = "Cookie")]
+    public async Task<ActionResult> Index()
+    {
+        ViewBag.TotalCount = LeaveMessageService.Count(m => m.ParentId == null && m.Status == Status.Published);
+        var text = await new FileInfo(Path.Combine(HostEnvironment.WebRootPath, "template", "agreement.html")).ShareReadWrite().ReadAllTextAsync(Encoding.UTF8);
+        return CurrentUser.IsAdmin ? View("Index_Admin", text) : View(model: text);
+    }
+
+    /// <summary>
+    /// 获取留言
+    /// </summary>
+    /// <param name="page"></param>
+    /// <param name="size"></param>
+    /// <param name="cid"></param>
+    /// <returns></returns>
+    public async Task<ActionResult> GetMsgs([Range(1, int.MaxValue, ErrorMessage = "页码必须大于0")] int page = 1, [Range(1, 50, ErrorMessage = "页大小必须在0到50之间")] int size = 15, int? cid = null)
+    {
+        if (cid > 0)
+        {
+            var message = await LeaveMessageService.GetByIdAsync(cid.Value) ?? throw new NotFoundException("留言未找到");
+            var layer = LeaveMessageService.GetQueryNoTracking(e => e.GroupTag == message.GroupTag).ToPooledListScope();
+            foreach (var m in layer)
+            {
+                m.PostDate = m.PostDate.ToTimeZone(HttpContext.Session.Get<string>(SessionKey.TimeZone));
+                if (!CurrentUser.IsAdmin)
+                {
+                    m.Email = null;
+                    m.IP = null;
+                    m.Location = null;
+                }
+            }
+
+            return ResultData(new
+            {
+                total = 1,
+                parentTotal = 1,
+                page,
+                size,
+                rows = Mapper.Map<IList<LeaveMessageViewModel>>(layer.ToTree(e => e.Id, e => e.ParentId))
+            });
+        }
+
+        var parent = await LeaveMessageService.GetPagesAsync(page, size, m => m.ParentId == null && (m.Status == Status.Published || CurrentUser.IsAdmin), m => m.PostDate, false);
+        if (!parent.Data.Any())
+        {
+            return ResultData(null, false, "没有留言");
+        }
+        var total = parent.TotalCount;
+        var tags = parent.Data.Select(c => c.GroupTag).ToArray();
+        var messages = LeaveMessageService.GetQueryNoTracking(c => tags.Contains(c.GroupTag)).ToPooledListScope();
+        messages.ForEach(m =>
+        {
+            m.PostDate = m.PostDate.ToTimeZone(HttpContext.Session.Get<string>(SessionKey.TimeZone));
+            if (!CurrentUser.IsAdmin)
+            {
+                m.Email = null;
+                m.IP = null;
+                m.Location = null;
+            }
+        });
+        if (total > 0)
+        {
+            return ResultData(new
+            {
+                total,
+                parentTotal = total,
+                page,
+                size,
+                rows = Mapper.Map<List<LeaveMessageViewModel>>(messages.OrderByDescending(c => c.PostDate).ToTree(c => c.Id, c => c.ParentId))
+            });
+        }
+
+        return ResultData(null, false, "没有留言");
+    }
+
+    /// <summary>
+    /// 发表留言
+    /// </summary>
+    /// <param name="mailSender"></param>
+    /// <param name="cmd"></param>
+    /// <returns></returns>
+    [HttpPost, ValidateAntiForgeryToken]
+    public async Task<ActionResult> Submit([FromServices] IMailSender mailSender, LeaveMessageCommand cmd)
+    {
+        var match = Regex.Match(cmd.NickName + cmd.Content.RemoveHtmlTag(), CommonHelper.BanRegex);
+        if (match.Success)
+        {
+            LogManager.Info($"提交内容:{cmd.NickName}/{cmd.Content},敏感词:{match.Value}");
+            return ResultData(null, false, "您提交的内容包含敏感词,被禁止发表,请检查您的内容后尝试重新提交!");
+        }
+
+        var error = await ValidateEmailCode(mailSender, cmd.Email, cmd.Code);
+        if (!string.IsNullOrEmpty(error))
+        {
+            return ResultData(null, false, error);
+        }
+
+        if (cmd.ParentId > 0 && DateTime.Now - LeaveMessageService[cmd.ParentId.Value, m => m.PostDate] > TimeSpan.FromDays(180))
+        {
+            return ResultData(null, false, "当前留言过于久远,不再允许回复!");
+        }
+
+        cmd.Content = cmd.Content.Trim().Replace("<p><br></p>", string.Empty);
+        var ip = ClientIP.ToString();
+
+        if (!CurrentUser.IsAdmin)
+        {
+            if (await RedisHelper.SAddAsync("Comments:" + ip, cmd.Content) == 0)
+            {
+                await RedisHelper.ExpireAsync("Comments:" + ip, TimeSpan.FromMinutes(2));
+                return ResultData(null, false, "您已发表了相同的评论内容,请稍后再发表吧!");
+            }
+
+            if (await RedisHelper.SCardAsync("Comments:" + ip) > 2)
+            {
+                await RedisHelper.ExpireAsync("Comments:" + ip, TimeSpan.FromMinutes(2));
+                return ResultData(null, false, "您的发言频率过快,请稍后再发表吧!");
+            }
+        }
+
+        var msg = Mapper.Map<LeaveMessage>(cmd);
+        if (cmd.ParentId > 0)
+        {
+            msg.GroupTag = LeaveMessageService.GetQuery(c => c.Id == cmd.ParentId).Select(c => c.GroupTag).FirstOrDefault();
+            msg.Path = (LeaveMessageService.GetQuery(c => c.Id == cmd.ParentId).Select(c => c.Path).FirstOrDefault() + "," + cmd.ParentId).Trim(',');
+        }
+        else
+        {
+            msg.GroupTag = SnowFlake.NewId;
+            msg.Path = SnowFlake.NewId;
+        }
+
+        if (Regex.Match(cmd.NickName + cmd.Content, CommonHelper.ModRegex).Length <= 0)
+        {
+            msg.Status = Status.Published;
+        }
+
+        msg.PostDate = DateTime.Now;
+        var user = HttpContext.Session.Get<UserInfoDto>(SessionKey.UserInfo);
+        if (user != null)
+        {
+            msg.NickName = user.NickName;
+            msg.Email = user.Email;
+            if (user.IsAdmin)
+            {
+                msg.Status = Status.Published;
+                msg.IsMaster = true;
+            }
+        }
+
+        msg.Content = await cmd.Content.HtmlSanitizerStandard().ClearImgAttributes();
+        msg.Browser = cmd.Browser ?? Request.Headers[HeaderNames.UserAgent];
+        msg.IP = ip;
+        msg.Location = Request.Location();
+        msg = LeaveMessageService.AddEntitySaved(msg);
+        if (msg == null)
+        {
+            return ResultData(null, false, "留言发表失败!");
+        }
+
+        Response.Cookies.Append("NickName", msg.NickName, new CookieOptions()
+        {
+            Expires = DateTimeOffset.Now.AddYears(1),
+            SameSite = SameSiteMode.Lax
+        });
+        WriteEmailKeyCookie(cmd.Email);
+        await RedisHelper.ExpireAsync("Comments:" + ip, TimeSpan.FromMinutes(1));
+        var email = CommonHelper.SystemSettings["ReceiveEmail"];
+        var content = new Template(await new FileInfo(HostEnvironment.WebRootPath + "/template/notify.html").ShareReadWrite().ReadAllTextAsync(Encoding.UTF8)).Set("title", "网站留言板").Set("time", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")).Set("nickname", msg.NickName).Set("content", msg.Content);
+        if (msg.Status == Status.Published)
+        {
+            if (!msg.IsMaster)
+            {
+                await MessageService.AddEntitySavedAsync(new InternalMessage()
+                {
+                    Title = $"来自【{msg.NickName}】的新留言",
+                    Content = msg.Content,
+                    Link = Url.Action("Index", "Msg", new { cid = msg.Id })
+                });
+            }
+            if (msg.ParentId == null)
+            {
+                //新评论,只通知博主
+                BackgroundJob.Enqueue<IMailSender>(sender => sender.Send(Request.Host + "|博客新留言:", content.Set("link", Url.Action("Index", "Msg", new { cid = msg.Id }, Request.Scheme)).Render(false), email, ip));
+            }
+            else
+            {
+                //通知博主和上层所有关联的评论访客
+                var emails = LeaveMessageService.GetQuery(e => e.GroupTag == msg.GroupTag).Select(c => c.Email).Distinct().AsEnumerable().Append(email).Except(new[] { msg.Email }).ToHashSet();
+                string link = Url.Action("Index", "Msg", new { cid = msg.Id }, Request.Scheme);
+                foreach (var s in emails)
+                {
+                    BackgroundJob.Enqueue<IMailSender>(sender => sender.Send($"{Request.Host}{CommonHelper.SystemSettings["Title"]} 留言回复:", content.Set("link", link).Render(false), s, ip));
+                }
+            }
+            return ResultData(null, true, "留言发表成功,服务器正在后台处理中,这会有一定的延迟,稍后将会显示到列表中!");
+        }
+
+        BackgroundJob.Enqueue<IMailSender>(sender => sender.Send(Request.Host + "|博客新留言(待审核):", content.Set("link", Url.Action("Index", "Msg", new
+        {
+            cid = msg.Id
+        }, Request.Scheme)).Render(false) + "<p style='color:red;'>(待审核)</p>", email, ip));
+        return ResultData(null, true, "留言发表成功,待站长审核通过以后将显示到列表中!");
+    }
+
+    /// <summary>
+    /// 审核
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [MyAuthorize]
+    public async Task<ActionResult> Pass(int id)
+    {
+        var msg = await LeaveMessageService.GetByIdAsync(id);
+        msg.Status = Status.Published;
+        bool b = await LeaveMessageService.SaveChangesAsync() > 0;
+        if (b)
+        {
+            var content = new Template(await new FileInfo(Path.Combine(HostEnvironment.WebRootPath, "template", "notify.html")).ShareReadWrite().ReadAllTextAsync(Encoding.UTF8)).Set("time", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")).Set("nickname", msg.NickName).Set("content", msg.Content);
+            var emails = LeaveMessageService.GetQuery(m => m.GroupTag == msg.GroupTag).Select(m => m.Email).Distinct().AsEnumerable().Except(new List<string> { msg.Email, CurrentUser.Email }).ToPooledSetScope();
+            var link = Url.Action("Index", "Msg", new { cid = id }, Request.Scheme);
+            foreach (var s in emails)
+            {
+                BackgroundJob.Enqueue<IMailSender>(sender => sender.Send($"{Request.Host}{CommonHelper.SystemSettings["Title"]} 留言回复:", content.Set("link", link).Render(false), s, ClientIP.ToString()));
+            }
+        }
+
+        return ResultData(null, b, b ? "审核通过!" : "审核失败!");
+    }
+
+    /// <summary>
+    /// 删除留言
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [MyAuthorize]
+    public ActionResult Delete(int id)
+    {
+        var b = LeaveMessageService.DeleteById(id);
+        return ResultData(null, b, b ? "删除成功!" : "删除失败!");
+    }
+
+    /// <summary>
+    /// 获取待审核的留言
+    /// </summary>
+    /// <returns></returns>
+    [MyAuthorize]
+    public async Task<ActionResult> GetPendingMsgs([Range(1, int.MaxValue, ErrorMessage = "页码必须大于0")] int page = 1, [Range(1, 50, ErrorMessage = "页大小必须在0到50之间")] int size = 15)
+    {
+        var list = await LeaveMessageService.GetPagesAsync<DateTime, LeaveMessageDto>(page, size, m => m.Status == Status.Pending, l => l.PostDate, false);
+        foreach (var m in list.Data)
+        {
+            m.PostDate = m.PostDate.ToTimeZone(HttpContext.Session.Get<string>(SessionKey.TimeZone));
+        }
+
+        return Ok(list);
+    }
+
+    #region 站内消息
+
+    /// <summary>
+    /// 已读站内信
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [MyAuthorize]
+    public async Task<ActionResult> Read(int id)
+    {
+        await MessageService.GetQuery(m => m.Id == id).ExecuteUpdateAsync(s => s.SetProperty(m => m.Read, true));
+        return Content("ok");
+    }
+
+    /// <summary>
+    /// 标记为未读
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [MyAuthorize]
+    public async Task<ActionResult> Unread(int id)
+    {
+        await MessageService.GetQuery(m => m.Id == id).ExecuteUpdateAsync(s => s.SetProperty(m => m.Read, false));
+        return Content("ok");
+    }
+
+    /// <summary>
+    /// 标记为已读
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [MyAuthorize]
+    public async Task<ActionResult> MarkRead(int id)
+    {
+        await MessageService.GetQuery(m => m.Id <= id).ExecuteUpdateAsync(s => s.SetProperty(m => m.Read, true));
+        return ResultData(null);
+    }
+
+    /// <summary>
+    /// 删除站内信
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [MyAuthorize]
+    public async Task<ActionResult> DeleteMsg(int id)
+    {
+        bool b = await MessageService.DeleteByIdAsync(id) > 0;
+        return ResultData(null, b, b ? "站内消息删除成功!" : "站内消息删除失败!");
+    }
+
+    /// <summary>
+    /// 获取站内信
+    /// </summary>
+    /// <param name="page"></param>
+    /// <param name="size"></param>
+    /// <returns></returns>
+    [MyAuthorize]
+    public ActionResult GetInternalMsgs([Range(1, int.MaxValue, ErrorMessage = "页码必须大于0")] int page = 1, [Range(1, 50, ErrorMessage = "页大小必须在0到50之间")] int size = 15)
+    {
+        var msgs = MessageService.GetPagesNoTracking(page, size, m => true, m => m.Time, false);
+        return Ok(msgs);
+    }
+
+    /// <summary>
+    /// 获取未读消息
+    /// </summary>
+    /// <returns></returns>
+    [MyAuthorize]
+    public async Task<ActionResult> GetUnreadMsgs(CancellationToken cancellationToken)
+    {
+        Response.ContentType = "text/event-stream";
+        while (true)
+        {
+            if (cancellationToken.IsCancellationRequested)
+            {
+                break;
+            }
+            await Response.WriteAsync($"event: message\n", cancellationToken);
+            var msgs = MessageService.GetQueryNoTracking(m => !m.Read, m => m.Time, false).ToPooledListScope();
+            await Response.WriteAsync("data:" + msgs.ToJsonString() + "\r\r");
+            await Response.Body.FlushAsync(cancellationToken);
+            await Task.Delay(5000, cancellationToken);
+        }
+
+        Response.Body.Close();
+        return Ok();
+    }
+
+    /// <summary>
+    /// 清除站内信
+    /// </summary>
+    /// <returns></returns>
+    [MyAuthorize]
+    public async Task<ActionResult> ClearMsgs()
+    {
+        await MessageService.DeleteEntitySavedAsync(m => m.Read);
+        return ResultData(null, true, "站内消息清除成功!");
+    }
+
+    #endregion 站内消息
 }

+ 8 - 0
src/Masuit.MyBlogs.Core/Extensions/PerfCounterFilterAttribute.cs

@@ -10,6 +10,10 @@ public class PerfCounterFilterAttribute : ActionFilterAttribute
     /// <inheritdoc />
     public override void OnActionExecuted(ActionExecutedContext context)
     {
+        if (context.HttpContext.Response.Headers.IsReadOnly)
+        {
+            return;
+        }
         context.HttpContext.Response.Headers.AddOrUpdate("X-Action-Time", Stopwatch.ElapsedMilliseconds + "ms", Stopwatch.ElapsedMilliseconds + "ms");
     }
 
@@ -19,6 +23,10 @@ public class PerfCounterFilterAttribute : ActionFilterAttribute
         Stopwatch.Restart();
         context.HttpContext.Response.OnStarting(() =>
         {
+            if (context.HttpContext.Response.Headers.IsReadOnly)
+            {
+                return Task.CompletedTask;
+            }
             context.HttpContext.Response.Headers.AddOrUpdate("X-Result-Time", Stopwatch.ElapsedMilliseconds + "ms", Stopwatch.ElapsedMilliseconds + "ms");
             return Task.CompletedTask;
         });

+ 11 - 4
src/Masuit.MyBlogs.Core/Infrastructure/Drive/DriveAccountService.cs

@@ -14,6 +14,15 @@ public sealed class DriveAccountService(DriveContext siteContext, TokenService t
     /// <value></value>
     public Microsoft.Graph.GraphServiceClient Graph { get; set; } = tokenService.Graph;
 
+    public DriveContext SiteContext
+    {
+        get => siteContext;
+
+        set
+        {
+        }
+    }
+
     /// <summary>
     /// 返回 Oauth 验证url
     /// </summary>
@@ -42,10 +51,8 @@ public sealed class DriveAccountService(DriveContext siteContext, TokenService t
         }
         else
         {
-            using var httpClient = new HttpClient
-            {
-                Timeout = TimeSpan.FromSeconds(20)
-            };
+            using var httpClient = new HttpClient();
+            httpClient.Timeout = TimeSpan.FromSeconds(20);
             var apiCaller = new ProtectedApiCallHelper(httpClient);
             await apiCaller.CallWebApiAndProcessResultASync($"{OneDriveConfiguration.GraphApi}/v1.0/sites/{OneDriveConfiguration.DominName}:/sites/{siteName}", GetToken(), result =>
             {

+ 2 - 0
src/Masuit.MyBlogs.Core/Infrastructure/Drive/IDriveAccountService.cs

@@ -4,6 +4,8 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Drive;
 
 public interface IDriveAccountService
 {
+    public DriveContext SiteContext { get; set; }
+
     /// <summary>
     /// 返回 Oauth 验证url
     /// </summary>