瀏覽代碼

1. 后台管理文章列表:如果超过一年未更新显示黄色背景,如果日均访问量低于1显示红色背景
2. 定时检测目标链接是否404

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

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

@@ -51,6 +51,7 @@ namespace Masuit.MyBlogs.Core.Configs
                 .ForMember(p => p.Status, e => e.MapFrom(p => p.Status.GetDisplay()))
                 .ForMember(p => p.ModifyCount, e => e.MapFrom(p => p.PostHistoryVersion.Count))
                 .ForMember(p => p.ViewCount, e => e.MapFrom(p => p.TotalViewCount))
+                .ForMember(p => p.AverageViewCount, e => e.MapFrom(p => p.PostVisitRecordStats.Sum(t => t.Count) / (p.PostVisitRecordStats.Max(s => s.Date) - p.PostVisitRecordStats.Min(s => s.Date)).TotalDays))
                 .ForMember(p => p.Seminars, e => e.MapFrom(p => p.Seminar.Select(s => s.Id).ToArray()))
                 .ForMember(p => p.LimitDesc, e => e.MapFrom(p => p.LimitMode > RegionLimitMode.All ? string.Format(p.LimitMode.GetDescription(), p.Regions, p.ExceptRegions) : "无限制"));
 

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

@@ -147,7 +147,7 @@ namespace Masuit.MyBlogs.Core.Controllers
                 var privateKey = HttpContext.Session.Get<string>(nameof(RsaKey.PrivateKey));
                 password = password.RSADecrypt(privateKey);
             }
-            catch (Exception e)
+            catch (Exception)
             {
                 LogManager.Info("登录失败,私钥:" + HttpContext.Session.Get<string>(nameof(RsaKey.PrivateKey)));
                 throw;

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

@@ -659,7 +659,7 @@ public class PostController : BaseController
 
 		var list = orderby switch
 		{
-			OrderBy.Trending => await PostService.GetQuery(where).OrderByDescending(p => p.Status).ThenByDescending(p => p.IsFixedTop).ThenByDescending(p => p.PostVisitRecordStats.Sum(t => t.Count) / p.PostVisitRecordStats.Count).ToPagedListAsync<Post, PostDataModel>(page, size, MapperConfig),
+			OrderBy.Trending => await PostService.GetQuery(where).OrderByDescending(p => p.Status).ThenByDescending(p => p.IsFixedTop).ThenByDescending(p => p.PostVisitRecordStats.Average(t => t.Count)).ToPagedListAsync<Post, PostDataModel>(page, size, MapperConfig),
 			_ => await PostService.GetQuery(where).OrderBy($"{nameof(Post.Status)} desc,{nameof(Post.IsFixedTop)} desc,{orderby.GetDisplay()} desc").ToPagedListAsync<Post, PostDataModel>(page, size, MapperConfig)
 		};
 		foreach (var item in list.Data)

+ 4 - 2
src/Masuit.MyBlogs.Core/Controllers/ToolsController.cs

@@ -111,7 +111,8 @@ namespace Masuit.MyBlogs.Core.Controllers
                 return View(address);
             }
 
