瀏覽代碼

文章增加自动过期功能

懒得勤快 2 年之前
父節點
當前提交
ccf9e5b56d

+ 6 - 6
src/Masuit.MyBlogs.Core/Controllers/PostController.cs

@@ -583,18 +583,18 @@ public class PostController : BaseController
 	}
 
 	/// <summary>
-	/// 删除
+	/// 下架文章
 	/// </summary>
 	/// <param name="id"></param>
 	/// <returns></returns>
 	[MyAuthorize]
-	public async Task<ActionResult> Delete(int id)
+	public async Task<ActionResult> Takedown(int id)
 	{
 		var post = await PostService.GetByIdAsync(id) ?? throw new NotFoundException("文章未找到");
-		post.Status = Status.Deleted;
+		post.Status = Status.Takedown;
 		bool b = await PostService.SaveChangesAsync(true) > 0;
 		SearchEngine.LuceneIndexer.Delete(post);
-		return ResultData(null, b, b ? "删除成功!" : "删除失败!");
+		return ResultData(null, b, b ? $"文章《{post.Title}》已下架!" : "下架失败!");
 	}
 
 	/// <summary>
@@ -603,13 +603,13 @@ public class PostController : BaseController
 	/// <param name="id"></param>
 	/// <returns></returns>
 	[MyAuthorize]
-	public async Task<ActionResult> Restore(int id)
+	public async Task<ActionResult> Takeup(int id)
 	{
 		var post = await PostService.GetByIdAsync(id) ?? throw new NotFoundException("文章未找到");
 		post.Status = Status.Published;
 		bool b = await PostService.SaveChangesAsync() > 0;
 		SearchEngine.LuceneIndexer.Add(post);
-		return ResultData(null, b, b ? "恢复成功!" : "恢复失败!");
+		return ResultData(null, b, b ? "上架成功!" : "上架失败!");
 	}
 
 	/// <summary>

+ 3 - 1
src/Masuit.MyBlogs.Core/Extensions/Hangfire/HangfireBackJob.cs

@@ -149,15 +149,17 @@ namespace Masuit.MyBlogs.Core.Extensions.Hangfire
 		public void EverydayJob()
 		{
 			CommonHelper.IPErrorTimes.RemoveWhere(kv => kv.Value < 100); //将访客访问出错次数少于100的移开
-			DateTime time = DateTime.Now.AddMonths(-1);
+			var time = DateTime.Now.AddMonths(-1);
 			var searchDetailsService = _serviceScope.ServiceProvider.GetRequiredService<ISearchDetailsService>();
 			var advertisementService = _serviceScope.ServiceProvider.GetRequiredService<IAdvertisementService>();
 			var noticeService = _serviceScope.ServiceProvider.GetRequiredService<INoticeService>();
+			var postService = _serviceScope.ServiceProvider.GetRequiredService<IPostService>();
 			searchDetailsService.DeleteEntitySaved(s => s.SearchTime < time);
 			TrackData.DumpLog();
 			advertisementService.GetQuery(a => DateTime.Now >= a.ExpireTime).ExecuteUpdate(s => s.SetProperty(a => a.Status, Status.Unavailable));
 			noticeService.GetQuery(n => n.NoticeStatus == NoticeStatus.UnStart && n.StartTime < DateTime.Now).ExecuteUpdate(s => s.SetProperty(e => e.NoticeStatus, NoticeStatus.Normal).SetProperty(e => e.PostDate, DateTime.Now).SetProperty(e => e.ModifyDate, DateTime.Now));
 			noticeService.GetQuery(n => n.NoticeStatus == NoticeStatus.Normal && n.EndTime < DateTime.Now).ExecuteUpdate(s => s.SetProperty(e => e.NoticeStatus, NoticeStatus.Expired).SetProperty(e => e.ModifyDate, DateTime.Now));
+			postService.GetQuery(p => p.ExpireAt < DateTime.Now && p.Status == Status.Published).ExecuteUpdate(s => s.SetProperty(p => p.Status, Status.Takedown));
 		}
 
 		/// <summary>

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

