using CacheManager.Core; using Hangfire; using Masuit.MyBlogs.Core.Common; using Masuit.MyBlogs.Core.Common.Mails; using Masuit.MyBlogs.Core.Extensions; using Masuit.MyBlogs.Core.Infrastructure.Services.Interface; using Masuit.MyBlogs.Core.Models.Command; using Masuit.MyBlogs.Core.Models.DTO; using Masuit.MyBlogs.Core.Models.Entity; using Masuit.MyBlogs.Core.Models.Enum; using Masuit.MyBlogs.Core.Models.ViewModel; using Masuit.Tools; using Masuit.Tools.Core.Net; using Masuit.Tools.Html; using Masuit.Tools.Logging; using Masuit.Tools.Models; using Masuit.Tools.Strings; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using SameSiteMode = Microsoft.AspNetCore.Http.SameSiteMode; namespace Masuit.MyBlogs.Core.Controllers { /// /// 评论管理 /// public class CommentController : BaseController { public ICommentService CommentService { get; set; } public IPostService PostService { get; set; } public IWebHostEnvironment HostEnvironment { get; set; } public ICacheManager CommentFeq { get; set; } /// /// 发表评论 /// /// /// /// /// [HttpPost, ValidateAntiForgeryToken] public async Task Submit([FromServices] IInternalMessageService messageService, [FromServices] IMailSender mailSender, CommentCommand dto) { var match = Regex.Match(dto.NickName + dto.Content.RemoveHtmlTag(), CommonHelper.BanRegex); if (match.Success) { LogManager.Info($"提交内容:{dto.NickName}/{dto.Content},敏感词:{match.Value}"); return ResultData(null, false, "您提交的内容包含敏感词,被禁止发表,请检查您的内容后尝试重新提交!"); } var error = await ValidateEmailCode(mailSender, dto.Email, dto.Code); if (!string.IsNullOrEmpty(error)) { return ResultData(null, false, error); } Post post = await PostService.GetByIdAsync(dto.PostId) ?? throw new NotFoundException("评论失败,文章未找到"); if (post.DisableComment) { return ResultData(null, false, "本文已禁用评论功能,不允许任何人回复!"); } dto.Content = dto.Content.Trim().Replace("


", string.Empty); if (CommentFeq.GetOrAdd("Comments:" + ClientIP, 1) > 2) { CommentFeq.Expire("Comments:" + ClientIP, TimeSpan.FromMinutes(1)); return ResultData(null, false, "您的发言频率过快,请稍后再发表吧!"); } var comment = dto.Mapper(); if (Regex.Match(dto.NickName + dto.Content, CommonHelper.ModRegex).Length <= 0) { comment.Status = Status.Published; } comment.CommentDate = DateTime.Now; var user = HttpContext.Session.Get(SessionKey.UserInfo); if (user != null) { comment.NickName = user.NickName; comment.Email = user.Email; if (user.IsAdmin) { comment.Status = Status.Published; comment.IsMaster = true; } } comment.Content = await dto.Content.HtmlSantinizerStandard().ClearImgAttributes(); comment.Browser = dto.Browser ?? Request.Headers[HeaderNames.UserAgent]; comment.IP = ClientIP; comment.Location = Request.Location(); comment = CommentService.AddEntitySaved(comment); if (comment == null) { return ResultData(null, false, "评论失败"); } Response.Cookies.Append("NickName", comment.NickName, new CookieOptions() { Expires = DateTimeOffset.Now.AddYears(1), SameSite = SameSiteMode.Lax }); WriteEmailKeyCookie(dto.Email); CommentFeq.AddOrUpdate("Comments:" + ClientIP, 1, i => i + 1, 5); CommentFeq.Expire("Comments:" + ClientIP, TimeSpan.FromMinutes(1)); var emails = new HashSet(); var email = CommonHelper.SystemSettings["ReceiveEmail"]; //站长邮箱 emails.Add(email); var content = new Template(await System.IO.File.ReadAllTextAsync(HostEnvironment.WebRootPath + "/template/notify.html")) .Set("title", post.Title) .Set("time", DateTime.Now.ToTimeZoneF(HttpContext.Session.Get(SessionKey.TimeZone))) .Set("nickname", comment.NickName) .Set("content", comment.Content); if (comment.Status == Status.Published) { if (!comment.IsMaster) { await messageService.AddEntitySavedAsync(new InternalMessage() { Title = $"来自【{comment.NickName}】在文章《{post.Title}》的新评论", Content = comment.Content, Link = Url.Action("Details", "Post", new { id = comment.PostId, cid = comment.Id }) + "#comment" }); } if (comment.ParentId == 0) { emails.Add(post.Email); emails.Add(post.ModifierEmail); //新评论,只通知博主和楼主 foreach (var s in emails) { BackgroundJob.Enqueue(() => CommonHelper.SendMail(Request.Host + "|博客文章新评论:", content.Set("link", Url.Action("Details", "Post", new { id = comment.PostId, cid = comment.Id }, Request.Scheme) + "#comment").Render(false), s, ClientIP)); } } else { //通知博主和上层所有关联的评论访客 var parent = await CommentService.GetByIdAsync(comment.ParentId); emails.AddRange(parent.Root().Flatten().Select(c => c.Email).ToArray()); emails.AddRange(post.Email, post.ModifierEmail); emails.Remove(comment.Email); string link = Url.Action("Details", "Post", new { id = comment.PostId, cid = comment.Id }, Request.Scheme) + "#comment"; foreach (var s in emails) { BackgroundJob.Enqueue(() => CommonHelper.SendMail($"{Request.Host}{CommonHelper.SystemSettings["Title"]}文章评论回复:", content.Set("link", link).Render(false), s, ClientIP)); } } return ResultData(null, true, "评论发表成功,服务器正在后台处理中,这会有一定的延迟,稍后将显示到评论列表中"); } foreach (var s in emails) { BackgroundJob.Enqueue(() => CommonHelper.SendMail(Request.Host + "|博客文章新评论(待审核):", content.Set("link", Url.Action("Details", "Post", new { id = comment.PostId, cid = comment.Id }, Request.Scheme) + "#comment").Render(false) + "

(待审核)

", s, ClientIP)); } return ResultData(null, true, "评论成功,待站长审核通过以后将显示"); } /// /// 评论投票 /// /// /// [HttpPost] public async Task CommentVote(int id) { if (HttpContext.Session.Get("cm" + id) != null) { return ResultData(null, false, "您刚才已经投过票了,感谢您的参与!"); } var cm = await CommentService.GetAsync(c => c.Id == id && c.Status == Status.Published) ?? throw new NotFoundException("评论不存在!"); cm.VoteCount++; bool b = await CommentService.SaveChangesAsync() > 0; if (b) { HttpContext.Session.Set("cm" + id, id.GetBytes()); } return ResultData(null, b, b ? "投票成功" : "投票失败"); } /// /// 获取评论 /// /// /// /// /// /// public async Task GetComments(int? id, [Range(1, int.MaxValue, ErrorMessage = "页码必须大于0")] int page = 1, [Range(1, 50, ErrorMessage = "页大小必须在0到50之间")] int size = 15, int cid = 0) { if (cid != 0) { var comment = await CommentService.GetByIdAsync(cid) ?? throw new NotFoundException("评论未找到"); var single = new[] { comment.Root() }; foreach (var c in single.Flatten()) { c.CommentDate = c.CommentDate.ToTimeZone(HttpContext.Session.Get(SessionKey.TimeZone)); } return ResultData(new { total = 1, parentTotal = 1, page, size, rows = single.Mapper>() }); } var parent = await CommentService.GetPagesAsync(page, size, c => c.PostId == id && c.ParentId == 0 && (c.Status == Status.Published || CurrentUser.IsAdmin), c => c.CommentDate, false); if (!parent.Data.Any()) { return ResultData(null, false, "没有评论"); } int total = parent.TotalCount; //总条数,用于前台分页 parent.Data.Flatten().ForEach(c => { c.CommentDate = c.CommentDate.ToTimeZone(HttpContext.Session.Get(SessionKey.TimeZone)); }); if (total > 0) { return ResultData(new { total, parentTotal = total, page, size, rows = parent.Data.Mapper>() }); } return ResultData(null, false, "没有评论"); } /// /// 审核评论 /// /// /// [MyAuthorize] public async Task Pass(int id) { Comment comment = await CommentService.GetByIdAsync(id) ?? throw new NotFoundException("评论不存在!"); comment.Status = Status.Published; Post post = await PostService.GetByIdAsync(comment.PostId); bool b = await CommentService.SaveChangesAsync() > 0; if (b) { var content = new Template(await System.IO.File.ReadAllTextAsync(Path.Combine(HostEnvironment.WebRootPath, "template", "notify.html"))) .Set("title", post.Title) .Set("time", DateTime.Now.ToTimeZoneF(HttpContext.Session.Get(SessionKey.TimeZone))) .Set("nickname", comment.NickName) .Set("content", comment.Content); var root = comment.Root(); var emails = root.Flatten().Select(c => c.Email).Append(post.ModifierEmail).Except(new List { comment.Email, CurrentUser.Email }).ToHashSet(); var link = Url.Action("Details", "Post", new { id = comment.PostId, cid = root.Id }, Request.Scheme) + "#comment"; foreach (var email in emails) { BackgroundJob.Enqueue(() => CommonHelper.SendMail($"{Request.Host}{CommonHelper.SystemSettings["Title"]}文章评论回复:", content.Set("link", link).Render(false), email, ClientIP)); } return ResultData(null, true, "审核通过!"); } return ResultData(null, false, "审核失败!"); } /// /// 删除评论 /// /// /// [MyAuthorize] public ActionResult Delete(int id) { var b = CommentService.DeleteByIdSaved(id); return ResultData(null, b, b ? "删除成功!" : "删除失败!"); } /// /// 获取未审核的评论 /// /// [MyAuthorize] public async Task GetPendingComments([Range(1, int.MaxValue, ErrorMessage = "页码必须大于0")] int page = 1, [Range(1, 50, ErrorMessage = "页大小必须在0到50之间")] int size = 15) { var pages = await CommentService.GetPagesAsync(page, size, c => c.Status == Status.Pending, c => c.CommentDate, false); foreach (var item in pages.Data) { item.CommentDate = item.CommentDate.ToTimeZone(HttpContext.Session.Get(SessionKey.TimeZone)); } return Ok(pages); } } }