-            var s = await _httpClient.GetStringAsync($"http://api.map.baidu.com/geocoder/v2/?location={lat},{lng}&output=json&pois=1&ak={AppConfig.BaiduAK}", new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token).ContinueWith(t =>
+            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
+            var s = await _httpClient.GetStringAsync($"http://api.map.baidu.com/geocoder/v2/?location={lat},{lng}&output=json&pois=1&ak={AppConfig.BaiduAK}", cts.Token).ContinueWith(t =>
              {
                  if (t.IsCompletedSuccessfully)
                  {
@@ -163,7 +164,8 @@ namespace Masuit.MyBlogs.Core.Controllers
             }
 
             ViewBag.Address = addr;
-            var physicsAddress = await _httpClient.GetStringAsync($"http://api.map.baidu.com/geocoder/v2/?output=json&address={addr}&ak={AppConfig.BaiduAK}", new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token).ContinueWith(t =>
+            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
+            var physicsAddress = await _httpClient.GetStringAsync($"http://api.map.baidu.com/geocoder/v2/?output=json&address={addr}&ak={AppConfig.BaiduAK}", cts.Token).ContinueWith(t =>
              {
                  if (t.IsCompletedSuccessfully)
                  {

+ 277 - 236
src/Masuit.MyBlogs.Core/Extensions/Hangfire/HangfireBackJob.cs

@@ -7,260 +7,301 @@ using Masuit.MyBlogs.Core.Models.DTO;
 using Masuit.MyBlogs.Core.Models.Entity;
 using Masuit.MyBlogs.Core.Models.Enum;
 using Masuit.Tools;
+using Masuit.Tools.Logging;
 using Masuit.Tools.Strings;
 using Masuit.Tools.Systems;
 using Microsoft.EntityFrameworkCore;
+using System.Net;
 
 namespace Masuit.MyBlogs.Core.Extensions.Hangfire
 {
-    /// <summary>
-    /// hangfire后台任务
-    /// </summary>
-    public class HangfireBackJob : Disposable, IHangfireBackJob
-    {
-        private readonly IHttpClientFactory _httpClientFactory;
-        private readonly IWebHostEnvironment _hostEnvironment;
-        private readonly IServiceScope _serviceScope;
-        private readonly IRedisClient _redisClient;
-        private readonly IConfiguration _configuration;
+	/// <summary>
+	/// hangfire后台任务
+	/// </summary>
+	public class HangfireBackJob : Disposable, IHangfireBackJob
+	{
+		private readonly IHttpClientFactory _httpClientFactory;
+		private readonly IWebHostEnvironment _hostEnvironment;
+		private readonly IServiceScope _serviceScope;
+		private readonly IRedisClient _redisClient;
+		private readonly IConfiguration _configuration;
 
-        /// <summary>
-        /// hangfire后台任务
-        /// </summary>
-        public HangfireBackJob(IServiceProvider serviceProvider, IHttpClientFactory httpClientFactory, IWebHostEnvironment hostEnvironment, IRedisClient redisClient, IConfiguration configuration)
-        {
-            _httpClientFactory = httpClientFactory;
-            _hostEnvironment = hostEnvironment;
-            _redisClient = redisClient;
-            _configuration = configuration;
-            _serviceScope = serviceProvider.CreateScope();
-        }
+		/// <summary>
+		/// hangfire后台任务
+		/// </summary>
+		public HangfireBackJob(IServiceProvider serviceProvider, IHttpClientFactory httpClientFactory, IWebHostEnvironment hostEnvironment, IRedisClient redisClient, IConfiguration configuration)
+		{
+			_httpClientFactory = httpClientFactory;
+			_hostEnvironment = hostEnvironment;
+			_redisClient = redisClient;
+			_configuration = configuration;
+			_serviceScope = serviceProvider.CreateScope();
+		}
 
-        /// <summary>
-        /// 登录记录
-        /// </summary>
-        /// <param name="userInfo"></param>
-        /// <param name="ip"></param>
-        /// <param name="type"></param>
-        public void LoginRecord(UserInfoDto userInfo, string ip, LoginType type)
-        {
-            var record = new LoginRecord()
-            {
-                IP = ip,
-                LoginTime = DateTime.Now,
-                LoginType = type,
-                PhysicAddress = ip.GetIPLocation()
-            };
-            var userInfoService = _serviceScope.ServiceProvider.GetRequiredService<IUserInfoService>();
-            var settingService = _serviceScope.ServiceProvider.GetRequiredService<ISystemSettingService>();
-            var u = userInfoService.GetByUsername(userInfo.Username);
-            u.LoginRecord.Add(record);
-            userInfoService.SaveChanges();
-            var content = new Template(File.ReadAllText(Path.Combine(_hostEnvironment.WebRootPath, "template", "login.html")))
-                .Set("name", u.Username)
-                .Set("time", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))
-                .Set("ip", record.IP)
-                .Set("address", record.PhysicAddress).Render();
-            CommonHelper.SendMail(settingService.Get(s => s.Name.Equals("Title")).Value + "账号登录通知", content, settingService.Get(s => s.Name.Equals("ReceiveEmail")).Value, "127.0.0.1");
-        }
+		/// <summary>
+		/// 登录记录
+		/// </summary>
+		/// <param name="userInfo"></param>
+		/// <param name="ip"></param>
+		/// <param name="type"></param>
+		public void LoginRecord(UserInfoDto userInfo, string ip, LoginType type)
+		{
+			var record = new LoginRecord()
+			{
+				IP = ip,
+				LoginTime = DateTime.Now,
+				LoginType = type,
+				PhysicAddress = ip.GetIPLocation()
+			};
+			var userInfoService = _serviceScope.ServiceProvider.GetRequiredService<IUserInfoService>();
+			var settingService = _serviceScope.ServiceProvider.GetRequiredService<ISystemSettingService>();
+			var u = userInfoService.GetByUsername(userInfo.Username);
+			u.LoginRecord.Add(record);
+			userInfoService.SaveChanges();
+			var content = new Template(File.ReadAllText(Path.Combine(_hostEnvironment.WebRootPath, "template", "login.html")))
+				.Set("name", u.Username)
+				.Set("time", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))
+				.Set("ip", record.IP)
+				.Set("address", record.PhysicAddress).Render();
+			CommonHelper.SendMail(settingService.Get(s => s.Name.Equals("Title")).Value + "账号登录通知", content, settingService.Get(s => s.Name.Equals("ReceiveEmail")).Value, "127.0.0.1");
+		}
 
-        /// <summary>
-        /// 文章定时发布
-        /// </summary>
-        /// <param name="p"></param>
-        public void PublishPost(Post p)
-        {
-            p.Status = Status.Published;
-            p.PostDate = DateTime.Now;
-            p.ModifyDate = DateTime.Now;
-            var postService = _serviceScope.ServiceProvider.GetRequiredService<IPostService>();
-            var post = postService.GetById(p.Id);
-            if (post is null)
-            {
-                postService.AddEntitySaved(p);
-            }
-            else
-            {
-                post.Status = Status.Published;
-                post.PostDate = DateTime.Now;
-                post.ModifyDate = DateTime.Now;
-                postService.SaveChanges();
-            }
-        }
+		/// <summary>
+		/// 文章定时发布
+		/// </summary>
+		/// <param name="p"></param>
+		public void PublishPost(Post p)
+		{
+			p.Status = Status.Published;
+			p.PostDate = DateTime.Now;
+			p.ModifyDate = DateTime.Now;
+			var postService = _serviceScope.ServiceProvider.GetRequiredService<IPostService>();
+			var post = postService.GetById(p.Id);
+			if (post is null)
+			{
+				postService.AddEntitySaved(p);
+			}
+			else
+			{
+				post.Status = Status.Published;
+				post.PostDate = DateTime.Now;
+				post.ModifyDate = DateTime.Now;
+				postService.SaveChanges();
+			}
+		}
 
-        /// <summary>
-        /// 文章访问记录
-        /// </summary>
-        /// <param name="pid"></param>
-        /// <param name="ip"></param>
-        /// <param name="refer"></param>
-        /// <param name="url"></param>
-        public void RecordPostVisit(int pid, string ip, string refer, string url)
-        {
-            var lastQuarter = DateTime.Now.AddMonths(-6);
-            var last3Year = DateTime.Now.AddYears(-3);
-            var recordService = _serviceScope.ServiceProvider.GetRequiredService<IPostVisitRecordService>();
-            var recordStatsService = _serviceScope.ServiceProvider.GetRequiredService<IPostVisitRecordStatsService>();
-            var postService = _serviceScope.ServiceProvider.GetRequiredService<IPostService>();
-            recordService.GetQuery(b => b.Time < lastQuarter).DeleteFromQuery();
-            recordStatsService.GetQuery(b => b.Date < last3Year).DeleteFromQuery();
-            var post = postService.GetById(pid);
-            if (post == null)
-            {
-                return;
-            }
+		/// <summary>
+		/// 文章访问记录
+		/// </summary>
+		/// <param name="pid"></param>
+		/// <param name="ip"></param>
+		/// <param name="refer"></param>
+		/// <param name="url"></param>
+		public void RecordPostVisit(int pid, string ip, string refer, string url)
+		{
+			var lastQuarter = DateTime.Now.AddMonths(-6);
+			var last3Year = DateTime.Now.AddYears(-3);
+			var recordService = _serviceScope.ServiceProvider.GetRequiredService<IPostVisitRecordService>();
+			var recordStatsService = _serviceScope.ServiceProvider.GetRequiredService<IPostVisitRecordStatsService>();
+			var postService = _serviceScope.ServiceProvider.GetRequiredService<IPostService>();
+			recordService.GetQuery(b => b.Time < lastQuarter).DeleteFromQuery();
+			recordStatsService.GetQuery(b => b.Date < last3Year).DeleteFromQuery();
+			var post = postService.GetById(pid);
+			if (post == null)
+			{
+				return;
+			}
 
-            post.TotalViewCount += 1;
-            post.AverageViewCount = recordService.GetQuery(e => e.PostId == pid).GroupBy(r => r.Time.Date).Select(g => g.Count()).DefaultIfEmpty().Average();
-            recordService.AddEntity(new PostVisitRecord()
-            {
-                IP = ip,
-                Referer = refer,
-                Location = ip.GetIPLocation(),
-                Time = DateTime.Now,
-                RequestUrl = url,
-                PostId = pid
-            });
-            var stats = recordStatsService.Get(e => e.PostId == pid && e.Date >= DateTime.Today);
-            if (stats != null)
-            {
-                stats.Count = recordService.Count(e => e.PostId == pid & e.Time >= DateTime.Today) + 1;
-                stats.UV = recordService.GetQuery(e => e.PostId == pid & e.Time >= DateTime.Today).Select(e => e.IP).Distinct().Count() + 1;
-            }
-            else
-            {
-                recordStatsService.AddEntity(new PostVisitRecordStats()
-                {
-                    Count = 1,
-                    UV = 1,
-                    Date = DateTime.Today,
-                    PostId = pid
-                });
-            }
+			post.TotalViewCount += 1;
+			post.AverageViewCount = recordService.GetQuery(e => e.PostId == pid).GroupBy(r => r.Time.Date).Select(g => g.Count()).DefaultIfEmpty().Average();
+			recordService.AddEntity(new PostVisitRecord()
+			{
+				IP = ip,
+				Referer = refer,
+				Location = ip.GetIPLocation(),
+				Time = DateTime.Now,
+				RequestUrl = url,
+				PostId = pid
+			});
+			var stats = recordStatsService.Get(e => e.PostId == pid && e.Date >= DateTime.Today);
+			if (stats != null)
+			{
+				stats.Count = recordService.Count(e => e.PostId == pid & e.Time >= DateTime.Today) + 1;
+				stats.UV = recordService.GetQuery(e => e.PostId == pid & e.Time >= DateTime.Today).Select(e => e.IP).Distinct().Count() + 1;
+			}
+			else
+			{
+				recordStatsService.AddEntity(new PostVisitRecordStats()
+				{
+					Count = 1,
+					UV = 1,
+					Date = DateTime.Today,
+					PostId = pid
+				});
+			}
 
-            postService.SaveChanges();
-        }
+			postService.SaveChanges();
+		}
 
-        /// <summary>
-        /// 每天的任务
-        /// </summary>
-        public void EverydayJob()
-        {
-            CommonHelper.IPErrorTimes.RemoveWhere(kv => kv.Value < 100); //将访客访问出错次数少于100的移开
-            DateTime time = DateTime.Now.AddMonths(-1);
-            var searchDetailsService = _serviceScope.ServiceProvider.GetRequiredService<ISearchDetailsService>();
-            var advertisementService = _serviceScope.ServiceProvider.GetRequiredService<IAdvertisementService>();
-            var noticeService = _serviceScope.ServiceProvider.GetRequiredService<INoticeService>();
-            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));
-        }
+		/// <summary>
+		/// 每天的任务
+		/// </summary>
+		public void EverydayJob()
+		{
+			CommonHelper.IPErrorTimes.RemoveWhere(kv => kv.Value < 100); //将访客访问出错次数少于100的移开
+			DateTime time = DateTime.Now.AddMonths(-1);
+			var searchDetailsService = _serviceScope.ServiceProvider.GetRequiredService<ISearchDetailsService>();
+			var advertisementService = _serviceScope.ServiceProvider.GetRequiredService<IAdvertisementService>();
+			var noticeService = _serviceScope.ServiceProvider.GetRequiredService<INoticeService>();
+			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));
+		}
 
-        /// <summary>
-        /// 每月的任务
-        /// </summary>
-        public void EverymonthJob()
-        {
-            var advertisementService = _serviceScope.ServiceProvider.GetRequiredService<IAdvertisementService>();
-            advertisementService.GetAll().ExecuteUpdate(s => s.SetProperty(a => a.DisplayCount, 0));
-        }
+		/// <summary>
+		/// 每月的任务
+		/// </summary>
+		public void EverymonthJob()
+		{
+			var advertisementService = _serviceScope.ServiceProvider.GetRequiredService<IAdvertisementService>();
+			advertisementService.GetAll().ExecuteUpdate(s => s.SetProperty(a => a.DisplayCount, 0));
+		}
 
-        /// <summary>
-        /// 检查友链
-        /// </summary>
-        public void CheckLinks()
-        {
-            var client = _httpClientFactory.CreateClient();
-            client.DefaultRequestHeaders.UserAgent.TryParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36 Edg/93.0.961.47");
-            client.DefaultRequestHeaders.Add("X-Forwarded-For", "1.1.1.1");
-            client.DefaultRequestHeaders.Add("X-Forwarded-Host", "1.1.1.1");
-            client.DefaultRequestHeaders.Add("X-Real-IP", "1.1.1.1");
-            client.DefaultRequestHeaders.Referrer = new Uri("https://google.com");
-            client.Timeout = TimeSpan.FromSeconds(10);
-            var linksService = _serviceScope.ServiceProvider.GetRequiredService<ILinksService>();
-            linksService.GetQuery(l => !l.Except).AsParallel().ForAll(link =>
-            {
-                var prev = link.Status;
-                client.GetStringAsync(_configuration["HttpClientProxy:UriPrefix"] + link.Url, new CancellationTokenSource(client.Timeout).Token).ContinueWith(t =>
-                {
-                    if (t.IsCanceled || t.IsFaulted)
-                    {
-                        link.Status = Status.Unavailable;
-                    }
-                    else
-                    {
-                        link.Status = !t.Result.Contains(CommonHelper.SystemSettings["Domain"].Split("|")) ? Status.Unavailable : Status.Available;
-                    }
+		public void CheckAdvertisements()
+		{
+			var advertisementService = _serviceScope.ServiceProvider.GetRequiredService<IAdvertisementService>();
+			var client = _httpClientFactory.CreateClient();
+			client.DefaultRequestHeaders.UserAgent.TryParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36 Edg/93.0.961.47");
+			client.DefaultRequestHeaders.Add("X-Forwarded-For", "114.114.114.114");
+			client.DefaultRequestHeaders.Add("X-Forwarded-Host", "114.114.114.114");
+			client.DefaultRequestHeaders.Add("X-Real-IP", "114.114.114.114");
+			client.DefaultRequestHeaders.Referrer = new Uri("https://baidu.com");
+			client.Timeout = TimeSpan.FromSeconds(10);
+			var baseAddr = bool.Parse(_configuration["Https:Enabled"]) ? $"https://127.1:{_configuration["Https:Port"]}" : $"http://127.1:{_configuration["Port"]}";
+			advertisementService.GetQuery(a => a.Status == Status.Available).AsParallel().ForAll(e =>
+			{
+				var url = e.Url;
+				if (e.Url.StartsWith("/"))
+				{
+					url = baseAddr + e.Url;
+				}
 
-                    if (link.Status != prev)
-                    {
-                        link.UpdateTime = DateTime.Now;
-                    }
-                }).Wait();
-            });
-            linksService.SaveChanges();
-        }
+				using var cts = new CancellationTokenSource(client.Timeout);
+				client.GetAsync(url, cts.Token).ContinueWith(t =>
+				{
+					if (t.IsCanceled || t.IsFaulted)
+					{
+						LogManager.Info($"广告【[{e.Id}] {e.Title}】因访问超时被自动下架!");
+						e.Status = Status.Unavailable;
+					}
 
-        /// <summary>
-        /// 更新友链权重
-        /// </summary>
-        /// <param name="referer"></param>
-        /// <param name="ip"></param>
-        public void UpdateLinkWeight(string referer, string ip)
-        {
-            var linksService = _serviceScope.ServiceProvider.GetRequiredService<ILinksService>();
-            var loopbackService = _serviceScope.ServiceProvider.GetRequiredService<ILinkLoopbackService>();
-            var list = linksService.GetQuery(l => referer.Contains(l.UrlBase)).ToList();
-            foreach (var link in list)
-            {
-                link.Loopbacks.Add(new LinkLoopback()
-                {
-                    IP = ip,
-                    Referer = referer,
-                    Time = DateTime.Now
-                });
-            }
-            var time = DateTime.Now.AddMonths(-1);
-            loopbackService.GetQuery(b => b.Time < time).DeleteFromQuery();
-            linksService.SaveChanges();
-        }
+					if (t.Result.StatusCode == HttpStatusCode.NotFound)
+					{
+						LogManager.Info($"广告【[{e.Id}] {e.Title}】因广告链接404被自动下架!");
+						e.Status = Status.Unavailable;
+					}
+				}).Wait();
+			});
+			advertisementService.SaveChanges();
+		}
 
-        /// <summary>
-        /// 重建Lucene索引库
-        /// </summary>
-        public void CreateLuceneIndex()
-        {
-            var searchEngine = _serviceScope.ServiceProvider.GetRequiredService<ISearchEngine<DataContext>>();
-            var postService = _serviceScope.ServiceProvider.GetRequiredService<IPostService>();
-            searchEngine.LuceneIndexer.DeleteAll();
-            searchEngine.CreateIndex(new List<string>()
-            {
-                nameof(DataContext.Post),
-            });
-            var list = postService.GetQuery(p => p.Status != Status.Published || p.LimitMode == RegionLimitMode.OnlyForSearchEngine).ToList();
-            searchEngine.LuceneIndexer.Delete(list);
-        }
+		/// <summary>
+		/// 检查友链
+		/// </summary>
+		public void CheckLinks()
+		{
+			var client = _httpClientFactory.CreateClient();
+			client.DefaultRequestHeaders.UserAgent.TryParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36 Edg/93.0.961.47");
+			client.DefaultRequestHeaders.Add("X-Forwarded-For", "1.1.1.1");
+			client.DefaultRequestHeaders.Add("X-Forwarded-Host", "1.1.1.1");
+			client.DefaultRequestHeaders.Add("X-Real-IP", "1.1.1.1");
+			client.DefaultRequestHeaders.Referrer = new Uri("https://google.com");
+			client.Timeout = TimeSpan.FromSeconds(10);
+			var linksService = _serviceScope.ServiceProvider.GetRequiredService<ILinksService>();
+			linksService.GetQuery(l => !l.Except).AsParallel().ForAll(link =>
+			{
+				var prev = link.Status;
+				using var cts = new CancellationTokenSource(client.Timeout);
+				client.GetStringAsync(_configuration["HttpClientProxy:UriPrefix"] + link.Url, cts.Token).ContinueWith(t =>
+				{
+					if (t.IsCanceled || t.IsFaulted)
+					{
+						link.Status = Status.Unavailable;
+					}
+					else
+					{
+						link.Status = !t.Result.Contains(CommonHelper.SystemSettings["Domain"].Split("|")) ? Status.Unavailable : Status.Available;
+					}
 
-        /// <summary>
-        /// 搜索统计
-        /// </summary>
-        public void StatisticsSearchKeywords()
-        {
-            var searchDetailsService = _serviceScope.ServiceProvider.GetRequiredService<ISearchDetailsService>();
-            _redisClient.Set("SearchRank:Month", searchDetailsService.GetRanks(DateTime.Today.AddMonths(-1)));
-            _redisClient.Set("SearchRank:Week", searchDetailsService.GetRanks(DateTime.Today.AddDays(-7)));
-            _redisClient.Set("SearchRank:Today", searchDetailsService.GetRanks(DateTime.Today));
-        }
+					if (link.Status != prev)
+					{
+						link.UpdateTime = DateTime.Now;
+					}
+				}).Wait();
+			});
+			linksService.SaveChanges();
+		}
 
-        /// <summary>
-        /// 释放
-        /// </summary>
-        /// <param name="disposing"></param>
-        public override void Dispose(bool disposing)
-        {
-            _serviceScope.Dispose();
-        }
-    }
+		/// <summary>
+		/// 更新友链权重
+		/// </summary>
+		/// <param name="referer"></param>
+		/// <param name="ip"></param>
+		public void UpdateLinkWeight(string referer, string ip)
+		{
+			var linksService = _serviceScope.ServiceProvider.GetRequiredService<ILinksService>();
+			var loopbackService = _serviceScope.ServiceProvider.GetRequiredService<ILinkLoopbackService>();
+			var list = linksService.GetQuery(l => referer.Contains(l.UrlBase)).ToList();
+			foreach (var link in list)
+			{
+				link.Loopbacks.Add(new LinkLoopback()
+				{
+					IP = ip,
+					Referer = referer,
+					Time = DateTime.Now
+				});
+			}
+			var time = DateTime.Now.AddMonths(-1);
+			loopbackService.GetQuery(b => b.Time < time).DeleteFromQuery();
+			linksService.SaveChanges();
+		}
+
+		/// <summary>
+		/// 重建Lucene索引库
+		/// </summary>
+		public void CreateLuceneIndex()
+		{
+			var searchEngine = _serviceScope.ServiceProvider.GetRequiredService<ISearchEngine<DataContext>>();
+			var postService = _serviceScope.ServiceProvider.GetRequiredService<IPostService>();
+			searchEngine.LuceneIndexer.DeleteAll();
+			searchEngine.CreateIndex(new List<string>()
+			{
+				nameof(DataContext.Post),
+			});
+			var list = postService.GetQuery(p => p.Status != Status.Published || p.LimitMode == RegionLimitMode.OnlyForSearchEngine).ToList();
+			searchEngine.LuceneIndexer.Delete(list);
+		}
+
+		/// <summary>
+		/// 搜索统计
+		/// </summary>
+		public void StatisticsSearchKeywords()
+		{
+			var searchDetailsService = _serviceScope.ServiceProvider.GetRequiredService<ISearchDetailsService>();
+			_redisClient.Set("SearchRank:Month", searchDetailsService.GetRanks(DateTime.Today.AddMonths(-1)));
+			_redisClient.Set("SearchRank:Week", searchDetailsService.GetRanks(DateTime.Today.AddDays(-7)));
+			_redisClient.Set("SearchRank:Today", searchDetailsService.GetRanks(DateTime.Today));
+		}
+
+		/// <summary>
+		/// 释放
+		/// </summary>
+		/// <param name="disposing"></param>
+		public override void Dispose(bool disposing)
+		{
+			_serviceScope.Dispose();
+		}
+	}
 }

+ 1 - 0
src/Masuit.MyBlogs.Core/Extensions/Hangfire/HangfireJobInit.cs

@@ -13,6 +13,7 @@ namespace Masuit.MyBlogs.Core.Extensions.Hangfire
         public static void Start()
         {
             RecurringJob.AddOrUpdate<IHangfireBackJob>(job => job.CheckLinks(), "0 */5 * * *"); //每5h检查友链
+            RecurringJob.AddOrUpdate<IHangfireBackJob>(job => job.CheckAdvertisements(), Cron.Daily); //每5h检查友链
             RecurringJob.AddOrUpdate<IHangfireBackJob>(job => job.EverydayJob(), Cron.Daily(5), TimeZoneInfo.Local); //每天的任务
             RecurringJob.AddOrUpdate<IHangfireBackJob>(job => job.CreateLuceneIndex(), Cron.Weekly(DayOfWeek.Monday, 5), TimeZoneInfo.Local); //每周的任务
             RecurringJob.AddOrUpdate<IHangfireBackJob>(job => job.EverymonthJob(), Cron.Monthly(1, 0, 0), TimeZoneInfo.Local); //每月的任务

+ 2 - 0
src/Masuit.MyBlogs.Core/Extensions/Hangfire/IHangfireBackJob.cs

@@ -47,6 +47,8 @@ namespace Masuit.MyBlogs.Core.Extensions.Hangfire
         /// </summary>
         void CheckLinks();
 
+        void CheckAdvertisements();
+
         /// <summary>
         /// 更新友链权重
         /// </summary>

+ 156 - 157
src/Masuit.MyBlogs.Core/Infrastructure/DataContext.cs

@@ -1,168 +1,167 @@
 using Masuit.MyBlogs.Core.Models.Entity;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.EntityFrameworkCore.Diagnostics;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
 
 namespace Masuit.MyBlogs.Core.Infrastructure
 {
-    public class DataContext : DbContext
-    {
-        public DataContext(DbContextOptions<DataContext> options) : base(options)
-        {
-        }
-
-        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
-        {
-            optionsBuilder.EnableDetailedErrors().UseLazyLoadingProxies().UseQueryTrackingBehavior(QueryTrackingBehavior.TrackAll).ConfigureWarnings(builder => builder.Ignore(CoreEventId.DetachedLazyLoadingWarning));
-        }
-
-        protected override void OnModelCreating(ModelBuilder modelBuilder)
-        {
-            base.OnModelCreating(modelBuilder);
-            modelBuilder.Entity<Category>().HasMany(e => e.Post).WithOne(e => e.Category).OnDelete(DeleteBehavior.Cascade);
-            modelBuilder.Entity<Category>().HasMany(e => e.PostHistoryVersion).WithOne(e => e.Category).HasForeignKey(r => r.CategoryId).OnDelete(DeleteBehavior.Cascade);
-            modelBuilder.Entity<Category>().HasMany(e => e.Children).WithOne(c => c.Parent).IsRequired(false).HasForeignKey(c => c.ParentId).OnDelete(DeleteBehavior.Cascade);
-            modelBuilder.Entity<Category>().Property(c => c.Path).IsRequired();
-
-            modelBuilder.Entity<Post>().HasMany(e => e.Comment).WithOne(e => e.Post).HasForeignKey(r => r.PostId).OnDelete(DeleteBehavior.Cascade);
-            modelBuilder.Entity<Post>().HasMany(e => e.PostHistoryVersion).WithOne(e => e.Post).HasForeignKey(r => r.PostId).OnDelete(DeleteBehavior.Cascade);
-            modelBuilder.Entity<Post>().HasMany(e => e.Seminar).WithMany(s => s.Post).UsingEntity(builder => builder.ToTable("SeminarPost"));
-            modelBuilder.Entity<Post>().HasMany(e => e.PostMergeRequests).WithOne(s => s.Post).HasForeignKey(r => r.PostId).OnDelete(DeleteBehavior.Cascade);
-            modelBuilder.Entity<Post>().HasMany(e => e.PostVisitRecords).WithOne().HasForeignKey(r => r.PostId).OnDelete(DeleteBehavior.Cascade);
-            modelBuilder.Entity<Post>().HasMany(e => e.PostVisitRecordStats).WithOne().HasForeignKey(r => r.PostId).OnDelete(DeleteBehavior.Cascade);
-            modelBuilder.Entity<PostHistoryVersion>().HasMany(e => e.Seminar).WithMany(s => s.PostHistoryVersion).UsingEntity(builder => builder.ToTable("SeminarPostHistoryVersion"));
-
-            modelBuilder.Entity<UserInfo>().HasMany(e => e.LoginRecord).WithOne(e => e.UserInfo).OnDelete(DeleteBehavior.Cascade);
-
-            modelBuilder.Entity<Menu>().HasMany(e => e.Children).WithOne(m => m.Parent).IsRequired(false).OnDelete(DeleteBehavior.Cascade);
-            modelBuilder.Entity<Menu>().Property(c => c.Path).IsRequired();
-
-            modelBuilder.Entity<Comment>().HasMany(e => e.Children).WithOne(c => c.Parent).HasForeignKey(c => c.ParentId).IsRequired(false).OnDelete(DeleteBehavior.Cascade);
-            modelBuilder.Entity<Comment>().Property(c => c.Path).IsRequired();
-            modelBuilder.Entity<Comment>().Property(c => c.GroupTag).IsRequired();
-
-            modelBuilder.Entity<LeaveMessage>().HasMany(e => e.Children).WithOne(c => c.Parent).HasForeignKey(c => c.ParentId).IsRequired(false).OnDelete(DeleteBehavior.Cascade);
-            modelBuilder.Entity<LeaveMessage>().Property(c => c.Path).IsRequired();
-            modelBuilder.Entity<LeaveMessage>().Property(c => c.GroupTag).IsRequired();
-
-            modelBuilder.Entity<Links>().HasMany(e => e.Loopbacks).WithOne(l => l.Links).IsRequired().HasForeignKey(e => e.LinkId).OnDelete(DeleteBehavior.Cascade);
-
-            modelBuilder.Entity<Advertisement>().HasMany(e => e.ClickRecords).WithOne().HasForeignKey(e => e.AdvertisementId).IsRequired().OnDelete(DeleteBehavior.Cascade);
-
-            modelBuilder.Entity<Advertisement>().HasIndex(a => a.Price).HasSortOrder(SortOrder.Descending);
-            modelBuilder.Entity<AdvertisementClickRecord>().HasIndex(a => a.Time).HasSortOrder(SortOrder.Ascending);
-            modelBuilder.Entity<AdvertisementClickRecord>().HasIndex(a => a.AdvertisementId).HasSortOrder(SortOrder.Ascending);
-            modelBuilder.Entity<Category>().HasIndex(a => a.ParentId).HasSortOrder(SortOrder.Ascending);
-            modelBuilder.Entity<Comment>().HasIndex(a => a.PostId).HasSortOrder(SortOrder.Ascending);
-            modelBuilder.Entity<LeaveMessage>().HasIndex(a => a.PostDate).HasSortOrder(SortOrder.Descending);
-            modelBuilder.Entity<LinkLoopback>().HasIndex(a => a.LinkId).HasSortOrder(SortOrder.Ascending);
-            modelBuilder.Entity<Links>().HasIndex(a => a.Recommend).HasSortOrder(SortOrder.Ascending);
-            modelBuilder.Entity<LoginRecord>().HasIndex(a => a.UserInfoId).HasSortOrder(SortOrder.Ascending);
-            modelBuilder.Entity<Menu>().HasIndex(a => a.Sort).HasSortOrder(SortOrder.Ascending);
-            modelBuilder.Entity<Menu>().HasIndex(a => a.ParentId).HasSortOrder(SortOrder.Ascending);
-            modelBuilder.Entity<Notice>().HasIndex(a => a.ModifyDate).HasSortOrder(SortOrder.Descending);
-            modelBuilder.Entity<Post>().HasIndex(a => a.CategoryId).HasSortOrder(SortOrder.Ascending);
-            modelBuilder.Entity<Post>().HasIndex(a => a.ModifyDate).HasSortOrder(SortOrder.Descending);
-            modelBuilder.Entity<Post>().HasIndex(a => a.AverageViewCount).HasSortOrder(SortOrder.Descending);
-            modelBuilder.Entity<Post>().HasIndex(a => a.TotalViewCount).HasSortOrder(SortOrder.Descending);
-            modelBuilder.Entity<PostHistoryVersion>().HasIndex(a => a.CategoryId).HasSortOrder(SortOrder.Ascending);
-            modelBuilder.Entity<PostHistoryVersion>().HasIndex(a => a.PostId).HasSortOrder(SortOrder.Ascending);
-            modelBuilder.Entity<PostMergeRequest>().HasIndex(a => a.PostId).HasSortOrder(SortOrder.Ascending);
-            modelBuilder.Entity<PostVisitRecord>().HasIndex(a => a.PostId).HasSortOrder(SortOrder.Ascending);
-            modelBuilder.Entity<PostVisitRecord>().HasIndex(a => a.Time).HasSortOrder(SortOrder.Descending);
-            modelBuilder.Entity<PostVisitRecordStats>().HasIndex(a => a.Date).HasSortOrder(SortOrder.Descending);
-            modelBuilder.Entity<PostVisitRecordStats>().HasIndex(a => a.PostId).HasSortOrder(SortOrder.Ascending);
-            modelBuilder.Entity<SearchDetails>().HasIndex(a => a.SearchTime).HasSortOrder(SortOrder.Descending);
-        }
-
-        public override int SaveChanges()
-        {
-            DbUpdateConcurrencyException ex = null;
-            for (int i = 0; i < 5; i++)
-            {
-                try
-                {
-                    return base.SaveChanges();
-                }
-                catch (DbUpdateConcurrencyException e)
-                {
-                    ex = e;
-                    var entry = e.Entries.Single();
-                    var databaseValues = entry.GetDatabaseValues();
-                    var resolvedValues = databaseValues.Clone();
-                    entry.OriginalValues.SetValues(databaseValues);
-                    entry.CurrentValues.SetValues(resolvedValues);
-                }
-            }
-
-            throw ex;
-        }
-
-        public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new())
-        {
-            DbUpdateConcurrencyException ex = null;
-            for (int i = 0; i < 5; i++)
-            {
-                try
-                {
-                    return await base.SaveChangesAsync(cancellationToken);
-                }
-                catch (DbUpdateConcurrencyException e)
-                {
-                    ex = e;
-                    var entry = e.Entries.Single();
-                    var databaseValues = await entry.GetDatabaseValuesAsync(cancellationToken);
-                    var resolvedValues = databaseValues.Clone();
-                    entry.OriginalValues.SetValues(databaseValues);
-                    entry.CurrentValues.SetValues(resolvedValues);
-                }
-            }
-
-            throw ex;
-        }
-
-        public virtual DbSet<Category> Category { get; set; }
-
-        public virtual DbSet<Comment> Comment { get; set; }
-
-        public virtual DbSet<LeaveMessage> LeaveMessage { get; set; }
-
-        public virtual DbSet<Links> Links { get; set; }
-
-        public virtual DbSet<Menu> Menu { get; set; }
+	public class DataContext : DbContext
+	{
+		public DataContext(DbContextOptions<DataContext> options) : base(options)
+		{
+		}
+
+		protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+		{
+			optionsBuilder.EnableDetailedErrors().UseLazyLoadingProxies().UseQueryTrackingBehavior(QueryTrackingBehavior.TrackAll).ConfigureWarnings(builder => builder.Ignore(CoreEventId.DetachedLazyLoadingWarning));
+		}
+
+		protected override void OnModelCreating(ModelBuilder modelBuilder)
+		{
+			base.OnModelCreating(modelBuilder);
+			modelBuilder.Entity<Category>().HasMany(e => e.Post).WithOne(e => e.Category).OnDelete(DeleteBehavior.Cascade);
+			modelBuilder.Entity<Category>().HasMany(e => e.PostHistoryVersion).WithOne(e => e.Category).HasForeignKey(r => r.CategoryId).OnDelete(DeleteBehavior.Cascade);
+			modelBuilder.Entity<Category>().HasMany(e => e.Children).WithOne(c => c.Parent).IsRequired(false).HasForeignKey(c => c.ParentId).OnDelete(DeleteBehavior.Cascade);
+			modelBuilder.Entity<Category>().Property(c => c.Path).IsRequired();
+
+			modelBuilder.Entity<Post>().HasMany(e => e.Comment).WithOne(e => e.Post).HasForeignKey(r => r.PostId).OnDelete(DeleteBehavior.Cascade);
+			modelBuilder.Entity<Post>().HasMany(e => e.PostHistoryVersion).WithOne(e => e.Post).HasForeignKey(r => r.PostId).OnDelete(DeleteBehavior.Cascade);
+			modelBuilder.Entity<Post>().HasMany(e => e.Seminar).WithMany(s => s.Post).UsingEntity(builder => builder.ToTable("SeminarPost"));
+			modelBuilder.Entity<Post>().HasMany(e => e.PostMergeRequests).WithOne(s => s.Post).HasForeignKey(r => r.PostId).OnDelete(DeleteBehavior.Cascade);
+			modelBuilder.Entity<Post>().HasMany(e => e.PostVisitRecords).WithOne().HasForeignKey(r => r.PostId).OnDelete(DeleteBehavior.Cascade);
+			modelBuilder.Entity<Post>().HasMany(e => e.PostVisitRecordStats).WithOne().HasForeignKey(r => r.PostId).OnDelete(DeleteBehavior.Cascade);
+			modelBuilder.Entity<PostHistoryVersion>().HasMany(e => e.Seminar).WithMany(s => s.PostHistoryVersion).UsingEntity(builder => builder.ToTable("SeminarPostHistoryVersion"));
+
+			modelBuilder.Entity<UserInfo>().HasMany(e => e.LoginRecord).WithOne(e => e.UserInfo).OnDelete(DeleteBehavior.Cascade);
+
+			modelBuilder.Entity<Menu>().HasMany(e => e.Children).WithOne(m => m.Parent).IsRequired(false).OnDelete(DeleteBehavior.Cascade);
+			modelBuilder.Entity<Menu>().Property(c => c.Path).IsRequired();
+
+			modelBuilder.Entity<Comment>().HasMany(e => e.Children).WithOne(c => c.Parent).HasForeignKey(c => c.ParentId).IsRequired(false).OnDelete(DeleteBehavior.Cascade);
+			modelBuilder.Entity<Comment>().Property(c => c.Path).IsRequired();
+			modelBuilder.Entity<Comment>().Property(c => c.GroupTag).IsRequired();
+
+			modelBuilder.Entity<LeaveMessage>().HasMany(e => e.Children).WithOne(c => c.Parent).HasForeignKey(c => c.ParentId).IsRequired(false).OnDelete(DeleteBehavior.Cascade);
+			modelBuilder.Entity<LeaveMessage>().Property(c => c.Path).IsRequired();
+			modelBuilder.Entity<LeaveMessage>().Property(c => c.GroupTag).IsRequired();
+
+			modelBuilder.Entity<Links>().HasMany(e => e.Loopbacks).WithOne(l => l.Links).IsRequired().HasForeignKey(e => e.LinkId).OnDelete(DeleteBehavior.Cascade);
+
+			modelBuilder.Entity<Advertisement>().HasMany(e => e.ClickRecords).WithOne().HasForeignKey(e => e.AdvertisementId).IsRequired().OnDelete(DeleteBehavior.Cascade);
+
+			modelBuilder.Entity<Advertisement>().HasIndex(a => a.Price).IsDescending();
+			modelBuilder.Entity<AdvertisementClickRecord>().HasIndex(a => a.Time);
+			modelBuilder.Entity<AdvertisementClickRecord>().HasIndex(a => a.AdvertisementId);
+			modelBuilder.Entity<Category>().HasIndex(a => a.ParentId);
+			modelBuilder.Entity<Comment>().HasIndex(a => a.PostId);
+			modelBuilder.Entity<LeaveMessage>().HasIndex(a => a.PostDate).IsDescending();
+			modelBuilder.Entity<LinkLoopback>().HasIndex(a => a.LinkId);
+			modelBuilder.Entity<Links>().HasIndex(a => a.Recommend);
+			modelBuilder.Entity<LoginRecord>().HasIndex(a => a.UserInfoId);
+			modelBuilder.Entity<Menu>().HasIndex(a => a.Sort);
+			modelBuilder.Entity<Menu>().HasIndex(a => a.ParentId);
+			modelBuilder.Entity<Notice>().HasIndex(a => a.ModifyDate).IsDescending();
+			modelBuilder.Entity<Post>().HasIndex(a => a.CategoryId);
+			modelBuilder.Entity<Post>().HasIndex(a => a.ModifyDate).IsDescending();
+			modelBuilder.Entity<Post>().HasIndex(a => a.AverageViewCount).IsDescending();
+			modelBuilder.Entity<Post>().HasIndex(a => a.TotalViewCount).IsDescending();
+			modelBuilder.Entity<PostHistoryVersion>().HasIndex(a => a.CategoryId);
+			modelBuilder.Entity<PostHistoryVersion>().HasIndex(a => a.PostId);
+			modelBuilder.Entity<PostMergeRequest>().HasIndex(a => a.PostId);
+			modelBuilder.Entity<PostVisitRecord>().HasIndex(a => a.PostId);
+			modelBuilder.Entity<PostVisitRecord>().HasIndex(a => a.Time).IsDescending();
+			modelBuilder.Entity<PostVisitRecordStats>().HasIndex(a => a.Date).IsDescending();
+			modelBuilder.Entity<PostVisitRecordStats>().HasIndex(a => a.PostId);
+			modelBuilder.Entity<SearchDetails>().HasIndex(a => a.SearchTime).IsDescending();
+		}
+
+		public override int SaveChanges()
+		{
+			DbUpdateConcurrencyException ex = null;
+			for (int i = 0; i < 5; i++)
+			{
+				try
+				{
+					return base.SaveChanges();
+				}
+				catch (DbUpdateConcurrencyException e)
+				{
+					ex = e;
+					var entry = e.Entries.Single();
+					var databaseValues = entry.GetDatabaseValues();
+					var resolvedValues = databaseValues.Clone();
+					entry.OriginalValues.SetValues(databaseValues);
+					entry.CurrentValues.SetValues(resolvedValues);
+				}
+			}
+
+			throw ex;
+		}
+
+		public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new())
+		{
+			DbUpdateConcurrencyException ex = null;
+			for (int i = 0; i < 5; i++)
+			{
+				try
+				{
+					return await base.SaveChangesAsync(cancellationToken);
+				}
+				catch (DbUpdateConcurrencyException e)
+				{
+					ex = e;
+					var entry = e.Entries.Single();
+					var databaseValues = await entry.GetDatabaseValuesAsync(cancellationToken);
+					var resolvedValues = databaseValues.Clone();
+					entry.OriginalValues.SetValues(databaseValues);
+					entry.CurrentValues.SetValues(resolvedValues);
+				}
+			}
+
+			throw ex;
+		}
+
+		public virtual DbSet<Category> Category { get; set; }
+
+		public virtual DbSet<Comment> Comment { get; set; }
+
+		public virtual DbSet<LeaveMessage> LeaveMessage { get; set; }
+
+		public virtual DbSet<Links> Links { get; set; }
+
+		public virtual DbSet<Menu> Menu { get; set; }
 
-        public virtual DbSet<Misc> Misc { get; set; }
-
-        public virtual DbSet<Notice> Notice { get; set; }
-
-        public virtual DbSet<Post> Post { get; set; }
-
-        public virtual DbSet<PostHistoryVersion> PostHistoryVersion { get; set; }
-
-        public virtual DbSet<SearchDetails> SearchDetails { get; set; }
-
-        public virtual DbSet<SystemSetting> SystemSetting { get; set; }
-
-        public virtual DbSet<UserInfo> UserInfo { get; set; }
-
-        public virtual DbSet<LoginRecord> LoginRecord { get; set; }
-
-        public virtual DbSet<Donate> Donate { get; set; }
-
-        public virtual DbSet<Seminar> Seminar { get; set; }
-
-        public virtual DbSet<InternalMessage> InternalMessage { get; set; }
-
-        public virtual DbSet<FastShare> FastShare { get; set; }
-
-        public virtual DbSet<PostMergeRequest> PostMergeRequests { get; set; }
-
-        public virtual DbSet<Advertisement> Advertisements { get; set; }
+		public virtual DbSet<Misc> Misc { get; set; }
+
+		public virtual DbSet<Notice> Notice { get; set; }
+
+		public virtual DbSet<Post> Post { get; set; }
+
+		public virtual DbSet<PostHistoryVersion> PostHistoryVersion { get; set; }
+
+		public virtual DbSet<SearchDetails> SearchDetails { get; set; }
+
+		public virtual DbSet<SystemSetting> SystemSetting { get; set; }
+
+		public virtual DbSet<UserInfo> UserInfo { get; set; }
+
+		public virtual DbSet<LoginRecord> LoginRecord { get; set; }
+
+		public virtual DbSet<Donate> Donate { get; set; }
+
+		public virtual DbSet<Seminar> Seminar { get; set; }
+
+		public virtual DbSet<InternalMessage> InternalMessage { get; set; }
+
+		public virtual DbSet<FastShare> FastShare { get; set; }
+
+		public virtual DbSet<PostMergeRequest> PostMergeRequests { get; set; }
+
+		public virtual DbSet<Advertisement> Advertisements { get; set; }
 
-        public virtual DbSet<Variables> Variables { get; set; }
+		public virtual DbSet<Variables> Variables { get; set; }
 
-        public virtual DbSet<LinkLoopback> LinkLoopbacks { get; set; }
+		public virtual DbSet<LinkLoopback> LinkLoopbacks { get; set; }
 
-        public virtual DbSet<PostTag> PostTags { get; set; }
-    }
+		public virtual DbSet<PostTag> PostTags { get; set; }
+	}
 }

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

