فهرست منبع

新增文章公共编辑功能

懒得勤快 6 سال پیش
والد
کامیت
ea5837f7e3
66فایلهای تغییر یافته به همراه1589 افزوده شده و 385 حذف شده
  1. 9 1
      src/Masuit.MyBlogs.Core/Configs/MappingProfile.cs
  2. 0 9
      src/Masuit.MyBlogs.Core/Controllers/CategoryController.cs
  3. 18 27
      src/Masuit.MyBlogs.Core/Controllers/CommentController.cs
  4. 0 15
      src/Masuit.MyBlogs.Core/Controllers/DashboardController.cs
  5. 0 9
      src/Masuit.MyBlogs.Core/Controllers/DonateController.cs
  6. 1 1
      src/Masuit.MyBlogs.Core/Controllers/ErrorController.cs
  7. 10 18
      src/Masuit.MyBlogs.Core/Controllers/FileController.cs
  8. 0 5
      src/Masuit.MyBlogs.Core/Controllers/HomeController.cs
  9. 0 9
      src/Masuit.MyBlogs.Core/Controllers/MenuController.cs
  10. 146 0
      src/Masuit.MyBlogs.Core/Controllers/MergeController.cs
  11. 3 10
      src/Masuit.MyBlogs.Core/Controllers/MiscController.cs
  12. 3 16
      src/Masuit.MyBlogs.Core/Controllers/MsgController.cs
  13. 4 8
      src/Masuit.MyBlogs.Core/Controllers/NoticeController.cs
  14. 2 20
      src/Masuit.MyBlogs.Core/Controllers/PassportController.cs
  15. 132 58
      src/Masuit.MyBlogs.Core/Controllers/PostController.cs
  16. 3 17
      src/Masuit.MyBlogs.Core/Controllers/SearchController.cs
  17. 2 15
      src/Masuit.MyBlogs.Core/Controllers/SeminarController.cs
  18. 2 15
      src/Masuit.MyBlogs.Core/Controllers/SubscribeController.cs
  19. 0 11
      src/Masuit.MyBlogs.Core/Controllers/SystemController.cs
  20. 9 10
      src/Masuit.MyBlogs.Core/Controllers/UploadController.cs
  21. 1 12
      src/Masuit.MyBlogs.Core/Controllers/UserController.cs
  22. 41 0
      src/Masuit.MyBlogs.Core/Controllers/ValidateController.cs
  23. 0 5
      src/Masuit.MyBlogs.Core/Extensions/IApplicationBuilderExtensions.cs
  24. 50 0
      src/Masuit.MyBlogs.Core/Extensions/MyExceptionFilter.cs
  25. 11 0
      src/Masuit.MyBlogs.Core/Extensions/NotFoundException.cs
  26. 2 0
      src/Masuit.MyBlogs.Core/Infrastructure/DataContext.cs
  27. 1 0
      src/Masuit.MyBlogs.Core/Infrastructure/Repository/Interface/IBaseRepository.cs
  28. 3 0
      src/Masuit.MyBlogs.Core/Infrastructure/Repository/Repositories.cs
  29. 1 0
      src/Masuit.MyBlogs.Core/Infrastructure/Services/Interface/IServices.cs
  30. 6 0
      src/Masuit.MyBlogs.Core/Infrastructure/Services/Services.cs
  31. 2 2
      src/Masuit.MyBlogs.Core/Masuit.MyBlogs.Core.csproj
  32. 33 0
      src/Masuit.MyBlogs.Core/Models/DTO/PostMergeRequestInputDto.cs
  33. 23 0
      src/Masuit.MyBlogs.Core/Models/DTO/PostMergeRequestInputDtoBase.cs
  34. 13 0
      src/Masuit.MyBlogs.Core/Models/DTO/PostMergeRequestOutputDto.cs
  35. 46 0
      src/Masuit.MyBlogs.Core/Models/DTO/PostMergeRequestOutputDtoBase.cs
  36. 10 0
      src/Masuit.MyBlogs.Core/Models/DTO/PostOutputDto.cs
  37. 1 1
      src/Masuit.MyBlogs.Core/Models/DTO/UserInfoOutputDto.cs
  38. 16 0
      src/Masuit.MyBlogs.Core/Models/Entity/Post.cs
  39. 10 0
      src/Masuit.MyBlogs.Core/Models/Entity/PostHistoryVersion.cs
  40. 51 0
      src/Masuit.MyBlogs.Core/Models/Entity/PostMergeRequest.cs
  41. 23 0
      src/Masuit.MyBlogs.Core/Models/Enum/MergeStatus.cs
  42. 3 1
      src/Masuit.MyBlogs.Core/Startup.cs
  43. 2 2
      src/Masuit.MyBlogs.Core/Views/Post/CompareVersion.cshtml
  44. 5 2
      src/Masuit.MyBlogs.Core/Views/Post/Details.cshtml
  45. 5 2
      src/Masuit.MyBlogs.Core/Views/Post/Details_Admin.cshtml
  46. 6 3
      src/Masuit.MyBlogs.Core/Views/Post/History.cshtml
  47. 1 1
      src/Masuit.MyBlogs.Core/Views/Post/HistoryVersion.cshtml
  48. 1 1
      src/Masuit.MyBlogs.Core/Views/Post/HistoryVersion_Admin.cshtml
  49. 17 8
      src/Masuit.MyBlogs.Core/Views/Post/Publish.cshtml
  50. 195 0
      src/Masuit.MyBlogs.Core/Views/Post/PushMerge.cshtml
  51. 196 0
      src/Masuit.MyBlogs.Core/Views/Post/RepushMerge.cshtml
  52. 24 0
      src/Masuit.MyBlogs.Core/bundleconfig.json
  53. 30 0
      src/Masuit.MyBlogs.Core/wwwroot/ng-views/app/route.config.js
  54. 26 0
      src/Masuit.MyBlogs.Core/wwwroot/ng-views/controllers/main.js
  55. 189 0
      src/Masuit.MyBlogs.Core/wwwroot/ng-views/controllers/merge.js
  56. 0 0
      src/Masuit.MyBlogs.Core/wwwroot/ng-views/controllers/merge.min.js
  57. 60 42
      src/Masuit.MyBlogs.Core/wwwroot/ng-views/controllers/post.js
  58. 0 0
      src/Masuit.MyBlogs.Core/wwwroot/ng-views/controllers/post.min.js
  59. 1 0
      src/Masuit.MyBlogs.Core/wwwroot/ng-views/template/sidebar-left.html
  60. 49 0
      src/Masuit.MyBlogs.Core/wwwroot/ng-views/views/merge/compare.html
  61. 12 0
      src/Masuit.MyBlogs.Core/wwwroot/ng-views/views/merge/edit.html
  62. 54 0
      src/Masuit.MyBlogs.Core/wwwroot/ng-views/views/merge/list.html
  63. 0 29
      src/Masuit.MyBlogs.Core/wwwroot/ng-views/views/post/postlist.html
  64. 8 0
      src/Masuit.MyBlogs.Core/wwwroot/template/merge-pass.html
  65. 9 0
      src/Masuit.MyBlogs.Core/wwwroot/template/merge-reject.html
  66. 8 0
      src/Masuit.MyBlogs.Core/wwwroot/template/merge-request.html

+ 9 - 1
src/Masuit.MyBlogs.Core/Configs/MappingProfile.cs

@@ -52,7 +52,7 @@ namespace Masuit.MyBlogs.Core.Configs
 
             CreateMap<Post, PostInputDto>().ReverseMap();
             CreateMap<Post, PostModelBase>();
-            CreateMap<Post, PostHistoryVersion>().ForMember(v => v.PostId, e => e.MapFrom(p => p.Id));
+            CreateMap<Post, PostHistoryVersion>().ForMember(p => p.Id, e => e.Ignore()).ForMember(v => v.PostId, e => e.MapFrom(p => p.Id));
             CreateMap<Post, PostOutputDto>().ForMember(p => p.CategoryName, e => e.MapFrom(p => p.Category.Name)).ForMember(p => p.CommentCount, e => e.MapFrom(p => p.Comment.Count(c => c.Status == Status.Pended))).ReverseMap();
             CreateMap<PostInputDto, PostOutputDto>().ReverseMap();
             CreateMap<PostHistoryVersion, PostOutputDto>().ForMember(p => p.CategoryName, e => e.MapFrom(p => p.Category.Name)).ReverseMap();
@@ -73,6 +73,14 @@ namespace Masuit.MyBlogs.Core.Configs
             CreateMap<SeminarInputDto, SeminarOutputDto>().ReverseMap();
 
             CreateMap<SeminarPost, SeminarPostHistoryVersion>().ForMember(s => s.PostHistoryVersionId, e => e.MapFrom(s => s.PostId)).ReverseMap();
+
+            CreateMap<PostMergeRequestInputDtoBase, PostMergeRequest>().ForMember(p => p.Id, e => e.Ignore()).ForMember(p => p.MergeState, e => e.Ignore()).ReverseMap();
+            CreateMap<PostMergeRequestInputDto, PostMergeRequest>().ForMember(p => p.Id, e => e.Ignore()).ForMember(p => p.MergeState, e => e.Ignore()).ReverseMap();
+            CreateMap<PostMergeRequestInputDto, Post>().ForMember(p => p.Id, e => e.Ignore()).ReverseMap();
+            CreateMap<PostMergeRequest, PostMergeRequestOutputDtoBase>().ForMember(p => p.PostTitle, e => e.MapFrom(r => r.Post.Title));
+            CreateMap<PostMergeRequest, PostMergeRequestOutputDto>().ForMember(p => p.PostTitle, e => e.MapFrom(r => r.Post.Title));
+            CreateMap<PostMergeRequest, Post>().ForMember(p => p.Id, e => e.Ignore()).ReverseMap();
+            CreateMap<Post, PostMergeRequestOutputDto>().ReverseMap();
         }
     }
 }

+ 0 - 9
src/Masuit.MyBlogs.Core/Controllers/CategoryController.cs

@@ -19,15 +19,6 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// </summary>
         public ICategoryService CategoryService { get; set; }
 
-        /// <summary>
-        /// 文章分类
-        /// </summary>
-        /// <param name="categoryService"></param>
-        public CategoryController(ICategoryService categoryService)
-        {
-            CategoryService = categoryService;
-        }
-
         /// <summary>
         /// 获取所有分类
         /// </summary>

+ 18 - 27
src/Masuit.MyBlogs.Core/Controllers/CommentController.cs

@@ -6,6 +6,7 @@ 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 Microsoft.AspNetCore.Hosting;
@@ -25,25 +26,10 @@ namespace Masuit.MyBlogs.Core.Controllers
     /// </summary>
     public class CommentController : BaseController
     {
-        private ICommentService CommentService { get; }
-        private IPostService PostService { get; }
-        private IInternalMessageService MessageService { get; }
-        private readonly IHostingEnvironment _hostingEnvironment;
-
-        /// <summary>
-        /// 评论管理
-        /// </summary>
-        /// <param name="commentService"></param>
-        /// <param name="postService"></param>
-        /// <param name="messageService"></param>
-        /// <param name="hostingEnvironment"></param>
-        public CommentController(ICommentService commentService, IPostService postService, IInternalMessageService messageService, IHostingEnvironment hostingEnvironment)
-        {
-            CommentService = commentService;
-            PostService = postService;
-            MessageService = messageService;
-            _hostingEnvironment = hostingEnvironment;
-        }
+        public ICommentService CommentService { get; set; }
+        public IPostService PostService { get; set; }
+        public IInternalMessageService MessageService { get; set; }
+        public IHostingEnvironment HostingEnvironment { get; set; }
 
         /// <summary>
         /// 发表评论
@@ -100,10 +86,10 @@ namespace Masuit.MyBlogs.Core.Controllers
             if (com != null)
             {
                 HttpContext.Session.Set("comment" + comment.PostId, comment.Content.RemoveHtmlTag().Trim());
-                var emails = new List<string>();
+                var emails = new HashSet<string>();
                 var email = CommonHelper.SystemSettings["ReceiveEmail"]; //站长邮箱
                 emails.Add(email);
-                string content = System.IO.File.ReadAllText(_hostingEnvironment.WebRootPath + "/template/notify.html").Replace("{{title}}", post.Title).Replace("{{time}}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")).Replace("{{nickname}}", com.NickName).Replace("{{content}}", com.Content);
+                string content = System.IO.File.ReadAllText(HostingEnvironment.WebRootPath + "/template/notify.html").Replace("{{title}}", post.Title).Replace("{{time}}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")).Replace("{{nickname}}", com.NickName).Replace("{{content}}", com.Content);
                 if (comment.Status == Status.Pended)
                 {
                     if (!com.IsMaster)
@@ -118,18 +104,22 @@ namespace Masuit.MyBlogs.Core.Controllers
 #if !DEBUG
                     if (com.ParentId == 0)
                     {
-                        emails.Add(PostService.GetById(com.PostId).Email);
+                        emails.Add(post.Email);
+                        emails.Add(post.ModifierEmail);
                         //新评论,只通知博主和楼主
-                        foreach (var s in emails.Distinct())
+                        foreach (var s in emails)
                         {
                             BackgroundJob.Enqueue(() => CommonHelper.SendMail(CommonHelper.SystemSettings["Domain"] + "|博客文章新评论:", content.Replace("{{link}}", Url.Action("Details", "Post", new { id = com.PostId, cid = com.Id }, Request.Scheme) + "#comment"), s));
                         }
                     }
                     else
                     {
+                        emails.Add(post.Email);
+                        emails.Add(post.ModifierEmail);
                         //通知博主和上层所有关联的评论访客
                         var pid = CommentService.GetParentCommentIdByChildId(com.Id);
-                        emails = CommentService.GetSelfAndAllChildrenCommentsByParentId(pid).Select(c => c.Email).Except(new List<string> { com.Email }).Distinct().ToList();
+                        CommentService.GetSelfAndAllChildrenCommentsByParentId(pid).Select(c => c.Email).ForEach(s => emails.Add(s));
+                        emails.Remove(com.Email);
                         string link = Url.Action("Details", "Post", new { id = com.PostId, cid = com.Id }, Request.Scheme) + "#comment";
                         foreach (var s in emails)
                         {
@@ -139,7 +129,7 @@ namespace Masuit.MyBlogs.Core.Controllers
 #endif
                     return ResultData(null, true, "评论发表成功,服务器正在后台处理中,这会有一定的延迟,稍后将显示到评论列表中");
                 }
-                foreach (var s in emails.Distinct())
+                foreach (var s in emails)
                 {
                     BackgroundJob.Enqueue(() => CommonHelper.SendMail(CommonHelper.SystemSettings["Domain"] + "|博客文章新评论(待审核):", content.Replace("{{link}}", Url.Action("Details", "Post", new { id = com.PostId, cid = com.Id }, Request.Scheme) + "#comment") + "<p style='color:red;'>(待审核)</p>", s));
                 }
@@ -289,14 +279,15 @@ namespace Masuit.MyBlogs.Core.Controllers
             bool b = CommentService.UpdateEntitySaved(comment);
             var pid = comment.ParentId == 0 ? comment.Id : CommentService.GetParentCommentIdByChildId(id);
 #if !DEBUG
-            string content = System.IO.File.ReadAllText(Path.Combine(_hostingEnvironment.WebRootPath, "template", "notify.html")).Replace("{{title}}", post.Title).Replace("{{time}}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")).Replace("{{nickname}}", comment.NickName).Replace("{{content}}", comment.Content);
+            string content = System.IO.File.ReadAllText(Path.Combine(HostingEnvironment.WebRootPath, "template", "notify.html")).Replace("{{title}}", post.Title).Replace("{{time}}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")).Replace("{{nickname}}", comment.NickName).Replace("{{content}}", comment.Content);
             var emails = CommentService.GetSelfAndAllChildrenCommentsByParentId(pid).Select(c => c.Email).Distinct().Except(new List<string> { comment.Email, CommonHelper.SystemSettings["ReceiveEmail"] }).ToList();
+            var emailSet = emails.Append(post.ModifierEmail).ToHashSet();
             string link = Url.Action("Details", "Post", new
             {
                 id = comment.PostId,
                 cid = pid
             }, Request.Scheme) + "#comment";
-            foreach (var email in emails)
+            foreach (var email in emailSet)
             {
                 BackgroundJob.Enqueue(() => CommonHelper.SendMail($"{Request.Host}{CommonHelper.SystemSettings["Title"]}文章评论回复:", content.Replace("{{link}}", link), email));
             }

+ 0 - 15
src/Masuit.MyBlogs.Core/Controllers/DashboardController.cs

@@ -27,21 +27,6 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// </summary>
         public ICommentService CommentService { get; set; }
 
-        /// <summary>
-        /// 控制面板
-        /// </summary>
-        /// <param name="userInfoService"></param>
-        /// <param name="postService"></param>
-        /// <param name="commentService"></param>
-        /// <param name="leaveMessageService"></param>
-        public DashboardController(IUserInfoService userInfoService, IPostService postService, ICommentService commentService, ILeaveMessageService leaveMessageService)
-        {
-            UserInfoService = userInfoService;
-            CommentService = commentService;
-            LeaveMessageService = leaveMessageService;
-            PostService = postService;
-        }
-
         /// <summary>
         /// 控制面板
         /// </summary>