@@ -58,7 +58,7 @@
         <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.0" />
         <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.0" />
         <PackageReference Include="Microsoft.Extensions.Http.Polly" Version="7.0.0" />
-        <PackageReference Include="Microsoft.Graph" Version="4.47.0" />
+        <PackageReference Include="Microsoft.Graph" Version="4.48.0" />
         <PackageReference Include="Microsoft.Graph.Auth" Version="1.0.0-preview.7" />
         <PackageReference Include="MiniProfiler.AspNetCore.Mvc" Version="4.2.22" />
         <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.0" />
@@ -69,7 +69,7 @@
         <PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.23" />
         <PackageReference Include="TimeZoneConverter" Version="6.0.1" />
         <PackageReference Include="WilderMinds.RssSyndication" Version="1.7.0" />
-        <PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="7.17.0" />
+        <PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="7.17.1" />
     </ItemGroup>
     <ItemGroup>
         <Content Update="appsettings.json">

+ 126 - 122
src/Masuit.MyBlogs.Core/Models/DTO/PostCommand.cs

@@ -4,126 +4,130 @@ using Masuit.MyBlogs.Core.Models.Validation;
 using Masuit.Tools.Core.Validator;
 using System.ComponentModel.DataAnnotations;
 
-namespace Masuit.MyBlogs.Core.Models.DTO
+namespace Masuit.MyBlogs.Core.Models.DTO;
+
+/// <summary>
+/// 文章输入模型
+/// </summary>
+public class PostCommand : BaseEntity
 {
-    /// <summary>
-    /// 文章输入模型
-    /// </summary>
-    public class PostCommand : BaseEntity
-    {
-        public PostCommand()
-        {
-            Status = Status.Pending;
-        }
-
-        /// <summary>
-        /// 标题
-        /// </summary>
-        [Required(ErrorMessage = "文章标题不能为空!"), MaxLength(128, ErrorMessage = "文章标题最长支持128个字符!"), MinLength(4, ErrorMessage = "文章标题最少4个字符!")]
-        public string Title { get; set; }
-
-        /// <summary>
-        /// 作者
-        /// </summary>
-        [Required, MaxLength(24, ErrorMessage = "作者名最长支持24个字符!"), MinLength(2, ErrorMessage = "作者名最少2个字符!")]
-        public string Author { get; set; }
-
-        public string Modifier { get; set; }
-
-        public string ModifierEmail { get; set; }
-
-        /// <summary>
-        /// 内容
-        /// </summary>
-        [Required(ErrorMessage = "文章内容不能为空!"), SubmitCheck(20, 1000000, false)]
-        public string Content { get; set; }
-
-        /// <summary>
-        /// 文章关键词
-        /// </summary>
-        [StringLength(256, ErrorMessage = "文章关键词最大允许255个字符")]
-        public string Keyword { get; set; }
-
-        /// <summary>
-        /// 受保护的内容
-        /// </summary>
-        public string ProtectContent { get; set; }
-
-        /// <summary>
-        /// 受保护内容模式
-        /// </summary>
-        public ProtectContentMode ProtectContentMode { get; set; }
-
-        /// <summary>
-        /// 分类id
-        /// </summary>
-        public int CategoryId { get; set; }
-
-        /// <summary>
-        /// 作者邮箱
-        /// </summary>
-        [Required(ErrorMessage = "作者邮箱不能为空!"), MinLength(6, ErrorMessage = "邮箱格式不正确!"), IsEmail]
-        public string Email { get; set; }
-
-        /// <summary>
-        /// 标签
-        /// </summary>
-        [StringLength(255, ErrorMessage = "标签最大允许255个字符")]
-        public string Label { get; set; }
-
-        /// <summary>
-        /// 专题
-        /// </summary>
-        public string Seminars { get; set; }
-
-        /// <summary>
-        /// 禁止评论
-        /// </summary>
-        public bool DisableComment { get; set; }
-
-        /// <summary>
-        /// 禁止转载
-        /// </summary>
-        public bool DisableCopy { get; set; }
-
-        /// <summary>
-        /// 限制模式
-        /// </summary>
-        public RegionLimitMode? LimitMode { get; set; }
-
-        /// <summary>
-        /// 限制地区,竖线分隔
-        /// </summary>
-        public string Regions { get; set; }
-
-        /// <summary>
-        /// 限制排除地区,竖线分隔
-        /// </summary>
-        public string ExceptRegions { get; set; }
-
-        /// <summary>
-        /// 限制模式
-        /// </summary>
-        public RegionLimitMode? ProtectContentLimitMode { get; set; }
-
-        /// <summary>
-        /// 限制地区,竖线分隔
-        /// </summary>
-        public string ProtectContentRegions { get; set; }
-
-        /// <summary>
-        /// 保护密码
-        /// </summary>
-        public string ProtectPassword { get; set; }
-
-        /// <summary>
-        /// 跳转到第三方链接
-        /// </summary>
-        public string Redirect { get; set; }
-
-        /// <summary>
-        /// 保留历史版本
-        /// </summary>
-        public bool Reserve { get; set; }
-    }
-}
+	public PostCommand()
+	{
+		Status = Status.Pending;
+	}
+
+	/// <summary>
+	/// 标题
+	/// </summary>
+	[Required(ErrorMessage = "文章标题不能为空!"), MaxLength(128, ErrorMessage = "文章标题最长支持128个字符!"), MinLength(4, ErrorMessage = "文章标题最少4个字符!")]
+	public string Title { get; set; }
+
+	/// <summary>
+	/// 作者
+	/// </summary>
+	[Required, MaxLength(24, ErrorMessage = "作者名最长支持24个字符!"), MinLength(2, ErrorMessage = "作者名最少2个字符!")]
+	public string Author { get; set; }
+
+	public string Modifier { get; set; }
+
+	public string ModifierEmail { get; set; }
+
+	/// <summary>
+	/// 内容
+	/// </summary>
+	[Required(ErrorMessage = "文章内容不能为空!"), SubmitCheck(20, 1000000, false)]
+	public string Content { get; set; }
+
+	/// <summary>
+	/// 文章关键词
+	/// </summary>
+	[StringLength(256, ErrorMessage = "文章关键词最大允许255个字符")]
+	public string Keyword { get; set; }
+
+	/// <summary>
+	/// 受保护的内容
+	/// </summary>
+	public string ProtectContent { get; set; }
+
+	/// <summary>
+	/// 受保护内容模式
+	/// </summary>
+	public ProtectContentMode ProtectContentMode { get; set; }
+
+	/// <summary>
+	/// 分类id
+	/// </summary>
+	public int CategoryId { get; set; }
+
+	/// <summary>
+	/// 作者邮箱
+	/// </summary>
+	[Required(ErrorMessage = "作者邮箱不能为空!"), MinLength(6, ErrorMessage = "邮箱格式不正确!"), IsEmail]
+	public string Email { get; set; }
+
+	/// <summary>
+	/// 标签
+	/// </summary>
+	[StringLength(255, ErrorMessage = "标签最大允许255个字符")]
+	public string Label { get; set; }
+
+	/// <summary>
+	/// 专题
+	/// </summary>
+	public string Seminars { get; set; }
+
+	/// <summary>
+	/// 禁止评论
+	/// </summary>
+	public bool DisableComment { get; set; }
+
+	/// <summary>
+	/// 禁止转载
+	/// </summary>
+	public bool DisableCopy { get; set; }
+
+	/// <summary>
+	/// 限制模式
+	/// </summary>
+	public RegionLimitMode? LimitMode { get; set; }
+
+	/// <summary>
+	/// 限制地区,竖线分隔
+	/// </summary>
+	public string Regions { get; set; }
+
+	/// <summary>
+	/// 限制排除地区,竖线分隔
+	/// </summary>
+	public string ExceptRegions { get; set; }
+
+	/// <summary>
+	/// 限制模式
+	/// </summary>
+	public RegionLimitMode? ProtectContentLimitMode { get; set; }
+
+	/// <summary>
+	/// 限制地区,竖线分隔
+	/// </summary>
+	public string ProtectContentRegions { get; set; }
+
+	/// <summary>
+	/// 保护密码
+	/// </summary>
+	public string ProtectPassword { get; set; }
+
+	/// <summary>
+	/// 跳转到第三方链接
+	/// </summary>
+	public string Redirect { get; set; }
+
+	/// <summary>
+	/// 保留历史版本
+	/// </summary>
+	public bool Reserve { get; set; }
+
+	/// <summary>
+	/// 过期时间
+	/// </summary>
+	public DateTime? ExpireAt { get; set; }
+}