@@ -66,10 +66,10 @@
         <PackageReference Include="MiniProfiler.EntityFrameworkCore" Version="4.2.22" />
         <PackageReference Include="PanGu.HighLight" Version="1.0.0" />
         <PackageReference Include="SixLabors.ImageSharp.Web" Version="2.0.2" />
-        <PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.22" />
+        <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.0.0-rc.2.22472.11-04" />
+        <PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="7.17.0" />
     </ItemGroup>
     <ItemGroup>
         <Content Update="appsettings.json">

+ 5 - 0
src/Masuit.MyBlogs.Core/Models/ViewModel/PostDataModel.cs

@@ -47,6 +47,11 @@
         /// </summary>
         public int VoteDownCount { get; set; }
 
+        /// <summary>
+        /// 每日平均访问量
+        /// </summary>
+        public double AverageViewCount { get; set; }
+
         /// <summary>
         /// 分类id
         /// </summary>

+ 38 - 38
src/Masuit.MyBlogs.Core/Program.cs

@@ -12,64 +12,64 @@ using Z.EntityFramework.Plus;
 
 QueryCacheManager.DefaultMemoryCacheEntryOptions = new MemoryCacheEntryOptions()
 {
-    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
+	AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
 };
 AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
 
 if (Environment.OSVersion.Platform is not (PlatformID.MacOSX or PlatformID.Unix))
 {
-    // 设置相关进程优先级为高于正常,防止其他进程影响应用程序的运行性能
-    Process.GetProcessesByName("mysqld").ForEach(p => p.PriorityClass = ProcessPriorityClass.AboveNormal);
-    Process.GetProcessesByName("pg_ctl").ForEach(p => p.PriorityClass = ProcessPriorityClass.AboveNormal);
-    Process.GetProcessesByName("postgres").ForEach(p => p.PriorityClass = ProcessPriorityClass.AboveNormal);
-    Process.GetProcessesByName("redis-server").ForEach(p => p.PriorityClass = ProcessPriorityClass.AboveNormal);
-    Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.AboveNormal;
+	// 设置相关进程优先级为高于正常,防止其他进程影响应用程序的运行性能
+	Process.GetProcessesByName("mysqld").ForEach(p => p.PriorityClass = ProcessPriorityClass.AboveNormal);
+	Process.GetProcessesByName("pg_ctl").ForEach(p => p.PriorityClass = ProcessPriorityClass.AboveNormal);
+	Process.GetProcessesByName("postgres").ForEach(p => p.PriorityClass = ProcessPriorityClass.AboveNormal);
+	Process.GetProcessesByName("redis-server").ForEach(p => p.PriorityClass = ProcessPriorityClass.AboveNormal);
+	Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.AboveNormal;
 }
 
 // 确保IP数据库正常
 if (!"223.5.5.5".GetIPLocation().Contains("阿里"))
 {
-    throw new Exception("IP地址库初始化失败,请重启应用!");
+	throw new Exception("IP地址库初始化失败,请重启应用!");
 }
 
 InitOneDrive(); // 初始化Onedrive程序
 Host.CreateDefaultBuilder(args).UseServiceProviderFactory(new AutofacServiceProviderFactory()).ConfigureWebHostDefaults(hostBuilder => hostBuilder.UseKestrel(opt =>
 {
-    var config = opt.ApplicationServices.GetService<IConfiguration>();
-    var port = config["Port"] ?? "5000";
-    var sslport = config["Https:Port"] ?? "5001";
-    opt.ListenAnyIP(port.ToInt32());
-    if (bool.Parse(config["Https:Enabled"]))
-    {
-        opt.ListenAnyIP(sslport.ToInt32(), s =>
-        {
-            if (Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Major >= 10)
-            {
-                s.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
-            }
+	var config = opt.ApplicationServices.GetService<IConfiguration>();
+	var port = config["Port"] ?? "5000";
+	var sslport = config["Https:Port"] ?? "5001";
+	opt.ListenAnyIP(port.ToInt32());
+	if (bool.Parse(config["Https:Enabled"]))
+	{
+		opt.ListenAnyIP(sslport.ToInt32(), s =>
+		{
+			if (Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Major >= 10)
+			{
+				s.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
+			}
 
-            s.UseHttps(AppContext.BaseDirectory + config["Https:CertPath"], config["Https:CertPassword"]);
-        });
-    }
+			s.UseHttps(AppContext.BaseDirectory + config["Https:CertPath"], config["Https:CertPassword"]);
+		});
+	}
 
-    opt.Limits.MaxRequestBodySize = null;
-    Console.WriteLine($"应用程序监听端口:http:{port},https:{sslport}");
+	opt.Limits.MaxRequestBodySize = null;
+	Console.WriteLine($"应用程序监听端口:http:{port},https:{sslport}");
 }).UseStartup<Startup>()).Build().Run();
 
 static void InitOneDrive()
 {
-    //初始化
-    if (!File.Exists(Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "OneDrive.db")))
-    {
-        File.Copy(Path.Combine("App_Data", "OneDrive.template.db"), Path.Combine("App_Data", "OneDrive.db"));
-        Console.WriteLine("数据库创建成功");
-    }
+	//初始化
+	if (!File.Exists(Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "OneDrive.db")))
+	{
+		File.Copy(Path.Combine("App_Data", "OneDrive.template.db"), Path.Combine("App_Data", "OneDrive.db"));
+		Console.WriteLine("数据库创建成功");
+	}
 