+ 0 - 9
src/Masuit.MyBlogs.Core/Controllers/DonateController.cs

@@ -16,15 +16,6 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// </summary>
         public IDonateService DonateService { get; set; }
 
-        /// <summary>
-        /// 捐赠管理
-        /// </summary>
-        /// <param name="donateService"></param>
-        public DonateController(IDonateService donateService)
-        {
-            DonateService = donateService;
-        }
-
         /// <summary>
         /// 分页数据
         /// </summary>

+ 1 - 1
src/Masuit.MyBlogs.Core/Controllers/ErrorController.cs

@@ -12,7 +12,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// 404
         /// </summary>
         /// <returns></returns>
-        [Route("{*url}", Order = 99999), ResponseCache(Duration = 36000)]
+        [Route("error"), Route("{*url}", Order = 99999), ResponseCache(Duration = 36000)]
         public ActionResult Index()
         {
             Response.StatusCode = 404;

+ 10 - 18
src/Masuit.MyBlogs.Core/Controllers/FileController.cs

@@ -18,19 +18,11 @@ namespace Masuit.MyBlogs.Core.Controllers
     [Route("[controller]/[action]")]
     public class FileController : AdminController
     {
-        private readonly IHostingEnvironment _hostingEnvironment;
-        private readonly ISevenZipCompressor _sevenZipCompressor;
-
+        public IHostingEnvironment HostingEnvironment { get; set; }
         /// <summary>
-        /// 资源管理器
+        /// 
         /// </summary>
-        /// <param name="hostingEnvironment"></param>
-        /// <param name="sevenZipCompressor"></param>
-        public FileController(IHostingEnvironment hostingEnvironment, ISevenZipCompressor sevenZipCompressor)
-        {
-            _hostingEnvironment = hostingEnvironment;
-            _sevenZipCompressor = sevenZipCompressor;
-        }
+        public ISevenZipCompressor SevenZipCompressor { get; set; }
 
         /// <summary>
         /// 获取文件列表
@@ -39,7 +31,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// <returns></returns>
         public ActionResult GetFiles(string path)
         {
-            var files = Directory.GetFiles(_hostingEnvironment.WebRootPath + path).OrderByDescending(s => s).Select(s => new
+            var files = Directory.GetFiles(HostingEnvironment.WebRootPath + path).OrderByDescending(s => s).Select(s => new
             {
                 filename = Path.GetFileName(s),
                 path = s
@@ -94,7 +86,7 @@ namespace Masuit.MyBlogs.Core.Controllers
             {
                 foreach (var t in Request.Form.Files)
                 {
-                    string path = Path.Combine(_hostingEnvironment.ContentRootPath, CommonHelper.SystemSettings["PathRoot"].TrimStart('\\', '/'), destination.TrimStart('\\', '/'), t.FileName);
+                    string path = Path.Combine(HostingEnvironment.ContentRootPath, CommonHelper.SystemSettings["PathRoot"].TrimStart('\\', '/'), destination.TrimStart('\\', '/'), t.FileName);
                     using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite))
                     {
                         t.CopyTo(fs);
@@ -116,7 +108,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         public ActionResult Handle([FromBody]FileRequest req)
         {
             List<object> list = new List<object>();
-            var root = Path.Combine(_hostingEnvironment.ContentRootPath, CommonHelper.SystemSettings["PathRoot"].TrimStart('\\', '/'));
+            var root = Path.Combine(HostingEnvironment.ContentRootPath, CommonHelper.SystemSettings["PathRoot"].TrimStart('\\', '/'));
             switch (req.Action)
             {
                 case "list":
@@ -247,7 +239,7 @@ namespace Masuit.MyBlogs.Core.Controllers
                     break;
                 case "compress":
                     string filename = Path.Combine(Path.Combine(root, req.Destination.TrimStart('\\', '/')), Path.GetFileNameWithoutExtension(req.CompressedFilename) + ".zip");
-                    _sevenZipCompressor.Zip(req.Items.Select(s => Path.Combine(root, s.TrimStart('\\', '/'))).ToList(), filename);
+                    SevenZipCompressor.Zip(req.Items.Select(s => Path.Combine(root, s.TrimStart('\\', '/'))).ToList(), filename);
 
                     list.Add(new
                     {
@@ -257,7 +249,7 @@ namespace Masuit.MyBlogs.Core.Controllers
                 case "extract":
                     string folder = Path.Combine(Path.Combine(root, req.Destination.TrimStart('\\', '/')), req.FolderName.Trim('/', '\\'));
                     string zip = Path.Combine(root, req.Item.TrimStart('\\', '/'));
-                    _sevenZipCompressor.Extract(zip, folder);
+                    SevenZipCompressor.Extract(zip, folder);
                     list.Add(new
                     {
                         success = "true"
@@ -282,7 +274,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         {
             path = path?.TrimStart('\\', '/') ?? "";
             var root = CommonHelper.SystemSettings["PathRoot"].TrimStart('\\', '/');
-            var file = Path.Combine(_hostingEnvironment.ContentRootPath, root, path);
+            var file = Path.Combine(HostingEnvironment.ContentRootPath, root, path);
             switch (Request.Query["action"])
             {
                 case "download":
@@ -292,7 +284,7 @@ namespace Masuit.MyBlogs.Core.Controllers
                     }
                     break;
                 case "downloadMultiple":
-                    byte[] buffer = _sevenZipCompressor.ZipStream(items.Select(s => Path.Combine(_hostingEnvironment.ContentRootPath, root, s.TrimStart('\\', '/'))).ToList()).ToArray();
+                    byte[] buffer = SevenZipCompressor.ZipStream(items.Select(s => Path.Combine(HostingEnvironment.ContentRootPath, root, s.TrimStart('\\', '/'))).ToList()).ToArray();
                     return this.ResumeFile(buffer, Path.GetFileName(toFilename));
             }
             return Content("null");

+ 0 - 5
src/Masuit.MyBlogs.Core/Controllers/HomeController.cs

@@ -32,11 +32,6 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// </summary>
         public ICategoryService CategoryService { get; set; }
 
-        /// <summary>
-        /// 搜索关键词推荐
-        /// </summary>
-        public ISearchDetailsService SearchDetailsService { get; set; }
-
         /// <summary>
         /// 网站公告
         /// </summary>

+ 0 - 9
src/Masuit.MyBlogs.Core/Controllers/MenuController.cs

@@ -22,15 +22,6 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// </summary>
         public IMenuService MenuService { get; set; }
 
-        /// <summary>
-        /// 菜单管理
-        /// </summary>
-        /// <param name="menuService"></param>
-        public MenuController(IMenuService menuService)
-        {
-            MenuService = menuService;
-        }
-
         /// <summary>
         /// 获取菜单
         /// </summary>

+ 146 - 0
src/Masuit.MyBlogs.Core/Controllers/MergeController.cs

@@ -0,0 +1,146 @@
+using AutoMapper;
+using AutoMapper.QueryableExtensions;
+using Hangfire;
+using Masuit.LuceneEFCore.SearchEngine.Linq;
+using Masuit.MyBlogs.Core.Common;
+using Masuit.MyBlogs.Core.Extensions;
+using Masuit.MyBlogs.Core.Infrastructure.Services.Interface;
+using Masuit.MyBlogs.Core.Models.DTO;
+using Masuit.MyBlogs.Core.Models.Entity;
+using Masuit.MyBlogs.Core.Models.Enum;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text.RegularExpressions;
+
+namespace Masuit.MyBlogs.Core.Controllers
+{
+    [Route("merge/")]
+    public class MergeController : AdminController
+    {
+        public IPostMergeRequestService PostMergeRequestService { get; set; }
+        public IHostingEnvironment HostingEnvironment { get; set; }
+        public MapperConfiguration MapperConfig { get; set; }
+
+        /// <summary>
+        /// 获取合并详情
+        /// </summary>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet("{id}")]
+        public ActionResult Get(int id)
+        {
+            return ResultData(Mapper.Map<PostMergeRequestOutputDto>(PostMergeRequestService.GetById(id)));
+        }
+
+        /// <summary>
+        /// 获取分页数据
+        /// </summary>
+        /// <param name="page"></param>
+        /// <param name="size"></param>
+        /// <param name="kw"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public ActionResult GetPageData(int page = 1, int size = 10, string kw = "")
+        {
+            Expression<Func<PostMergeRequest, bool>> where = r => true;
+            if (!string.IsNullOrEmpty(kw))
+            {
+                where = where.And(r => r.Title.Contains(kw) || r.Content.Contains(kw) || r.Modifier.Contains(kw) || r.ModifierEmail.Contains(kw));
+            }
+
+            var list = PostMergeRequestService.LoadEntities(where).OrderBy(d => d.MergeState).ThenByDescending(r => r.Id).Skip((page - 1) * size).Take(size).ProjectTo<PostMergeRequestOutputDtoBase>(MapperConfig).ToList();
+            var count = PostMergeRequestService.Count(where);
+            var pageCount = Math.Ceiling(count * 1.0 / size).ToInt32();
+            return PageResult(list, pageCount, count);
+        }
+
+        /// <summary>
+        /// 版本对比
+        /// </summary>
+        /// <param name="mid"></param>
+        /// <returns></returns>
+        [HttpGet("compare/{mid}")]
+        public IActionResult MergeCompare(int mid)
+        {
+            var newer = PostMergeRequestService.GetById(mid) ?? throw new NotFoundException("待合并文章未找到");
+            var old = newer.Post;
+            HtmlDiff.HtmlDiff diffHelper = new HtmlDiff.HtmlDiff(old.Content, newer.Content);
+            string diffOutput = diffHelper.Build();
+            old.Content = Regex.Replace(Regex.Replace(diffOutput, "<ins.+?</ins>", string.Empty), @"<\w+></\w+>", string.Empty);
+            newer.Content = Regex.Replace(Regex.Replace(diffOutput, "<del.+?</del>", string.Empty), @"<\w+></\w+>", string.Empty);
+            return ResultData(new { old = old.Mapper<PostMergeRequestOutputDto>(), newer = newer.Mapper<PostMergeRequestOutputDto>() });
+        }
+
+        /// <summary>
+        /// 直接合并
+        /// </summary>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpPost("{id}")]
+        public IActionResult Merge(int id)
+        {
+            var merge = PostMergeRequestService.GetById(id) ?? throw new NotFoundException("待合并文章未找到");
+            var history = merge.Post.Mapper<PostHistoryVersion>();
+            history.Id = 0;
+            merge.Post = Mapper.Map(merge, merge.Post);
+            merge.Post.PostHistoryVersion.Add(history);
+            merge.Post.ModifyDate = DateTime.Now;
+            merge.MergeState = MergeStatus.Merged;
+            var b = PostMergeRequestService.UpdateEntitySaved(merge);
+            if (b)
+            {
+                string link = Request.Scheme + "://" + Request.Host + "/" + merge.Post.Id;
+                string content = System.IO.File.ReadAllText(HostingEnvironment.WebRootPath + "/template/merge-pass.html").Replace("{{link}}", link).Replace("{{title}}", merge.Post.Title);
+                BackgroundJob.Enqueue(() => CommonHelper.SendMail(CommonHelper.SystemSettings["Title"] + "博客你提交的修改已通过", content, merge.ModifierEmail));
+                return ResultData(null, true, "文章合并完成!");
+            }
+
+            return ResultData(null, false, "文章合并失败!");
+        }
+
+        /// <summary>
+        /// 编辑并合并
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost]
+        public IActionResult Merge([FromForm]PostMergeRequestInputDtoBase dto)
+        {
+            var merge = PostMergeRequestService.GetById(dto.Id) ?? throw new NotFoundException("待合并文章未找到");
+            Mapper.Map(dto, merge);
+            var b = PostMergeRequestService.UpdateEntitySaved(merge);
+            if (b)
+            {
+                return Merge(merge.Id);
+            }
+
+            return ResultData(null, false, "文章合并失败!");
+        }
+
+        /// <summary>
+        /// 拒绝合并
+        /// </summary>
+        /// <param name="id"></param>
+        /// <param name="reason"></param>
+        /// <returns></returns>
+        [HttpPost("reject/{id}")]
+        public ActionResult Reject(int id, string reason)
+        {
+            var merge = PostMergeRequestService.GetById(id) ?? throw new NotFoundException("待合并文章未找到");
+            merge.MergeState = MergeStatus.Reject;
+            var b = PostMergeRequestService.UpdateEntitySaved(merge);
+            if (b)
+            {
+                string link = Request.Scheme + "://" + Request.Host + "/" + merge.Post.Id + "/merge/" + id;
+                string content = System.IO.File.ReadAllText(HostingEnvironment.WebRootPath + "/template/merge-reject.html").Replace("{{link}}", link).Replace("{{title}}", merge.Post.Title).Replace("{{reason}}", reason);
+                BackgroundJob.Enqueue(() => CommonHelper.SendMail(CommonHelper.SystemSettings["Title"] + "博客你提交的修改已被拒绝", content, merge.ModifierEmail));
+                return ResultData(null, true, "合并已拒绝!");
+            }
+
+            return ResultData(null, false, "操作失败!");
+        }
+    }
+}

+ 3 - 10
src/Masuit.MyBlogs.Core/Controllers/MiscController.cs

@@ -14,7 +14,6 @@ using System;
 using System.IO;
 using System.Linq;
 using System.Net.Http;
-using System.Text.RegularExpressions;
 using System.Threading.Tasks;
 
 namespace Masuit.MyBlogs.Core.Controllers
@@ -34,21 +33,15 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// </summary>
         public IDonateService DonateService { get; set; }
 
-        private readonly IHostingEnvironment _hostingEnvironment;
+        public IHostingEnvironment HostingEnvironment { get; set; }
         private readonly ImagebedClient _imagebedClient;
 
         /// <summary>
         /// 杂项页
         /// </summary>
-        /// <param name="miscService"></param>
-        /// <param name="donateService"></param>
-        /// <param name="hostingEnvironment"></param>
         /// <param name="httpClientFactory"></param>
-        public MiscController(IMiscService miscService, IDonateService donateService, IHostingEnvironment hostingEnvironment, IHttpClientFactory httpClientFactory)
+        public MiscController(IHttpClientFactory httpClientFactory)
         {
-            MiscService = miscService;
-            DonateService = donateService;
-            _hostingEnvironment = hostingEnvironment;
             _imagebedClient = new ImagebedClient(httpClientFactory.CreateClient());
         }
 
@@ -163,7 +156,7 @@ namespace Masuit.MyBlogs.Core.Controllers
                 {
                     try
                     {
-                        System.IO.File.Delete(Path.Combine(_hostingEnvironment.WebRootPath + path));
+                        System.IO.File.Delete(Path.Combine(HostingEnvironment.WebRootPath + path));
                     }
                     catch (IOException)
                     {

+ 3 - 16
src/Masuit.MyBlogs.Core/Controllers/MsgController.cs

@@ -34,20 +34,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// </summary>
         public IInternalMessageService MessageService { get; set; }
 
-        private readonly IHostingEnvironment _hostingEnvironment;
-
-        /// <summary>
-        /// 留言板和站内信
-        /// </summary>
-        /// <param name="leaveMessageService"></param>
-        /// <param name="messageService"></param>
-        /// <param name="hostingEnvironment"></param>
-        public MsgController(ILeaveMessageService leaveMessageService, IInternalMessageService messageService, IHostingEnvironment hostingEnvironment)
-        {
-            LeaveMessageService = leaveMessageService;
-            MessageService = messageService;
-            _hostingEnvironment = hostingEnvironment;
-        }
+        public IHostingEnvironment HostingEnvironment { get; set; }
 
         /// <summary>
         /// 留言板
@@ -159,7 +146,7 @@ namespace Masuit.MyBlogs.Core.Controllers
             {
                 HttpContext.Session.Set("msg", msg.Content.RemoveHtmlTag().Trim());
                 var email = CommonHelper.SystemSettings["ReceiveEmail"];
-                string content = System.IO.File.ReadAllText(_hostingEnvironment.WebRootPath + "/template/notify.html").Replace("{{title}}", "网站留言板").Replace("{{time}}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")).Replace("{{nickname}}", msg2.NickName).Replace("{{content}}", msg2.Content);
+                string content = System.IO.File.ReadAllText(HostingEnvironment.WebRootPath + "/template/notify.html").Replace("{{title}}", "网站留言板").Replace("{{time}}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")).Replace("{{nickname}}", msg2.NickName).Replace("{{content}}", msg2.Content);
                 if (msg.Status == Status.Pended)
                 {
                     if (!msg2.IsMaster)
@@ -214,7 +201,7 @@ namespace Masuit.MyBlogs.Core.Controllers
             bool b = LeaveMessageService.UpdateEntitySaved(msg);
 #if !DEBUG
             var pid = msg.ParentId == 0 ? msg.Id : LeaveMessageService.GetParentMessageIdByChildId(id);
-            string content = System.IO.File.ReadAllText(Path.Combine(_hostingEnvironment.WebRootPath, "template", "notify.html")).Replace("{{time}}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")).Replace("{{nickname}}", msg.NickName).Replace("{{content}}", msg.Content);
+            string content = System.IO.File.ReadAllText(Path.Combine(HostingEnvironment.WebRootPath, "template", "notify.html")).Replace("{{time}}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")).Replace("{{nickname}}", msg.NickName).Replace("{{content}}", msg.Content);
             var emails = LeaveMessageService.GetSelfAndAllChildrenMessagesByParentId(pid).Select(c => c.Email).Distinct().Except(new List<string>() { msg.Email }).ToList();
             string link = Url.Action("Index", "Msg", new { cid = pid }, Request.Scheme);
             foreach (var s in emails)

+ 4 - 8
src/Masuit.MyBlogs.Core/Controllers/NoticeController.cs

@@ -16,7 +16,6 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Net.Http;
-using System.Text.RegularExpressions;
 using System.Threading.Tasks;
 
 namespace Masuit.MyBlogs.Core.Controllers
@@ -31,19 +30,16 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// </summary>
         public INoticeService NoticeService { get; set; }
 
-        private readonly IHostingEnvironment _hostingEnvironment;
+        public IHostingEnvironment HostingEnvironment { get; set; }
 
         private readonly ImagebedClient _imagebedClient;
 
         /// <summary>
         /// 网站公告
         /// </summary>
-        /// <param name="noticeService"></param>
-        /// <param name="hostingEnvironment"></param>
-        public NoticeController(INoticeService noticeService, IHostingEnvironment hostingEnvironment, IHttpClientFactory httpClientFactory)
+        /// <param name="httpClientFactory"></param>
+        public NoticeController(IHttpClientFactory httpClientFactory)
         {
-            NoticeService = noticeService;
-            _hostingEnvironment = hostingEnvironment;
             _imagebedClient = new ImagebedClient(httpClientFactory.CreateClient());
         }
 
@@ -134,7 +130,7 @@ namespace Masuit.MyBlogs.Core.Controllers
                 {
                     try
                     {
-                        System.IO.File.Delete(_hostingEnvironment.WebRootPath + path);
+                        System.IO.File.Delete(HostingEnvironment.WebRootPath + path);
                     }
                     catch
                     {

+ 2 - 20
src/Masuit.MyBlogs.Core/Controllers/PassportController.cs

@@ -28,25 +28,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// 用户
         /// </summary>
         public IUserInfoService UserInfoService { get; set; }
-        /// <summary>
-        /// 登录记录
-        /// </summary>
-        public ILoginRecordService LoginRecordService { get; set; }
-
-        private readonly IHostingEnvironment _env;
-
-        /// <summary>
-        /// 登录授权
-        /// </summary>
-        /// <param name="userInfoService"></param>
-        /// <param name="loginRecordService"></param>
-        /// <param name="env"></param>
-        public PassportController(IUserInfoService userInfoService, ILoginRecordService loginRecordService, IHostingEnvironment env)
-        {
-            UserInfoService = userInfoService;
-            LoginRecordService = loginRecordService;
-            _env = env;
-        }
+        public IHostingEnvironment HostingEnvironment { get; set; }
 
         /// <summary>
         /// 
@@ -178,7 +160,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         public ActionResult GetUserInfo()
         {
             UserInfoOutputDto user = HttpContext.Session.Get<UserInfoOutputDto>(SessionKey.UserInfo);
-            if (_env.IsDevelopment())
+            if (HostingEnvironment.IsDevelopment())
             {
                 user = UserInfoService.GetByUsername("masuit").Mapper<UserInfoOutputDto>();
             }

+ 132 - 58
src/Masuit.MyBlogs.Core/Controllers/PostController.cs

@@ -1,5 +1,4 @@
-using AutoMapper;
-using EFSecondLevelCache.Core;
+using EFSecondLevelCache.Core;
 using Hangfire;
 using Masuit.LuceneEFCore.SearchEngine.Interfaces;
 using Masuit.MyBlogs.Core.Common;
@@ -38,35 +37,24 @@ namespace Masuit.MyBlogs.Core.Controllers
     /// </summary>
     public class PostController : BaseController
     {
-        private IPostService PostService { get; set; }
-        private ICategoryService CategoryService { get; set; }
-        private IBroadcastService BroadcastService { get; set; }
-        private ISeminarService SeminarService { get; set; }
-        private IPostHistoryVersionService PostHistoryVersionService { get; set; }
-
-        private readonly IHostingEnvironment _hostingEnvironment;
-        private readonly ISearchEngine<DataContext> _searchEngine;
+        public IPostService PostService { get; set; }
+        public ICategoryService CategoryService { get; set; }
+        public IBroadcastService BroadcastService { get; set; }
+        public ISeminarService SeminarService { get; set; }
+        public IPostHistoryVersionService PostHistoryVersionService { get; set; }
+
+        public IInternalMessageService MessageService { get; set; }
+
+        public IHostingEnvironment HostingEnvironment { get; set; }
+        public ISearchEngine<DataContext> SearchEngine { get; set; }
         private readonly ImagebedClient _imagebedClient;
 
         /// <summary>
         /// 文章管理
         /// </summary>
-        /// <param name="postService"></param>
-        /// <param name="categoryService"></param>
-        /// <param name="broadcastService"></param>
-        /// <param name="seminarService"></param>
-        /// <param name="postHistoryVersionService"></param>
-        /// <param name="hostingEnvironment"></param>
-        /// <param name="searchEngine"></param>
-        public PostController(IPostService postService, ICategoryService categoryService, IBroadcastService broadcastService, ISeminarService seminarService, IPostHistoryVersionService postHistoryVersionService, IHostingEnvironment hostingEnvironment, ISearchEngine<DataContext> searchEngine, IHttpClientFactory httpClientFactory)
+        /// <param name="httpClientFactory"></param>
+        public PostController(IHttpClientFactory httpClientFactory)
         {
-            PostService = postService;
-            CategoryService = categoryService;
-            BroadcastService = broadcastService;
-            SeminarService = seminarService;
-            PostHistoryVersionService = postHistoryVersionService;
-            _hostingEnvironment = hostingEnvironment;
-            _searchEngine = searchEngine;
             _imagebedClient = new ImagebedClient(httpClientFactory.CreateClient());
         }
 
@@ -89,7 +77,7 @@ namespace Masuit.MyBlogs.Core.Controllers
                 ViewBag.Prev = PostService.GetFirstEntity<DateTime, PostModelBase>(p => p.ModifyDate < modifyDate && (p.Status == Status.Pended || user.IsAdmin), p => p.ModifyDate, false);
                 if (!string.IsNullOrEmpty(kw))
                 {
-                    ViewData["keywords"] = post.Content.Contains(kw) ? $"['{kw}']" : _searchEngine.LuceneIndexSearcher.CutKeywords(kw).ToJsonString();
+                    ViewData["keywords"] = post.Content.Contains(kw) ? $"['{kw}']" : SearchEngine.LuceneIndexSearcher.CutKeywords(kw).ToJsonString();
                 }
 
                 if (user.IsAdmin)
@@ -261,12 +249,6 @@ namespace Masuit.MyBlogs.Core.Controllers
                 }
             });
             ViewBag.Category = CategoryService.LoadEntitiesNoTracking(c => c.Status == Status.Available).ToList();
-            UserInfoOutputDto user = HttpContext.Session.Get<UserInfoOutputDto>(SessionKey.UserInfo);
-            if (user != null)
-            {
-                return View("Publish_Admin", result.Distinct().OrderBy(s => s));
-            }
-
             return View(result.Distinct().OrderBy(s => s));
         }
 
@@ -276,14 +258,18 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// <param name="post"></param>
         /// <returns></returns>
         [HttpPost, ValidateAntiForgeryToken]
-        public async Task<ActionResult> Publish(PostInputDto post)
+        public async Task<ActionResult> Publish(PostInputDto post, string code)
         {
+            if (RedisHelper.Get("code:" + post.Email) != code)
+            {
+                return ResultData(null, false, "验证码错误!");
+            }
+
             if (Regex.Match(post.Content, CommonHelper.BanRegex).Length > 0)
             {
                 return ResultData(null, false, "您提交的内容包含敏感词,被禁止发表,请注意改善您的言辞!");
             }
 
-            UserInfoOutputDto user = HttpContext.Session.Get<UserInfoOutputDto>(SessionKey.UserInfo);
             if (!CategoryService.Any(c => c.Id == post.CategoryId && c.Status == Status.Available))
             {
                 return ResultData(null, message: "请选择一个分类");
@@ -305,26 +291,22 @@ namespace Masuit.MyBlogs.Core.Controllers
             post.Status = Status.Pending;
             post.PostDate = DateTime.Now;
             post.ModifyDate = DateTime.Now;
-            if (user != null && user.IsAdmin)
-            {
-                post.Status = Status.Pended;
-            }
-            else
-            {
-                post.Content = await _imagebedClient.ReplaceImgSrc(post.Content.HtmlSantinizerStandard().ClearImgAttributes());
-            }
-
+            post.Status = Status.Pended;
+            post.Content = await _imagebedClient.ReplaceImgSrc(post.Content.HtmlSantinizerStandard().ClearImgAttributes());
             ViewBag.CategoryId = new SelectList(CategoryService.LoadEntitiesNoTracking(c => c.Status == Status.Available), "Id", "Name", post.CategoryId);
             Post p = post.Mapper<Post>();
             p.IP = HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString();
+            p.Modifier = p.Author;
+            p.ModifierEmail = p.Email;
             p = PostService.AddEntitySaved(p);
             if (p != null)
             {
+                RedisHelper.Expire("code:" + p.Email, 1);
                 if (p.Status == Status.Pending)
                 {
                     var email = CommonHelper.SystemSettings["ReceiveEmail"];
                     string link = Url.Action("Details", "Post", new { id = p.Id }, Request.Scheme);
-                    string content = System.IO.File.ReadAllText(_hostingEnvironment.WebRootPath + "/template/publish.html")
+                    string content = System.IO.File.ReadAllText(HostingEnvironment.WebRootPath + "/template/publish.html")
                         .Replace("{{link}}", link)
                         .Replace("{{time}}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))
                         .Replace("{{title}}", p.Title);
@@ -422,14 +404,14 @@ namespace Masuit.MyBlogs.Core.Controllers
         [HttpPost, ValidateAntiForgeryToken, AllowAccessFirewall, ResponseCache(Duration = 115, VaryByQueryKeys = new[] { "email" })]
         public ActionResult GetViewToken(string email)
         {
-            if (!string.IsNullOrEmpty(email) && !email.MatchEmail())
+            if (string.IsNullOrEmpty(email) || !email.MatchEmail())
             {
                 return ResultData(null, false, "请输入正确的邮箱!");
             }
 
-            if (RedisHelper.Exists("code:" + email))
+            if (RedisHelper.Exists("get:" + email))
             {
-                RedisHelper.Expire("code:" + email, 120);
+                RedisHelper.Expire("get:" + email, 120);
                 return ResultData(null, false, "发送频率限制,请在2分钟后重新尝试发送邮件!请检查你的邮件,若未收到,请检查你的邮箱地址或邮件垃圾箱!");
             }
 
@@ -437,14 +419,102 @@ namespace Masuit.MyBlogs.Core.Controllers
             {
                 string token = SnowFlake.GetInstance().GetUniqueShortId(6);
                 RedisHelper.Set("token:" + email, token, 86400);
-                CommonHelper.SendMail(CommonHelper.SystemSettings["Domain"] + "博客访问验证码", $"{CommonHelper.SystemSettings["Domain"]}本次验证码是:<span style='color:red'>{token}</span>,有效期为24h,请按时使用!", email);
-                RedisHelper.Set("code:" + email, token, 120);
+                BackgroundJob.Enqueue(() => CommonHelper.SendMail(CommonHelper.SystemSettings["Domain"] + "博客访问验证码", $"{CommonHelper.SystemSettings["Domain"]}本次验证码是:<span style='color:red'>{token}</span>,有效期为24h,请按时使用!", email));
+                RedisHelper.Set("get:" + email, token, 120);
                 return ResultData(null);
             }
 
             return ResultData(null, false, "您目前没有权限访问这个链接,请联系站长开通访问权限!");
         }
 
+        /// <summary>
+        /// 文章合并
+        /// </summary>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet("{id}/merge")]
+        public ActionResult PushMerge(int id)
+        {
+            var post = PostService.GetById(id) ?? throw new NotFoundException("文章未找到");
+            return View(post);
+        }
+
+        /// <summary>
+        /// 文章合并
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("{id}/pushmerge")]
+        public ActionResult PushMerge(PostMergeRequestInputDto dto)
+        {
+            if (RedisHelper.Get("code:" + dto.ModifierEmail) != dto.Code)
+            {
+                return ResultData(null, false, "验证码错误!");
+            }
+
+            var post = PostService.GetById(dto.PostId) ?? throw new NotFoundException("文章未找到");
+            if (post.Title.Equals(dto.Title) && post.Content.Equals(dto.Content))
+            {
+                return ResultData(null, false, "内容未被修改!");
+            }
+
+            #region 直接合并
+
+            if (post.Email.Equals(dto.ModifierEmail))
+            {
+                var history = post.Mapper<PostHistoryVersion>();
+                Mapper.Map(dto, post);
+                post.PostHistoryVersion.Add(history);
+                post.ModifyDate = DateTime.Now;
+                return PostService.UpdateEntitySaved(post) ? ResultData(null, true, "你是文章原作者,无需审核,文章已自动更新并在首页展示!") : ResultData(null, false, "操作失败!");
+            }
+
+            #endregion
+
+            var merge = post.PostMergeRequests.FirstOrDefault(r => r.Id == dto.Id && r.MergeState != MergeStatus.Merged);
+            if (merge != null)
+            {
+                Mapper.Map(dto, merge);
+                merge.SubmitTime = DateTime.Now;
+                merge.MergeState = MergeStatus.Pending;
+            }
+            else
+            {
+                merge = Mapper.Map<PostMergeRequest>(dto);
+                post.PostMergeRequests.Add(merge);
+            }
+
+            var b = PostService.UpdateEntitySaved(post);
+            if (b)
+            {
+                RedisHelper.Expire("code:" + dto.ModifierEmail, 1);
+                MessageService.AddEntitySaved(new InternalMessage()
+                {
+                    Title = $"来自【{dto.Modifier}】的文章修改合并请求",
+                    Content = dto.Title,
+                    Link = "#/merge/compare?id=" + merge.Id
+                });
+                var content = System.IO.File.ReadAllText(HostingEnvironment.WebRootPath + "/template/merge-request.html").Replace("{{title}}", post.Title).Replace("{{link}}", Url.Action("Index", "Dashboard", new { }, Request.Scheme) + "#/merge/compare?id=" + merge.Id);
+                BackgroundJob.Enqueue(() => CommonHelper.SendMail("博客文章修改请求:", content, CommonHelper.SystemSettings["ReceiveEmail"]));
+            }
+
+            return ResultData(null, b, b ? "修改请求已提交!" : "操作失败!");
+        }
+
+        /// <summary>
+        /// 文章合并
+        /// </summary>
+        /// <param name="id"></param>
+        /// <param name="mid"></param>
+        /// <returns></returns>
+        [HttpGet("{id}/merge/{mid}")]
+        public ActionResult RepushMerge(int id, int mid)
+        {
+            var post = PostService.GetById(id) ?? throw new NotFoundException("文章未找到");
+            var merge = post.PostMergeRequests.FirstOrDefault(p => p.Id == mid && p.MergeState != MergeStatus.Merged) ?? throw new NotFoundException("待合并文章未找到");
+            return View(merge);
+        }
+
         #region 后端管理
 
         /// <summary>
@@ -490,7 +560,7 @@ namespace Masuit.MyBlogs.Core.Controllers
                 cast.ForEach(c =>
                 {
                     var ts = DateTime.Now.GetTotalMilliseconds();
-                    string content = System.IO.File.ReadAllText(_hostingEnvironment.WebRootPath + "/template/broadcast.html")
+                    string content = System.IO.File.ReadAllText(HostingEnvironment.WebRootPath + "/template/broadcast.html")
                         .Replace("{{link}}", link + "?email=" + c.Email)
                         .Replace("{{time}}", post.ModifyDate.ToString("yyyy-MM-dd HH:mm:ss"))
                         .Replace("{{title}}", post.Title)
@@ -522,7 +592,7 @@ namespace Masuit.MyBlogs.Core.Controllers
             var post = PostService.GetById(id);
             post.Status = Status.Deleted;
             bool b = PostService.UpdateEntitySaved(post);
-            _searchEngine.LuceneIndexer.Delete(post);
+            SearchEngine.LuceneIndexer.Delete(post);
             return ResultData(null, b, b ? "删除成功!" : "删除失败!");
         }
 
@@ -558,8 +628,8 @@ namespace Masuit.MyBlogs.Core.Controllers
             {
                 try
                 {
-                    System.IO.File.Delete(Path.Combine(_hostingEnvironment.WebRootPath + "/upload", post.ResourceName));
-                    Directory.Delete(Path.Combine(_hostingEnvironment.WebRootPath + "/upload", Path.GetFileNameWithoutExtension(post.ResourceName)), true);
+                    System.IO.File.Delete(Path.Combine(HostingEnvironment.WebRootPath + "/upload", post.ResourceName));
+                    Directory.Delete(Path.Combine(HostingEnvironment.WebRootPath + "/upload", Path.GetFileNameWithoutExtension(post.ResourceName)), true);
                 }
                 catch (IOException)
                 {
@@ -573,7 +643,7 @@ namespace Masuit.MyBlogs.Core.Controllers
                 {
                     try
                     {
-                        System.IO.File.Delete(_hostingEnvironment.WebRootPath + path);
+                        System.IO.File.Delete(HostingEnvironment.WebRootPath + path);
                     }
                     catch (IOException)
                     {
@@ -775,14 +845,16 @@ namespace Masuit.MyBlogs.Core.Controllers
             Post p = PostService.GetById(post.Id);
             if (reserve)
             {
-                post.ModifyDate = DateTime.Now;
                 var history = p.Mapper<PostHistoryVersion>();
-                history.Id = 0;
                 p.PostHistoryVersion.Add(history);
+                post.ModifyDate = DateTime.Now;
             }
 
             p.IP = HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString();
             Mapper.Map(post, p);
+            var user = HttpContext.Session.Get<UserInfoOutputDto>(SessionKey.UserInfo);
+            p.Modifier = user.NickName;
+            p.ModifierEmail = user.Email;
             if (!string.IsNullOrEmpty(post.Seminars))
             {
                 var tmp = post.Seminars.Split(',').Distinct();
@@ -814,7 +886,7 @@ namespace Masuit.MyBlogs.Core.Controllers
                     cast.ForEach(c =>
                     {
                         var ts = DateTime.Now.GetTotalMilliseconds();
-                        string content = System.IO.File.ReadAllText(Path.Combine(_hostingEnvironment.WebRootPath, "template", "broadcast.html"))
+                        string content = System.IO.File.ReadAllText(Path.Combine(HostingEnvironment.WebRootPath, "template", "broadcast.html"))
                             .Replace("{{link}}", link + "?email=" + c.Email)
                             .Replace("{{time}}", post.ModifyDate.ToString("yyyy-MM-dd HH:mm:ss"))
                             .Replace("{{title}}", post.Title)
@@ -882,6 +954,8 @@ namespace Masuit.MyBlogs.Core.Controllers
             post.PostDate = DateTime.Now;
             post.ModifyDate = DateTime.Now;
             Post p = post.Mapper<Post>();
+            p.Modifier = p.Author;
+            p.ModifierEmail = p.Email;
             p.IP = HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString();
             if (!string.IsNullOrEmpty(post.Seminars))
             {
@@ -924,7 +998,7 @@ namespace Masuit.MyBlogs.Core.Controllers
                     cast.ForEach(c =>
                     {
                         var ts = DateTime.Now.GetTotalMilliseconds();
-                        string content = System.IO.File.ReadAllText(_hostingEnvironment.WebRootPath + "/template/broadcast.html")
+                        string content = System.IO.File.ReadAllText(HostingEnvironment.WebRootPath + "/template/broadcast.html")
                             .Replace("{{link}}", link + "?email=" + c.Email)
                             .Replace("{{time}}", post.ModifyDate.ToString("yyyy-MM-dd HH:mm:ss"))
                             .Replace("{{title}}", post.Title).Replace("{{author}}", post.Author)

+ 3 - 17
src/Masuit.MyBlogs.Core/Controllers/SearchController.cs

@@ -1,7 +1,5 @@
-using Masuit.LuceneEFCore.SearchEngine.Interfaces;
-using Masuit.MyBlogs.Core.Common;
+using Masuit.MyBlogs.Core.Common;
 using Masuit.MyBlogs.Core.Extensions;
-using Masuit.MyBlogs.Core.Infrastructure;
 using Masuit.MyBlogs.Core.Infrastructure.Services.Interface;
 using Masuit.MyBlogs.Core.Models.DTO;
 using Masuit.MyBlogs.Core.Models.Entity;
@@ -24,19 +22,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// 
         /// </summary>
         public ISearchDetailsService SearchDetailsService { get; set; }
-        private readonly IPostService _postService;
-
-        /// <summary>
-        /// 站内搜索
-        /// </summary>
-        /// <param name="searchDetailsService"></param>
-        /// <param name="postService"></param>
-        /// <param name="searchEngine"></param>
-        public SearchController(ISearchDetailsService searchDetailsService, IPostService postService, ISearchEngine<DataContext> searchEngine)
-        {
-            SearchDetailsService = searchDetailsService;
-            _postService = postService;
-        }
+        public IPostService PostService { get; set; }
 
         /// <summary>
         /// 搜索页
@@ -82,7 +68,7 @@ namespace Masuit.MyBlogs.Core.Controllers
                     HttpContext.Session.Set("search:" + wd, wd.ToByteArray());
                 }
 
-                var posts = _postService.SearchPage(page, size, wd);
+                var posts = PostService.SearchPage(page, size, wd);
                 ViewBag.Elapsed = posts.Elapsed;
                 ViewBag.Total = posts.Total;
                 if (posts.Total > 1)

+ 2 - 15
src/Masuit.MyBlogs.Core/Controllers/SeminarController.cs

@@ -29,20 +29,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// </summary>
         public IPostService PostService { get; set; }
 
-        private readonly ISeminarPostService _seminarPostService;
-
-        /// <summary>
-        /// 专题页
-        /// </summary>
-        /// <param name="seminarService"></param>
-        /// <param name="postService"></param>
-        /// <param name="seminarPostService"></param>
-        public SeminarController(ISeminarService seminarService, IPostService postService, ISeminarPostService seminarPostService)
-        {
-            SeminarService = seminarService;
-            PostService = postService;
-            _seminarPostService = seminarPostService;
-        }
+        public ISeminarPostService SeminarPostService { get; set; }
 
         /// <summary>
         /// 专题页
@@ -215,7 +202,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         {
             Seminar seminar = SeminarService.GetById(id);
             Post post = PostService.GetById(pid);
-            bool b = _seminarPostService.DeleteEntitySaved(s => s.SeminarId == id && s.PostId == pid) > 0;
+            bool b = SeminarPostService.DeleteEntitySaved(s => s.SeminarId == id && s.PostId == pid) > 0;
             return ResultData(null, b, b ? $"已成功将【{post.Title}】从专题【{seminar.Title}】移除" : "添加失败!");
         }
 

+ 2 - 15
src/Masuit.MyBlogs.Core/Controllers/SubscribeController.cs

@@ -37,20 +37,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// </summary>
         public IPostService PostService { get; set; }
 
-        private readonly IHostingEnvironment _hostingEnvironment;
-
-        /// <summary>
-        /// 订阅服务
-        /// </summary>
-        /// <param name="broadcastService"></param>
-        /// <param name="postService"></param>
-        /// <param name="hostingEnvironment"></param>
-        public SubscribeController(IBroadcastService broadcastService, IPostService postService, IHostingEnvironment hostingEnvironment)
-        {
-            BroadcastService = broadcastService;
-            PostService = postService;
-            _hostingEnvironment = hostingEnvironment;
-        }
+        public IHostingEnvironment HostingEnvironment { get; set; }
 
         /// <summary>
         /// 响应数据
@@ -160,7 +147,7 @@ namespace Masuit.MyBlogs.Core.Controllers
                     timespan = ts,
                     hash = (email + "verify" + guid + ts).AESEncrypt(AppConfig.BaiduAK)
                 }, "http");
-                BackgroundJob.Enqueue(() => CommonHelper.SendMail(CommonHelper.SystemSettings["Title"] + "博客订阅:" + Request.Host, System.IO.File.ReadAllText(_hostingEnvironment.WebRootPath + "/template/subscribe.html").Replace("{{link}}", link), email));
+                BackgroundJob.Enqueue(() => CommonHelper.SendMail(CommonHelper.SystemSettings["Title"] + "博客订阅:" + Request.Host, System.IO.File.ReadAllText(HostingEnvironment.WebRootPath + "/template/subscribe.html").Replace("{{link}}", link), email));
                 BroadcastService.SaveChanges();
                 return ResultData(null, message: "订阅成功,请打开您的邮箱确认操作后便可收到订阅更新!");
             }

+ 0 - 11
src/Masuit.MyBlogs.Core/Controllers/SystemController.cs

@@ -30,17 +30,6 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// </summary>
         public ISystemSettingService SystemSettingService { get; set; }
 
-        /// <summary>
-        /// 系统设置
-        /// </summary>
-        /// <param name="userInfoService"></param>
-        /// <param name="systemSettingService"></param>
-        public SystemController(IUserInfoService userInfoService, ISystemSettingService systemSettingService)
-        {
-            UserInfoService = userInfoService;
-            SystemSettingService = systemSettingService;
-        }
-
         /// <summary>
         /// 获取硬件基本信息
         /// </summary>

+ 9 - 10
src/Masuit.MyBlogs.Core/Controllers/UploadController.cs

@@ -29,7 +29,7 @@ namespace Masuit.MyBlogs.Core.Controllers
     [ApiExplorerSettings(IgnoreApi = true)]
     public class UploadController : Controller
     {
-        private readonly IHostingEnvironment _hostingEnvironment;
+        public IHostingEnvironment HostingEnvironment { get; set; }
 
         private readonly ImagebedClient _imagebedClient;
 
@@ -37,9 +37,8 @@ namespace Masuit.MyBlogs.Core.Controllers
         /// 文件上传
         /// </summary>
         /// <param name="hostingEnvironment"></param>
-        public UploadController(IHostingEnvironment hostingEnvironment, IHttpClientFactory httpClientFactory)
+        public UploadController(IHttpClientFactory httpClientFactory)
         {
-            _hostingEnvironment = hostingEnvironment;
             _imagebedClient = new ImagebedClient(httpClientFactory.CreateClient());
         }
 
@@ -84,7 +83,7 @@ namespace Masuit.MyBlogs.Core.Controllers
 
                 if (fileName != null)
                 {
-                    string upload = _hostingEnvironment.WebRootPath + "/upload";
+                    string upload = HostingEnvironment.WebRootPath + "/upload";
                     if (!Directory.Exists(upload))
                     {
                         Directory.CreateDirectory(upload);
@@ -140,7 +139,7 @@ namespace Masuit.MyBlogs.Core.Controllers
         public ActionResult Download(string path)
         {
             if (string.IsNullOrEmpty(path)) return Content("null");
-            var file = Path.Combine(_hostingEnvironment.WebRootPath + "/upload", path.Trim('.', '/', '\\'));
+            var file = Path.Combine(HostingEnvironment.WebRootPath + "/upload", path.Trim('.', '/', '\\'));
             if (System.IO.File.Exists(file))
             {
                 return this.ResumePhysicalFile(file, Path.GetFileName(file));
@@ -242,7 +241,7 @@ namespace Masuit.MyBlogs.Core.Controllers
                         return ResultData(url);
                     }
 
-                    path = Path.Combine(_hostingEnvironment.WebRootPath, "upload", "images", filename);
+                    path = Path.Combine(HostingEnvironment.WebRootPath, "upload", "images", filename);
                     var dir = Path.GetDirectoryName(path);
                     if (!Directory.Exists(dir))
                     {
@@ -256,13 +255,13 @@ namespace Masuit.MyBlogs.Core.Controllers
 
                     break;
                 case var _ when file.ContentType.StartsWith("audio") || file.ContentType.StartsWith("video"):
-                    path = Path.Combine(_hostingEnvironment.WebRootPath, "upload", "media", filename);
+                    path = Path.Combine(HostingEnvironment.WebRootPath, "upload", "media", filename);
                     break;
                 case var _ when file.ContentType.StartsWith("text") || (ContentType.Doc + "," + ContentType.Xls + "," + ContentType.Ppt + "," + ContentType.Pdf).Contains(file.ContentType):
-                    path = Path.Combine(_hostingEnvironment.WebRootPath, "upload", "docs", filename);
+                    path = Path.Combine(HostingEnvironment.WebRootPath, "upload", "docs", filename);
                     break;
                 default:
-                    path = Path.Combine(_hostingEnvironment.WebRootPath, "upload", "files", filename);
+                    path = Path.Combine(HostingEnvironment.WebRootPath, "upload", "files", filename);
                     break;
             }
             try
@@ -278,7 +277,7 @@ namespace Masuit.MyBlogs.Core.Controllers
                     file.CopyTo(fs);
                 }
 
-                return ResultData(path.Substring(_hostingEnvironment.WebRootPath.Length).Replace("\\", "/"));
+                return ResultData(path.Substring(HostingEnvironment.WebRootPath.Length).Replace("\\", "/"));
             }
             catch (Exception e)
             {

+ 1 - 12
src/Masuit.MyBlogs.Core/Controllers/UserController.cs

@@ -1,6 +1,4 @@
-using AutoMapper;
-using Masuit.MyBlogs.Core.Infrastructure.Services.Interface;
-using Masuit.MyBlogs.Core.Models.DTO;
+using Masuit.MyBlogs.Core.Models.DTO;
 using Masuit.MyBlogs.Core.Models.Entity;
 using Microsoft.AspNetCore.Mvc;
 
@@ -11,15 +9,6 @@ namespace Masuit.MyBlogs.Core.Controllers
     /// </summary>
     public class UserController : AdminController
     {
-        /// <summary>
-        /// 用户管理
-        /// </summary>
-        /// <param name="userInfoService"></param>
-        public UserController(IUserInfoService userInfoService)
-        {
-            UserInfoService = userInfoService;
-        }
-
         /// <summary>
         /// 修改用户名
         /// </summary>

+ 41 - 0
src/Masuit.MyBlogs.Core/Controllers/ValidateController.cs

@@ -0,0 +1,41 @@
+using Hangfire;
+using Masuit.MyBlogs.Core.Common;
+using Masuit.Tools;
+using Masuit.Tools.Systems;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Masuit.MyBlogs.Core.Controllers
+{
+    public class ValidateController : BaseController
+    {
+        /// <summary>
+        /// 发送验证码
+        /// </summary>
+        /// <param name="email"></param>
+        /// <returns></returns>
+        [HttpPost, ValidateAntiForgeryToken, ResponseCache(Duration = 115, VaryByQueryKeys = new[] { "email" })]
+        public ActionResult SendCode(string email)
+        {
+            if (string.IsNullOrEmpty(email) || !email.MatchEmail())
+            {
+                return ResultData(null, false, "请输入正确的邮箱!");
+            }
+
+            if (RedisHelper.Exists("get:" + email))
+            {
+                RedisHelper.Expire("get:" + email, 120);
+                return ResultData(null, false, "发送频率限制,请在2分钟后重新尝试发送邮件!请检查你的邮件,若未收到,请检查你的邮箱地址或邮件垃圾箱!");
+            }
+
+            string code = SnowFlake.GetInstance().GetUniqueShortId(6);
+            RedisHelper.Set("code:" + email, code, 86400);
+            BackgroundJob.Enqueue(() => CommonHelper.SendMail(CommonHelper.SystemSettings["Domain"] + "博客验证码", $"{CommonHelper.SystemSettings["Domain"]}本次验证码是:<span style='color:red'>{code}</span>,有效期为24h,请按时使用!", email));
+            RedisHelper.Set("get:" + email, code, 120);
+#if !DEBUG
+            return ResultData(null, true, "验证码发送成功!");
+#else
+            return ResultData(null, true, "验证码:" + code);
+#endif
+        }
+    }
+}

+ 0 - 5
src/Masuit.MyBlogs.Core/Extensions/IApplicationBuilderExtensions.cs

@@ -4,11 +4,6 @@ namespace Masuit.MyBlogs.Core.Extensions
 {
     public static class IApplicationBuilderExtensions
     {
-        public static IApplicationBuilder UseException(this IApplicationBuilder builder)
-        {
-            return builder.UseMiddleware<ExceptionMiddleware>();
-        }
-
         public static IApplicationBuilder UseRequestIntercept(this IApplicationBuilder builder)
         {
             return builder.UseMiddleware<RequestInterceptMiddleware>();

+ 50 - 0
src/Masuit.MyBlogs.Core/Extensions/MyExceptionFilter.cs

@@ -0,0 +1,50 @@
+using Masuit.Tools.Logging;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Text;
+using System.Web;
+
+namespace Masuit.MyBlogs.Core.Extensions
+{
+    public class MyExceptionFilter : ExceptionFilterAttribute
+    {
+        /// <inheritdoc />
+        public override void OnException(ExceptionContext context)
+        {
+            base.OnException(context);
+            string err;
+            var req = context.HttpContext.Request;
+            switch (context.Exception)
+            {
+                case DbUpdateConcurrencyException ex:
+                    err = $"异常源:{ex.Source},异常类型:{ex.GetType().Name},\n请求路径:{req.Scheme}://{req.Host}{HttpUtility.UrlDecode(req.Path)},请求参数:{HttpUtility.UrlDecode(req.Body.ReadToEnd(Encoding.UTF8))},客户端用户代理:{req.Headers["User-Agent"]},客户端IP:{context.HttpContext.Connection.RemoteIpAddress}\t{ex.InnerException?.Message}\t";
+                    LogManager.Error(err, ex);
+                    break;
+                case DbUpdateException ex:
+                    err = $"异常源:{ex.Source},异常类型:{ex.GetType().Name},\n请求路径:{req.Scheme}://{req.Host}{HttpUtility.UrlDecode(req.Path)},请求参数:{HttpUtility.UrlDecode(req.Body.ReadToEnd(Encoding.UTF8))},客户端用户代理:{req.Headers["User-Agent"]},客户端IP:{context.HttpContext.Connection.RemoteIpAddress}\t{ex?.InnerException?.Message}\t";
+                    LogManager.Error(err, ex);
+                    break;
+                case AggregateException ex:
+                    LogManager.Debug("↓↓↓" + ex.Message + "↓↓↓");
+                    ex.Handle(e =>
+                    {
+                        LogManager.Error($"异常源:{e.Source},异常类型:{e.GetType().Name},\n请求路径:{req.Scheme}://{req.Host}{HttpUtility.UrlDecode(req.Path)},请求参数:{HttpUtility.UrlDecode(req.Body.ReadToEnd(Encoding.UTF8))},客户端用户代理:{req.Headers["User-Agent"]},客户端IP:{context.HttpContext.Connection.RemoteIpAddress}\t", e);
+                        return true;
+                    });
+                    break;
+                case NotFoundException _:
+                    context.Result = new RedirectToActionResult("Index", "Error", new { });
+                    context.ExceptionHandled = true;
+                    return;
+                default:
+                    LogManager.Error($"异常源:{context.Exception.Source},异常类型:{context.Exception.GetType().Name},\n请求路径:{req.Scheme}://{req.Host}{HttpUtility.UrlDecode(req.Path)},请求参数:{HttpUtility.UrlDecode(req.Body.ReadToEnd(Encoding.UTF8))},客户端用户代理:{req.Headers["User-Agent"]},客户端IP:{context.HttpContext.Connection.RemoteIpAddress}\t", context.Exception);
+                    break;
+            }
+
+            context.Result = new RedirectToActionResult("ServiceUnavailable", "Error", null);
+            context.ExceptionHandled = true;
+        }
+    }
+}

+ 11 - 0
src/Masuit.MyBlogs.Core/Extensions/NotFoundException.cs

@@ -0,0 +1,11 @@
+using System;
+
+namespace Masuit.MyBlogs.Core.Extensions
+{
+    public class NotFoundException : Exception
+    {
+        public NotFoundException(string msg) : base(msg)
+        {
+        }
+    }
+}

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

@@ -29,6 +29,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure
             modelBuilder.Entity<Post>().HasMany(e => e.Comment).WithOne(e => e.Post).OnDelete(DeleteBehavior.Cascade);
             modelBuilder.Entity<Post>().HasMany(e => e.PostHistoryVersion).WithOne(e => e.Post).OnDelete(DeleteBehavior.Cascade);
             modelBuilder.Entity<Post>().HasMany(e => e.Seminar).WithOne(s => s.Post).OnDelete(DeleteBehavior.Cascade);
+            modelBuilder.Entity<Post>().HasMany(e => e.PostMergeRequests).WithOne(s => s.Post).OnDelete(DeleteBehavior.Cascade);
             modelBuilder.Entity<PostHistoryVersion>().HasMany(e => e.Seminar).WithOne(s => s.PostHistoryVersion);
             modelBuilder.Entity<SearchDetails>().Property(e => e.KeyWords).IsUnicode();
             modelBuilder.Entity<UserInfo>().HasMany(e => e.LoginRecord).WithOne(e => e.UserInfo).OnDelete(DeleteBehavior.Cascade);
@@ -78,6 +79,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure
         public virtual DbSet<InternalMessage> InternalMessage { get; set; }
         public virtual DbSet<FastShare> FastShare { get; set; }
         public virtual DbSet<Banner> Banner { get; set; }
+        public virtual DbSet<PostMergeRequest> PostMergeRequests { get; set; }
     }
 
     /// <summary>

+ 1 - 0
src/Masuit.MyBlogs.Core/Infrastructure/Repository/Interface/IBaseRepository.cs

@@ -548,5 +548,6 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository.Interface
     public partial interface ISeminarPostRepository : IBaseRepository<SeminarPost> { }
     public partial interface ISeminarPostHistoryVersionRepository : IBaseRepository<SeminarPostHistoryVersion> { }
     public partial interface IBannerRepository : IBaseRepository<Banner> { }
+    public partial interface IPostMergeRequestRepository : IBaseRepository<PostMergeRequest> { }
 
 }

+ 3 - 0
src/Masuit.MyBlogs.Core/Infrastructure/Repository/Repositories.cs

@@ -82,4 +82,7 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Repository
     public partial class BannerRepository : BaseRepository<Banner>, IBannerRepository
     {
     }
+    public partial class PostMergeRequestRepository : BaseRepository<PostMergeRequest>, IPostMergeRequestRepository
+    {
+    }
 }

+ 1 - 0
src/Masuit.MyBlogs.Core/Infrastructure/Services/Interface/IServices.cs

@@ -29,5 +29,6 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Services.Interface
     public partial interface ISeminarPostService : IBaseService<SeminarPost> { }
     public partial interface ISeminarPostHistoryVersionService : IBaseService<SeminarPostHistoryVersion> { }
     public partial interface IBannerService : IBaseService<Banner> { }
+    public partial interface IPostMergeRequestService : IBaseService<PostMergeRequest> { }
 
 }

+ 6 - 0
src/Masuit.MyBlogs.Core/Infrastructure/Services/Services.cs

@@ -108,4 +108,10 @@ namespace Masuit.MyBlogs.Core.Infrastructure.Services
         {
         }
     }
+    public partial class PostMergeRequestService : BaseService<PostMergeRequest>, IPostMergeRequestService
+    {
+        public PostMergeRequestService(IBaseRepository<PostMergeRequest> repository, ISearchEngine<DataContext> searchEngine, ILuceneIndexSearcher searcher) : base(repository, searchEngine, searcher)
+        {
+        }
+    }
 }

+ 2 - 2
src/Masuit.MyBlogs.Core/Masuit.MyBlogs.Core.csproj

@@ -35,7 +35,7 @@
     <PackageReference Include="EFSecondLevelCache.Core" Version="2.6.2" />
     <PackageReference Include="Hangfire" Version="1.7.6" />
     <PackageReference Include="Hangfire.Autofac" Version="2.3.1" />
-    <PackageReference Include="Hangfire.MemoryStorage" Version="1.6.1" />
+    <PackageReference Include="Hangfire.MemoryStorage" Version="1.6.2" />
     <PackageReference Include="Hangfire.Redis.StackExchange" Version="1.8.0" />
     <PackageReference Include="htmldiff.net-core" Version="1.3.6" />
     <PackageReference Include="IP2Region" Version="1.2.0" />
@@ -43,7 +43,7 @@
     <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="2.2.6" />
     <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.2.3" />
     <PackageReference Include="PanGu.HighLight" Version="1.0.0" />
-    <PackageReference Include="Polly" Version="7.1.0" />
+    <PackageReference Include="Polly" Version="7.1.1" />
     <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.2.0" />
     <PackageReference Include="WilderMinds.RssSyndication" Version="1.5.0" />
     <PackageReference Include="Z.EntityFramework.Extensions.EFCore" Version="2.6.16" />

+ 33 - 0
src/Masuit.MyBlogs.Core/Models/DTO/PostMergeRequestInputDto.cs

@@ -0,0 +1,33 @@
+using Masuit.MyBlogs.Core.Models.Validation;
+using System.ComponentModel.DataAnnotations;
+
+namespace Masuit.MyBlogs.Core.Models.DTO
+{
+    /// <summary>
+    /// 文章修改请求
+    /// </summary>
+    public class PostMergeRequestInputDto : PostMergeRequestInputDtoBase
+    {
+        /// <summary>
+        /// 文章id
+        /// </summary>
+        public int PostId { get; set; }
+
+        /// <summary>
+        /// 修改人
+        /// </summary>
+        [Required, MaxLength(36, ErrorMessage = "修改人名字最长支持36个字符!"), MinLength(2, ErrorMessage = "修改人名字最少2个字符!")]
+        public string Modifier { get; set; }
+
+        /// <summary>
+        /// 修改人邮箱
+        /// </summary>
+        [Required(ErrorMessage = "邮箱不能为空!"), MinLength(6, ErrorMessage = "邮箱格式不正确!"), IsEmail]
+        public string ModifierEmail { get; set; }
+        /// <summary>
+        /// 验证码
+        /// </summary>
+        [Required(ErrorMessage = "验证码不能为空!")]
+        public string Code { get; set; }
+    }
+}

+ 23 - 0
src/Masuit.MyBlogs.Core/Models/DTO/PostMergeRequestInputDtoBase.cs

@@ -0,0 +1,23 @@
+using System.ComponentModel.DataAnnotations;
+using Masuit.MyBlogs.Core.Models.Validation;
+
+namespace Masuit.MyBlogs.Core.Models.DTO
+{
+    /// <summary>
+    /// 文章修改请求
+    /// </summary>
+    public class PostMergeRequestInputDtoBase : BaseDto
+    {
+        /// <summary>
+        /// 标题
+        /// </summary>
+        [Required(ErrorMessage = "文章标题不能为空!"), MaxLength(128, ErrorMessage = "文章标题最长支持128个字符!"), MinLength(4, ErrorMessage = "文章标题最少4个字符!")]
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 文章内容
+        /// </summary>
+        [Required(ErrorMessage = "文章内容不能为空!"), SubmitCheck(20, 1000000, false)]
+        public string Content { get; set; }
+    }
+}

+ 13 - 0
src/Masuit.MyBlogs.Core/Models/DTO/PostMergeRequestOutputDto.cs

@@ -0,0 +1,13 @@
+namespace Masuit.MyBlogs.Core.Models.DTO
+{
+    /// <summary>
+    /// ÎÄÕÂÐÞ¸ÄÇëÇó
+    /// </summary>
+    public class PostMergeRequestOutputDto : PostMergeRequestOutputDtoBase
+    {
+        /// <summary>
+        /// ÎÄÕÂÄÚÈÝ
+        /// </summary>
+        public string Content { get; set; }
+    }
+}

+ 46 - 0
src/Masuit.MyBlogs.Core/Models/DTO/PostMergeRequestOutputDtoBase.cs

@@ -0,0 +1,46 @@
+using Masuit.MyBlogs.Core.Models.Enum;
+using System;
+
+namespace Masuit.MyBlogs.Core.Models.DTO
+{
+    /// <summary>
+    /// 文章修改请求
+    /// </summary>
+    public class PostMergeRequestOutputDtoBase : BaseDto
+    {
+        /// <summary>
+        /// 原文id
+        /// </summary>
+        public int PostId { get; set; }
+
+        /// <summary>
+        /// 原标题
+        /// </summary>
+        public string PostTitle { get; set; }
+
+        /// <summary>
+        /// 标题
+        /// </summary>
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 修改人
+        /// </summary>
+        public string Modifier { get; set; }
+
+        /// <summary>
+        /// 修改人邮箱
+        /// </summary>
+        public string ModifierEmail { get; set; }
+
+        /// <summary>
+        /// 合并状态
+        /// </summary>
+        public MergeStatus MergeState { get; set; }
+
+        /// <summary>
+        /// 提交时间
+        /// </summary>
+        public DateTime SubmitTime { get; set; }
+    }
+}

+ 10 - 0
src/Masuit.MyBlogs.Core/Models/DTO/PostOutputDto.cs

@@ -72,6 +72,16 @@ namespace Masuit.MyBlogs.Core.Models.DTO
         /// </summary>
         public string Email { get; set; }
 
+        /// <summary>
+        /// 修改人名字
+        /// </summary>
+        public string Modifier { get; set; }
+
+        /// <summary>
+        /// 修改人邮箱
+        /// </summary>
+        public string ModifierEmail { get; set; }
+
         /// <summary>
         /// 标签
         /// </summary>

+ 1 - 1
src/Masuit.MyBlogs.Core/Models/DTO/UserInfoOutputDto.cs

@@ -26,7 +26,7 @@ namespace Masuit.MyBlogs.Core.Models.DTO
         /// 是否是管理员
         /// </summary>
         [DefaultValue(false)]
-        public bool IsAdmin { get; set; }
+        public bool IsAdmin { get; set; } = false;
 
         /// <summary>
         /// QQ或微信

+ 16 - 0
src/Masuit.MyBlogs.Core/Models/Entity/Post.cs

@@ -24,6 +24,7 @@ namespace Masuit.MyBlogs.Core.Models.Entity
             Status = Status.Pending;
             IsWordDocument = false;
             Seminar = new HashSet<SeminarPost>();
+            PostMergeRequests = new HashSet<PostMergeRequest>();
         }
 
         /// <summary>
@@ -87,6 +88,16 @@ namespace Masuit.MyBlogs.Core.Models.Entity
         [Required(ErrorMessage = "作者邮箱不能为空!"), EmailAddress, LuceneIndex]
         public string Email { get; set; }
 
+        /// <summary>
+        /// 修改人名字
+        /// </summary>
+        public string Modifier { get; set; }
+
+        /// <summary>
+        /// 修改人邮箱
+        /// </summary>
+        public string ModifierEmail { get; set; }
+
         /// <summary>
         /// 标签
         /// </summary>
@@ -150,5 +161,10 @@ namespace Masuit.MyBlogs.Core.Models.Entity
         /// 文章历史版本
         /// </summary>
         public virtual ICollection<PostHistoryVersion> PostHistoryVersion { get; set; }
+
+        /// <summary>
+        /// 文章修改请求
+        /// </summary>
+        public virtual ICollection<PostMergeRequest> PostMergeRequests { get; set; }
     }
 }

+ 10 - 0
src/Masuit.MyBlogs.Core/Models/Entity/PostHistoryVersion.cs

@@ -79,6 +79,16 @@ namespace Masuit.MyBlogs.Core.Models.Entity
         [StringLength(255), IsEmail]
         public string Email { get; set; }
 
+        /// <summary>
+        /// 修改人名字
+        /// </summary>
+        public string Modifier { get; set; }
+
+        /// <summary>
+        /// 修改人邮箱
+        /// </summary>
+        public string ModifierEmail { get; set; }
+
         /// <summary>
         /// 标签
         /// </summary>

+ 51 - 0
src/Masuit.MyBlogs.Core/Models/Entity/PostMergeRequest.cs

@@ -0,0 +1,51 @@
+using Masuit.MyBlogs.Core.Models.Enum;
+using System;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Masuit.MyBlogs.Core.Models.Entity
+{
+    /// <summary>
+    /// 文章修改请求
+    /// </summary>
+    [Table("PostMergeRequest")]
+    public class PostMergeRequest : BaseEntity
+    {
+        /// <summary>
+        /// 文章id
+        /// </summary>
+        public int PostId { get; set; }
+
+        /// <summary>
+        /// 标题
+        /// </summary>
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 文章内容
+        /// </summary>
+        public string Content { get; set; }
+
+        /// <summary>
+        /// 修改人
+        /// </summary>
+        public string Modifier { get; set; }
+
+        /// <summary>
+        /// 修改人邮箱
+        /// </summary>
+        public string ModifierEmail { get; set; }
+
+        /// <summary>
+        /// 合并状态
+        /// </summary>
+        public MergeStatus MergeState { get; set; }
+
+        /// <summary>
+        /// 提交时间
+        /// </summary>
+        public DateTime SubmitTime { get; set; }
+
+        [ForeignKey("PostId")]
+        public virtual Post Post { get; set; }
+    }
+}

+ 23 - 0
src/Masuit.MyBlogs.Core/Models/Enum/MergeStatus.cs

@@ -0,0 +1,23 @@
+namespace Masuit.MyBlogs.Core.Models.Enum
+{
+    /// <summary>
+    /// 文章合并状态
+    /// </summary>
+    public enum MergeStatus
+    {
+        /// <summary>
+        /// 待合并
+        /// </summary>
+        Pending,
+
+        /// <summary>
+        /// 已合并
+        /// </summary>
+        Merged,
+
+        /// <summary>
+        /// 拒绝
+        /// </summary>
+        Reject
+    }
+}

+ 3 - 1
src/Masuit.MyBlogs.Core/Startup.cs

@@ -130,6 +130,9 @@ namespace Masuit.MyBlogs.Core
 
             services.AddMvc(options =>
             {
+#if !DEBUG
+                options.Filters.Add<MyExceptionFilter>(); 
+#endif
                 options.CacheProfiles.Add("Default", new CacheProfile()
                 {
                     Location = ResponseCacheLocation.Any,
@@ -202,7 +205,6 @@ namespace Masuit.MyBlogs.Core
             else
             {
                 app.UseExceptionHandler("/Home/Error");
-                app.UseException();
             }
 
             //db.Database.Migrate();

+ 2 - 2
src/Masuit.MyBlogs.Core/Views/Post/CompareVersion.cshtml

@@ -49,7 +49,7 @@
                                     <div class="row">
                                         <div class="col-sm-7">
                                             <div class="padding-bot10">
-                                                修改于<span class="text-success">@Model[1].ModifyDate.ToString("yyyy-MM-dd HH:mm:ss")</span> | 原分类:<i class="icon-map-pin"></i>@Html.ActionLink(Model[1].Category.Name, "Category", "Home", new { id = Model[1].CategoryId }, new { @class = "label label-" + colors[new Random().Next() % colors.Length] })
+                                                <span class="label label-@colors[new Random().Next() % colors.Length]">@Model[1].Modifier</span>修改于<span class="text-success">@Model[1].ModifyDate.ToString("yyyy-MM-dd HH:mm:ss")</span> | 原分类:<i class="icon-map-pin"></i>@Html.ActionLink(Model[1].Category.Name, "Category", "Home", new { id = Model[1].CategoryId }, new { @class = "label label-" + colors[new Random().Next() % colors.Length] })
                                                 @if (Model[1].Seminar.Any())
                                                 {
                                                     <span> | 原所属专题:</span>
@@ -107,7 +107,7 @@
                                     <div class="row">
                                         <div class="col-sm-7">
                                             <div class="padding-bot10">
-                                                修改于<span class="text-success">@Model[2].ModifyDate.ToString("yyyy-MM-dd HH:mm:ss")</span> | 原分类:<i class="icon-map-pin"></i>@Html.ActionLink(Model[2].Category.Name, "Category", "Home", new { id = Model[2].CategoryId }, new { @class = "label label-" + colors[new Random().Next() % colors.Length] })
+                                                <span class="label label-@colors[new Random().Next() % colors.Length]">@Model[2].Modifier</span>修改于<span class="text-success">@Model[2].ModifyDate.ToString("yyyy-MM-dd HH:mm:ss")</span> | 原分类:<i class="icon-map-pin"></i>@Html.ActionLink(Model[2].Category.Name, "Category", "Home", new { id = Model[2].CategoryId }, new { @class = "label label-" + colors[new Random().Next() % colors.Length] })
                                                 @if (Model[2].Seminar.Any())
                                                 {
                                                     <span> | 原所属专题:</span>

+ 5 - 2
src/Masuit.MyBlogs.Core/Views/Post/Details.cshtml

@@ -43,8 +43,8 @@
                             <div class="row">
                                 <div class="col-sm-7">
                                     <div class="padding-bot10">
-                                        发表于<span class="text-info">@Model.PostDate.ToString("yyyy-MM-dd HH:mm:ss")</span> |
-                                        <span class="label label-@colors[new Random().Next() % colors.Length]">@Model.Author</span>最后修改于<span class="text-success">@Model.ModifyDate.ToString("yyyy-MM-dd HH:mm:ss")</span>
+                                        <span class="label label-@colors[new Random().Next() % colors.Length]">@Model.Author</span>发表于<span class="text-info">@Model.PostDate.ToString("yyyy-MM-dd HH:mm:ss")</span> |
+                                        <span class="label label-@colors[new Random().Next() % colors.Length]">@Model.Modifier</span>最后修改于<span class="text-success">@Model.ModifyDate.ToString("yyyy-MM-dd HH:mm:ss")</span>
                                     </div>
                                 </div>
                                 @{
@@ -78,6 +78,9 @@
                                             @Html.ActionLink(s.Seminar.Title, "Index", "Seminar", new { id = s.SeminarId }, new { @class = "label label-" + colors[new Random().Next() % colors.Length] })<text> </text>
                                         }
                                     }
+                                    <div class="pull-right margin-right20">
+                                        @Html.ActionLink("我要编辑", "PushMerge", "Post", new { id = Model.Id }, new { @class = "btn btn-success" })
+                                    </div>
                                 </div>
                             </div>
                         </header>

+ 5 - 2
src/Masuit.MyBlogs.Core/Views/Post/Details_Admin.cshtml

@@ -55,8 +55,8 @@
                             <div class="row">
                                 <div class="col-sm-7">
                                     <div class="padding-bot10">
-                                        发表于<span class="text-info">@Model.PostDate.ToString("yyyy-MM-dd HH:mm:ss")</span> |
-                                        <span class="label label-@colors[new Random().Next() % colors.Length]">@Model.Author</span>最后修改于<span class="text-success">@Model.ModifyDate.ToString("yyyy-MM-dd HH:mm:ss")</span>
+                                        <span class="label label-@colors[new Random().Next() % colors.Length]">@Model.Author</span>发表于<span class="text-info">@Model.PostDate.ToString("yyyy-MM-dd HH:mm:ss")</span> |
+                                        <span class="label label-@colors[new Random().Next() % colors.Length]">@Model.Modifier</span>最后修改于<span class="text-success">@Model.ModifyDate.ToString("yyyy-MM-dd HH:mm:ss")</span>
                                     </div>
                                 </div>
                                 @{
@@ -90,6 +90,9 @@
                                             @Html.ActionLink(s.Seminar.Title, "Index", "Seminar", new { id = s.SeminarId }, new { @class = "label label-" + colors[new Random().Next() % colors.Length] })<text> </text>
                                         }
                                     }
+                                    <div class="pull-right margin-right20">
+                                        @Html.ActionLink("我要编辑", "PushMerge", "Post", new { id = Model.Id }, new { @class = "btn btn-success" })
+                                    </div>
                                 </div>
                             </div>
                         </header>

+ 6 - 3
src/Masuit.MyBlogs.Core/Views/Post/History.cshtml

@@ -2,7 +2,7 @@
 @model List<Masuit.MyBlogs.Core.Models.Entity.PostHistoryVersion>
 @{
     var post = (PostOutputDto)ViewBag.Primary;
-    ViewBag.Title = post.Title+"的历史版本";
+    ViewBag.Title = post.Title + "的历史版本";
     Layout = "~/Views/Shared/_Layout.cshtml";
 }
 <div class="container" style="min-height: 70vh">
@@ -21,12 +21,13 @@
                 <th>标题</th>
                 <th>原分类</th>
                 <th>修改时间</th>
+                <th>修改人</th>
             </tr>
         </thead>
         <tbody>
             <tr>
                 <td style="text-align: center">
-                    <input type="checkbox" name="vers" id="0"/>
+                    <input type="checkbox" name="vers" id="0" />
                 </td>
                 <td>
                     @Html.ActionLink(post.Title, "Details", "Post", new { id = post.Id }, null) <span class="text-red">(最新版本)</span>
@@ -35,12 +36,13 @@
                     @Html.ActionLink(post.CategoryName, "Category", "Home", new { id = post.CategoryId }, null)
                 </td>
                 <td>@post.ModifyDate.ToString("yyyy-MM-dd HH:mm:ss")</td>
+                <td>@post.Modifier</td>
             </tr>
             @foreach (var p in Model)
             {
                 <tr>
                     <td style="text-align: center">
-                        <input type="checkbox" name="vers" id="@p.Id"/>
+                        <input type="checkbox" name="vers" id="@p.Id" />
                     </td>
                     <td>
                         @Html.ActionLink(p.Title, "HistoryVersion", "Post", new { id = p.PostId, hid = p.Id }, null)
@@ -49,6 +51,7 @@
                         @Html.ActionLink(p.Category.Name, "Category", "Home", new { id = post.CategoryId }, null)
                     </td>
                     <td>@p.ModifyDate.ToString("yyyy-MM-dd HH:mm:ss")</td>
+                    <td>@p.Modifier</td>
                 </tr>
             }
         </tbody>

+ 1 - 1
src/Masuit.MyBlogs.Core/Views/Post/HistoryVersion.cshtml

@@ -36,7 +36,7 @@
                             <div class="row">
                                 <div class="col-sm-7">
                                     <div class="padding-bot10">
-                                        修改于<span class="text-success">@Model.ModifyDate.ToString("yyyy-MM-dd HH:mm:ss")</span> |
+                                        <span class="label label-@colors[new Random().Next() % colors.Length]">@Model.Modifier</span>修改于<span class="text-success">@Model.ModifyDate.ToString("yyyy-MM-dd HH:mm:ss")</span> |
                                         原分类:<i class="icon-map-pin"></i>@Html.ActionLink(Model.Category.Name, "Category", "Home", new { id = Model.CategoryId }, new { @class = "label label-" + colors[new Random().Next() % colors.Length] })
                                         @if (Model.Seminar.Any())
                                         {

+ 1 - 1
src/Masuit.MyBlogs.Core/Views/Post/HistoryVersion_Admin.cshtml

@@ -32,7 +32,7 @@
                             <div class="row">
                                 <div class="col-sm-7">
                                     <div class="padding-bot10">
-                                        修改于<span class="text-success">@Model.ModifyDate.ToString("yyyy-MM-dd HH:mm:ss")</span> |
+                                        <span class="label label-@colors[new Random().Next() % colors.Length]">@Model.Modifier</span>修改于<span class="text-success">@Model.ModifyDate.ToString("yyyy-MM-dd HH:mm:ss")</span> |
                                         原分类:<i class="icon-map-pin"></i>@Html.ActionLink(Model.Category.Name, "Category", "Home", new { id = Model.CategoryId }, new { @class = "label label-" + colors[new Random().Next() % colors.Length] })
                                         @if (Model.Seminar.Any())
                                         {

+ 17 - 8
src/Masuit.MyBlogs.Core/Views/Post/Publish.cshtml

@@ -54,12 +54,6 @@
                 </div>
             </div>
             <div class="col-md-4 col-sm-6">
-                <div class="input-group">
-                    <span class="input-group-addon"><label for="Email">邮箱:</label></span>
-                    <input type="email" class="form-control" id="Email" name="Email" required placeholder="您的电子邮箱(非常重要)">
-                </div>
-            </div>
-            <div class="col-md-12 col-sm-6">
                 <div class="input-group">
                     <span class="input-group-addon"><label for="Label">标签:</label></span>
                     <div class="ui fluid multiple search selection dropdown tags">
@@ -73,8 +67,23 @@
                             }
                         </div>
                     </div>
+                </div>
+            </div>
+            <div class="col-md-6 col-sm-6">
+                <div class="input-group">
+                    <span class="input-group-addon"><label for="Email">邮箱:</label></span>
+                    <input type="email" class="form-control" id="Email" name="Email" required placeholder="您的电子邮箱(非常重要)">
+                </div>
+            </div>
+            <div class="col-md-6 col-sm-6">
+                <div class="input-group">
+                    <span class="input-group-addon"><label for="Code">验证码:</label></span>
+                    <input type="text" class="form-control" id="Code" name="Code" required placeholder="验证码">
                     <span class="input-group-btn">
-                        <button type="button" id="submit" class="btn btn-info">
+                        <button type="button" id="getcode" class="btn btn-danger">
+                            获取验证码
+                        </button>
+                        <button type="submit" id="submit" class="btn btn-info">
                             <i class="icon-rocket2"></i>
                             马上投递
                         </button>
@@ -86,5 +95,5 @@
 </div>
 <script src="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.js"></script>
 <script src="~/Assets/UEditor/ueditor.config.front.min.js"></script>
-<script src="https://apps.bdimg.com/libs/ueditor/1.4.3.1/ueditor.all.min.js"></script>
+<script src="~/Assets/UEditor/ueditor.all.min.js"></script>
 <script src="~/Scripts/publish/publish.min.js"></script>

+ 195 - 0
src/Masuit.MyBlogs.Core/Views/Post/PushMerge.cshtml

@@ -0,0 +1,195 @@
+@model Post
+@using Masuit.MyBlogs.Core.Models.Entity
+@{
+    ViewBag.Title = "正在编辑:" + Model.Title;
+    Layout = "~/Views/Shared/_Layout.cshtml";
+}
+<link href="https://cdn.bootcss.com/limonte-sweetalert2/8.11.8/sweetalert2.min.css" rel="stylesheet">
+<div class="container">
+    <ol class="cd-breadcrumb triangle">
+        <li>@Html.ActionLink("首页", "Index", "Home")</li>
+        <li>@Html.ActionLink("文章列表", "Post", "Home")</li>
+        <li>@Html.ActionLink(Model.Title.Length > 50 ? Model.Title.Substring(0, 50) + "..." : Model.Title, "Details", "Post", new { id = Model.Id }, null)</li>
+        <li class="current"><em>创建合并请求</em></li>
+    </ol>
+    <hr />
+    <form class="form-group" id="merge-form" method="post">
+        @Html.AntiForgeryToken()
+        <div class="input-group">
+            <span class="input-group-addon size18"><label for="title">文章标题:</label></span>
+            <input type="text" id="title" class="form-control input-lg" name="Title" required placeholder="请输入文章标题" value="@Model.Title">
+            <input type="hidden" name="PostId" value="@Model.Id">
+        </div>
+        <div class="form-group overlay animated bounceInDown">
+            <textarea id="editor" style="height: calc(100vh - 350px);" class="ueditor" name="Content" type="text/plain">@Model.Content</textarea>
+        </div>
+        <div class="row">
+            <div class="col-md-3 col-sm-4">
+                <div class="input-group">
+                    <span class="input-group-addon"><label for="Modifier">提交人:</label></span>
+                    <input type="text" class="form-control" id="Modifier" name="Modifier" required placeholder="投稿人真名或网名(非常重要)">
+                </div>
+            </div>
+            <div class="col-md-4 col-sm-4">
+                <div class="input-group">
+                    <span class="input-group-addon"><label for="ModifierEmail">你的邮箱:</label></span>
+                    <input type="email" class="form-control" id="ModifierEmail" name="ModifierEmail" required placeholder="您的电子邮箱(非常重要)">
+                </div>
+            </div>
+            <div class="col-md-5 col-sm-4">
+                <div class="input-group">
+                    <span class="input-group-addon"><label for="Code">验证码:</label></span>
+                    <input type="text" class="form-control" id="Code" name="Code" required placeholder="验证码">
+                    <span class="input-group-btn">
+                        <button type="button" id="getcode" class="btn btn-danger">
+                            获取验证码
+                        </button>
+                        <button type="submit" id="submit" class="btn btn-info">
+                            <i class="icon-rocket2"></i>
+                            马上投递
+                        </button>
+                    </span>
+                </div>
+            </div>
+        </div>
+    </form>
+</div>
+<script src="~/Assets/UEditor/ueditor.config.front.min.js"></script>
+<script src="~/Assets/UEditor/ueditor.all.min.js"></script>
+<script src="https://cdn.bootcss.com/limonte-sweetalert2/8.11.8/sweetalert2.min.js"></script>
+<script>
+    $(function () {
+        if (window.UE) {
+		    window.ue = UE.getEditor('editor', {
+			    initialFrameWidth: null
+		    });
+	    }
+        var user = JSON.parse(localStorage.getItem("user"));
+        if (user) {
+            $("[name='Modifier']").val(user.NickName);
+            $("[name='ModifierEmail']").val(user.Email);
+        }
+
+	    //检查草稿
+        if (localStorage.getItem("merge-post-draft-" [email protected])) {
+            notie.confirm({
+                text: "检查到上次有未提交的草稿,是否加载?",
+                submitText: "确定",
+                cancelText: "取消",
+                position: "bottom",
+                submitCallback: function () {
+                    var post = JSON.parse(localStorage.getItem("merge-post-draft-" [email protected]));
+                    $("#title").val(post.Title);
+                    ue.setContent(post.Content);
+                    window.interval = setInterval(function () {
+                        localStorage.setItem("merge-post-draft-" [email protected], JSON.stringify($("#merge-form").serializeObject()));
+                    }, 5000);
+                },
+                cancelCallback: function () {
+                    window.interval = setInterval(function () {
+                        localStorage.setItem("merge-post-draft-" [email protected], JSON.stringify($("#merge-form").serializeObject()));
+                    }, 5000);
+                }
+            });
+        } else {
+            window.interval = setInterval(function () {
+                localStorage.setItem("merge-post-draft-" [email protected], JSON.stringify($("#merge-form").serializeObject()));
+            }, 5000);
+        }
+
+        $("#getcode").on("click", function (e) {
+            e.preventDefault();
+            $.post("/validate/sendcode", {
+                __RequestVerificationToken: $("[name=__RequestVerificationToken]").val(),
+                email: $("#ModifierEmail").val()
+            }, function (data) {
+                if (data.Success) {
+                    layer.tips('验证码发送成功,请注意查收邮件,若未收到,请检查你的邮箱地址或邮件垃圾箱!', '#getcode', {
+                      tips: [1, '#3595CC'],
+                      time: 4000
+                    });
+                    user.NickName = $("[name='Modifier']").val();
+                    user.Email = $("[name='ModifierEmail']").val();
+                    localStorage.setItem("user", JSON.stringify(user));
+                    $("#getcode").attr('disabled', true);
+                    var count = 0;
+                    var timer = setInterval(function () {
+                        count++;
+                        $("#getcode").text('重新发送(' + (120 - count) + ')');
+                        if (count > 120) {
+                            clearInterval(timer);
+                            $("#getcode").attr('disabled', false);
+                            $("#getcode").text('重新发送');
+                        }
+                    }, 1000);
+                } else {
+                    layer.tips(data.Message, '#getcode', {
+                      tips: [1, '#3595CC'],
+                      time: 4000
+                    });
+                }
+            });
+        });
+
+        //异步提交表单开始
+        $("#merge-form").on("submit", function (e) {
+            e.preventDefault();
+            loading();
+            var formData = $(this).serializeObject();
+            if (formData["Title"].trim().length <= 2 || formData["Title"].trim().length > 128) {
+                window.notie.alert({
+                    type: 3,
+                    text: '文章标题必须在2到128个字符以内!',
+                    time: 4
+                });
+                loadingDone();
+                return;
+            }
+            if (formData["Modifier"].trim().length <= 2 || formData["Modifier"].trim().length > 36) {
+                window.notie.alert({
+                    type: 3,
+                    text: '修改人名字至少2个字,最多36个字!',
+                    time: 4
+                });
+                loadingDone();
+                return;
+            }
+            if (!/^\w+([-+.]\w+)*@@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test($("#ModifierEmail").val().trim())) {
+                window.notie.alert({
+                    type: 3,
+                    text: '请输入正确的邮箱格式!',
+                    time: 4
+                });
+                loadingDone();
+                return;
+            }
+            if (ue.getContent().length < 200 || ue.getContent().length > 1000000) {
+                window.notie.alert({
+                    type: 3,
+                    text: '文章内容过短或者超长的,字数200-1000000字!',
+                    time: 4
+                });
+                loadingDone();
+                return;
+            }
+            $.post("/@Model.Id/pushmerge", $(this).serialize(), (data) => {
+                loadingDone();
+                if (data.Success) {
+                    window.notie.alert({
+                        type: 1,
+                        text: data.Message,
+                        time: 4
+                    });
+                    $("[name='Title']").val("");
+                    ue.setContent("");
+                } else {
+                    window.notie.alert({
+                        type: 3,
+                        text: data.Message,
+                        time: 4
+                    });
+                }
+            });
+        });
+    });
+</script>

+ 196 - 0
src/Masuit.MyBlogs.Core/Views/Post/RepushMerge.cshtml

@@ -0,0 +1,196 @@
+@model PostMergeRequest
+@using Masuit.MyBlogs.Core.Models.Entity
+@{
+    ViewBag.Title = "正在编辑:" + Model.Post.Title;
+    Layout = "~/Views/Shared/_Layout.cshtml";
+}
+<link href="https://cdn.bootcss.com/limonte-sweetalert2/8.11.8/sweetalert2.min.css" rel="stylesheet">
+<div class="container">
+    <ol class="cd-breadcrumb triangle">
+        <li>@Html.ActionLink("首页", "Index", "Home")</li>
+        <li>@Html.ActionLink("文章列表", "Post", "Home")</li>
+        <li>@Html.ActionLink(Model.Post.Title.Length > 50 ? Model.Post.Title.Substring(0, 50) + "..." : Model.Post.Title, "Details", "Post", new { id = Model.Post.Id }, null)</li>
+        <li class="current"><em>重新编辑合并请求</em></li>
+    </ol>
+    <hr />
+    <form class="form-group" id="merge-form" method="post">
+        @Html.AntiForgeryToken()
+        <div class="input-group">
+            <span class="input-group-addon size18"><label for="title">文章标题:</label></span>
+            <input type="text" id="title" class="form-control input-lg" name="Title" required placeholder="请输入文章标题" value="@Model.Title">
+            <input type="hidden" name="PostId" value="@Model.Post.Id">
+            <input type="hidden" name="Id" value="@Model.Id">
+        </div>
+        <div class="form-group overlay animated bounceInDown">
+            <textarea id="editor" style="height: calc(100vh - 350px);" class="ueditor" name="Content" type="text/plain">@Model.Content</textarea>
+        </div>
+        <div class="row">
+            <div class="col-md-3 col-sm-4">
+                <div class="input-group">
+                    <span class="input-group-addon"><label for="Modifier">提交人:</label></span>
+                    <input type="text" class="form-control" id="Modifier" name="Modifier" required placeholder="投稿人真名或网名(非常重要)">
+                </div>
+            </div>
+            <div class="col-md-4 col-sm-4">
+                <div class="input-group">
+                    <span class="input-group-addon"><label for="ModifierEmail">你的邮箱:</label></span>
+                    <input type="email" class="form-control" id="ModifierEmail" name="ModifierEmail" required placeholder="您的电子邮箱(非常重要)">
+                </div>
+            </div>
+            <div class="col-md-5 col-sm-4">
+                <div class="input-group">
+                    <span class="input-group-addon"><label for="Code">验证码:</label></span>
+                    <input type="text" class="form-control" id="Code" name="Code" required placeholder="验证码">
+                    <span class="input-group-btn">
+                        <button type="button" id="getcode" class="btn btn-danger">
+                            获取验证码
+                        </button>
+                        <button type="submit" id="submit" class="btn btn-info">
+                            <i class="icon-rocket2"></i>
+                            马上投递
+                        </button>
+                    </span>
+                </div>
+            </div>
+        </div>
+    </form>
+</div>
+<script src="~/Assets/UEditor/ueditor.config.front.min.js"></script>
+<script src="~/Assets/UEditor/ueditor.all.min.js"></script>
+<script src="https://cdn.bootcss.com/limonte-sweetalert2/8.11.8/sweetalert2.min.js"></script>
+<script>
+    $(function () {
+        if (window.UE) {
+		    window.ue = UE.getEditor('editor', {
+			    initialFrameWidth: null
+		    });
+	    }
+        var user = JSON.parse(localStorage.getItem("user"));
+        if (user) {
+            $("[name='Modifier']").val(user.NickName);
+            $("[name='ModifierEmail']").val(user.Email);
+        }
+
+	    //检查草稿
+        if (localStorage.getItem("merge-post-draft-" [email protected])) {
+            notie.confirm({
+                text: "检查到上次有未提交的草稿,是否加载?",
+                submitText: "确定",
+                cancelText: "取消",
+                position: "bottom",
+                submitCallback: function () {
+                    var post = JSON.parse(localStorage.getItem("merge-post-draft-" [email protected]));
+                    $("#title").val(post.Title);
+                    ue.setContent(post.Content);
+                    window.interval = setInterval(function () {
+		                localStorage.setItem("merge-post-draft-"[email protected],JSON.stringify($("#merge-form").serializeObject()));
+	                },5000);
+                },
+                cancelCallback: function() {
+                    window.interval = setInterval(function () {
+		                localStorage.setItem("merge-post-draft-"[email protected],JSON.stringify($("#merge-form").serializeObject()));
+	                },5000);
+                }
+            });
+        } else {
+            window.interval = setInterval(function () {
+		        localStorage.setItem("merge-post-draft-"[email protected],JSON.stringify($("#merge-form").serializeObject()));
+	        },5000);
+        }
+
+        $("#getcode").on("click", function (e) {
+            e.preventDefault();
+            $.post("/validate/sendcode", {
+                __RequestVerificationToken: $("[name=__RequestVerificationToken]").val(),
+                email: $("#ModifierEmail").val()
+            }, function (data) {
+                if (data.Success) {
+                    layer.tips('验证码发送成功,请注意查收邮件,若未收到,请检查你的邮箱地址或邮件垃圾箱!', '#getcode', {
+                      tips: [1, '#3595CC'],
+                      time: 5000
+                    });
+                    user.NickName = $("[name='Modifier']").val();
+                    user.Email = $("[name='ModifierEmail']").val();
+                    localStorage.setItem("user", JSON.stringify(user));
+                    $("#getcode").attr('disabled', true);
+                    var count = 0;
+                    var timer = setInterval(function () {
+                        count++;
+                        $("#getcode").text('重新发送(' + (120 - count) + ')');
+                        if (count > 120) {
+                            clearInterval(timer);
+                            $("#getcode").attr('disabled', false);
+                            $("#getcode").text('重新发送');
+                        }
+                    }, 1000);
+                } else {
+                    layer.tips(data.Message, '#getcode', {
+                      tips: [1, '#3595CC'],
+                      time: 5000
+                    });
+                }
+            });
+        });
+
+        //异步提交表单开始
+        $("#merge-form").on("submit", function (e) {
+            e.preventDefault();
+            loading();
+            var formData = $(this).serializeObject();
+            if (formData["Title"].trim().length <= 2 || formData["Title"].trim().length > 128) {
+                window.notie.alert({
+                    type: 3,
+                    text: '文章标题必须在2到128个字符以内!',
+                    time: 4
+                });
+                loadingDone();
+                return;
+            }
+            if (formData["Modifier"].trim().length <= 2 || formData["Modifier"].trim().length > 36) {
+                window.notie.alert({
+                    type: 3,
+                    text: '修改人名字至少2个字,最多36个字!',
+                    time: 4
+                });
+                loadingDone();
+                return;
+            }
+            if (!/^\w+([-+.]\w+)*@@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test($("#ModifierEmail").val().trim())) {
+                window.notie.alert({
+                    type: 3,
+                    text: '请输入正确的邮箱格式!',
+                    time: 4
+                });
+                loadingDone();
+                return;
+            }
+            if (ue.getContent().length < 200 || ue.getContent().length > 1000000) {
+                window.notie.alert({
+                    type: 3,
+                    text: '文章内容过短或者超长的,字数200-1000000字!',
+                    time: 4
+                });
+                loadingDone();
+                return;
+            }
+            $.post("/@Model.Post.Id/pushmerge", $(this).serialize(), (data) => {
+                loadingDone();
+                if (data.Success) {
+                    window.notie.alert({
+                        type: 1,
+                        text: data.Message,
+                        time: 4
+                    });
+                    $("[name='Title']").val("");
+                    ue.setContent("");
+                } else {
+                    window.notie.alert({
+                        type: 3,
+                        text: data.Message,
+                        time: 4
+                    });
+                }
+            });
+        });
+    });
+</script>

+ 24 - 0
src/Masuit.MyBlogs.Core/bundleconfig.json

@@ -16,5 +16,29 @@
     "inputFiles": [
       "wwwroot/ng-views/controllers/menu.js"
     ]
+  },
+  {
+    "outputFileName": "wwwroot/ng-views/controllers/merge.min.js",
+    "inputFiles": [
+      "wwwroot/ng-views/controllers/merge.js"
+    ]
+  },
+  {
+    "outputFileName": "wwwroot/ng-views/controllers/post.min.js",
+    "inputFiles": [
+      "wwwroot/ng-views/controllers/post.js"
+    ]
+  },
+  {
+    "outputFileName": "wwwroot/Scripts/publish/publish.min.js",
+    "inputFiles": [
+      "wwwroot/Scripts/publish/publish.js"
+    ]
+  },
+  {
+    "outputFileName": "wwwroot/Assets/UEditor/ueditor.all.min.js",
+    "inputFiles": [
+      "wwwroot/Assets/UEditor/ueditor.all.js"
+    ]
   }
 ]

+ 30 - 0
src/Masuit.MyBlogs.Core/wwwroot/ng-views/app/route.config.js

@@ -29,6 +29,36 @@ myApp.config(["$stateProvider", "$urlRouterProvider", "$locationProvider",
 					},cpath + "/post.js"]);
 				}]
 			}
+		}).state("merge-list", {
+			url: "/merge/list",
+			templateUrl: vpath + "/merge/list.html",
+			controller: "mergelist as list",
+			resolve: {
+				deps: ["$ocLazyLoad", function($ocLazyLoad) {
+					return $ocLazyLoad.load([cpath + "/merge.js"]);
+				}]
+			}
+		}).state("merge-compare", {
+			url: "/merge/compare",
+			templateUrl: vpath + "/merge/compare.html",
+			controller: "mergecompare",
+			resolve: {
+				deps: ["$ocLazyLoad", function($ocLazyLoad) {
+					return $ocLazyLoad.load([cpath + "/merge.js"]);
+				}]
+			}
+		}).state("merge-edit", {
+			url: "/merge/edit",
+			templateUrl: vpath + "/merge/edit.html",
+			controller: "mergeedit",
+			resolve: {
+				deps: ["$ocLazyLoad", function($ocLazyLoad) {
+					return $ocLazyLoad.load([{
+						files: ["https://apps.bdimg.com/libs/ueditor/1.4.3.1/ueditor.all.js","/Assets/semantic/semantic.css","https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.js"],
+						cache: true
+					},cpath + "/merge.js"]);
+				}]
+			}
 		}).state("post-pending", {
 			url: "/postpending",
 			templateUrl: vpath + "/post/pending.html",

+ 26 - 0
src/Masuit.MyBlogs.Core/wwwroot/ng-views/controllers/main.js

@@ -236,6 +236,32 @@
 			$scope.loadingDone();
 		});
 	}
+
+	$scope.get = function(url, success, error) {
+		$http.get(url).then(function(res) {
+			if(res.data.Success) {
+				success(res.data);
+			} else {
+				if(error) {
+					error(res.data);
+				} else {
+					window.notie.alert({
+						type:3,
+						text:res.data.Message,
+						time:4
+					});
+				}
+				$scope.CheckLogin(res.data);
+			}
+		}, function() {
+			window.notie.alert({
+				type:3,
+				text:'服务请求失败!',
+				time:4
+			});
+		});
+	}
+
 	this.request = $scope.request;
 	$scope.CheckLogin = function(data) {
 		if(!data.IsLogin) {

+ 189 - 0
src/Masuit.MyBlogs.Core/wwwroot/ng-views/controllers/merge.js

@@ -0,0 +1,189 @@
+myApp.controller("mergelist", ["$scope", "$http", "NgTableParams", "$timeout", function ($scope, $http, NgTableParams, $timeout) {
+	window.hub.stop();
+	var self = this;
+	$scope.loading();
+	$scope.kw = "";
+	$scope.orderby = 1;
+	$scope.paginationConf = {
+		currentPage: $scope.currentPage ? $scope.currentPage : 1,
+		itemsPerPage: 10,
+		pagesLength: 25,
+		perPageOptions: [1, 5, 10, 15, 20, 30, 40, 50, 100, 200],
+		rememberPerPage: 'perPageItems',
+		onChange: function() {
+			self.GetPageData($scope.paginationConf.currentPage, $scope.paginationConf.itemsPerPage);
+		}
+	};
+	this.GetPageData = function(page, size) {
+		$scope.loading();
+		$http.get("/merge", {
+			page,
+			size,
+			search:$scope.kw
+		}).then(function(res) {
+			$scope.paginationConf.currentPage = page;
+			$scope.paginationConf.totalItems = res.data.TotalCount;
+			$("div[ng-table-pagination]").remove();
+			self.tableParams = new NgTableParams({
+				count: 50000
+			}, {
+				filterDelay: 0,
+				dataset: res.data.Data
+			});
+			$scope.loadingDone();
+		});
+	};
+	self.pass = function(row) {
+        swal({
+			title: "确认直接合并这篇文章吗?",
+			text: row.Title,
+			showCancelButton: true,
+			confirmButtonColor: "#DD6B55",
+			confirmButtonText: "确定",
+			cancelButtonText: "取消",
+			showLoaderOnConfirm: true,
+			animation: true,
+			allowOutsideClick: false
+		}).then(function() {
+			$scope.request("/merge/"+row.Id, null, function(data) {
+			    window.notie.alert({
+				    type: 1,
+				    text: data.Message,
+				    time: 4
+			    });
+			    self.stats = [];
+			    self.GetPageData($scope.paginationConf.currentPage, $scope.paginationConf.itemsPerPage);
+		    });
+		}, function() {
+		}).catch(swal.noop);
+    }
+
+	self.reject = function(row) {
+        swal({
+			title: "确认拒绝合并这篇文章吗?",
+			text: row.Title,
+			showCancelButton: true,
+			confirmButtonColor: "#DD6B55",
+			confirmButtonText: "确定",
+			cancelButtonText: "取消",
+			showLoaderOnConfirm: true,
+			animation: true,
+			allowOutsideClick: false
+		}).then(function() {
+			$scope.request("/merge/reject/"+row.Id, null, function(data) {
+			    window.notie.alert({
+				    type: 1,
+				    text: data.Message,
+				    time: 4
+			    });
+			    self.stats = [];
+			    self.GetPageData($scope.paginationConf.currentPage, $scope.paginationConf.itemsPerPage);
+		    });
+		}, function() {
+		}).catch(swal.noop);
+    }
+
+	var _timeout;
+	$scope.search = function (kw) {
+		if (_timeout) {
+			$timeout.cancel(_timeout);
+		}
+		_timeout = $timeout(function () {
+			$scope.kw = kw;
+			self.GetPageData($scope.paginationConf.currentPage, $scope.paginationConf.itemsPerPage, $scope.kw);
+			_timeout = null;
+		}, 500);
+	}
+	$scope.loadingDone();
+}]);
+myApp.controller("mergecompare", ["$scope", "$http", "$timeout","$location", function ($scope, $http, $timeout,$location) {
+	window.hub.stop();
+	clearInterval(window.interval);
+	$scope.loading();
+	$scope.id = $location.search()['id'];
+	$scope.get("/merge/compare/"+$scope.id, function(res) {
+		var data = res.Data;
+        $scope.old=data.old;
+        $scope.newer=data.newer;
+	    $scope.loadingDone();
+	});
+    
+	$scope.pass = function() {
+        swal({
+			title: "确认直接合并这篇文章吗?",
+			showCancelButton: true,
+			confirmButtonColor: "#DD6B55",
+			confirmButtonText: "确定",
+			cancelButtonText: "取消",
+			showLoaderOnConfirm: true,
+			animation: true,
+			allowOutsideClick: false
+		}).then(function() {
+			$scope.request("/merge/"+$scope.id, null, function(data) {
+			    window.notie.alert({
+				    type: 1,
+				    text: data.Message,
+				    time: 4
+			    });
+                window.location.href = "#/merge/list";
+		    });
+		}, function() {
+		}).catch(swal.noop);
+    }
+
+	$scope.reject = function() {
+        swal({
+			title: "拒绝合并理由:",
+            input: 'textarea',
+			showCancelButton: true,
+			confirmButtonColor: "#DD6B55",
+			confirmButtonText: "确定",
+			cancelButtonText: "取消",
+			showLoaderOnConfirm: true,
+			animation: true,
+			allowOutsideClick: false,
+            inputValidator: function(value) {
+            return new Promise(function(resolve, reject) {
+              if (value) {
+                resolve();
+              } else {
+                reject('请填写拒绝理由!');
+              }
+            });
+          }
+		}).then(function(reason) {
+            $scope.request("/merge/reject/" + $scope.id, {reason:reason}, function(data) {
+			    window.notie.alert({
+				    type: 1,
+				    text: data.Message,
+				    time: 4
+			    });
+                window.location.href = "#/merge/list";
+		    });
+		}, function() {
+		}).catch(swal.noop);
+    }
+
+}]);
+myApp.controller("mergeedit", ["$scope", "$http", "$timeout","$location", function ($scope, $http, $timeout,$location) {
+	window.hub.stop();
+	clearInterval(window.interval);
+	$scope.loading();
+	$scope.id = $location.search()['id'];
+	$scope.get("/merge/"+$scope.id, function(res) {
+		$scope.post= res.Data;
+	    $scope.loadingDone();
+	});
+    $scope.merge= function() {
+	    $scope.loading();
+        $scope.request("/merge",$scope.post, function(res) {
+		    window.notie.alert({
+				type: 1,
+				text: res.Message,
+				time: 4
+			});
+	        $scope.loadingDone();
+            window.location.href = "#/merge/list";
+	    });
+    }
+}]);

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
src/Masuit.MyBlogs.Core/wwwroot/ng-views/controllers/merge.min.js


+ 60 - 42
src/Masuit.MyBlogs.Core/wwwroot/ng-views/controllers/post.js

@@ -198,9 +198,6 @@ myApp.controller("writeblog", ["$scope", "$http", "$timeout", function ($scope,
 		Keyword:""
 	};
 	$scope.loading();
-	window.interval = setInterval(function () {
-		localStorage.setItem("write-post-draft",JSON.stringify($scope.post));
-	},5000);
 	$scope.post.Author = $scope.user.NickName || $scope.user.Username;
 	$scope.post.Email = $scope.user.Email;
 	$scope.getCategory = function () {
@@ -366,24 +363,36 @@ myApp.controller("writeblog", ["$scope", "$http", "$timeout", function ($scope,
 	}
 
 	//检查草稿
-	if (localStorage.getItem("write-post-draft")) {
-		notie.confirm({
-			text: "检查到上次有未提交的草稿,是否加载?",
-			submitText: "确定", 
-			cancelText: "取消",
-			position: "bottom", 
-			submitCallback: function () {
-				$scope.post=JSON.parse(localStorage.getItem("write-post-draft"));
-				$scope.$apply();
-				$timeout(function () {
-					$('.ui.dropdown.category').dropdown('set selected', [$scope.post.CategoryId]);
-					$('.ui.dropdown.tags').dropdown('set selected', $scope.post.Label.split(','));
-					$('.ui.dropdown.keyword').dropdown('set selected', $scope.post.Keyword.split(','));
-					$('.ui.dropdown.seminar').dropdown('set selected', $scope.post.Seminars.split(','));
-				}, 10);
-			}
-		});	
-	}
+    if (localStorage.getItem("write-post-draft")) {
+        notie.confirm({
+            text: "检查到上次有未提交的草稿,是否加载?",
+            submitText: "确定",
+            cancelText: "取消",
+            position: "bottom",
+            submitCallback: function () {
+                $scope.post = JSON.parse(localStorage.getItem("write-post-draft"));
+                $scope.$apply();
+                $timeout(function () {
+                    $('.ui.dropdown.category').dropdown('set selected', [$scope.post.CategoryId]);
+                    $('.ui.dropdown.tags').dropdown('set selected', $scope.post.Label.split(','));
+                    $('.ui.dropdown.keyword').dropdown('set selected', $scope.post.Keyword.split(','));
+                    $('.ui.dropdown.seminar').dropdown('set selected', $scope.post.Seminars.split(','));
+                }, 10);
+                window.interval = setInterval(function () {
+		            localStorage.setItem("write-post-draft",JSON.stringify($scope.post));
+	            },5000);
+            },
+            cancelCallback: function() {
+                window.interval = setInterval(function () {
+		            localStorage.setItem("write-post-draft",JSON.stringify($scope.post));
+	            },5000);
+            }
+        });
+    } else {
+        window.interval = setInterval(function () {
+		    localStorage.setItem("write-post-draft",JSON.stringify($scope.post));
+	    },5000);
+    }
 }]);
 myApp.controller("postedit", ["$scope", "$http", "$location", "$timeout", function ($scope, $http, $location, $timeout) {
 	window.hub.stop();
@@ -395,9 +404,6 @@ myApp.controller("postedit", ["$scope", "$http", "$location", "$timeout", functi
 		id: $scope.id
 	}, function (data) {
 		$scope.post = data.Data;
-		window.interval = setInterval(function () {
-			localStorage.setItem("post-draft-"+$scope.id,JSON.stringify($scope.post));
-		},5000);
 		$scope.request("/post/gettag", null, function (res) {
 			$scope.Tags = res.Data;
 			$('.ui.dropdown.tags').dropdown({
@@ -583,24 +589,36 @@ myApp.controller("postedit", ["$scope", "$http", "$location", "$timeout", functi
 	//异步提交表单结束
 	
 	//检查草稿
-	if (localStorage.getItem("post-draft-"+$scope.id)) {
-		notie.confirm({
-		  text: "检查到上次有未提交的草稿,是否加载?",
-		  submitText: "确定", 
-		  cancelText: "取消",
-		  position: "bottom", 
-			submitCallback: function () {
-				$scope.post=JSON.parse(localStorage.getItem("post-draft-"+$scope.id));
-				$scope.$apply();
-				$timeout(function () {
-					$('.ui.dropdown.category').dropdown('set selected', [$scope.post.CategoryId]);
-					$('.ui.dropdown.tags').dropdown('set selected', $scope.post.Label.split(','));
-					$('.ui.dropdown.keyword').dropdown('set selected', $scope.post.Keyword.split(','));
-					$('.ui.dropdown.seminar').dropdown('set selected', $scope.post.Seminars.split(','));
-				}, 10);
-			}
-		});	
-	}
+    if (localStorage.getItem("post-draft-" + $scope.id)) {
+        notie.confirm({
+            text: "检查到上次有未提交的草稿,是否加载?",
+            submitText: "确定",
+            cancelText: "取消",
+            position: "bottom",
+            submitCallback: function () {
+                $scope.post = JSON.parse(localStorage.getItem("post-draft-" + $scope.id));
+                $scope.$apply();
+                $timeout(function () {
+                    $('.ui.dropdown.category').dropdown('set selected', [$scope.post.CategoryId]);
+                    $('.ui.dropdown.tags').dropdown('set selected', $scope.post.Label.split(','));
+                    $('.ui.dropdown.keyword').dropdown('set selected', $scope.post.Keyword.split(','));
+                    $('.ui.dropdown.seminar').dropdown('set selected', $scope.post.Seminars.split(','));
+                }, 10);
+                window.interval = setInterval(function () {
+			        localStorage.setItem("post-draft-"+$scope.id,JSON.stringify($scope.post));
+		        },5000);
+            },
+            cancelCallback: function() {
+                window.interval = setInterval(function () {
+			        localStorage.setItem("post-draft-"+$scope.id,JSON.stringify($scope.post));
+		        },5000);
+            }
+        });
+    } else {
+        window.interval = setInterval(function () {
+			localStorage.setItem("post-draft-"+$scope.id,JSON.stringify($scope.post));
+		},5000);
+    }
 }]);
 myApp.controller("toppost", ["$scope", "$http", "$location", "$timeout", function ($scope, $http, $location, $timeout) {
 	window.hub.stop();

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
src/Masuit.MyBlogs.Core/wwwroot/ng-views/controllers/post.min.js


+ 1 - 0
src/Masuit.MyBlogs.Core/wwwroot/ng-views/template/sidebar-left.html

@@ -42,6 +42,7 @@
             <ul>
                 <li><a data-ui-sref-active="active" data-ui-sref="post-pending" data-ng-click="mactrl.sidebarStat($event)">文章审核</a></li>
                 <li><a data-ui-sref-active="active" data-ui-sref="post-list" data-ng-click="mactrl.sidebarStat($event)">文章列表</a></li>
+                <li><a data-ui-sref-active="active" data-ui-sref="merge-list" data-ng-click="mactrl.sidebarStat($event)">文章合并</a></li>
                 <li><a data-ui-sref-active="active" data-ui-sref="write-blog" data-ng-click="mactrl.sidebarStat($event)">写文章</a></li>
                 <li><a data-ui-sref-active="active" data-ui-sref="top-post" data-ng-click="mactrl.sidebarStat($event)">banner头图管理</a></li>
                 <li><a data-ui-sref-active="active" data-ui-sref="post-cat" data-ng-click="mactrl.sidebarStat($event)">文章分类管理</a></li>

+ 49 - 0
src/Masuit.MyBlogs.Core/wwwroot/ng-views/views/merge/compare.html

@@ -0,0 +1,49 @@
+<style>
+    ins {
+        background-color: #cfc;
+        text-decoration: none;
+    }
+
+    del {
+        color: #999;
+        background-color: #FEC8C8;
+    }
+    article img {
+        max-width: 100%
+    }
+</style>
+<div>
+    <div class="btn-group-lg">
+        <button class="btn btn-info" ng-click="pass()">接受合并</button>
+        <a class="btn btn-success" ng-href="#/merge/edit?id={{newer.Id}}">编辑并合并</a>
+        <button class="btn btn-danger" ng-click="reject()">拒绝合并</button>
+    </div>
+    <div class="row">
+        <div class="col-md-6">
+            <h2>原文:</h2>
+            <section>
+                <header class="page-header">
+                    <div class="text-center">
+                        <h2 class="padding-bot10">
+                            {{old.Title}}
+                        </h2>
+                    </div>
+                </header>
+                <article class="article" ng-bind-html="old.Content|htmlString"></article>
+            </section>
+        </div>
+        <div class="col-md-6">
+            <h2>修改后:</h2>
+            <section>
+                <header class="page-header">
+                    <div class="text-center">
+                        <h2 class="padding-bot10">
+                            {{newer.Title}}
+                        </h2>
+                    </div>
+                </header>
+                <article class="article" ng-bind-html="newer.Content|htmlString"></article>
+            </section>
+        </div>
+    </div>
+</div>

+ 12 - 0
src/Masuit.MyBlogs.Core/wwwroot/ng-views/views/merge/edit.html

@@ -0,0 +1,12 @@
+<form class="form-group" id="article-form" method="post" onsubmit="return false">
+    <div class="input-group">
+        <span class="input-group-addon size18">
+            <label for="article">文章标题:</label>
+        </span>
+        <input class="form-control input-lg" id="article" ng-model="post.Title" placeholder="请输入文章标题" required type="text">
+    </div>
+    <div class="animated bounceInDown form-group overlay">
+        <div class="ueditor" ng-model="post.Content" style="height: 56vh;" type="text/plain"></div>
+    </div>
+    <button class="btn btn-info btn-lg waves-effect" ng-click="merge(post)" type="button">确认合并</button>
+</form>

+ 54 - 0
src/Masuit.MyBlogs.Core/wwwroot/ng-views/views/merge/list.html

@@ -0,0 +1,54 @@
+<style>
+    .page-list {
+        display: flex;
+    }
+</style>
+<div>
+    <div class="form-inline pull-right">
+        <button class="btn btn-info waves-effect" ng-click="list.GetPageData(paginationConf.currentPage, paginationConf.itemsPerPage);">
+            <span class="glyphicon glyphicon-refresh"></span>
+        </button>
+        <div class="input-group">
+            <span class="input-group-addon">全局搜索:</span>
+            <div class="fg-line">
+                <input type="text" class="form-control" ng-change="search(kw)" ng-model="kw" placeholder="全局搜索" />
+            </div>
+        </div>
+    </div>
+    <table ng-table="list.tableParams" class="table table-bordered table-hover table-condensed editable-table listTable" ng-form="list.tableForm" disable-filter="list.isAdding" tracked-table="list.tableTracker">
+        <tr ng-repeat="row in $data" ng-form="rowForm" tracked-table-row="row">
+            <td title="'原标题'">
+                <a ng-href="/{{row.PostId}}" target="_blank">{{row.PostTitle}}</a>
+            </td>
+            <td title="'新标题'">
+                <a ng-href="/{{row.PostId}}" target="_blank">{{row.Title}}</a>
+            </td>
+            <td title="'修改人'">
+                {{row.Modifier}}
+            </td>
+            <td title="'修改人邮箱'">
+                {{row.ModifierEmail}}
+            </td>
+            <td title="'合并状态'">
+                {{row.MergeState==0?'待合并':row.MergeState==1?'已合并':'已拒绝'}}
+            </td>
+            <td title="'操作'" ng-if="row.MergeState==0">
+                <div class="btn-group">
+                    <a class="btn btn-info btn-sm waves-effect" ng-href="#/merge/compare?id={{row.Id}}">
+                        对比
+                    </a>
+                    <button class="btn btn-success btn-sm waves-effect" ng-click="list.pass(row)">
+                        直接合并
+                    </button>
+                    <a class="btn btn-info btn-sm waves-effect" ng-href="#/merge/edit?id={{row.Id}}">
+                        编辑并合并
+                    </a>
+                    <button class="btn btn-danger btn-sm waves-effect" ng-click="list.reject(row)">
+                        拒绝合并
+                    </button>
+                </div>
+            </td>
+        </tr>
+    </table>
+    <tm-pagination conf="paginationConf"></tm-pagination>
+</div>

+ 0 - 29
src/Masuit.MyBlogs.Core/wwwroot/ng-views/views/post/postlist.html

@@ -115,33 +115,4 @@
         </tr>
     </table>
     <tm-pagination conf="paginationConf"></tm-pagination>
-</div>
-<div id="modal" class="modal">
-    <div class="container-fluid m-t-10 m-b-10">
-        <div class="row m-l-30 m-r-30">
-            <div class="col-sm-4 col-md-2">
-                <div class="mini-charts-item bgm-cyan">
-                    <div class="clearfix">
-                        <div class="chart stats-bar" data-sparkline-bar></div>
-                        <div class="count">
-                            <small>历史最高({{postAnalyse.highDate|date:'yyyy-MM-dd'}})</small>
-                            <h2>{{postAnalyse.high}}次</h2>
-                        </div>
-                    </div>
-                </div>
-            </div>
-            <div class="col-sm-4 col-md-2">
-                <div class="mini-charts-item bgm-amber">
-                    <div class="clearfix">
-                        <div class="chart stats-bar" data-sparkline-bar></div>
-                        <div class="count">
-                            <small>每日平均</small>
-                            <h2>{{postAnalyse.aver|number:2}}次</h2>
-                        </div>
-                    </div>
-                </div>
-            </div>
-        </div>
-        <div id="chart" style="height: 500px;"></div>
-    </div>
 </div>

+ 8 - 0
src/Masuit.MyBlogs.Core/wwwroot/template/merge-pass.html

@@ -0,0 +1,8 @@
+<h2>你在懒得勤快的博客的文章《{{title}}》已被站长接受合并通过!</h2>
+<div>
+    <p>《{{title}}》</p>
+    <a href="{{link}}">查看详情</a>
+</div>
+<p style="color: red">
+    本邮件由系统自动发出,请勿回复本邮件!
+</p>

+ 9 - 0
src/Masuit.MyBlogs.Core/wwwroot/template/merge-reject.html

@@ -0,0 +1,9 @@
+<h2>很遗憾的通知你:你在懒得勤快的博客的文章《{{title}}》的修改已被站长拒绝!</h2>
+<div>
+    <p>《{{title}}》</p>
+    <p>拒绝合并的理由:{{reason}}</p>
+    <a href="{{link}}">点击这里重新修改>></a>
+</div>
+<p style="color: red">
+    本邮件由系统自动发出,请勿回复本邮件!
+</p>

+ 8 - 0
src/Masuit.MyBlogs.Core/wwwroot/template/merge-request.html

@@ -0,0 +1,8 @@
+<h2>文章合并修改请求!</h2>
+<div>
+    <p>《{{title}}》</p>
+    <a href="{{link}}">查看详情</a>
+</div>
+<p>
+    本邮件由系统自动发出,请勿回复本邮件!
+</p>

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است