+ 5 - 0
src/Masuit.MyBlogs.Core/Models/DTO/PostDto.cs

@@ -57,6 +57,11 @@ namespace Masuit.MyBlogs.Core.Models.DTO
         /// </summary>
         public DateTime ModifyDate { get; set; }
 
+        /// <summary>
+        /// 过期时间
+        /// </summary>
+        public DateTime? ExpireAt { get; set; }
+
         /// <summary>
         /// 是否置顶
         /// </summary>

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

@@ -189,6 +189,11 @@ namespace Masuit.MyBlogs.Core.Models.Entity
         /// </summary>
         public string Redirect { get; set; }
 
+        /// <summary>
+        /// ¹ýÆÚʱ¼ä
+        /// </summary>
+		public DateTime? ExpireAt { get; set; }
+
         /// <summary>
         /// ·ÖÀà
         /// </summary>

+ 2 - 2
src/Masuit.MyBlogs.Core/Models/Enum/Status.cs

@@ -23,9 +23,9 @@ namespace Masuit.MyBlogs.Core.Models.Enum
         [Display(Name = "禁用")] Forbidden,
 
         /// <summary>
-        /// 已删除
+        /// 已下架
         /// </summary>
-        [Display(Name = "已删除")] Deleted,
+        [Display(Name = "已下架")] Takedown,
 
         /// <summary>
         /// 审核中