-    using var settingService = new SettingService(new DriveContext());
-    if (settingService.Get("IsInit") != "true")
-    {
-        settingService.Set("IsInit", "true").Wait();
-        Console.WriteLine("数据初始化成功");
-        Console.WriteLine($"请登录 {OneDriveConfiguration.BaseUri}/#/admin 进行身份及其他配置");
-    }
+	using var settingService = new SettingService(new DriveContext());
+	if (settingService.Get("IsInit") != "true")
+	{
+		settingService.Set("IsInit", "true").Wait();
+		Console.WriteLine("数据初始化成功");
+		Console.WriteLine($"请登录 {OneDriveConfiguration.BaseUri}/#/admin 进行身份及其他配置");
+	}
 }

+ 174 - 171
src/Masuit.MyBlogs.Core/Startup.cs

@@ -34,188 +34,191 @@ using System.Text.RegularExpressions;
 
 namespace Masuit.MyBlogs.Core
 {
-    /// <summary>
-    /// asp.net core核心配置
-    /// </summary>
-    public class Startup
-    {
-        /// <summary>
-        /// 依赖注入容器
-        /// </summary>
-        public static IServiceProvider ServiceProvider { get; private set; }
+	/// <summary>
+	/// asp.net core核心配置
+	/// </summary>
+	public class Startup
+	{
+		/// <summary>
+		/// 依赖注入容器
+		/// </summary>
+		public static IServiceProvider ServiceProvider { get; private set; }
 
-        /// <summary>
-        /// 配置中心
-        /// </summary>
-        public IConfiguration Configuration { get; set; }
+		/// <summary>
+		/// 配置中心
+		/// </summary>
+		public IConfiguration Configuration { get; set; }
 
-        private readonly IWebHostEnvironment _env;
+		private readonly IWebHostEnvironment _env;
 
-        /// <summary>
-        /// asp.net core核心配置
-        /// </summary>
-        /// <param name="configuration"></param>
-        public Startup(IConfiguration configuration, IWebHostEnvironment env)
-        {
-            _env = env;
+		/// <summary>
+		/// asp.net core核心配置
+		/// </summary>
+		/// <param name="configuration"></param>
+		public Startup(IConfiguration configuration, IWebHostEnvironment env)
+		{
+			_env = env;
 
-            void BindConfig()
-            {
-                Configuration = configuration;
-                AppConfig.ConnString = configuration["Database:" + nameof(AppConfig.ConnString)];
-                AppConfig.BaiduAK = configuration[nameof(AppConfig.BaiduAK)];
-                AppConfig.Redis = configuration[nameof(AppConfig.Redis)];
-                AppConfig.TrueClientIPHeader = configuration[nameof(AppConfig.TrueClientIPHeader)] ?? "CF-Connecting-IP";
-                AppConfig.EnableIPDirect = bool.Parse(configuration[nameof(AppConfig.EnableIPDirect)] ?? "false");
-                configuration.Bind("Imgbed:Gitlabs", AppConfig.GitlabConfigs);
-                configuration.AddToMasuitTools();
-            }
+			void BindConfig()
+			{
+				Configuration = configuration;
+				AppConfig.ConnString = configuration["Database:" + nameof(AppConfig.ConnString)];
+				AppConfig.BaiduAK = configuration[nameof(AppConfig.BaiduAK)];
+				AppConfig.Redis = configuration[nameof(AppConfig.Redis)];
+				AppConfig.TrueClientIPHeader = configuration[nameof(AppConfig.TrueClientIPHeader)] ?? "CF-Connecting-IP";
+				AppConfig.EnableIPDirect = bool.Parse(configuration[nameof(AppConfig.EnableIPDirect)] ?? "false");
+				configuration.Bind("Imgbed:Gitlabs", AppConfig.GitlabConfigs);
+				configuration.AddToMasuitTools();
+			}
 
-            ChangeToken.OnChange(configuration.GetReloadToken, BindConfig);
-            BindConfig();
-        }
+			ChangeToken.OnChange(configuration.GetReloadToken, BindConfig);
+			BindConfig();
+		}
 
-        /// <summary>
-        /// ConfigureServices
-        /// </summary>
-        /// <param name="services"></param>
-        /// <returns></returns>
-        public void ConfigureServices(IServiceCollection services)
-        {
-            services.AddDbContext<DataContext>((serviceProvider, opt) => opt.UseNpgsql(AppConfig.ConnString, builder => builder.EnableRetryOnFailure(10)).EnableSensitiveDataLogging()); //配置数据库
-            services.AddDbContext<LoggerDbContext>(opt => opt.UseNpgsql(AppConfig.ConnString)); //配置数据库
-            services.ConfigureOptions();
-            services.AddHttpsRedirection(options =>
-            {
-                options.RedirectStatusCode = StatusCodes.Status301MovedPermanently;
-            });
-            services.AddSession().AddAntiforgery(); //注入Session
-            services.AddResponseCache().AddCacheConfig();
-            services.AddHangfireServer().AddHangfire((serviceProvider, configuration) =>
-            {
-                configuration.UseActivator(new HangfireActivator(serviceProvider));
-                configuration.UseFilter(new AutomaticRetryAttribute());
-                configuration.UseMemoryStorage();
-            }); //配置hangfire
+		/// <summary>
+		/// ConfigureServices
+		/// </summary>
+		/// <param name="services"></param>
+		/// <returns></returns>
+		public void ConfigureServices(IServiceCollection services)
+		{
+			services.AddDbContext<DataContext>((serviceProvider, opt) => opt.UseNpgsql(AppConfig.ConnString, builder => builder.EnableRetryOnFailure(10)).EnableSensitiveDataLogging()); //配置数据库
+			services.AddDbContext<LoggerDbContext>(opt => opt.UseNpgsql(AppConfig.ConnString)); //配置数据库
+			services.ConfigureOptions();
+			services.AddHttpsRedirection(options =>
+			{
+				options.RedirectStatusCode = StatusCodes.Status301MovedPermanently;
+			});
+			services.AddSession().AddAntiforgery(); //注入Session
+			services.AddResponseCache().AddCacheConfig();
+			services.AddHangfireServer().AddHangfire((serviceProvider, configuration) =>
+			{
+				configuration.UseActivator(new HangfireActivator(serviceProvider));
+				configuration.UseFilter(new AutomaticRetryAttribute());
+				configuration.UseMemoryStorage();
+			}); //配置hangfire
 
-            services.AddSevenZipCompressor().AddResumeFileResult().AddSearchEngine<DataContext>(new LuceneIndexerOptions()
-            {
-                Path = "lucene"
-            }); // 配置7z和断点续传和Redis和Lucene搜索引擎
+			services.AddSevenZipCompressor().AddResumeFileResult().AddSearchEngine<DataContext>(new LuceneIndexerOptions()
+			{
+				Path = "lucene"
+			}); // 配置7z和断点续传和Redis和Lucene搜索引擎
 
-            services.AddHttpClient("").AddTransientHttpErrorPolicy(builder => builder.Or<TaskCanceledException>().Or<OperationCanceledException>().Or<TimeoutException>().OrResult(res => !res.IsSuccessStatusCode).RetryAsync(5)).ConfigurePrimaryHttpMessageHandler(() =>
-            {
-                if (bool.TryParse(Configuration["HttpClientProxy:Enabled"], out var b) && b)
-                {
-                    return new HttpClientHandler
-                    {
-                        Proxy = new WebProxy(Configuration["HttpClientProxy:Uri"], true)
-                    };
-                }
+			services.AddHttpClient("").AddTransientHttpErrorPolicy(builder => builder.Or<TaskCanceledException>().Or<OperationCanceledException>().Or<TimeoutException>().OrResult(res => !res.IsSuccessStatusCode).RetryAsync(5)).ConfigurePrimaryHttpMessageHandler(() =>
+			{
+				var handler = new HttpClientHandler
+				{
+					AutomaticDecompression = DecompressionMethods.All,
+					ClientCertificateOptions = ClientCertificateOption.Manual,
+					ServerCertificateCustomValidationCallback = (_, _, _, _) => true
+				};
+				if (bool.TryParse(Configuration["HttpClientProxy:Enabled"], out var b) && b)
+				{
+					handler.Proxy = new WebProxy(Configuration["HttpClientProxy:Uri"], true);
+				}
 
-                return new HttpClientHandler();
-            }); //注入HttpClient
-            services.AddHttpClient<ImagebedClient>().AddTransientHttpErrorPolicy(builder => builder.Or<TaskCanceledException>().Or<OperationCanceledException>().Or<TimeoutException>().OrResult(res => !res.IsSuccessStatusCode).RetryAsync(3)); //注入HttpClient
-            services.AddMailSender(Configuration).AddFirewallReporter(Configuration).AddRequestLogger(Configuration).AddPerfCounterManager(Configuration);
-            services.AddBundling().UseDefaults(_env).UseNUglify().EnableMinification().EnableChangeDetection().EnableCacheHeader(TimeSpan.FromHours(1));
-            services.AddSingleton<IRedisClient>(new RedisClient(AppConfig.Redis)
-            {
-                Serialize = JsonConvert.SerializeObject,
-                Deserialize = JsonConvert.DeserializeObject
-            });
-            services.SetupMiniProfile();
-            services.AddSingleton<IMimeMapper, MimeMapper>(p => new MimeMapper());
-            services.AddOneDrive();
-            services.AutoRegisterServices();
-            services.AddRazorPages();
-            services.AddServerSideBlazor();
-            services.AddMapper().AddMyMvc().AddHealthChecks();
-            services.AddImageSharp(options =>
-                {
-                    options.MemoryStreamManager = new RecyclableMemoryStreamManager();
-                    options.BrowserMaxAge = TimeSpan.FromDays(7);
-                    options.CacheMaxAge = TimeSpan.FromDays(365);
-                    options.Configuration = SixLabors.ImageSharp.Configuration.Default;
-                }).SetRequestParser<QueryCollectionRequestParser>()
-                .Configure<PhysicalFileSystemCacheOptions>(options =>
-                {
-                    options.CacheRootPath = null;
-                    options.CacheFolder = "static/image_cache";
-                })
-                .SetCache<PhysicalFileSystemCache>()
-                .SetCacheKey<UriRelativeLowerInvariantCacheKey>()
-                .SetCacheHash<SHA256CacheHash>()
-                .Configure<PhysicalFileSystemProviderOptions>(options =>
-                {
-                    options.ProviderRootPath = null;
-                })
-                .AddProvider<PhysicalFileSystemProvider>()
-                .AddProcessor<ResizeWebProcessor>()
-                .AddProcessor<FormatWebProcessor>()
-                .AddProcessor<BackgroundColorWebProcessor>()
-                .AddProcessor<QualityWebProcessor>()
-                .AddProcessor<AutoOrientWebProcessor>();
-        }
+				return handler;
+			}); //注入HttpClient
+			services.AddHttpClient<ImagebedClient>().AddTransientHttpErrorPolicy(builder => builder.Or<TaskCanceledException>().Or<OperationCanceledException>().Or<TimeoutException>().OrResult(res => !res.IsSuccessStatusCode).RetryAsync(3)); //注入HttpClient
+			services.AddMailSender(Configuration).AddFirewallReporter(Configuration).AddRequestLogger(Configuration).AddPerfCounterManager(Configuration);
+			services.AddBundling().UseDefaults(_env).UseNUglify().EnableMinification().EnableChangeDetection().EnableCacheHeader(TimeSpan.FromHours(1));
+			services.AddSingleton<IRedisClient>(new RedisClient(AppConfig.Redis)
+			{
+				Serialize = JsonConvert.SerializeObject,
+				Deserialize = JsonConvert.DeserializeObject
+			});
+			services.SetupMiniProfile();
+			services.AddSingleton<IMimeMapper, MimeMapper>(p => new MimeMapper());
+			services.AddOneDrive();
+			services.AutoRegisterServices();
+			services.AddRazorPages();
+			services.AddServerSideBlazor();
+			services.AddMapper().AddMyMvc().AddHealthChecks();
+			services.AddImageSharp(options =>
+				{
+					options.MemoryStreamManager = new RecyclableMemoryStreamManager();
+					options.BrowserMaxAge = TimeSpan.FromDays(7);
+					options.CacheMaxAge = TimeSpan.FromDays(365);
+					options.Configuration = SixLabors.ImageSharp.Configuration.Default;
+				}).SetRequestParser<QueryCollectionRequestParser>()
+				.Configure<PhysicalFileSystemCacheOptions>(options =>
+				{
+					options.CacheRootPath = null;
+					options.CacheFolder = "static/image_cache";
+				})
+				.SetCache<PhysicalFileSystemCache>()
+				.SetCacheKey<UriRelativeLowerInvariantCacheKey>()
+				.SetCacheHash<SHA256CacheHash>()
+				.Configure<PhysicalFileSystemProviderOptions>(options =>
+				{
+					options.ProviderRootPath = null;
+				})
+				.AddProvider<PhysicalFileSystemProvider>()
+				.AddProcessor<ResizeWebProcessor>()
+				.AddProcessor<FormatWebProcessor>()
+				.AddProcessor<BackgroundColorWebProcessor>()
+				.AddProcessor<QualityWebProcessor>()
+				.AddProcessor<AutoOrientWebProcessor>();
+		}
 
-        public void ConfigureContainer(ContainerBuilder builder)
-        {
-            builder.RegisterModule(new AutofacModule());
-        }
+		public void ConfigureContainer(ContainerBuilder builder)
+		{
+			builder.RegisterModule(new AutofacModule());
+		}
 
-        /// <summary>
-        /// Configure
-        /// </summary>
-        /// <param name="app"></param>
-        /// <param name="env"></param>
-        /// <param name="hangfire"></param>
-        /// <param name="luceneIndexerOptions"></param>
-        /// <param name="maindb"></param>
-        /// <param name="loggerdb"></param>
-        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHangfireBackJob hangfire, LuceneIndexerOptions luceneIndexerOptions, DataContext maindb, LoggerDbContext loggerdb)
-        {
-            ServiceProvider = app.ApplicationServices;
-            maindb.Database.EnsureCreated();
-            loggerdb.Database.EnsureCreated();
-            app.InitSettings();
-            app.UseLuceneSearch(env, hangfire, luceneIndexerOptions);
-            app.UseForwardedHeaders().UseCertificateForwarding(); // X-Forwarded-For
-            if (env.IsDevelopment())
-            {
-                app.UseDeveloperExceptionPage();
-            }
-            else
-            {
-                app.UseExceptionHandler("/ServiceUnavailable");
-            }
+		/// <summary>
+		/// Configure
+		/// </summary>
+		/// <param name="app"></param>
+		/// <param name="env"></param>
+		/// <param name="hangfire"></param>
+		/// <param name="luceneIndexerOptions"></param>
+		/// <param name="maindb"></param>
+		/// <param name="loggerdb"></param>
+		public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHangfireBackJob hangfire, LuceneIndexerOptions luceneIndexerOptions, DataContext maindb, LoggerDbContext loggerdb)
+		{
+			ServiceProvider = app.ApplicationServices;
+			maindb.Database.EnsureCreated();
+			loggerdb.Database.EnsureCreated();
+			app.InitSettings();
+			app.UseLuceneSearch(env, hangfire, luceneIndexerOptions);
+			app.UseForwardedHeaders().UseCertificateForwarding(); // X-Forwarded-For
+			if (env.IsDevelopment())
+			{
+				app.UseDeveloperExceptionPage();
+			}
+			else
+			{
+				app.UseExceptionHandler("/ServiceUnavailable");
+			}
 
-            app.UseBundles();
-            app.SetupHttpsRedirection(Configuration);
-            app.UseDefaultFiles().UseWhen(c => Regex.IsMatch(c.Request.Path.Value + "", @"(\.jpg|\.jpeg|\.png|\.bmp|\.webp|\.tiff|\.pbm)$", RegexOptions.IgnoreCase), builder => builder.UseImageSharp()).UseStaticFiles();
-            app.UseSession().UseCookiePolicy(); //注入Session
-            app.UseWhen(c => c.Session.Get<UserInfoDto>(SessionKey.UserInfo)?.IsAdmin == true, builder =>
-            {
-                builder.UseMiniProfiler();
-                builder.UseCLRStatsDashboard();
-            });
-            app.UseWhen(c => !c.Request.Path.StartsWithSegments("/_blazor"), builder => builder.UseMiddleware<RequestInterceptMiddleware>()); //启用网站请求拦截
-            app.SetupHangfire();
-            app.UseResponseCaching().UseResponseCompression(); //启动Response缓存
-            app.UseMiddleware<TranslateMiddleware>();
-            app.UseRouting().UseEndpoints(endpoints =>
-            {
-                endpoints.MapBlazorHub(options =>
-                {
-                    options.ApplicationMaxBufferSize = 4194304;
-                    options.LongPolling.PollTimeout = TimeSpan.FromSeconds(10);
-                    options.TransportMaxBufferSize = 8388608;
-                });
-                endpoints.MapHealthChecks("/health");
-                endpoints.MapControllers(); // 属性路由
-                endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}"); // 默认路由
-                endpoints.MapFallbackToController("Index", "Error");
-            });
+			app.UseBundles();
+			app.SetupHttpsRedirection(Configuration);
+			app.UseDefaultFiles().UseWhen(c => Regex.IsMatch(c.Request.Path.Value + "", @"(\.jpg|\.jpeg|\.png|\.bmp|\.webp|\.tiff|\.pbm)$", RegexOptions.IgnoreCase), builder => builder.UseImageSharp()).UseStaticFiles();
+			app.UseSession().UseCookiePolicy(); //注入Session
+			app.UseWhen(c => c.Session.Get<UserInfoDto>(SessionKey.UserInfo)?.IsAdmin == true, builder =>
+			{
+				builder.UseMiniProfiler();
+				builder.UseCLRStatsDashboard();
+			});
+			app.UseWhen(c => !c.Request.Path.StartsWithSegments("/_blazor"), builder => builder.UseMiddleware<RequestInterceptMiddleware>()); //启用网站请求拦截
+			app.SetupHangfire();
+			app.UseResponseCaching().UseResponseCompression(); //启动Response缓存
+			app.UseMiddleware<TranslateMiddleware>();
+			app.UseRouting().UseEndpoints(endpoints =>
+			{
+				endpoints.MapBlazorHub(options =>
+				{
+					options.ApplicationMaxBufferSize = 4194304;
+					options.LongPolling.PollTimeout = TimeSpan.FromSeconds(10);
+					options.TransportMaxBufferSize = 8388608;
+				});
+				endpoints.MapHealthChecks("/health");
+				endpoints.MapControllers(); // 属性路由
+				endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}"); // 默认路由
+				endpoints.MapFallbackToController("Index", "Error");
+			});
 
-            Console.WriteLine("网站启动完成");
-        }
-    }
+			Console.WriteLine("网站启动完成");
+		}
+	}
 }

