MsgController.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. using CacheManager.Core;
  2. using Hangfire;
  3. using Masuit.MyBlogs.Core.Common;
  4. using Masuit.MyBlogs.Core.Common.Mails;
  5. using Masuit.MyBlogs.Core.Extensions;
  6. using Masuit.MyBlogs.Core.Infrastructure.Services.Interface;
  7. using Masuit.MyBlogs.Core.Models.Command;
  8. using Masuit.MyBlogs.Core.Models.DTO;
  9. using Masuit.MyBlogs.Core.Models.Entity;
  10. using Masuit.MyBlogs.Core.Models.Enum;
  11. using Masuit.MyBlogs.Core.Models.ViewModel;
  12. using Masuit.Tools.Core.Net;
  13. using Masuit.Tools.Html;
  14. using Masuit.Tools.Logging;
  15. using Masuit.Tools.Strings;
  16. using Microsoft.AspNetCore.Hosting;
  17. using Microsoft.AspNetCore.Http;
  18. using Microsoft.AspNetCore.Mvc;
  19. using Microsoft.Net.Http.Headers;
  20. using System;
  21. using System.Collections.Generic;
  22. using System.ComponentModel.DataAnnotations;
  23. using System.IO;
  24. using System.Linq;
  25. using System.Text.RegularExpressions;
  26. using System.Threading.Tasks;
  27. using SameSiteMode = Microsoft.AspNetCore.Http.SameSiteMode;
  28. namespace Masuit.MyBlogs.Core.Controllers
  29. {
  30. /// <summary>
  31. /// 留言板和站内信
  32. /// </summary>
  33. public class MsgController : BaseController
  34. {
  35. /// <summary>
  36. /// 留言
  37. /// </summary>
  38. public ILeaveMessageService LeaveMessageService { get; set; }
  39. /// <summary>
  40. /// 站内信
  41. /// </summary>
  42. public IInternalMessageService MessageService { get; set; }
  43. public IWebHostEnvironment HostEnvironment { get; set; }
  44. public ICacheManager<int> MsgFeq { get; set; }
  45. /// <summary>
  46. /// 留言板
  47. /// </summary>
  48. /// <returns></returns>
  49. [ResponseCache(Duration = 600, VaryByHeader = "Cookie"), Route("msg")]
  50. public async Task<ActionResult> Index()
  51. {
  52. ViewBag.TotalCount = LeaveMessageService.Count(m => m.ParentId == 0 && m.Status == Status.Published);
  53. var text = await System.IO.File.ReadAllTextAsync(Path.Combine(HostEnvironment.WebRootPath, "template", "agreement.html"));
  54. return CurrentUser.IsAdmin ? View("Index_Admin", text) : View(model: text);
  55. }
  56. /// <summary>
  57. /// 获取留言
  58. /// </summary>
  59. /// <param name="page"></param>
  60. /// <param name="size"></param>
  61. /// <param name="cid"></param>
  62. /// <returns></returns>
  63. public ActionResult GetMsgs([Range(1, int.MaxValue, ErrorMessage = "页码必须大于0")] int page = 1, [Range(1, 50, ErrorMessage = "页大小必须在0到50之间")] int size = 15, int cid = 0)
  64. {
  65. int total;
  66. if (cid != 0)
  67. {
  68. int pid = LeaveMessageService.GetParentMessageIdByChildId(cid);
  69. var single = LeaveMessageService.GetSelfAndAllChildrenMessagesByParentId(pid).ToList();
  70. if (single.Any())
  71. {
  72. total = 1;
  73. foreach (var m in single)
  74. {
  75. m.PostDate = m.PostDate.ToTimeZone(HttpContext.Session.Get<string>(SessionKey.TimeZone));
  76. }
  77. return ResultData(new
  78. {
  79. total,
  80. parentTotal = total,
  81. page,
  82. size,
  83. rows = single.Mapper<IList<LeaveMessageViewModel>>()
  84. });
  85. }
  86. }
  87. var parent = LeaveMessageService.GetPagesNoTracking(page, size, m => m.ParentId == 0 && (m.Status == Status.Published || CurrentUser.IsAdmin), m => m.PostDate, false);
  88. if (!parent.Data.Any())
  89. {
  90. return ResultData(null, false, "没有留言");
  91. }
  92. total = parent.TotalCount;
  93. var qlist = parent.Data.SelectMany(c => LeaveMessageService.GetSelfAndAllChildrenMessagesByParentId(c.Id)).Where(c => c.Status == Status.Published || CurrentUser.IsAdmin).Select(m =>
  94. {
  95. m.PostDate = m.PostDate.ToTimeZone(HttpContext.Session.Get<string>(SessionKey.TimeZone));
  96. return m;
  97. });
  98. if (total > 0)
  99. {
  100. return ResultData(new
  101. {
  102. total,
  103. parentTotal = total,
  104. page,
  105. size,
  106. rows = Mapper.Map<List<LeaveMessageViewModel>>(qlist)
  107. });
  108. }
  109. return ResultData(null, false, "没有留言");
  110. }
  111. /// <summary>
  112. /// 发表留言
  113. /// </summary>
  114. /// <param name="mailSender"></param>
  115. /// <param name="dto"></param>
  116. /// <returns></returns>
  117. [HttpPost, ValidateAntiForgeryToken]
  118. public async Task<ActionResult> Submit([FromServices] IMailSender mailSender, LeaveMessageCommand dto)
  119. {
  120. var match = Regex.Match(dto.NickName + dto.Content.RemoveHtmlTag(), CommonHelper.BanRegex);
  121. if (match.Success)
  122. {
  123. LogManager.Info($"提交内容:{dto.NickName}/{dto.Content},敏感词:{match.Value}");
  124. return ResultData(null, false, "您提交的内容包含敏感词,被禁止发表,请检查您的内容后尝试重新提交!");
  125. }
  126. if (mailSender.HasBounced(dto.Email) || (!CurrentUser.IsAdmin && dto.Email.EndsWith(CommonHelper.SystemSettings["Domain"])))
  127. {
  128. Response.Cookies.Delete("Email");
  129. Response.Cookies.Delete("QQorWechat");
  130. Response.Cookies.Delete("NickName");
  131. return ResultData(null, false, "邮箱地址错误,请刷新页面后重新使用有效的邮箱地址!");
  132. }
  133. dto.Content = dto.Content.Trim().Replace("<p><br></p>", string.Empty);
  134. if (MsgFeq.GetOrAdd("Comments:" + ClientIP, 1) > 2)
  135. {
  136. MsgFeq.Expire("Comments:" + ClientIP, TimeSpan.FromMinutes(1));
  137. return ResultData(null, false, "您的发言频率过快,请稍后再发表吧!");
  138. }
  139. var msg = dto.Mapper<LeaveMessage>();
  140. if (Regex.Match(dto.NickName + dto.Content, CommonHelper.ModRegex).Length <= 0)
  141. {
  142. msg.Status = Status.Published;
  143. }
  144. msg.PostDate = DateTime.Now;
  145. var user = HttpContext.Session.Get<UserInfoDto>(SessionKey.UserInfo);
  146. if (user != null)
  147. {
  148. msg.NickName = user.NickName;
  149. msg.QQorWechat = user.QQorWechat;
  150. msg.Email = user.Email;
  151. if (user.IsAdmin)
  152. {
  153. msg.Status = Status.Published;
  154. msg.IsMaster = true;
  155. }
  156. }
  157. msg.Content = dto.Content.HtmlSantinizerStandard().ClearImgAttributes();
  158. msg.Browser = dto.Browser ?? Request.Headers[HeaderNames.UserAgent];
  159. msg.IP = ClientIP;
  160. msg.Location = Request.Location();
  161. msg = LeaveMessageService.AddEntitySaved(msg);
  162. if (msg == null)
  163. {
  164. return ResultData(null, false, "留言发表失败!");
  165. }
  166. Response.Cookies.Append("Email", msg.Email, new CookieOptions()
  167. {
  168. Expires = DateTimeOffset.Now.AddYears(1),
  169. SameSite = SameSiteMode.Lax
  170. });
  171. Response.Cookies.Append("QQorWechat", msg.QQorWechat + "", new CookieOptions()
  172. {
  173. Expires = DateTimeOffset.Now.AddYears(1),
  174. SameSite = SameSiteMode.Lax
  175. });
  176. Response.Cookies.Append("NickName", msg.NickName, new CookieOptions()
  177. {
  178. Expires = DateTimeOffset.Now.AddYears(1),
  179. SameSite = SameSiteMode.Lax
  180. });
  181. MsgFeq.AddOrUpdate("Comments:" + ClientIP, 1, i => i + 1, 5);
  182. MsgFeq.Expire("Comments:" + ClientIP, TimeSpan.FromMinutes(1));
  183. var email = CommonHelper.SystemSettings["ReceiveEmail"];
  184. var content = new Template(await System.IO.File.ReadAllTextAsync(HostEnvironment.WebRootPath + "/template/notify.html")).Set("title", "网站留言板").Set("time", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")).Set("nickname", msg.NickName).Set("content", msg.Content);
  185. if (msg.Status == Status.Published)
  186. {
  187. if (!msg.IsMaster)
  188. {
  189. await MessageService.AddEntitySavedAsync(new InternalMessage()
  190. {
  191. Title = $"来自【{msg.NickName}】的新留言",
  192. Content = msg.Content,
  193. Link = Url.Action("Index", "Msg", new { cid = msg.Id })
  194. });
  195. }
  196. if (msg.ParentId == 0)
  197. {
  198. //新评论,只通知博主
  199. BackgroundJob.Enqueue(() => CommonHelper.SendMail(Request.Host + "|博客新留言:", content.Set("link", Url.Action("Index", "Msg", new { cid = msg.Id }, Request.Scheme)).Render(false), email, ClientIP));
  200. }
  201. else
  202. {
  203. //通知博主和上层所有关联的评论访客
  204. var pid = LeaveMessageService.GetParentMessageIdByChildId(msg.Id);
  205. var emails = LeaveMessageService.GetSelfAndAllChildrenMessagesByParentId(pid).Select(c => c.Email).Append(email).Except(new[] { msg.Email }).ToHashSet();
  206. string link = Url.Action("Index", "Msg", new { cid = msg.Id }, Request.Scheme);
  207. foreach (var s in emails)
  208. {
  209. BackgroundJob.Enqueue(() => CommonHelper.SendMail($"{Request.Host}{CommonHelper.SystemSettings["Title"]} 留言回复:", content.Set("link", link).Render(false), s, ClientIP));
  210. }
  211. }
  212. return ResultData(null, true, "留言发表成功,服务器正在后台处理中,这会有一定的延迟,稍后将会显示到列表中!");
  213. }
  214. BackgroundJob.Enqueue(() => CommonHelper.SendMail(Request.Host + "|博客新留言(待审核):", content.Set("link", Url.Action("Index", "Msg", new
  215. {
  216. cid = msg.Id
  217. }, Request.Scheme)).Render(false) + "<p style='color:red;'>(待审核)</p>", email, ClientIP));
  218. return ResultData(null, true, "留言发表成功,待站长审核通过以后将显示到列表中!");
  219. }
  220. /// <summary>
  221. /// 审核
  222. /// </summary>
  223. /// <param name="id"></param>
  224. /// <returns></returns>
  225. [MyAuthorize]
  226. public async Task<ActionResult> Pass(int id)
  227. {
  228. var msg = await LeaveMessageService.GetByIdAsync(id);
  229. msg.Status = Status.Published;
  230. bool b = await LeaveMessageService.SaveChangesAsync() > 0;
  231. var pid = msg.ParentId == 0 ? msg.Id : LeaveMessageService.GetParentMessageIdByChildId(id);
  232. var content = new Template(await System.IO.File.ReadAllTextAsync(Path.Combine(HostEnvironment.WebRootPath, "template", "notify.html"))).Set("time", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")).Set("nickname", msg.NickName).Set("content", msg.Content);
  233. var emails = LeaveMessageService.GetSelfAndAllChildrenMessagesByParentId(pid).Select(c => c.Email).Except(new List<string> { msg.Email, CurrentUser.Email }).ToHashSet();
  234. var link = Url.Action("Index", "Msg", new { cid = pid }, Request.Scheme);
  235. foreach (var s in emails)
  236. {
  237. BackgroundJob.Enqueue(() => CommonHelper.SendMail($"{Request.Host}{CommonHelper.SystemSettings["Title"]} 留言回复:", content.Set("link", link).Render(false), s, ClientIP));
  238. }
  239. return ResultData(null, b, b ? "审核通过!" : "审核失败!");
  240. }
  241. /// <summary>
  242. /// 删除留言
  243. /// </summary>
  244. /// <param name="id"></param>
  245. /// <returns></returns>
  246. [MyAuthorize]
  247. public ActionResult Delete(int id)
  248. {
  249. var b = LeaveMessageService.DeleteEntitiesSaved(LeaveMessageService.GetSelfAndAllChildrenMessagesByParentId(id).ToList());
  250. return ResultData(null, b, b ? "删除成功!" : "删除失败!");
  251. }
  252. /// <summary>
  253. /// 获取待审核的留言
  254. /// </summary>
  255. /// <returns></returns>
  256. [MyAuthorize]
  257. public ActionResult GetPendingMsgs([Range(1, int.MaxValue, ErrorMessage = "页码必须大于0")] int page = 1, [Range(1, 50, ErrorMessage = "页大小必须在0到50之间")] int size = 15)
  258. {
  259. var list = LeaveMessageService.GetPages<DateTime, LeaveMessageDto>(page, size, m => m.Status == Status.Pending, l => l.PostDate, false);
  260. foreach (var m in list.Data)
  261. {
  262. m.PostDate = m.PostDate.ToTimeZone(HttpContext.Session.Get<string>(SessionKey.TimeZone));
  263. }
  264. return Ok(list);
  265. }
  266. #region 站内消息
  267. /// <summary>
  268. /// 已读站内信
  269. /// </summary>
  270. /// <param name="id"></param>
  271. /// <returns></returns>
  272. [MyAuthorize]
  273. public async Task<ActionResult> Read(int id)
  274. {
  275. var msg = await MessageService.GetByIdAsync(id);
  276. msg.Read = true;
  277. await MessageService.SaveChangesAsync();
  278. return Content("ok");
  279. }
  280. /// <summary>
  281. /// 标记为未读
  282. /// </summary>
  283. /// <param name="id"></param>
  284. /// <returns></returns>
  285. [MyAuthorize]
  286. public async Task<ActionResult> Unread(int id)
  287. {
  288. var msg = await MessageService.GetByIdAsync(id);
  289. msg.Read = false;
  290. await MessageService.SaveChangesAsync();
  291. return Content("ok");
  292. }
  293. /// <summary>
  294. /// 删除站内信
  295. /// </summary>
  296. /// <param name="id"></param>
  297. /// <returns></returns>
  298. [MyAuthorize]
  299. public async Task<ActionResult> DeleteMsg(int id)
  300. {
  301. bool b = await MessageService.DeleteByIdSavedAsync(id) > 0;
  302. return ResultData(null, b, b ? "站内消息删除成功!" : "站内消息删除失败!");
  303. }
  304. /// <summary>
  305. /// 获取站内信
  306. /// </summary>
  307. /// <param name="page"></param>
  308. /// <param name="size"></param>
  309. /// <returns></returns>
  310. [MyAuthorize]
  311. public ActionResult GetInternalMsgs([Range(1, int.MaxValue, ErrorMessage = "页码必须大于0")] int page = 1, [Range(1, 50, ErrorMessage = "页大小必须在0到50之间")] int size = 15)
  312. {
  313. var msgs = MessageService.GetPagesNoTracking(page, size, m => true, m => m.Time, false);
  314. return Ok(msgs);
  315. }
  316. /// <summary>
  317. /// 获取未读消息
  318. /// </summary>
  319. /// <returns></returns>
  320. [MyAuthorize]
  321. public ActionResult GetUnreadMsgs()
  322. {
  323. var msgs = MessageService.GetQueryNoTracking(m => !m.Read, m => m.Time, false).ToList();
  324. return ResultData(msgs);
  325. }
  326. /// <summary>
  327. /// 清除站内信
  328. /// </summary>
  329. /// <returns></returns>
  330. [MyAuthorize]
  331. public async Task<ActionResult> ClearMsgs()
  332. {
  333. await MessageService.DeleteEntitySavedAsync(m => m.Read);
  334. return ResultData(null, true, "站内消息清除成功!");
  335. }
  336. /// <summary>
  337. /// 标记为已读
  338. /// </summary>
  339. /// <param name="id"></param>
  340. /// <returns></returns>
  341. [MyAuthorize]
  342. public ActionResult MarkRead(int id)
  343. {
  344. var msgs = MessageService.GetQuery(m => m.Id <= id).ToList();
  345. foreach (var t in msgs)
  346. {
  347. t.Read = true;
  348. }
  349. MessageService.SaveChanges();
  350. return ResultData(null);
  351. }
  352. #endregion
  353. }
  354. }