+ 5 - 1
src/Masuit.MyBlogs.Core/Program.cs

@@ -1,4 +1,5 @@
-using Autofac.Extensions.DependencyInjection;
+using AngleSharp.Text;
+using Autofac.Extensions.DependencyInjection;
 using Masuit.MyBlogs.Core;
 using Masuit.MyBlogs.Core.Common;
 using Masuit.MyBlogs.Core.Extensions.DriveHelpers;
@@ -10,6 +11,9 @@ using Microsoft.Extensions.Caching.Memory;
 using System.Diagnostics;
 using Z.EntityFramework.Plus;
 
+var stringBuilder = StringBuilderPool.Obtain();
+var builder = stringBuilder.Append("a");
+Console.WriteLine(stringBuilder);
 QueryCacheManager.DefaultMemoryCacheEntryOptions = new MemoryCacheEntryOptions()
 {
 	AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)

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

@@ -121,7 +121,7 @@
 										}
 										<a asp-controller="Dashboard" asp-action="Index" asp-fragment="/post/[email protected]" class="btn btn-primary" target="_blank">修改</a>
 										<a asp-controller="Dashboard" asp-action="Index" asp-fragment="/[email protected]" class="btn btn-info" target="_blank">复制</a>
-										<button class="btn btn-danger" id="del">删除</button>
+										<button class="btn btn-danger" id="takedown">下架</button>
 										<a asp-controller="Post" asp-action="PostVisitRecordInsight" asp-route-id="@Model.Id" class="btn btn-primary" target="_blank">洞察</a>
 									</div>
 								</div>
@@ -321,9 +321,9 @@
 				});
 			}
 		});