+ 8 - 0
src/Masuit.MyBlogs.Core/wwwroot/ng-views/controllers/post.js

@@ -340,7 +340,15 @@
           content: '/'+row.Id+'/insight'
         }));
     }
+    
+    $scope.diffDateFromNow = function(date){
+          var dateOut = new Date(date);
 
+          var timeDiff = Math.abs(new Date().getTime() - dateOut.getTime());
+          var diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24)); 
+          return diffDays;
+    };
+    
     showCharts=function() {
         echarts.init(document.getElementById('chart')).dispose();
 		var period=document.getElementById("period").value;

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

@@ -34,8 +34,8 @@
             </div>
         </div>
     <table ng-table="list.tableParams" class="table table-striped table-vmiddle table-bordered table-hover table-condensed">
-        <tr ng-repeat="row in $data">
-            <td data-title="'标题'">
+        <tr ng-repeat="row in $data" ng-class="{'warning':(diffDateFromNow(row.ModifyDate)>365)}">
+            <td data-title="'标题'" ng-class="{'danger':(row.AverageViewCount<1)}">
                 <a ng-href="/{{row.Id}}" target="_blank">{{row.Title}}</a>
             </td>
             <td data-title="'作者'">
@@ -44,7 +44,7 @@
             <td data-title="'作者邮箱'">
                 {{row.Email}}
             </td>
-            <td data-title="'阅读'" ng-if="!aclMode">
+            <td data-title="'阅读'" ng-if="!aclMode" ng-class="{'danger':(row.AverageViewCount<1)}">
                 {{row.ViewCount}}
             </td>
             <td data-title="'在看'" ng-if="!aclMode">
@@ -101,7 +101,7 @@
                     <span class="el-switch-style" ng-click="lockedSwitch(row.Id)"></span>
                 </label>
             </td>
-            <td data-title="'操作'" style="width: 195px;">
+            <td data-title="'操作'" style="width: 195px;" ng-class="{'danger':(row.AverageViewCount<1)}">
                 <div class="btn-group">
                     <button class="btn btn-default btn-sm waves-effect" ng-click="list.pass(row)" ng-if="row.Status=='审核中'">
                         <i class="icon-checkmark"></i>