-		$("#del").on("click", function (e) {
+		$("#takedown").on("click", function (e) {
 			swal({
-				title: "确认删除这篇文章吗?",
+				title: "确认下架这篇文章吗?",
 				text: '@Model.Title',
 				showCancelButton: true,
 				confirmButtonColor: "#DD6B55",
@@ -333,7 +333,7 @@
 				animation: true,
 				allowOutsideClick: false
 			}).then(function () {
-				window.fetch("/post/delete/"[email protected], {
+				window.fetch("/post/takedown/"[email protected], {
 		            credentials: 'include',
 		            method: 'POST',
 		            mode: 'cors'

+ 44 - 5
src/Masuit.MyBlogs.Core/wwwroot/ng-views/controllers/post.js

@@ -192,9 +192,9 @@
         });
     }
 
-    self.del = function(row) {
+    self.takedown = function(row) {
         swal({
-            title: "确认删除这篇文章吗?",
+            title: "确认下架这篇文章吗?",
             text: row.Title,
             showCancelButton: true,
             confirmButtonColor: "#DD6B55",
@@ -204,7 +204,7 @@
             animation: true,
             allowOutsideClick: false
         }).then(function() {
-            $scope.request("/post/delete/"+row.Id, null, function(data) {
+            $scope.request("/post/takedown/"+row.Id, null, function(data) {
                 window.notie.alert({
                     type: 1,
                     text: data.Message,
@@ -257,8 +257,8 @@
             self.GetPageData($scope.paginationConf.currentPage, $scope.paginationConf.itemsPerPage);
         });
     }
-    self.restore = function(row) {
-        $scope.request("/post/restore/"+row.Id,null, function(data) {
+    self.takeup = function(row) {
+        $scope.request("/post/Takeup/"+row.Id,null, function(data) {
             window.notie.alert({
                 type: 1,
                 text: data.Message,
@@ -774,6 +774,24 @@ myApp.controller("writeblog", ["$scope", "$http", "$timeout","$location", functi
     $scope.get("/post/GetRegions?name=ExceptRegions", function(data) {
         $scope.ExceptRegions=data.Data;
     });
+    
+    // 绑定过期时间表单元素
+    layui.use('laydate', function(){
+        var laydate = layui.laydate;
+        laydate.render({
+        elem: '#expireAt',
+        type: 'datetime',
+        calendar: true,
+        min: new Date(new Date().setMinutes(new Date().getMinutes()+10)).Format("yyyy-MM-dd hh:mm:ss"),
+        done: function(value, date, endDate) {
+            if (value) {
+                $scope.post.expireAt=value;
+            } else {
+                delete $scope.post.expireAt;
+            }
+        }
+        });
+    });
 }]);
 
 myApp.controller("postedit", ["$scope", "$http", "$location", "$timeout", function ($scope, $http, $location, $timeout) {
@@ -784,6 +802,9 @@ myApp.controller("postedit", ["$scope", "$http", "$location", "$timeout", functi
     $scope.reserve = true;
     $scope.get("/post/get/" + $scope.id, function (data) {
         $scope.post = data.Data;
+        if ($scope.post.ExpireAt) {
+            $scope.post.ExpireAt=new Date($scope.post.ExpireAt).Format("yyyy-MM-dd hh:mm:ss");
+        }
         $scope.get("/post/gettag", function (res) {
             $scope.Tags = res.Data;
             var tags=[];
@@ -1084,6 +1105,24 @@ myApp.controller("postedit", ["$scope", "$http", "$location", "$timeout", functi
     $scope.get("/post/GetRegions?name=ExceptRegions", function(data) {
         $scope.ExceptRegions=data.Data;
     });
+    
+    // 绑定过期时间表单元素
+    layui.use('laydate', function(){
+        var laydate = layui.laydate;
+        laydate.render({
+        elem: '#expireAt',
+        type: 'datetime',
+        calendar: true,
+        min: new Date(new Date().setMinutes(new Date().getMinutes()+10)).Format("yyyy-MM-dd hh:mm:ss"),
+        done: function(value, date, endDate) {
+            if (value) {
+                $scope.post.expireAt=value;
+            } else {
+                delete $scope.post.expireAt;
+            }
+        }
+        });
+    });
 }]);
 myApp.controller("category", ["$scope", "$http", "$timeout", function($scope, $http, $timeout) {
     $scope.category = {};

+ 8 - 2
src/Masuit.MyBlogs.Core/wwwroot/ng-views/views/post/edit.html

@@ -14,7 +14,7 @@
     <div class="form-group overlay animated">
         <div style="height: 56vh;" class="ueditor" ng-model="post.Content" type="text/plain"></div>
         <h3 class="size16">
-            文章加密内容
+            文章加密:
             <small class="form-group form-inline">
                 <div class="input-group">
                     <select ng-model="post.ProtectContentMode" class="form-control">
@@ -172,7 +172,7 @@
         </div>
     </div>
     <div class="row">
-        <div class="col-md-12">
+        <div class="col-md-9">
             <div class="input-group">
                 <span class="input-group-addon">跳转到第三方链接:</span>
                 <div class="fg-line">
@@ -180,6 +180,12 @@
                 </div>
             </div>
         </div>
+        <div class="col-md-3">
+            <div class="input-group">
+                <span class="input-group-addon">过期时间:</span>
+                <input id="expireAt" class="form-control" ng-model="post.ExpireAt" readonly="readonly" type="datetime" placeholder="请选择时间(留空不过期)" />
+            </div>
+        </div>
     </div>
 </form>
 <div style="position: absolute; left: -20000px;height:0;overflow:hidden;">

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

@@ -112,16 +112,16 @@
                     <a class="btn btn-info btn-sm waves-effect" ng-href="#/writeblog?refer={{row.Id}}">
                         <i class="zmdi zmdi-copy zmdi-hc-fw"></i>
                     </a>
-                    <button class="btn btn-danger btn-sm waves-effect" ng-click="list.del(row)" ng-if="row.Status!='已删除'">
-                        <i class="icon-bin"></i>
+                    <button class="btn btn-danger btn-sm waves-effect" ng-click="list.takedown(row)" ng-if="row.Status!='已下架'">
+                        <i class="icon-box-add"></i>
                     </button>
-                    <a class="btn btn-danger btn-sm waves-effect" ng-click="list.truncate(row)" ng-if="row.Status=='已删除'">
-                        <i class="icon-cross"></i>
+                    <a class="btn btn-success btn-sm waves-effect" ng-click="list.takeup(row)" ng-if="row.Status=='已下架'">
+                        <i class="icon-box-remove"></i>
                     </a>
-                    <a class="btn btn-info btn-sm waves-effect" ng-click="list.restore(row)" ng-if="row.Status=='已删除'">
-                        <i class="icon-undo2"></i>
+                    <a class="btn btn-danger btn-sm waves-effect" ng-click="list.truncate(row)" ng-if="row.Status=='已下架'">
+                        <i class="icon-cross"></i>
                     </a>
-                    <a class="btn  btn-sm waves-effect" ng-class="{true:'btn-success',false:'btn-primary'}[row.IsFixedTop]" ng-click="list.fixtop(row.Id)" ng-if="row.Status!='已删除'">
+                    <a class="btn  btn-sm waves-effect" ng-class="{true:'btn-success',false:'btn-primary'}[row.IsFixedTop]" ng-click="list.fixtop(row.Id)" ng-if="row.Status!='已下架'">
                         <i class="icon-pushpin" ng-class="{true:'text-danger'}[row.IsFixedTop]"></i>
                     </a>
                     <a class="btn btn-info btn-sm waves-effect" ng-click="list.insight(row)">

+ 8 - 2
src/Masuit.MyBlogs.Core/wwwroot/ng-views/views/post/writeblog.html

@@ -16,7 +16,7 @@
     <div class="animated form-group overlay">
         <div class="ueditor" ng-model="post.Content" style="height: 56vh;" type="text/plain"></div>
         <h3 class="size16">
-            文章加密内容
+            文章加密:
             <small class="form-group form-inline">
                 <div class="input-group">
                     <select class="form-control" ng-model="post.ProtectContentMode" ng-init="post.ProtectContentMode=0">
@@ -128,7 +128,7 @@
         </div>
     </div>
     <div class="row">
-        <div class="col-md-12">
+        <div class="col-md-9">
             <div class="input-group">
                 <span class="input-group-addon">跳转到第三方链接:</span>
                 <div class="fg-line">
@@ -136,6 +136,12 @@
                 </div>
             </div>
         </div>
+        <div class="col-md-3">
+            <div class="input-group">
+                <span class="input-group-addon">过期时间:</span>
+                <input id="expireAt" class="form-control" ng-model="post.ExpireAt" readonly="readonly" type="datetime" placeholder="请选择时间(留空不过期)" />
+            </div>
+        </div>
     </div>
     <div class="row">
         <div class="col-md-3">