Explorar el Código

修正路由匹配的bug

懒得勤快 hace 2 años
padre
commit
bbaebe12df

+ 60 - 60
src/Masuit.MyBlogs.Core/Controllers/AdminController.cs

@@ -14,70 +14,70 @@ namespace Masuit.MyBlogs.Core.Controllers;
 [MyAuthorize, ApiExplorerSettings(IgnoreApi = true)]
 public class AdminController : Controller
 {
-	/// <summary>
-	/// UserInfoService
-	/// </summary>
-	public IUserInfoService UserInfoService { get; set; }
-	public IRedisClient RedisHelper { get; set; }
+    /// <summary>
+    /// UserInfoService
+    /// </summary>
+    public IUserInfoService UserInfoService { get; set; }
+    public IRedisClient RedisHelper { get; set; }
 
-	public IMapper Mapper { get; set; }
+    public IMapper Mapper { get; set; }
 
-	/// <summary>
-	/// 返回结果json
-	/// </summary>
-	/// <param name="data">响应数据</param>
-	/// <param name="success">响应状态</param>
-	/// <param name="message">响应消息</param>
-	/// <param name="isLogin">登录状态</param>
-	/// <returns></returns>
-	public ActionResult ResultData(object data, bool success = true, string message = "", bool isLogin = true)
-	{
-		return Ok(new
-		{
-			IsLogin = isLogin,
-			Success = success,
-			Message = message,
-			Data = data
-		});
-	}
+    /// <summary>
+    /// 返回结果json
+    /// </summary>
+    /// <param name="data">响应数据</param>
+    /// <param name="success">响应状态</param>
+    /// <param name="message">响应消息</param>
+    /// <param name="isLogin">登录状态</param>
+    /// <returns></returns>
+    internal ActionResult ResultData(object data, bool success = true, string message = "", bool isLogin = true)
+    {
+        return Ok(new
+        {
+            IsLogin = isLogin,
+            Success = success,
+            Message = message,
+            Data = data
+        });
+    }
 
-	/// <summary>在调用操作方法前调用。</summary>
-	/// <param name="filterContext">有关当前请求和操作的信息。</param>
-	public override void OnActionExecuting(ActionExecutingContext filterContext)
-	{
-		base.OnActionExecuting(filterContext);
-		var user = filterContext.HttpContext.Session.Get<UserInfoDto>(SessionKey.UserInfo);
+    /// <summary>在调用操作方法前调用。</summary>
+    /// <param name="filterContext">有关当前请求和操作的信息。</param>
+    public override void OnActionExecuting(ActionExecutingContext filterContext)
+    {
+        base.OnActionExecuting(filterContext);
+        var user = filterContext.HttpContext.Session.Get<UserInfoDto>(SessionKey.UserInfo);
 #if DEBUG
-		user = Mapper.Map<UserInfoDto>(UserInfoService.GetByUsername("masuit"));
-		filterContext.HttpContext.Session.Set(SessionKey.UserInfo, user);
+        user = Mapper.Map<UserInfoDto>(UserInfoService.GetByUsername("masuit"));
+        filterContext.HttpContext.Session.Set(SessionKey.UserInfo, user);
 #endif
-		if (user == null && Request.Cookies.Any(x => x.Key == "username" || x.Key == "password")) //执行自动登录
-		{
-			string name = Request.Cookies["username"];
-			string pwd = Request.Cookies["password"]?.DesDecrypt(AppConfig.BaiduAK);
-			var userInfo = UserInfoService.Login(name, pwd);
-			if (userInfo != null)
-			{
-				Response.Cookies.Append("username", name, new CookieOptions
-				{
-					Expires = DateTime.Now.AddYears(1),
-					SameSite = SameSiteMode.Lax
-				});
-				Response.Cookies.Append("password", Request.Cookies["password"], new CookieOptions
-				{
-					Expires = DateTime.Now.AddYears(1),
-					SameSite = SameSiteMode.Lax
-				});
-				filterContext.HttpContext.Session.Set(SessionKey.UserInfo, userInfo);
-			}
-		}
-		if (ModelState.IsValid) return;
-		var errmsgs = ModelState.SelectMany(kv => kv.Value.Errors.Select(e => e.ErrorMessage)).ToPooledListScope();
-		for (var i = 0; i < errmsgs.Count; i++)
-		{
-			errmsgs[i] = i + 1 + ". " + errmsgs[i];
-		}
+        if (user == null && Request.Cookies.Any(x => x.Key == "username" || x.Key == "password")) //执行自动登录
+        {
+            string name = Request.Cookies["username"];
+            string pwd = Request.Cookies["password"]?.DesDecrypt(AppConfig.BaiduAK);
+            var userInfo = UserInfoService.Login(name, pwd);
+            if (userInfo != null)
+            {
+                Response.Cookies.Append("username", name, new CookieOptions
+                {
+                    Expires = DateTime.Now.AddYears(1),
+                    SameSite = SameSiteMode.Lax
+                });
+                Response.Cookies.Append("password", Request.Cookies["password"], new CookieOptions
+                {
+                    Expires = DateTime.Now.AddYears(1),
+                    SameSite = SameSiteMode.Lax
+                });
+                filterContext.HttpContext.Session.Set(SessionKey.UserInfo, userInfo);
+            }
+        }
+        if (ModelState.IsValid) return;
+        var errmsgs = ModelState.SelectMany(kv => kv.Value.Errors.Select(e => e.ErrorMessage)).ToPooledListScope();
+        for (var i = 0; i < errmsgs.Count; i++)
+        {
+            errmsgs[i] = i + 1 + ". " + errmsgs[i];
+        }
 
-		filterContext.Result = ResultData(null, false, "数据校验失败,错误信息:" + string.Join(" | ", errmsgs));
-	}
+        filterContext.Result = ResultData(null, false, "数据校验失败,错误信息:" + string.Join(" | ", errmsgs));
+    }
 }

+ 317 - 317
src/Masuit.MyBlogs.Core/Controllers/BaseController.cs

@@ -20,99 +20,99 @@ namespace Masuit.MyBlogs.Core.Controllers;
 [ApiExplorerSettings(IgnoreApi = true), ServiceFilter(typeof(FirewallAttribute))]
 public class BaseController : Controller
 {
-	public IUserInfoService UserInfoService { get; set; }
-
-	public ILinksService LinksService { get; set; }
-
-	public IAdvertisementService AdsService { get; set; }
-
-	public IVariablesService VariablesService { get; set; }
-
-	public IMapper Mapper { get; set; }
-
-	public MapperConfiguration MapperConfig { get; set; }
-	public IRedisClient RedisHelper { get; set; }
-	public UserInfoDto CurrentUser => HttpContext.Session.Get<UserInfoDto>(SessionKey.UserInfo) ?? new UserInfoDto();
-
-	/// <summary>
-	/// 客户端的真实IP
-	/// </summary>
-	public IPAddress ClientIP => HttpContext.Connection.RemoteIpAddress;
-
-	/// <summary>
-	/// 普通访客是否token合法
-	/// </summary>
-	public bool VisitorTokenValid => Request.Cookies.ContainsKey("FullAccessToken") && Request.Cookies["Email"].MDString(AppConfig.BaiduAK).Equals(Request.Cookies["FullAccessToken"]);
-
-	public int[] HideCategories => Request.GetHideCategories();
-
-	public bool SafeMode => !Request.Cookies.ContainsKey("Nsfw") || Request.Cookies["Nsfw"] == "1";
-
-	/// <summary>
-	/// 响应数据
-	/// </summary>
-	/// <param name="data">数据</param>
-	/// <param name="success">响应状态</param>
-	/// <param name="message">响应消息</param>
-	/// <param name="isLogin">登录状态</param>
-	/// <param name="code">http响应码</param>
-	/// <returns></returns>
-	public ActionResult ResultData(object data, bool success = true, string message = "", bool isLogin = true, HttpStatusCode code = HttpStatusCode.OK)
-	{
-		return Ok(new
-		{
-			IsLogin = isLogin,
-			Success = success,
-			Message = message,
-			Data = data,
-			code
-		});
-	}
-
-	protected string ReplaceVariables(string text, int depth = 0)
-	{
-		if (string.IsNullOrEmpty(text))
-		{
-			return text;
-		}
-
-		var location = Request.Location();
-		var template = Template.Create(text)
-			.Set("clientip", ClientIP.ToString())
-			.Set("location", location.Address)
-			.Set("network", location.Network)
-			.Set("domain", Request.Host.Host)
-			.Set("path", Request.Path.ToUriComponent());
-		if (text.Contains("{{browser}}") || text.Contains("{{os}}"))
-		{
-			var agent = UserAgent.Parse(Request.Headers[HeaderNames.UserAgent] + "");
-			template.Set("browser", agent.Browser + " " + agent.BrowserVersion).Set("os", agent.Platform);
-		}
-
-		var pattern = @"\{\{[\w._-]+\}\}";
-		var keys = Regex.Matches(template.Render(), pattern).Select(m => m.Value.Trim('{', '}')).ToArray();
-		if (keys.Length > 0)
-		{
-			var dic = VariablesService.GetQueryFromCache(v => keys.Contains(v.Key)).ToDictionary(v => v.Key, v => v.Value);
-			foreach (var (key, value) in dic)
-			{
-				string valve = value;
-				if (Regex.IsMatch(valve, pattern) && depth < 32)
-				{
-					valve = ReplaceVariables(valve, depth++);
-				}
-
-				template.Set(key, valve);
-			}
-		}
-
-		return template.Render();
-	}
-
-	public override Task OnActionExecutionAsync(ActionExecutingContext filterContext, ActionExecutionDelegate next)
-	{
-		ViewBag.Desc = CommonHelper.SystemSettings["Description"];
-		var user = filterContext.HttpContext.Session.Get<UserInfoDto>(SessionKey.UserInfo);
+    public IUserInfoService UserInfoService { get; set; }
+
+    public ILinksService LinksService { get; set; }
+
+    public IAdvertisementService AdsService { get; set; }
+
+    public IVariablesService VariablesService { get; set; }
+
+    public IMapper Mapper { get; set; }
+
+    public MapperConfiguration MapperConfig { get; set; }
+    public IRedisClient RedisHelper { get; set; }
+    public UserInfoDto CurrentUser => HttpContext.Session.Get<UserInfoDto>(SessionKey.UserInfo) ?? new UserInfoDto();
+
+    /// <summary>
+    /// 客户端的真实IP
+    /// </summary>
+    public IPAddress ClientIP => HttpContext.Connection.RemoteIpAddress;
+
+    /// <summary>
+    /// 普通访客是否token合法
+    /// </summary>
+    public bool VisitorTokenValid => Request.Cookies.ContainsKey("FullAccessToken") && Request.Cookies["Email"].MDString(AppConfig.BaiduAK).Equals(Request.Cookies["FullAccessToken"]);
+
+    public int[] HideCategories => Request.GetHideCategories();
+
+    public bool SafeMode => !Request.Cookies.ContainsKey("Nsfw") || Request.Cookies["Nsfw"] == "1";
+
+    /// <summary>
+    /// 响应数据
+    /// </summary>
+    /// <param name="data">数据</param>
+    /// <param name="success">响应状态</param>
+    /// <param name="message">响应消息</param>
+    /// <param name="isLogin">登录状态</param>
+    /// <param name="code">http响应码</param>
+    /// <returns></returns>
+    internal ActionResult ResultData(object data, bool success = true, string message = "", bool isLogin = true, HttpStatusCode code = HttpStatusCode.OK)
+    {
+        return Ok(new
+        {
+            IsLogin = isLogin,
+            Success = success,
+            Message = message,
+            Data = data,
+            code
+        });
+    }
+
+    protected string ReplaceVariables(string text, int depth = 0)
+    {
+        if (string.IsNullOrEmpty(text))
+        {
+            return text;
+        }
+
+        var location = Request.Location();
+        var template = Template.Create(text)
+            .Set("clientip", ClientIP.ToString())
+            .Set("location", location.Address)
+            .Set("network", location.Network)
+            .Set("domain", Request.Host.Host)
+            .Set("path", Request.Path.ToUriComponent());
+        if (text.Contains("{{browser}}") || text.Contains("{{os}}"))
+        {
+            var agent = UserAgent.Parse(Request.Headers[HeaderNames.UserAgent] + "");
+            template.Set("browser", agent.Browser + " " + agent.BrowserVersion).Set("os", agent.Platform);
+        }
+
+        var pattern = @"\{\{[\w._-]+\}\}";
+        var keys = Regex.Matches(template.Render(), pattern).Select(m => m.Value.Trim('{', '}')).ToArray();
+        if (keys.Length > 0)
+        {
+            var dic = VariablesService.GetQueryFromCache(v => keys.Contains(v.Key)).ToDictionary(v => v.Key, v => v.Value);
+            foreach (var (key, value) in dic)
+            {
+                string valve = value;
+                if (Regex.IsMatch(valve, pattern) && depth < 32)
+                {
+                    valve = ReplaceVariables(valve, depth++);
+                }
+
+                template.Set(key, valve);
+            }
+        }
+
+        return template.Render();
+    }
+
+    public override Task OnActionExecutionAsync(ActionExecutingContext filterContext, ActionExecutionDelegate next)
+    {
+        ViewBag.Desc = CommonHelper.SystemSettings["Description"];
+        var user = filterContext.HttpContext.Session.Get<UserInfoDto>(SessionKey.UserInfo);
 #if DEBUG
 		if (HttpContext.Connection.RemoteIpAddress.IsPrivateIP())
 		{
@@ -120,228 +120,228 @@ public class BaseController : Controller
 			filterContext.HttpContext.Session.Set(SessionKey.UserInfo, user);
 		}
 #endif
-		if (CommonHelper.SystemSettings.GetOrAdd("CloseSite", "false") == "true" && user?.IsAdmin != true)
-		{
-			filterContext.Result = RedirectToAction("ComingSoon", "Error");
-			return Task.CompletedTask;
-		}
-
-		if (Request.Method == HttpMethods.Post && !Request.Path.Value.Contains("get", StringComparison.InvariantCultureIgnoreCase) && CommonHelper.SystemSettings.GetOrAdd("DataReadonly", "false") == "true" && !filterContext.Filters.Any(m => m.ToString().Contains(nameof(MyAuthorizeAttribute))))
-		{
-			filterContext.Result = ResultData("网站当前处于数据写保护状态,无法提交任何数据,如有疑问请联系网站管理员!", false, "网站当前处于数据写保护状态,无法提交任何数据,如有疑问请联系网站管理员!", user != null, HttpStatusCode.BadRequest);
-			return Task.CompletedTask;
-		}
-
-		if (user == null && Request.Cookies.ContainsKey("username") && Request.Cookies.ContainsKey("password")) //执行自动登录
-		{
-			var name = Request.Cookies["username"];
-			var pwd = Request.Cookies["password"];
-			var userInfo = UserInfoService.Login(name, pwd.DesDecrypt(AppConfig.BaiduAK));
-			if (userInfo != null)
-			{
-				Response.Cookies.Append("username", name, new CookieOptions
-				{
-					Expires = DateTime.Now.AddYears(1),
-					SameSite = SameSiteMode.Lax
-				});
-				Response.Cookies.Append("password", pwd, new CookieOptions
-				{
-					Expires = DateTime.Now.AddYears(1),
-					SameSite = SameSiteMode.Lax
-				});
-				filterContext.HttpContext.Session.Set(SessionKey.UserInfo, userInfo);
-			}
-		}
-
-		if (ModelState.IsValid) return next();
-		var errmsgs = ModelState.SelectMany(kv => kv.Value.Errors.Select(e => e.ErrorMessage)).Select((s, i) => $"{i + 1}. {s}");
-		filterContext.Result = true switch
-		{
-			_ when Request.HasJsonContentType() || Request.Method == HttpMethods.Post => ResultData(errmsgs, false, "数据校验失败,错误信息:" + errmsgs.Join(" | "), user != null, HttpStatusCode.BadRequest),
-			_ => base.BadRequest("参数错误:" + errmsgs.Join(" | "))
-		};
-		return Task.CompletedTask;
-	}
-
-	/// <summary>
-	/// 验证邮箱验证码
-	/// </summary>
-	/// <param name="mailSender"></param>
-	/// <param name="email">邮箱地址</param>
-	/// <param name="code">验证码</param>
-	/// <returns></returns>
-	internal string ValidateEmailCode(IMailSender mailSender, string email, string code)
-	{
-		if (CurrentUser.IsAdmin)
-		{
-			return string.Empty; ;
-		}
-
-		if (string.IsNullOrEmpty(Request.Cookies["ValidateKey"]))
-		{
-			if (string.IsNullOrEmpty(code))
-			{
-				return "请输入验证码!";
-			}
-			if (RedisHelper.Get("code:" + email) != code)
-			{
-				return "验证码错误!";
-			}
-		}
-		else if (Request.Cookies["ValidateKey"].DesDecrypt(AppConfig.BaiduAK) != email)
-		{
-			Response.Cookies.Delete("Email");
-			Response.Cookies.Delete("NickName");
-			Response.Cookies.Delete("ValidateKey");
-			return "邮箱验证信息已失效,请刷新页面后重新评论!";
-		}
-
-		if (mailSender.HasBounced(email))
-		{
-			Response.Cookies.Delete("Email");
-			Response.Cookies.Delete("NickName");
-			Response.Cookies.Delete("ValidateKey");
-			return "邮箱地址错误,请刷新页面后重新使用有效的邮箱地址!";
-		}
-
-		return string.Empty;
-	}
-
-	internal void WriteEmailKeyCookie(string email)
-	{
-		Response.Cookies.Append("Email", email, new CookieOptions()
-		{
-			Expires = DateTimeOffset.Now.AddYears(1),
-			SameSite = SameSiteMode.Lax
-		});
-		Response.Cookies.Append("ValidateKey", email.DesEncrypt(AppConfig.BaiduAK), new CookieOptions()
-		{
-			Expires = DateTimeOffset.Now.AddYears(1),
-			SameSite = SameSiteMode.Lax
-		});
-	}
-
-	protected Expression<Func<Post, bool>> PostBaseWhere()
-	{
-		if (CurrentUser.IsAdmin || Request.IsRobot())
-		{
-			return _ => true;
-		}
-
-		var ip = ClientIP.ToString();
-		Expression<Func<Post, bool>> where = p => p.Status == Status.Published && p.LimitMode != RegionLimitMode.OnlyForSearchEngine;
-		where = where.AndIf(HideCategories.Length > 0, p => !HideCategories.Contains(p.CategoryId)).AndIf(SafeMode, p => !p.IsNsfw);
-		if (VisitorTokenValid || CommonHelper.IPWhiteList.Contains(ip))
-		{
-			return where;
-		}
-
-		var ipLocation = Request.Location();
-		var location = ipLocation + ipLocation.Coodinate + "|" + Request.Headers[HeaderNames.Referer] + "|" + Request.Headers[HeaderNames.UserAgent];
-		if (Request.Cookies.TryGetValue(SessionKey.RawIP, out var rawip) && ip != rawip)
-		{
-			var s = rawip.Base64Decrypt();
-			if (ip != s)
-			{
-				location += "|" + s.GetIPLocation();
-			}
-		}
-
-		return where.And(p => p.LimitMode == null || p.LimitMode == RegionLimitMode.All ? true :
-			p.LimitMode == RegionLimitMode.AllowRegion ? Regex.IsMatch(location, p.Regions, RegexOptions.IgnoreCase) :
-			p.LimitMode == RegionLimitMode.ForbidRegion ? !Regex.IsMatch(location, p.Regions, RegexOptions.IgnoreCase) :
-			p.LimitMode == RegionLimitMode.AllowRegionExceptForbidRegion ? Regex.IsMatch(location, p.Regions, RegexOptions.IgnoreCase) && !Regex.IsMatch(location, p.ExceptRegions, RegexOptions.IgnoreCase) : !Regex.IsMatch(location, p.Regions, RegexOptions.IgnoreCase) || Regex.IsMatch(location, p.ExceptRegions, RegexOptions.IgnoreCase));
-	}
-
-	protected void CheckPermission(Post post)
-	{
-		var ip = ClientIP.ToString();
-		if (CurrentUser.IsAdmin || VisitorTokenValid || Request.IsRobot() || CommonHelper.IPWhiteList.Contains(ip))
-		{
-			return;
-		}
-
-		var ipLocation = Request.Location();
-		var location = ipLocation + ipLocation.Coodinate + "|" + Request.Headers[HeaderNames.Referer] + "|" + Request.Headers[HeaderNames.UserAgent];
-		if (Request.Cookies.TryGetValue(SessionKey.RawIP, out var rawip) && ip != rawip)
-		{
-			var s = rawip.Base64Decrypt();
-			if (ip != s)
-			{
-				location += "|" + s.GetIPLocation();
-			}
-		}
-
-		switch (post.LimitMode)
-		{
-			case RegionLimitMode.OnlyForSearchEngine:
-				Disallow(post);
-				break;
-
-			case RegionLimitMode.AllowRegion:
-				if (!Regex.IsMatch(location, post.Regions, RegexOptions.IgnoreCase))
-				{
-					Disallow(post);
-				}
-
-				break;
-
-			case RegionLimitMode.ForbidRegion:
-				if (Regex.IsMatch(location, post.Regions, RegexOptions.IgnoreCase))
-				{
-					Disallow(post);
-				}
-
-				break;
-
-			case RegionLimitMode.AllowRegionExceptForbidRegion:
-				if (Regex.IsMatch(location, post.ExceptRegions, RegexOptions.IgnoreCase))
-				{
-					Disallow(post);
-				}
-
-				goto case RegionLimitMode.AllowRegion;
-			case RegionLimitMode.ForbidRegionExceptAllowRegion:
-				if (Regex.IsMatch(location, post.ExceptRegions, RegexOptions.IgnoreCase))
-				{
-					break;
-				}
-
-				goto case RegionLimitMode.ForbidRegion;
-		}
-
-		if (HideCategories.Contains(post.CategoryId) || (SafeMode && post.IsNsfw))
-		{
-			throw new NotFoundException("文章未找到");
-		}
-	}
-
-	private void Disallow(Post post)
-	{
-		var remark = "无权限查看该文章";
-		var ip = ClientIP.ToString();
-		if (Request.Cookies.TryGetValue(SessionKey.RawIP, out var rawip) && ip != rawip.Base64Decrypt())
-		{
-			remark += ",发生了IP切换,原始IP:" + rawip.Base64Decrypt();
-		}
-
-		RedisHelper.IncrBy("interceptCount", 1);
-		RedisHelper.LPush("intercept", new IpIntercepter()
-		{
-			IP = ip,
-			RequestUrl = $"//{Request.Host}/{post.Id}",
-			Referer = Request.Headers[HeaderNames.Referer],
-			Time = DateTime.Now,
-			UserAgent = Request.Headers[HeaderNames.UserAgent],
-			Remark = remark,
-			Address = Request.Location(),
-			HttpVersion = Request.Protocol,
-			Headers = new
-			{
-				Request.Protocol,
-				Request.Headers
-			}.ToJsonString()
-		});
-		throw new NotFoundException("文章未找到");
-	}
+        if (CommonHelper.SystemSettings.GetOrAdd("CloseSite", "false") == "true" && user?.IsAdmin != true)
+        {
+            filterContext.Result = RedirectToAction("ComingSoon", "Error");
+            return Task.CompletedTask;
+        }
+
+        if (Request.Method == HttpMethods.Post && !Request.Path.Value.Contains("get", StringComparison.InvariantCultureIgnoreCase) && CommonHelper.SystemSettings.GetOrAdd("DataReadonly", "false") == "true" && !filterContext.Filters.Any(m => m.ToString().Contains(nameof(MyAuthorizeAttribute))))
+        {
+            filterContext.Result = ResultData("网站当前处于数据写保护状态,无法提交任何数据,如有疑问请联系网站管理员!", false, "网站当前处于数据写保护状态,无法提交任何数据,如有疑问请联系网站管理员!", user != null, HttpStatusCode.BadRequest);
+            return Task.CompletedTask;
+        }
+
+        if (user == null && Request.Cookies.ContainsKey("username") && Request.Cookies.ContainsKey("password")) //执行自动登录
+        {
+            var name = Request.Cookies["username"];
+            var pwd = Request.Cookies["password"];
+            var userInfo = UserInfoService.Login(name, pwd.DesDecrypt(AppConfig.BaiduAK));
+            if (userInfo != null)
+            {
+                Response.Cookies.Append("username", name, new CookieOptions
+                {
+                    Expires = DateTime.Now.AddYears(1),
+                    SameSite = SameSiteMode.Lax
+                });
+                Response.Cookies.Append("password", pwd, new CookieOptions
+                {
+                    Expires = DateTime.Now.AddYears(1),
+                    SameSite = SameSiteMode.Lax
+                });
+                filterContext.HttpContext.Session.Set(SessionKey.UserInfo, userInfo);
+            }
+        }
+
+        if (ModelState.IsValid) return next();
+        var errmsgs = ModelState.SelectMany(kv => kv.Value.Errors.Select(e => e.ErrorMessage)).Select((s, i) => $"{i + 1}. {s}");
+        filterContext.Result = true switch
+        {
+            _ when Request.HasJsonContentType() || Request.Method == HttpMethods.Post => ResultData(errmsgs, false, "数据校验失败,错误信息:" + errmsgs.Join(" | "), user != null, HttpStatusCode.BadRequest),
+            _ => base.BadRequest("参数错误:" + errmsgs.Join(" | "))
+        };
+        return Task.CompletedTask;
+    }
+
+    /// <summary>
+    /// 验证邮箱验证码
+    /// </summary>
+    /// <param name="mailSender"></param>
+    /// <param name="email">邮箱地址</param>
+    /// <param name="code">验证码</param>
+    /// <returns></returns>
+    internal string ValidateEmailCode(IMailSender mailSender, string email, string code)
+    {
+        if (CurrentUser.IsAdmin)
+        {
+            return string.Empty; ;
+        }
+
+        if (string.IsNullOrEmpty(Request.Cookies["ValidateKey"]))
+        {
+            if (string.IsNullOrEmpty(code))
+            {
+                return "请输入验证码!";
+            }
+            if (RedisHelper.Get("code:" + email) != code)
+            {
+                return "验证码错误!";
+            }
+        }
+        else if (Request.Cookies["ValidateKey"].DesDecrypt(AppConfig.BaiduAK) != email)
+        {
+            Response.Cookies.Delete("Email");
+            Response.Cookies.Delete("NickName");
+            Response.Cookies.Delete("ValidateKey");
+            return "邮箱验证信息已失效,请刷新页面后重新评论!";
+        }
+
+        if (mailSender.HasBounced(email))
+        {
+            Response.Cookies.Delete("Email");
+            Response.Cookies.Delete("NickName");
+            Response.Cookies.Delete("ValidateKey");
+            return "邮箱地址错误,请刷新页面后重新使用有效的邮箱地址!";
+        }
+
+        return string.Empty;
+    }
+
+    internal void WriteEmailKeyCookie(string email)
+    {
+        Response.Cookies.Append("Email", email, new CookieOptions()
+        {
+            Expires = DateTimeOffset.Now.AddYears(1),
+            SameSite = SameSiteMode.Lax
+        });
+        Response.Cookies.Append("ValidateKey", email.DesEncrypt(AppConfig.BaiduAK), new CookieOptions()
+        {
+            Expires = DateTimeOffset.Now.AddYears(1),
+            SameSite = SameSiteMode.Lax
+        });
+    }
+
+    protected Expression<Func<Post, bool>> PostBaseWhere()
+    {
+        if (CurrentUser.IsAdmin || Request.IsRobot())
+        {
+            return _ => true;
+        }
+
+        var ip = ClientIP.ToString();
+        Expression<Func<Post, bool>> where = p => p.Status == Status.Published && p.LimitMode != RegionLimitMode.OnlyForSearchEngine;
+        where = where.AndIf(HideCategories.Length > 0, p => !HideCategories.Contains(p.CategoryId)).AndIf(SafeMode, p => !p.IsNsfw);
+        if (VisitorTokenValid || CommonHelper.IPWhiteList.Contains(ip))
+        {
+            return where;
+        }
+
+        var ipLocation = Request.Location();
+        var location = ipLocation + ipLocation.Coodinate + "|" + Request.Headers[HeaderNames.Referer] + "|" + Request.Headers[HeaderNames.UserAgent];
+        if (Request.Cookies.TryGetValue(SessionKey.RawIP, out var rawip) && ip != rawip)
+        {
+            var s = rawip.Base64Decrypt();
+            if (ip != s)
+            {
+                location += "|" + s.GetIPLocation();
+            }
+        }
+
+        return where.And(p => p.LimitMode == null || p.LimitMode == RegionLimitMode.All ? true :
+            p.LimitMode == RegionLimitMode.AllowRegion ? Regex.IsMatch(location, p.Regions, RegexOptions.IgnoreCase) :
+            p.LimitMode == RegionLimitMode.ForbidRegion ? !Regex.IsMatch(location, p.Regions, RegexOptions.IgnoreCase) :
+            p.LimitMode == RegionLimitMode.AllowRegionExceptForbidRegion ? Regex.IsMatch(location, p.Regions, RegexOptions.IgnoreCase) && !Regex.IsMatch(location, p.ExceptRegions, RegexOptions.IgnoreCase) : !Regex.IsMatch(location, p.Regions, RegexOptions.IgnoreCase) || Regex.IsMatch(location, p.ExceptRegions, RegexOptions.IgnoreCase));
+    }
+
+    protected void CheckPermission(Post post)
+    {
+        var ip = ClientIP.ToString();
+        if (CurrentUser.IsAdmin || VisitorTokenValid || Request.IsRobot() || CommonHelper.IPWhiteList.Contains(ip))
+        {
+            return;
+        }
+
+        var ipLocation = Request.Location();
+        var location = ipLocation + ipLocation.Coodinate + "|" + Request.Headers[HeaderNames.Referer] + "|" + Request.Headers[HeaderNames.UserAgent];
+        if (Request.Cookies.TryGetValue(SessionKey.RawIP, out var rawip) && ip != rawip)
+        {
+            var s = rawip.Base64Decrypt();
+            if (ip != s)
+            {
+                location += "|" + s.GetIPLocation();
+            }
+        }
+
+        switch (post.LimitMode)
+        {
+            case RegionLimitMode.OnlyForSearchEngine:
+                Disallow(post);
+                break;
+
+            case RegionLimitMode.AllowRegion:
+                if (!Regex.IsMatch(location, post.Regions, RegexOptions.IgnoreCase))
+                {
+                    Disallow(post);
+                }
+
+                break;
+
+            case RegionLimitMode.ForbidRegion:
+                if (Regex.IsMatch(location, post.Regions, RegexOptions.IgnoreCase))
+                {
+                    Disallow(post);
+                }
+
+                break;
+
+            case RegionLimitMode.AllowRegionExceptForbidRegion:
+                if (Regex.IsMatch(location, post.ExceptRegions, RegexOptions.IgnoreCase))
+                {
+                    Disallow(post);
+                }
+
+                goto case RegionLimitMode.AllowRegion;
+            case RegionLimitMode.ForbidRegionExceptAllowRegion:
+                if (Regex.IsMatch(location, post.ExceptRegions, RegexOptions.IgnoreCase))
+                {
+                    break;
+                }
+
+                goto case RegionLimitMode.ForbidRegion;
+        }
+
+        if (HideCategories.Contains(post.CategoryId) || (SafeMode && post.IsNsfw))
+        {
+            throw new NotFoundException("文章未找到");
+        }
+    }
+
+    private void Disallow(Post post)
+    {
+        var remark = "无权限查看该文章";
+        var ip = ClientIP.ToString();
+        if (Request.Cookies.TryGetValue(SessionKey.RawIP, out var rawip) && ip != rawip.Base64Decrypt())
+        {
+            remark += ",发生了IP切换,原始IP:" + rawip.Base64Decrypt();
+        }
+
+        RedisHelper.IncrBy("interceptCount", 1);
+        RedisHelper.LPush("intercept", new IpIntercepter()
+        {
+            IP = ip,
+            RequestUrl = $"//{Request.Host}/{post.Id}",
+            Referer = Request.Headers[HeaderNames.Referer],
+            Time = DateTime.Now,
+            UserAgent = Request.Headers[HeaderNames.UserAgent],
+            Remark = remark,
+            Address = Request.Location(),
+            HttpVersion = Request.Protocol,
+            Headers = new
+            {
+                Request.Protocol,
+                Request.Headers
+            }.ToJsonString()
+        });
+        throw new NotFoundException("文章未找到");
+    }
 }

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

@@ -1,6 +1,7 @@
 using FreeRedis;
 using Hangfire;
 using Masuit.MyBlogs.Core.Common;
+using Masuit.MyBlogs.Core.Common.Mails;
 using Masuit.MyBlogs.Core.Configs;
 using Masuit.MyBlogs.Core.Extensions.Firewall;
 using Masuit.Tools.AspNetCore.Mime;
@@ -13,7 +14,6 @@ using Microsoft.Net.Http.Headers;
 using System.Diagnostics;
 using System.Text;
 using System.Web;
-using Masuit.MyBlogs.Core.Common.Mails;
 using SameSiteMode = Microsoft.AspNetCore.Http.SameSiteMode;
 
 namespace Masuit.MyBlogs.Core.Controllers;
@@ -24,204 +24,204 @@ namespace Masuit.MyBlogs.Core.Controllers;
 [ApiExplorerSettings(IgnoreApi = true)]
 public sealed class ErrorController : Controller
 {
-	public IRedisClient RedisClient { get; set; }
-
-	/// <summary>
-	/// 404
-	/// </summary>
-	/// <returns></returns>
-	[Route("error"), Route("{*url}", Order = 99999), ResponseCache(Duration = 36000)]
-	public ActionResult Index()
-	{
-		Response.StatusCode = 404;
-		string accept = Request.Headers[HeaderNames.Accept] + "";
-		return true switch
-		{
-			_ when accept.StartsWith("image") => File("/Assets/images/404/4044.jpg", ContentType.Jpeg),
-			_ when Request.HasJsonContentType() || Request.Method == HttpMethods.Post => Json(new
-			{
-				StatusCode = 404,
-				Success = false,
-				Message = "页面未找到!"
-			}),
-			_ => View("Index")
-		};
-	}
-
-	/// <summary>
-	/// 503
-	/// </summary>
-	/// <returns></returns>
-	[Route("ServiceUnavailable")]
-	public async Task<ActionResult> ServiceUnavailable()
-	{
-		var feature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
-		if (feature != null)
-		{
-			string err;
-			var ip = HttpContext.Connection.RemoteIpAddress;
-			switch (feature.Error)
-			{
-				case DbUpdateConcurrencyException ex:
-					err = $"数据库并发更新异常,更新表:{ex.Entries.Select(e => e.Metadata.Name)},请求路径({Request.Method}):{Request.Scheme}://{Request.Host}{HttpUtility.UrlDecode(feature.Path)}{Request.QueryString},客户端用户代理:{Request.Headers[HeaderNames.UserAgent]},客户端IP:{ip}\t{ex.InnerException?.Message},请求参数:\n{await GetRequestBody(Request)}\n堆栈信息:";
-					LogManager.Error(err, ex.Demystify());
-					break;
-
-				case DbUpdateException ex:
-					err = $"数据库更新时异常,更新表:{ex.Entries.Select(e => e.Metadata.Name)},请求路径({Request.Method}):{Request.Scheme}://{Request.Host}{HttpUtility.UrlDecode(feature.Path)}{Request.QueryString} ,客户端用户代理:{Request.Headers[HeaderNames.UserAgent]},客户端IP:{ip}\t{ex.InnerException?.Message},请求参数:\n{await GetRequestBody(Request)}\n堆栈信息:";
-					LogManager.Error(err, ex.Demystify());
-					break;
-
-				case AggregateException ex:
-					LogManager.Debug("↓↓↓" + ex.Message + "↓↓↓");
-					ex.Flatten().Handle(e =>
-					{
-						LogManager.Error($"异常源:{e.Source},异常类型:{e.GetType().Name},请求路径({Request.Method}):{Request.Scheme}://{Request.Host}{HttpUtility.UrlDecode(feature.Path)}{Request.QueryString} ,客户端用户代理:{Request.Headers[HeaderNames.UserAgent]},客户端IP:{ip}\t", e.Demystify());
-						return true;
-					});
-					var body = await GetRequestBody(Request);
-					if (!string.IsNullOrEmpty(body))
-					{
-						LogManager.Debug("↑↑↑请求参数:\n" + body);
-					}
-					break;
-
-				case AccessDenyException:
-					var entry = ip.GetIPLocation();
-					var tips = Template.Create(CommonHelper.SystemSettings.GetOrAdd("AccessDenyTips", @"<h4>遇到了什么问题?</h4>
+    public IRedisClient RedisClient { get; set; }
+
+    /// <summary>
+    /// 404
+    /// </summary>
+    /// <returns></returns>
+    [Route("error"), Route("{*url}", Order = 99999), ResponseCache(Duration = 36000)]
+    public ActionResult Index()
+    {
+        Response.StatusCode = 404;
+        string accept = Request.Headers[HeaderNames.Accept] + "";
+        return true switch
+        {
+            _ when accept.StartsWith("image") => File("/Assets/images/404/4044.jpg", ContentType.Jpeg),
+            _ when Request.HasJsonContentType() || Request.Method == HttpMethods.Post => Json(new
+            {
+                StatusCode = 404,
+                Success = false,
+                Message = "页面未找到!"
+            }),
+            _ => View("Index")
+        };
+    }
+
+    /// <summary>
+    /// 503
+    /// </summary>
+    /// <returns></returns>
+    [Route("ServiceUnavailable")]
+    public async Task<ActionResult> ServiceUnavailable()
+    {
+        var feature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
+        if (feature != null)
+        {
+            string err;
+            var ip = HttpContext.Connection.RemoteIpAddress;
+            switch (feature.Error)
+            {
+                case DbUpdateConcurrencyException ex:
+                    err = $"数据库并发更新异常,更新表:{ex.Entries.Select(e => e.Metadata.Name)},请求路径({Request.Method}):{Request.Scheme}://{Request.Host}{HttpUtility.UrlDecode(feature.Path)}{Request.QueryString},客户端用户代理:{Request.Headers[HeaderNames.UserAgent]},客户端IP:{ip}\t{ex.InnerException?.Message},请求参数:\n{await GetRequestBody(Request)}\n堆栈信息:";
+                    LogManager.Error(err, ex.Demystify());
+                    break;
+
+                case DbUpdateException ex:
+                    err = $"数据库更新时异常,更新表:{ex.Entries.Select(e => e.Metadata.Name)},请求路径({Request.Method}):{Request.Scheme}://{Request.Host}{HttpUtility.UrlDecode(feature.Path)}{Request.QueryString} ,客户端用户代理:{Request.Headers[HeaderNames.UserAgent]},客户端IP:{ip}\t{ex.InnerException?.Message},请求参数:\n{await GetRequestBody(Request)}\n堆栈信息:";
+                    LogManager.Error(err, ex.Demystify());
+                    break;
+
+                case AggregateException ex:
+                    LogManager.Debug("↓↓↓" + ex.Message + "↓↓↓");
+                    ex.Flatten().Handle(e =>
+                    {
+                        LogManager.Error($"异常源:{e.Source},异常类型:{e.GetType().Name},请求路径({Request.Method}):{Request.Scheme}://{Request.Host}{HttpUtility.UrlDecode(feature.Path)}{Request.QueryString} ,客户端用户代理:{Request.Headers[HeaderNames.UserAgent]},客户端IP:{ip}\t", e.Demystify());
+                        return true;
+                    });
+                    var body = await GetRequestBody(Request);
+                    if (!string.IsNullOrEmpty(body))
+                    {
+                        LogManager.Debug("↑↑↑请求参数:\n" + body);
+                    }
+                    break;
+
+                case AccessDenyException:
+                    var entry = ip.GetIPLocation();
+                    var tips = Template.Create(CommonHelper.SystemSettings.GetOrAdd("AccessDenyTips", @"<h4>遇到了什么问题?</h4>
                 <h4>基于主观因素考虑,您所在的地区暂时不允许访问本站,如有疑问,请联系站长!或者请联系站长开通本站的访问权限!</h4>")).Set("clientip", ip.ToString()).Set("location", entry.Address).Set("network", entry.Network).Render();
-					Response.StatusCode = 403;
-					return View("AccessDeny", tips);
-
-				case TempDenyException:
-					Response.StatusCode = 429;
-					return Request.HasJsonContentType() || Request.Method == HttpMethods.Post ? Json(new
-					{
-						StatusCode = 429,
-						Success = false,
-						Message = $"检测到您的IP({ip})访问过于频繁,已被本站暂时禁止访问,请稍后再试!"
-					}) : View("TempDeny");
-
-				default:
-					LogManager.Error($"异常源:{feature.Error.Source},异常类型:{feature.Error.GetType().Name},请求路径({Request.Method}):{Request.Scheme}://{Request.Host}{HttpUtility.UrlDecode(feature.Path)}{Request.QueryString} ,客户端用户代理:{Request.Headers[HeaderNames.UserAgent]},客户端IP:{ip},请求参数:\n{await GetRequestBody(Request)}\n堆栈信息:", feature.Error.Demystify());
-					break;
-			}
-		}
-
-		Response.StatusCode = 503;
-		return Request.HasJsonContentType() || Request.Method == HttpMethods.Post ? Json(new
-		{
-			StatusCode = 503,
-			Success = false,
-			Message = "服务器发生错误!"
-		}) : View();
-	}
-
-	private static async Task<string> GetRequestBody(HttpRequest req)
-	{
-		if (req.ContentLength > 5120)
-		{
-			return "请求体超长";
-		}
-
-		req.Body.Seek(0, SeekOrigin.Begin);
-		using var sr = new StreamReader(req.Body, Encoding.UTF8, false);
-		var body = await sr.ReadToEndAsync();
-		body = HttpUtility.UrlDecode(body);
-		req.Body.Position = 0;
-		return body;
-	}
-
-	/// <summary>
-	/// 网站升级中
-	/// </summary>
-	/// <returns></returns>
-	[Route("ComingSoon"), ResponseCache(Duration = 360000)]
-	public ActionResult ComingSoon()
-	{
-		return View();
-	}
-
-	/// <summary>
-	/// 检查访问密码
-	/// </summary>
-	/// <param name="email"></param>
-	/// <param name="token"></param>
-	/// <returns></returns>
-	[HttpPost, ValidateAntiForgeryToken, AllowAccessFirewall]
-	public ActionResult CheckViewToken(string email, string token)
-	{
-		if (string.IsNullOrEmpty(token))
-		{
-			return ResultData(null, false, "请输入访问密码!");
-		}
-
-		var s = RedisClient.Get("token:" + email);
-		if (!token.Equals(s))
-		{
-			return ResultData(null, false, "访问密码不正确!");
-		}
-
-		Response.Cookies.Append("Email", email, new CookieOptions
-		{
-			Expires = DateTime.Now.AddYears(1),
-			SameSite = SameSiteMode.Lax
-		});
-		Response.Cookies.Append("FullAccessToken", email.MDString(AppConfig.BaiduAK), new CookieOptions
-		{
-			Expires = DateTime.Now.AddYears(1),
-			SameSite = SameSiteMode.Lax
-		});
-		return ResultData(null);
-	}
-
-	/// <summary>
-	/// 检查授权邮箱
-	/// </summary>
-	/// <param name="userInfoService"></param>
-	/// <param name="email"></param>
-	/// <returns></returns>
-	[HttpPost, ValidateAntiForgeryToken, AllowAccessFirewall, ResponseCache(Duration = 100, VaryByQueryKeys = new[] { "email" })]
-	public ActionResult GetViewToken([FromServices] IUserInfoService userInfoService, string email)
-	{
-		var validator = new IsEmailAttribute();
-		if (!validator.IsValid(email))
-		{
-			return ResultData(null, false, validator.ErrorMessage);
-		}
-
-		if (RedisClient.Exists("get:" + email))
-		{
-			RedisClient.Expire("get:" + email, 120);
-			return ResultData(null, false, "发送频率限制,请在2分钟后重新尝试发送邮件!请检查你的邮件,若未收到,请检查你的邮箱地址或邮件垃圾箱!");
-		}
-
-		if (!userInfoService.Any(b => b.Email == email))
-		{
-			return ResultData(null, false, "您目前没有权限访问这个链接,请联系站长开通访问权限!");
-		}
-
-		var token = SnowFlake.GetInstance().GetUniqueShortId(6);
-		RedisClient.Set("token:" + email, token, 86400);
-		BackgroundJob.Enqueue<IMailSender>(sender => sender.Send(Request.Host + "博客访问验证码", $"{Request.Host}本次验证码是:<span style='color:red'>{token}</span>,有效期为24h,请按时使用!", email, HttpContext.Connection.RemoteIpAddress.ToString()));
-		RedisClient.Set("get:" + email, token, 120);
-		return ResultData(null);
-	}
-
-	/// <summary>
-	/// 响应数据
-	/// </summary>
-	/// <param name="data">数据</param>
-	/// <param name="success">响应状态</param>
-	/// <param name="message">响应消息</param>
-	/// <returns></returns>
-	public ActionResult ResultData(object data, bool success = true, string message = "")
-	{
-		return Ok(new
-		{
-			Success = success,
-			Message = message,
-			Data = data
-		});
-	}
+                    Response.StatusCode = 403;
+                    return View("AccessDeny", tips);
+
+                case TempDenyException:
+                    Response.StatusCode = 429;
+                    return Request.HasJsonContentType() || Request.Method == HttpMethods.Post ? Json(new
+                    {
+                        StatusCode = 429,
+                        Success = false,
+                        Message = $"检测到您的IP({ip})访问过于频繁,已被本站暂时禁止访问,请稍后再试!"
+                    }) : View("TempDeny");
+
+                default:
+                    LogManager.Error($"异常源:{feature.Error.Source},异常类型:{feature.Error.GetType().Name},请求路径({Request.Method}):{Request.Scheme}://{Request.Host}{HttpUtility.UrlDecode(feature.Path)}{Request.QueryString} ,客户端用户代理:{Request.Headers[HeaderNames.UserAgent]},客户端IP:{ip},请求参数:\n{await GetRequestBody(Request)}\n堆栈信息:", feature.Error.Demystify());
+                    break;
+            }
+        }
+
+        Response.StatusCode = 503;
+        return Request.HasJsonContentType() || Request.Method == HttpMethods.Post ? Json(new
+        {
+            StatusCode = 503,
+            Success = false,
+            Message = "服务器发生错误!"
+        }) : View();
+    }
+
+    private static async Task<string> GetRequestBody(HttpRequest req)
+    {
+        if (req.ContentLength > 5120)
+        {
+            return "请求体超长";
+        }
+
+        req.Body.Seek(0, SeekOrigin.Begin);
+        using var sr = new StreamReader(req.Body, Encoding.UTF8, false);
+        var body = await sr.ReadToEndAsync();
+        body = HttpUtility.UrlDecode(body);
+        req.Body.Position = 0;
+        return body;
+    }
+
+    /// <summary>
+    /// 网站升级中
+    /// </summary>
+    /// <returns></returns>
+    [Route("ComingSoon"), ResponseCache(Duration = 360000)]
+    public ActionResult ComingSoon()
+    {
+        return View();
+    }
+
+    /// <summary>
+    /// 检查访问密码
+    /// </summary>
+    /// <param name="email"></param>
+    /// <param name="token"></param>
+    /// <returns></returns>
+    [HttpPost, ValidateAntiForgeryToken, AllowAccessFirewall]
+    public ActionResult CheckViewToken(string email, string token)
+    {
+        if (string.IsNullOrEmpty(token))
+        {
+            return ResultData(null, false, "请输入访问密码!");
+        }
+
+        var s = RedisClient.Get("token:" + email);
+        if (!token.Equals(s))
+        {
+            return ResultData(null, false, "访问密码不正确!");
+        }
+
+        Response.Cookies.Append("Email", email, new CookieOptions
+        {
+            Expires = DateTime.Now.AddYears(1),
+            SameSite = SameSiteMode.Lax
+        });
+        Response.Cookies.Append("FullAccessToken", email.MDString(AppConfig.BaiduAK), new CookieOptions
+        {
+            Expires = DateTime.Now.AddYears(1),
+            SameSite = SameSiteMode.Lax
+        });
+        return ResultData(null);
+    }
+
+    /// <summary>
+    /// 检查授权邮箱
+    /// </summary>
+    /// <param name="userInfoService"></param>
+    /// <param name="email"></param>
+    /// <returns></returns>
+    [HttpPost, ValidateAntiForgeryToken, AllowAccessFirewall, ResponseCache(Duration = 100, VaryByQueryKeys = new[] { "email" })]
+    public ActionResult GetViewToken([FromServices] IUserInfoService userInfoService, string email)
+    {
+        var validator = new IsEmailAttribute();
+        if (!validator.IsValid(email))
+        {
+            return ResultData(null, false, validator.ErrorMessage);
+        }
+
+        if (RedisClient.Exists("get:" + email))
+        {
+            RedisClient.Expire("get:" + email, 120);
+            return ResultData(null, false, "发送频率限制,请在2分钟后重新尝试发送邮件!请检查你的邮件,若未收到,请检查你的邮箱地址或邮件垃圾箱!");
+        }
+
+        if (!userInfoService.Any(b => b.Email == email))
+        {
+            return ResultData(null, false, "您目前没有权限访问这个链接,请联系站长开通访问权限!");
+        }
+
+        var token = SnowFlake.GetInstance().GetUniqueShortId(6);
+        RedisClient.Set("token:" + email, token, 86400);
+        BackgroundJob.Enqueue<IMailSender>(sender => sender.Send(Request.Host + "博客访问验证码", $"{Request.Host}本次验证码是:<span style='color:red'>{token}</span>,有效期为24h,请按时使用!", email, HttpContext.Connection.RemoteIpAddress.ToString()));
+        RedisClient.Set("get:" + email, token, 120);
+        return ResultData(null);
+    }
+
+    /// <summary>
+    /// 响应数据
+    /// </summary>
+    /// <param name="data">数据</param>
+    /// <param name="success">响应状态</param>
+    /// <param name="message">响应消息</param>
+    /// <returns></returns>
+    private ActionResult ResultData(object data, bool success = true, string message = "")
+    {
+        return Ok(new
+        {
+            Success = success,
+            Message = message,
+            Data = data
+        });
+    }
 }

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

@@ -19,232 +19,232 @@ namespace Masuit.MyBlogs.Core.Controllers;
 [ApiExplorerSettings(IgnoreApi = true), ServiceFilter(typeof(FirewallAttribute))]
 public sealed class PassportController : Controller
 {
-	/// <summary>
-	/// 用户
-	/// </summary>
-	public IUserInfoService UserInfoService { get; set; }
-
-	public IFirewallRepoter FirewallRepoter { get; set; }
-	public IMapper Mapper { get; set; }
-
-	/// <summary>
-	/// 客户端的真实IP
-	/// </summary>
-	public string ClientIP => HttpContext.Connection.RemoteIpAddress.ToString();
-
-	/// <summary>
-	///
-	/// </summary>
-	/// <param name="data"></param>
-	/// <param name="isTrue"></param>
-	/// <param name="message"></param>
-	/// <returns></returns>
-	public ActionResult ResultData(object data, bool isTrue = true, string message = "")
-	{
-		return Json(new
-		{
-			Success = isTrue,
-			Message = message,
-			Data = data
-		});
-	}
-
-	/// <summary>
-	/// 登录页
-	/// </summary>
-	/// <returns></returns>
-	public ActionResult Login()
-	{
-		var keys = RsaCrypt.GenerateRsaKeys(RsaKeyType.PKCS1);
-		Response.Cookies.Append(nameof(keys.PublicKey), keys.PublicKey, new CookieOptions()
-		{
-			SameSite = SameSiteMode.Lax
-		});
-		HttpContext.Session.Set(nameof(keys.PrivateKey), keys.PrivateKey);
-		string from = Request.Query["from"];
-		if (!string.IsNullOrEmpty(from))
-		{
-			from = HttpUtility.UrlDecode(from);
-			Response.Cookies.Append("refer", from, new CookieOptions()
-			{
-				SameSite = SameSiteMode.Lax
-			});
-		}
-
-		if (HttpContext.Session.Get<UserInfoDto>(SessionKey.UserInfo) != null)
-		{
-			if (string.IsNullOrEmpty(from))
-			{
-				return RedirectToAction("Index", "Home");
-			}
-
-			return LocalRedirect(from);
-		}
-
-		if (Request.Cookies.Count > 2)
-		{
-			string name = Request.Cookies["username"];
-			string pwd = Request.Cookies["password"]?.DesDecrypt(AppConfig.BaiduAK);
-			var userInfo = UserInfoService.Login(name, pwd);
-			if (userInfo != null)
-			{
-				Response.Cookies.Append("username", name, new CookieOptions()
-				{
-					Expires = DateTime.Now.AddYears(1),
-					SameSite = SameSiteMode.Lax
-				});
-				Response.Cookies.Append("password", Request.Cookies["password"], new CookieOptions()
-				{
-					Expires = DateTime.Now.AddYears(1),
-					SameSite = SameSiteMode.Lax
-				});
-				HttpContext.Session.Set(SessionKey.UserInfo, userInfo);
-				BackgroundJob.Enqueue<IHangfireBackJob>(job => job.LoginRecord(userInfo, ClientIP, LoginType.Default));
-				if (string.IsNullOrEmpty(from))
-				{
-					return RedirectToAction("Index", "Home");
-				}
-
-				return LocalRedirect(from);
-			}
-		}
-
-		return View();
-	}
-
-	/// <summary>
-	/// 登陆检查
-	/// </summary>
-	/// <param name="username"></param>
-	/// <param name="password"></param>
-	/// <param name="valid"></param>
-	/// <param name="remem"></param>
-	/// <returns></returns>
-	[HttpPost, ValidateAntiForgeryToken]
-	public ActionResult Login([FromServices] ICacheManager<int> cacheManager, string username, string password, string valid, string remem)
-	{
-		string validSession = HttpContext.Session.Get<string>("valid") ?? string.Empty; //将验证码从Session中取出来,用于登录验证比较
-		if (string.IsNullOrEmpty(validSession) || !valid.Trim().Equals(validSession, StringComparison.InvariantCultureIgnoreCase))
-		{
-			return ResultData(null, false, "验证码错误");
-		}
-
-		HttpContext.Session.Remove("valid"); //验证成功就销毁验证码Session,非常重要
-		if (string.IsNullOrEmpty(username.Trim()) || string.IsNullOrEmpty(password.Trim()))
-		{
-			return ResultData(null, false, "用户名或密码不能为空");
-		}
-
-		try
-		{
-			var privateKey = HttpContext.Session.Get<string>(nameof(RsaKey.PrivateKey));
-			password = password.RSADecrypt(privateKey);
-		}
-		catch (Exception)
-		{
-			LogManager.Info("登录失败,私钥:" + HttpContext.Session.Get<string>(nameof(RsaKey.PrivateKey)));
-			throw;
-		}
-		var userInfo = UserInfoService.Login(username, password);
-		if (userInfo == null)
-		{
-			var times = cacheManager.AddOrUpdate("LoginError:" + ClientIP, 1, i => i + 1, 5);
-			if (times > 30)
-			{
-				FirewallRepoter.ReportAsync(IPAddress.Parse(ClientIP)).ContinueWith(_ => LogManager.Info($"多次登录用户名或密码错误,疑似爆破行为,已上报IP{ClientIP}至:" + FirewallRepoter.ReporterName));
-			}
-
-			return ResultData(null, false, "用户名或密码错误");
-		}
-
-		HttpContext.Session.Set(SessionKey.UserInfo, userInfo);
-		if (remem.Trim().Contains(new[] { "on", "true" })) //是否记住登录
-		{
-			Response.Cookies.Append("username", HttpUtility.UrlEncode(username.Trim()), new CookieOptions()
-			{
-				Expires = DateTime.Now.AddYears(1),
-				SameSite = SameSiteMode.Lax
-			});
-			Response.Cookies.Append("password", password.Trim().DesEncrypt(AppConfig.BaiduAK), new CookieOptions()
-			{
-				Expires = DateTime.Now.AddYears(1),
-				SameSite = SameSiteMode.Lax
-			});
-		}
-
-		BackgroundJob.Enqueue<IHangfireBackJob>(job => job.LoginRecord(userInfo, ClientIP, LoginType.Default));
-		string refer = Request.Cookies["refer"];
-		Response.Cookies.Delete(nameof(RsaKey.PublicKey), new CookieOptions()
-		{
-			SameSite = SameSiteMode.Lax
-		});
-		Response.Cookies.Delete("refer", new CookieOptions()
-		{
-			SameSite = SameSiteMode.Lax
-		});
-		HttpContext.Session.Remove(nameof(RsaKey.PrivateKey));
-		return ResultData(null, true, string.IsNullOrEmpty(refer) ? "/" : refer);
-	}
-
-	/// <summary>
-	/// 生成验证码
-	/// </summary>
-	/// <returns></returns>
-	public ActionResult ValidateCode()
-	{
-		string code = Tools.Strings.ValidateCode.CreateValidateCode(6);
-		HttpContext.Session.Set("valid", code); //将验证码生成到Session中
-		var buffer = HttpContext.CreateValidateGraphic(code);
-		return this.ResumeFile(buffer, ContentType.Jpeg, "验证码.jpg");
-	}
-
-	/// <summary>
-	/// 检查验证码
-	/// </summary>
-	/// <param name="code"></param>
-	/// <returns></returns>
-	[HttpPost]
-	public ActionResult CheckValidateCode(string code)
-	{
-		string validSession = HttpContext.Session.Get<string>("valid");
-		if (string.IsNullOrEmpty(validSession) || !code.Trim().Equals(validSession, StringComparison.InvariantCultureIgnoreCase))
-		{
-			return ResultData(null, false, "验证码错误");
-		}
-
-		return ResultData(null, false, "验证码正确");
-	}
-
-	/// <summary>
-	/// 获取用户信息
-	/// </summary>
-	/// <returns></returns>
-	public ActionResult GetUserInfo()
-	{
-		var user = HttpContext.Session.Get<UserInfoDto>(SessionKey.UserInfo);
+    /// <summary>
+    /// 用户
+    /// </summary>
+    public IUserInfoService UserInfoService { get; set; }
+
+    public IFirewallRepoter FirewallRepoter { get; set; }
+    public IMapper Mapper { get; set; }
+
+    /// <summary>
+    /// 客户端的真实IP
+    /// </summary>
+    public string ClientIP => HttpContext.Connection.RemoteIpAddress.ToString();
+
+    /// <summary>
+    ///
+    /// </summary>
+    /// <param name="data"></param>
+    /// <param name="isTrue"></param>
+    /// <param name="message"></param>
+    /// <returns></returns>
+    private ActionResult ResultData(object data, bool isTrue = true, string message = "")
+    {
+        return Json(new
+        {
+            Success = isTrue,
+            Message = message,
+            Data = data
+        });
+    }
+
+    /// <summary>
+    /// 登录页
+    /// </summary>
+    /// <returns></returns>
+    public ActionResult Login()
+    {
+        var keys = RsaCrypt.GenerateRsaKeys(RsaKeyType.PKCS1);
+        Response.Cookies.Append(nameof(keys.PublicKey), keys.PublicKey, new CookieOptions()
+        {
+            SameSite = SameSiteMode.Lax
+        });
+        HttpContext.Session.Set(nameof(keys.PrivateKey), keys.PrivateKey);
+        string from = Request.Query["from"];
+        if (!string.IsNullOrEmpty(from))
+        {
+            from = HttpUtility.UrlDecode(from);
+            Response.Cookies.Append("refer", from, new CookieOptions()
+            {
+                SameSite = SameSiteMode.Lax
+            });
+        }
+
+        if (HttpContext.Session.Get<UserInfoDto>(SessionKey.UserInfo) != null)
+        {
+            if (string.IsNullOrEmpty(from))
+            {
+                return RedirectToAction("Index", "Home");
+            }
+
+            return LocalRedirect(from);
+        }
+
+        if (Request.Cookies.Count > 2)
+        {
+            string name = Request.Cookies["username"];
+            string pwd = Request.Cookies["password"]?.DesDecrypt(AppConfig.BaiduAK);
+            var userInfo = UserInfoService.Login(name, pwd);
+            if (userInfo != null)
+            {
+                Response.Cookies.Append("username", name, new CookieOptions()
+                {
+                    Expires = DateTime.Now.AddYears(1),
+                    SameSite = SameSiteMode.Lax
+                });
+                Response.Cookies.Append("password", Request.Cookies["password"], new CookieOptions()
+                {
+                    Expires = DateTime.Now.AddYears(1),
+                    SameSite = SameSiteMode.Lax
+                });
+                HttpContext.Session.Set(SessionKey.UserInfo, userInfo);
+                BackgroundJob.Enqueue<IHangfireBackJob>(job => job.LoginRecord(userInfo, ClientIP, LoginType.Default));
+                if (string.IsNullOrEmpty(from))
+                {
+                    return RedirectToAction("Index", "Home");
+                }
+
+                return LocalRedirect(from);
+            }
+        }
+
+        return View();
+    }
+
+    /// <summary>
+    /// 登陆检查
+    /// </summary>
+    /// <param name="username"></param>
+    /// <param name="password"></param>
+    /// <param name="valid"></param>
+    /// <param name="remem"></param>
+    /// <returns></returns>
+    [HttpPost, ValidateAntiForgeryToken]
+    public ActionResult Login([FromServices] ICacheManager<int> cacheManager, string username, string password, string valid, string remem)
+    {
+        string validSession = HttpContext.Session.Get<string>("valid") ?? string.Empty; //将验证码从Session中取出来,用于登录验证比较
+        if (string.IsNullOrEmpty(validSession) || !valid.Trim().Equals(validSession, StringComparison.InvariantCultureIgnoreCase))
+        {
+            return ResultData(null, false, "验证码错误");
+        }
+
+        HttpContext.Session.Remove("valid"); //验证成功就销毁验证码Session,非常重要
+        if (string.IsNullOrEmpty(username.Trim()) || string.IsNullOrEmpty(password.Trim()))
+        {
+            return ResultData(null, false, "用户名或密码不能为空");
+        }
+
+        try
+        {
+            var privateKey = HttpContext.Session.Get<string>(nameof(RsaKey.PrivateKey));
+            password = password.RSADecrypt(privateKey);
+        }
+        catch (Exception)
+        {
+            LogManager.Info("登录失败,私钥:" + HttpContext.Session.Get<string>(nameof(RsaKey.PrivateKey)));
+            throw;
+        }
+        var userInfo = UserInfoService.Login(username, password);
+        if (userInfo == null)
+        {
+            var times = cacheManager.AddOrUpdate("LoginError:" + ClientIP, 1, i => i + 1, 5);
+            if (times > 30)
+            {
+                FirewallRepoter.ReportAsync(IPAddress.Parse(ClientIP)).ContinueWith(_ => LogManager.Info($"多次登录用户名或密码错误,疑似爆破行为,已上报IP{ClientIP}至:" + FirewallRepoter.ReporterName));
+            }
+
+            return ResultData(null, false, "用户名或密码错误");
+        }
+
+        HttpContext.Session.Set(SessionKey.UserInfo, userInfo);
+        if (remem.Trim().Contains(new[] { "on", "true" })) //是否记住登录
+        {
+            Response.Cookies.Append("username", HttpUtility.UrlEncode(username.Trim()), new CookieOptions()
+            {
+                Expires = DateTime.Now.AddYears(1),
+                SameSite = SameSiteMode.Lax
+            });
+            Response.Cookies.Append("password", password.Trim().DesEncrypt(AppConfig.BaiduAK), new CookieOptions()
+            {
+                Expires = DateTime.Now.AddYears(1),
+                SameSite = SameSiteMode.Lax
+            });
+        }
+
+        BackgroundJob.Enqueue<IHangfireBackJob>(job => job.LoginRecord(userInfo, ClientIP, LoginType.Default));
+        string refer = Request.Cookies["refer"];
+        Response.Cookies.Delete(nameof(RsaKey.PublicKey), new CookieOptions()
+        {
+            SameSite = SameSiteMode.Lax
+        });
+        Response.Cookies.Delete("refer", new CookieOptions()
+        {
+            SameSite = SameSiteMode.Lax
+        });
+        HttpContext.Session.Remove(nameof(RsaKey.PrivateKey));
+        return ResultData(null, true, string.IsNullOrEmpty(refer) ? "/" : refer);
+    }
+
+    /// <summary>
+    /// 生成验证码
+    /// </summary>
+    /// <returns></returns>
+    public ActionResult ValidateCode()
+    {
+        string code = Tools.Strings.ValidateCode.CreateValidateCode(6);
+        HttpContext.Session.Set("valid", code); //将验证码生成到Session中
+        var buffer = HttpContext.CreateValidateGraphic(code);
+        return this.ResumeFile(buffer, ContentType.Jpeg, "验证码.jpg");
+    }
+
+    /// <summary>
+    /// 检查验证码
+    /// </summary>
+    /// <param name="code"></param>
+    /// <returns></returns>
+    [HttpPost]
+    public ActionResult CheckValidateCode(string code)
+    {
+        string validSession = HttpContext.Session.Get<string>("valid");
+        if (string.IsNullOrEmpty(validSession) || !code.Trim().Equals(validSession, StringComparison.InvariantCultureIgnoreCase))
+        {
+            return ResultData(null, false, "验证码错误");
+        }
+
+        return ResultData(null, false, "验证码正确");
+    }
+
+    /// <summary>
+    /// 获取用户信息
+    /// </summary>
+    /// <returns></returns>
+    public ActionResult GetUserInfo()
+    {
+        var user = HttpContext.Session.Get<UserInfoDto>(SessionKey.UserInfo);
 #if DEBUG
-		user = Mapper.Map<UserInfoDto>(UserInfoService.GetByUsername("masuit"));
+        user = Mapper.Map<UserInfoDto>(UserInfoService.GetByUsername("masuit"));
 #endif
 
-		return ResultData(user);
-	}
-
-	/// <summary>
-	/// 注销登录
-	/// </summary>
-	/// <returns></returns>
-	public ActionResult Logout()
-	{
-		HttpContext.Session.Remove(SessionKey.UserInfo);
-		Response.Cookies.Delete("username", new CookieOptions()
-		{
-			SameSite = SameSiteMode.Lax
-		});
-		Response.Cookies.Delete("password", new CookieOptions()
-		{
-			SameSite = SameSiteMode.Lax
-		});
-		HttpContext.Session.Clear();
-		return Request.Method.Equals(HttpMethods.Get) ? RedirectToAction("Index", "Home") : ResultData(null, message: "注销成功!");
-	}
+        return ResultData(user);
+    }
+
+    /// <summary>
+    /// 注销登录
+    /// </summary>
+    /// <returns></returns>
+    public ActionResult Logout()
+    {
+        HttpContext.Session.Remove(SessionKey.UserInfo);
+        Response.Cookies.Delete("username", new CookieOptions()
+        {
+            SameSite = SameSiteMode.Lax
+        });
+        Response.Cookies.Delete("password", new CookieOptions()
+        {
+            SameSite = SameSiteMode.Lax
+        });
+        HttpContext.Session.Clear();
+        return Request.Method.Equals(HttpMethods.Get) ? RedirectToAction("Index", "Home") : ResultData(null, message: "注销成功!");
+    }
 }

+ 176 - 176
src/Masuit.MyBlogs.Core/Controllers/ToolsController.cs

@@ -22,184 +22,184 @@ namespace Masuit.MyBlogs.Core.Controllers;
 [Route("tools")]
 public sealed class ToolsController : BaseController
 {
-	private readonly HttpClient _httpClient;
-
-	public ToolsController(IHttpClientFactory httpClientFactory)
-	{
-		_httpClient = httpClientFactory.CreateClient();
-	}
-
-	/// <summary>
-	/// 获取ip地址详细信息
-	/// </summary>
-	/// <param name="ip"></param>
-	/// <returns></returns>
-	[Route("ip"), Route("ip/{ip?}", Order = 1), ResponseCache(Duration = 600, VaryByQueryKeys = new[] { "ip" })]
-	public async Task<ActionResult> GetIpInfo([IsIPAddress] string ip)
-	{
-		if (!IPAddress.TryParse(ip, out var ipAddress))
-		{
-			ipAddress = ClientIP;
-		}
-
-		if (ipAddress.IsPrivateIP())
-		{
-			return Ok("内网IP");
-		}
-
-		ViewBag.IP = ip;
-		var loc = ipAddress.GetIPLocation();
-		var asn = ipAddress.GetIPAsn();
-		var nslookup = new LookupClient();
-		using var cts = new CancellationTokenSource(2000);
-		var domain = await nslookup.QueryReverseAsync(ipAddress, cts.Token).ContinueWith(t => t.IsCompletedSuccessfully ? t.Result.Answers.Select(r => r.ToString()).Join("; ") : "无");
-		var address = new IpInfo
-		{
-			Location = loc.Coodinate,
-			Address = loc.Address,
-			Address2 = loc.Address2,
-			Network = new NetworkInfo
-			{
-				Asn = asn.AutonomousSystemNumber,
-				Router = asn.Network + "",
-				Organization = loc.ISP
-			},
-			TimeZone = loc.Coodinate.TimeZone + $"  UTC{TZConvert.GetTimeZoneInfo(loc.Coodinate.TimeZone ?? "Asia/Shanghai").BaseUtcOffset.Hours:+#;-#;0}",
-			IsProxy = loc.Network.Contains(new[] { "cloud", "Compute", "Serv", "Tech", "Solution", "Host", "云", "Datacenter", "Data Center", "Business", "ASN" }) || domain.Length > 1 || await IsProxy(ipAddress, cts.Token),
-			Domain = domain
-		};
-		if (Request.Method.Equals(HttpMethods.Get) || (Request.Headers[HeaderNames.Accept] + "").StartsWith(ContentType.Json))
-		{
-			return View(address);
-		}
-
-		return Json(address);
-	}
-
-	/// <summary>
-	/// 是否是代理ip
-	/// </summary>
-	/// <param name="ip"></param>
-	/// <param name="cancellationToken"></param>
-	/// <returns></returns>
-	public async Task<bool> IsProxy(IPAddress ip, CancellationToken cancellationToken = default)
-	{
-		_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36 Edg/92.0.902.62");
-		return await _httpClient.GetStringAsync("https://ipinfo.io/" + ip, cancellationToken).ContinueWith(t =>
-		{
-			if (t.IsCompletedSuccessfully)
-			{
-				var ctx = BrowsingContext.New(Configuration.Default);
-				var doc = ctx.OpenAsync(res => res.Content(t.Result)).Result;
-				var isAnycast = doc.DocumentElement.QuerySelectorAll(".title").Where(e => e.TextContent.Contains("Anycast")).Select(e => e.Parent).Any(n => n.TextContent.Contains("True"));
-				var isproxy = doc.DocumentElement.QuerySelectorAll("#block-privacy img").Any(e => e.OuterHtml.Contains("right"));
-				return isAnycast || isproxy;
-			}
-			return false;
-		});
-	}
-
-	/// <summary>
-	/// 根据经纬度获取详细地理信息
-	/// </summary>
-	/// <param name="lat"></param>
-	/// <param name="lng"></param>
-	/// <returns></returns>
-	[HttpGet("pos"), ResponseCache(Duration = 600, VaryByQueryKeys = new[] { "lat", "lng" })]
-	public async Task<ActionResult> Position(string lat, string lng)
-	{
-		if (string.IsNullOrEmpty(lat) || string.IsNullOrEmpty(lng))
-		{
-			var ip = ClientIP;
+    private readonly HttpClient _httpClient;
+
+    public ToolsController(IHttpClientFactory httpClientFactory)
+    {
+        _httpClient = httpClientFactory.CreateClient();
+    }
+
+    /// <summary>
+    /// 获取ip地址详细信息
+    /// </summary>
+    /// <param name="ip"></param>
+    /// <returns></returns>
+    [Route("ip"), Route("ip/{ip?}", Order = 1), ResponseCache(Duration = 600, VaryByQueryKeys = new[] { "ip" })]
+    public async Task<ActionResult> GetIpInfo([IsIPAddress] string ip)
+    {
+        if (!IPAddress.TryParse(ip, out var ipAddress))
+        {
+            ipAddress = ClientIP;
+        }
+
+        if (ipAddress.IsPrivateIP())
+        {
+            return Ok("内网IP");
+        }
+
+        ViewBag.IP = ip;
+        var loc = ipAddress.GetIPLocation();
+        var asn = ipAddress.GetIPAsn();
+        var nslookup = new LookupClient();
+        using var cts = new CancellationTokenSource(2000);
+        var domain = await nslookup.QueryReverseAsync(ipAddress, cts.Token).ContinueWith(t => t.IsCompletedSuccessfully ? t.Result.Answers.Select(r => r.ToString()).Join("; ") : "无");
+        var address = new IpInfo
+        {
+            Location = loc.Coodinate,
+            Address = loc.Address,
+            Address2 = loc.Address2,
+            Network = new NetworkInfo
+            {
+                Asn = asn.AutonomousSystemNumber,
+                Router = asn.Network + "",
+                Organization = loc.ISP
+            },
+            TimeZone = loc.Coodinate.TimeZone + $"  UTC{TZConvert.GetTimeZoneInfo(loc.Coodinate.TimeZone ?? "Asia/Shanghai").BaseUtcOffset.Hours:+#;-#;0}",
+            IsProxy = loc.Network.Contains(new[] { "cloud", "Compute", "Serv", "Tech", "Solution", "Host", "云", "Datacenter", "Data Center", "Business", "ASN" }) || domain.Length > 1 || await IsProxy(ipAddress, cts.Token),
+            Domain = domain
+        };
+        if (Request.Method.Equals(HttpMethods.Get) || (Request.Headers[HeaderNames.Accept] + "").StartsWith(ContentType.Json))
+        {
+            return View(address);
+        }
+
+        return Json(address);
+    }
+
+    /// <summary>
+    /// 是否是代理ip
+    /// </summary>
+    /// <param name="ip"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    private async Task<bool> IsProxy(IPAddress ip, CancellationToken cancellationToken = default)
+    {
+        _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36 Edg/92.0.902.62");
+        return await _httpClient.GetStringAsync("https://ipinfo.io/" + ip, cancellationToken).ContinueWith(t =>
+        {
+            if (t.IsCompletedSuccessfully)
+            {
+                var ctx = BrowsingContext.New(Configuration.Default);
+                var doc = ctx.OpenAsync(res => res.Content(t.Result)).Result;
+                var isAnycast = doc.DocumentElement.QuerySelectorAll(".title").Where(e => e.TextContent.Contains("Anycast")).Select(e => e.Parent).Any(n => n.TextContent.Contains("True"));
+                var isproxy = doc.DocumentElement.QuerySelectorAll("#block-privacy img").Any(e => e.OuterHtml.Contains("right"));
+                return isAnycast || isproxy;
+            }
+            return false;
+        });
+    }
+
+    /// <summary>
+    /// 根据经纬度获取详细地理信息
+    /// </summary>
+    /// <param name="lat"></param>
+    /// <param name="lng"></param>
+    /// <returns></returns>
+    [HttpGet("pos"), ResponseCache(Duration = 600, VaryByQueryKeys = new[] { "lat", "lng" })]
+    public async Task<ActionResult> Position(string lat, string lng)
+    {
+        if (string.IsNullOrEmpty(lat) || string.IsNullOrEmpty(lng))
+        {
+            var ip = ClientIP;
 #if DEBUG
-			var r = new Random();
-			ip = IPAddress.Parse($"{r.Next(210)}.{r.Next(255)}.{r.Next(255)}.{r.Next(255)}");
+            var r = new Random();
+            ip = IPAddress.Parse($"{r.Next(210)}.{r.Next(255)}.{r.Next(255)}.{r.Next(255)}");
 #endif
-			var location = Policy<CityResponse>.Handle<AddressNotFoundException>().Fallback(() => new CityResponse()).Execute(() => CommonHelper.MaxmindReader.City(ip));
-			var address = new PhysicsAddress()
-			{
-				Status = 0,
-				AddressResult = new AddressResult()
-				{
-					FormattedAddress = ip.GetIPLocation().Address,
-					Location = new Location()
-					{
-						Lng = (decimal)location.Location.Longitude.GetValueOrDefault(),
-						Lat = (decimal)location.Location.Latitude.GetValueOrDefault()
-					}
-				}
-			};
-			return View(address);
-		}
-
-		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)
-			{
-				return JsonConvert.DeserializeObject<PhysicsAddress>(t.Result);
-			}
-
-			return new PhysicsAddress();
-		});
-
-		return View(s);
-	}
-
-	/// <summary>
-	/// 详细地理信息转经纬度
-	/// </summary>
-	/// <param name="addr"></param>
-	/// <returns></returns>
-	[Route("addr"), ResponseCache(Duration = 600, VaryByQueryKeys = new[] { "addr" })]
-	public async Task<ActionResult> Address(string addr)
-	{
-		if (string.IsNullOrEmpty(addr))
-		{
-			var ip = ClientIP;
+            var location = Policy<CityResponse>.Handle<AddressNotFoundException>().Fallback(() => new CityResponse()).Execute(() => CommonHelper.MaxmindReader.City(ip));
+            var address = new PhysicsAddress()
+            {
+                Status = 0,
+                AddressResult = new AddressResult()
+                {
+                    FormattedAddress = ip.GetIPLocation().Address,
+                    Location = new Location()
+                    {
+                        Lng = (decimal)location.Location.Longitude.GetValueOrDefault(),
+                        Lat = (decimal)location.Location.Latitude.GetValueOrDefault()
+                    }
+                }
+            };
+            return View(address);
+        }
+
+        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)
+            {
+                return JsonConvert.DeserializeObject<PhysicsAddress>(t.Result);
+            }
+
+            return new PhysicsAddress();
+        });
+
+        return View(s);
+    }
+
+    /// <summary>
+    /// 详细地理信息转经纬度
+    /// </summary>
+    /// <param name="addr"></param>
+    /// <returns></returns>
+    [Route("addr"), ResponseCache(Duration = 600, VaryByQueryKeys = new[] { "addr" })]
+    public async Task<ActionResult> Address(string addr)
+    {
+        if (string.IsNullOrEmpty(addr))
+        {
+            var ip = ClientIP;
 #if DEBUG
-			Random r = new Random();
-			ip = IPAddress.Parse($"{r.Next(210)}.{r.Next(255)}.{r.Next(255)}.{r.Next(255)}");
+            Random r = new Random();
+            ip = IPAddress.Parse($"{r.Next(210)}.{r.Next(255)}.{r.Next(255)}.{r.Next(255)}");
 #endif
-			var location = Policy<CityResponse>.Handle<AddressNotFoundException>().Fallback(() => new CityResponse()).Execute(() => CommonHelper.MaxmindReader.City(ip));
-			var address = new PhysicsAddress()
-			{
-				Status = 0,
-				AddressResult = new AddressResult
-				{
-					FormattedAddress = ip.GetIPLocation().Address,
-					Location = new Location
-					{
-						Lng = (decimal)location.Location.Longitude.GetValueOrDefault(),
-						Lat = (decimal)location.Location.Latitude.GetValueOrDefault()
-					}
-				}
-			};
-			ViewBag.Address = address.AddressResult.FormattedAddress;
-			if (Request.Method.Equals(HttpMethods.Get) || (Request.Headers[HeaderNames.Accept] + "").StartsWith(ContentType.Json))
-			{
-				return View(address.AddressResult.Location);
-			}
-
-			return Json(address.AddressResult.Location);
-		}
-
-		ViewBag.Address = addr;
-		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)
-			{
-				return JsonConvert.DeserializeObject<PhysicsAddress>(t.Result);
-			}
-
-			return new PhysicsAddress();
-		});
-		if (Request.Method.Equals(HttpMethods.Get) || (Request.Headers[HeaderNames.Accept] + "").StartsWith(ContentType.Json))
-		{
-			return View(physicsAddress?.AddressResult?.Location);
-		}
-
-		return Json(physicsAddress?.AddressResult?.Location);
-	}
+            var location = Policy<CityResponse>.Handle<AddressNotFoundException>().Fallback(() => new CityResponse()).Execute(() => CommonHelper.MaxmindReader.City(ip));
+            var address = new PhysicsAddress()
+            {
+                Status = 0,
+                AddressResult = new AddressResult
+                {
+                    FormattedAddress = ip.GetIPLocation().Address,
+                    Location = new Location
+                    {
+                        Lng = (decimal)location.Location.Longitude.GetValueOrDefault(),
+                        Lat = (decimal)location.Location.Latitude.GetValueOrDefault()
+                    }
+                }
+            };
+            ViewBag.Address = address.AddressResult.FormattedAddress;
+            if (Request.Method.Equals(HttpMethods.Get) || (Request.Headers[HeaderNames.Accept] + "").StartsWith(ContentType.Json))
+            {
+                return View(address.AddressResult.Location);
+            }
+
+            return Json(address.AddressResult.Location);
+        }
+
+        ViewBag.Address = addr;
+        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)
+            {
+                return JsonConvert.DeserializeObject<PhysicsAddress>(t.Result);
+            }
+
+            return new PhysicsAddress();
+        });
+        if (Request.Method.Equals(HttpMethods.Get) || (Request.Headers[HeaderNames.Accept] + "").StartsWith(ContentType.Json))
+        {
+            return View(physicsAddress?.AddressResult?.Location);
+        }
+
+        return Json(physicsAddress?.AddressResult?.Location);
+    }
 }

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

@@ -26,7 +26,7 @@ public sealed class UploadController : Controller
 {
     public IWebHostEnvironment HostEnvironment { get; set; }
 
-    public ActionResult ResultData(object data, bool isTrue = true, string message = "")
+    private ActionResult ResultData(object data, bool isTrue = true, string message = "")
     {
         return Json(new
         {

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

@@ -58,7 +58,7 @@
         <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.1" />
         <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.1" />
         <PackageReference Include="Microsoft.Extensions.Http.Polly" Version="7.0.1" />
-        <PackageReference Include="Microsoft.Graph" Version="4.48.0" />
+        <PackageReference Include="Microsoft.Graph" Version="4.49.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" />

+ 36 - 37
src/Masuit.MyBlogs.Core/wwwroot/Scripts/Detector.js

@@ -1,55 +1,54 @@
 Detector = {
+    canvas : !! window.CanvasRenderingContext2D,
+    webgl : ( function () { try { return !! window.WebGLRenderingContext && !! document.createElement( 'canvas' ).getContext( 'experimental-webgl' ); } catch( e ) { return false; } } )(),
+    workers : !! window.Worker,
+    fileapi : window.File && window.FileReader && window.FileList && window.Blob,
 
-	canvas : !! window.CanvasRenderingContext2D,
-	webgl : ( function () { try { return !! window.WebGLRenderingContext && !! document.createElement( 'canvas' ).getContext( 'experimental-webgl' ); } catch( e ) { return false; } } )(),
-	workers : !! window.Worker,
-	fileapi : window.File && window.FileReader && window.FileList && window.Blob,
+    getWebGLErrorMessage : function () {
 
-	getWebGLErrorMessage : function () {
+        var domElement = document.createElement( 'div' );
 
-		var domElement = document.createElement( 'div' );
+        domElement.style.fontFamily = 'monospace';
+        domElement.style.fontSize = '13px';
+        domElement.style.textAlign = 'center';
+        domElement.style.background = '#eee';
+        domElement.style.color = '#000';
+        domElement.style.padding = '1em';
+        domElement.style.width = '475px';
+        domElement.style.margin = '5em auto 0';
 
-		domElement.style.fontFamily = 'monospace';
-		domElement.style.fontSize = '13px';
-		domElement.style.textAlign = 'center';
-		domElement.style.background = '#eee';
-		domElement.style.color = '#000';
-		domElement.style.padding = '1em';
-		domElement.style.width = '475px';
-		domElement.style.margin = '5em auto 0';
+        if ( ! this.webgl ) {
 
-		if ( ! this.webgl ) {
+            domElement.innerHTML = window.WebGLRenderingContext ? [
+                'Sorry, your graphics card doesn\'t support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation">WebGL</a>'
+            ].join( '\n' ) : [
+                'Sorry, your browser doesn\'t support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation">WebGL</a><br/>',
+                'Please try with',
+                '<a href="http://www.google.com/chrome">Chrome 10</a>, ',
+                '<a href="http://www.mozilla.com/en-US/firefox/all-beta.html">Firefox 4</a> or',
+                '<a href="http://nightly.webkit.org/">Safari 6</a>'
+            ].join( '\n' );
 
-			domElement.innerHTML = window.WebGLRenderingContext ? [
-				'Sorry, your graphics card doesn\'t support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation">WebGL</a>'
-			].join( '\n' ) : [
-				'Sorry, your browser doesn\'t support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation">WebGL</a><br/>',
-				'Please try with',
-				'<a href="http://www.google.com/chrome">Chrome 10</a>, ',
-				'<a href="http://www.mozilla.com/en-US/firefox/all-beta.html">Firefox 4</a> or',
-				'<a href="http://nightly.webkit.org/">Safari 6</a>'
-			].join( '\n' );
+        }
 
-		}
+        return domElement;
 
-		return domElement;
+    },
 
-	},
+    addGetWebGLMessage : function ( parameters ) {
 
-	addGetWebGLMessage : function ( parameters ) {
+        var parent, id, domElement;
 
-		var parent, id, domElement;
+        parameters = parameters || {};
 
-		parameters = parameters || {};
+        parent = parameters.parent !== undefined ? parameters.parent : document.body;
+        id = parameters.id !== undefined ? parameters.id : 'oldie';
 
-		parent = parameters.parent !== undefined ? parameters.parent : document.body;
-		id = parameters.id !== undefined ? parameters.id : 'oldie';
+        domElement = Detector.getWebGLErrorMessage();
+        domElement.id = id;
 
-		domElement = Detector.getWebGLErrorMessage();
-		domElement.id = id;
+        parent.appendChild( domElement );
 
-		parent.appendChild( domElement );
-
-	}
+    }
 
 };

+ 2 - 3
src/Masuit.MyBlogs.Core/wwwroot/Scripts/global/article.js

@@ -209,8 +209,7 @@ $("#getcode-reply").on("click", function (e) {
 	
 	//表单取消按钮
 	$(".btn-cancel").click(function() {
-		$(':input', '#reply-form').not(':button,:submit,:reset,:hidden').val('').removeAttr('checked')
-			.removeAttr('checked'); //评论成功清空表单
+		$(':input', '#reply-form').not(':button,:submit,:reset,:hidden').val('').removeAttr('checked').removeAttr('checked'); //评论成功清空表单
 		layer.closeAll();
 		setTimeout(function() {
 			$("#reply").css("display", "none");
@@ -455,7 +454,7 @@ function loadParentComments(data) {
 //加载子楼层
 function loadComments(comments, depth = 0) {
 	comments.sort(function(x, y) {
-		return x.Id - y.Id
+		return x.Id - y.Id;
 	});
 
 	var colors = ["info", "success", "primary", "warning", "danger"];

+ 100 - 100
src/Masuit.MyBlogs.Core/wwwroot/Scripts/global/counter.js

@@ -2,64 +2,64 @@
     var myChart = echarts.init(document.getElementById("container"));
     myChart.setOption({
         series: [{
-                type: 'gauge',
-                anchor: {
-                    show: true,
-                    showAbove: true,
-                    size: 18,
-                    itemStyle: {
-                        color: '#FAC858'
-                    }
-                },
-                pointer: {
-                    icon: 'path://M2.9,0.7L2.9,0.7c1.4,0,2.6,1.2,2.6,2.6v115c0,1.4-1.2,2.6-2.6,2.6l0,0c-1.4,0-2.6-1.2-2.6-2.6V3.3C0.3,1.9,1.4,0.7,2.9,0.7z',
-                    width: 8,
-                    length: '80%',
-                    offsetCenter: [0, '8%']
-                },
+            type: 'gauge',
+            anchor: {
+                show: true,
+                showAbove: true,
+                size: 18,
+                itemStyle: {
+                    color: '#FAC858'
+                }
+            },
+            pointer: {
+                icon: 'path://M2.9,0.7L2.9,0.7c1.4,0,2.6,1.2,2.6,2.6v115c0,1.4-1.2,2.6-2.6,2.6l0,0c-1.4,0-2.6-1.2-2.6-2.6V3.3C0.3,1.9,1.4,0.7,2.9,0.7z',
+                width: 8,
+                length: '80%',
+                offsetCenter: [0, '8%']
+            },
 
-                progress: {
-                    show: true,
-                    overlap: true,
-                    roundCap: true
-                },
-                axisLine: {
-                    roundCap: true
-                },
-                data: [{
-                        value: 0,
-                        name: 'CPU',
-                        title: {
-                            offsetCenter: ['-20%', '80%']
-                        },
-                        detail: {
-                            offsetCenter: ['-20%', '95%']
-                        }
+            progress: {
+                show: true,
+                overlap: true,
+                roundCap: true
+            },
+            axisLine: {
+                roundCap: true
+            },
+            data: [{
+                    value: 0,
+                    name: 'CPU',
+                    title: {
+                        offsetCenter: ['-20%', '80%']
                     },
-                    {
-                        value: 0,
-                        name: '内存',
-                        title: {
-                            offsetCenter: ['20%', '80%']
-                        },
-                        detail: {
-                            offsetCenter: ['20%', '95%']
-                        }
+                    detail: {
+                        offsetCenter: ['-20%', '95%']
                     }
-                ],
-                title: {
-                    fontSize: 14
                 },
-                detail: {
-                    width: 40,
-                    height: 14,
-                    fontSize: 14,
-                    color: '#fff',
-                    backgroundColor: 'auto',
-                    borderRadius: 3,
-                    formatter: '{value}%'
+                {
+                    value: 0,
+                    name: '内存',
+                    title: {
+                        offsetCenter: ['20%', '80%']
+                    },
+                    detail: {
+                        offsetCenter: ['20%', '95%']
+                    }
                 }
-            }]
+            ],
+            title: {
+                fontSize: 14
+            },
+            detail: {
+                width: 40,
+                height: 14,
+                fontSize: 14,
+                color: '#fff',
+                backgroundColor: 'auto',
+                borderRadius: 3,
+                formatter: '{value}%'
+            }
+        }]
     });
     return myChart;
 }
@@ -190,10 +190,10 @@ function showLine(ip) {
         var myChart = echarts.init(document.getElementById("container-cpu"));
         myChart.setOption({
             visualMap: [{
-                    show: false,
-                    type: 'continuous',
-                    seriesIndex: 0
-                }],
+                show: false,
+                type: 'continuous',
+                seriesIndex: 0
+            }],
             tooltip: {
                 trigger: 'axis',
                 axisPointer: {
@@ -201,15 +201,15 @@ function showLine(ip) {
                 }
             },
             dataZoom: [{
-                    type: 'inside',
-                    start: 70,
-                    end: 100,
-                    minValueSpan: 100
-                }, {
-                    start: 70,
-                    end: 100,
-                    minValueSpan: 100
-                }],
+                type: 'inside',
+                start: 70,
+                end: 100,
+                minValueSpan: 100
+            }, {
+                start: 70,
+                end: 100,
+                minValueSpan: 100
+            }],
             xAxis: {
                 type: 'time',
                 interval: 20000,
@@ -235,40 +235,40 @@ function showLine(ip) {
                 data: ['CPU使用率', '内存使用率']
             },
             series: [{
-                    name: 'CPU使用率',
-                    type: 'line',
-                    showSymbol: false,
-                    hoverAnimation: false,
-                    data: data.cpu,
-                    markPoint: {
-                        data: [
-                            { type: 'max', name: '最大值' },
-                            { type: 'min', name: '最小值' }
-                        ]
-                    },
-                    markLine: {
-                        data: [
-                            { type: 'average', name: '平均值' }
-                        ]
-                    }
-                }, {
-                    name: '内存使用率',
-                    type: 'line',
-                    showSymbol: false,
-                    hoverAnimation: false,
-                    data: data.mem,
-                    markPoint: {
-                        data: [
-                            { type: 'max', name: '最大值' },
-                            { type: 'min', name: '最小值' }
-                        ]
-                    },
-                    markLine: {
-                        data: [
-                            { type: 'average', name: '平均值' }
-                        ]
-                    }
-                }]
+                name: 'CPU使用率',
+                type: 'line',
+                showSymbol: false,
+                hoverAnimation: false,
+                data: data.cpu,
+                markPoint: {
+                    data: [
+                        { type: 'max', name: '最大值' },
+                        { type: 'min', name: '最小值' }
+                    ]
+                },
+                markLine: {
+                    data: [
+                        { type: 'average', name: '平均值' }
+                    ]
+                }
+            }, {
+                name: '内存使用率',
+                type: 'line',
+                showSymbol: false,
+                hoverAnimation: false,
+                data: data.mem,
+                markPoint: {
+                    data: [
+                        { type: 'max', name: '最大值' },
+                        { type: 'min', name: '最小值' }
+                    ]
+                },
+                markLine: {
+                    data: [
+                        { type: 'average', name: '平均值' }
+                    ]
+                }
+            }]
         });
         var rateChart = showSpeed();
         var ioChart = showIO(data);

+ 27 - 28
src/Masuit.MyBlogs.Core/wwwroot/Scripts/global/functions.js

@@ -36,7 +36,7 @@ function CopyrightProtect() {
                         clearSelect();
                         evt.cancelBubble = true;
                         evt.returnValue = false;
-                        return false;
+                        return true;
                     }
                 });
                 document.onkeydown = function(e) {
@@ -48,7 +48,7 @@ function CopyrightProtect() {
                         evt.returnValue = false;
                         return false;
                     }
-                    return false;
+                    return true;
                 }
                 document.ondragstart = function(e) {
                     e.returnValue = false;
@@ -74,33 +74,32 @@ function CopyrightProtect() {
 /**禁止编辑器内复制 */
 function CopyrightProtect4Editor() {
     setInterval(function() {
-            try {
-                (function() {}["constructor"]("debugger")());
-                document.getElementById("ueditor_0").contentWindow.document.body.onkeydown = function(e) {
-                    var currKey = 0, evt = e || window.event;
-                    currKey = evt.keyCode || evt.which || evt.charCode;
-                    if (currKey == 123 || (evt.ctrlKey && currKey == 67) || (evt.ctrlKey && currKey == 83) || (evt.ctrlKey && currKey == 85) || (evt.ctrlKey && currKey == 88) || (evt.ctrlKey && evt.shiftKey) || evt.altKey) {
-                        clearSelect();
-                        evt.cancelBubble = true;
-                        evt.returnValue = false;
-                        return false;
-                    }
-                    return false;
-                }
-                document.getElementById("ueditor_0").contentWindow.document.body.ondragstart = function(e) {
-                    e.returnValue = false;
-                    hackClip();
+        try {
+            (function() {}["constructor"]("debugger")());
+            document.getElementById("ueditor_0").contentWindow.document.body.onkeydown = function(e) {
+                var currKey = 0, evt = e || window.event;
+                currKey = evt.keyCode || evt.which || evt.charCode;
+                if (currKey == 123 || (evt.ctrlKey && currKey == 67) || (evt.ctrlKey && currKey == 83) || (evt.ctrlKey && currKey == 85) || (evt.ctrlKey && currKey == 88) || (evt.ctrlKey && evt.shiftKey) || evt.altKey) {
+                    clearSelect();
+                    evt.cancelBubble = true;
+                    evt.returnValue = false;
                     return false;
                 }
-                document.getElementById("ueditor_0").contentWindow.document.body.oncopy = function(e) {
-                    e.returnValue = false;
-                    hackClip();
-                    return false;
-                }
-            } catch (ex) {
-                console.error(ex);
+                return true;
             }
-        }, 500);
+            document.getElementById("ueditor_0").contentWindow.document.body.ondragstart = function(e) {
+                e.returnValue = false;
+                hackClip();
+                return false;
+            }
+            document.getElementById("ueditor_0").contentWindow.document.body.oncopy = function(e) {
+                e.returnValue = false;
+                hackClip();
+                return false;
+            }
+        } catch (ex) {
+            console.error(ex);
+        }}, 500);
 }
 
 /**禁止全局复制 */
@@ -117,7 +116,7 @@ function GlobalCopyrightProtect() {
                         clearSelect();
                         return false;
                     }
-                    return false;
+                    return true;
                 });
                 document.onkeydown = function(e) {
                     var currKey = 0, evt = e || window.event;
@@ -128,7 +127,7 @@ function GlobalCopyrightProtect() {
                         clearSelect();
                         return false;
                     }
-                    return false;
+                    return true;
                 }
                 document.ondragstart = function(e) {
                     e.returnValue = false;

+ 1 - 1
src/Masuit.MyBlogs.Core/wwwroot/Scripts/global/leavemsg.js

@@ -289,7 +289,7 @@ function loadParentMsgs(data) {
 //加载子楼层
 function loadMsgs(msg, depth = 0) {
 	msg.sort(function(x, y) {
-		return x.Id - y.Id
+		return x.Id - y.Id;
 	});
 
 	var colors = ["info", "success", "primary", "warning", "danger"];

+ 38 - 38
src/Masuit.MyBlogs.Core/wwwroot/Scripts/global/post-all.js

@@ -16,7 +16,7 @@ if (!Detector.webgl) Detector.addGetWebGLMessage();
         container.style.right = 0;
         container.style.bottom = 0;
         container.style.top = 0;
-		
+        
         document.body.appendChild(container);
         // Bg gradient
         var canvas = document.createElement('canvas');
@@ -30,7 +30,7 @@ if (!Detector.webgl) Detector.addGetWebGLMessage();
         context.fillRect(0, 0, canvas.width, canvas.height);
         container.style.background = 'url(' + canvas.toDataURL('image/png') + ')';
         container.style.backgroundSize = '32px 100%';
-		container.style.zIndex = -1;
+        container.style.zIndex = -1;
         camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 3000);
         camera.position.z = 6000;
         scene = new THREE.Scene();
@@ -90,50 +90,50 @@ if (!Detector.webgl) Detector.addGetWebGLMessage();
         renderer.render(scene, camera);
     }
 $('.choose').click(function () {
-	$('.choose').addClass('active');
-	$('.choose > .icon').addClass('active');
-	$('.pay').removeClass('active');
-	$('.wrap').removeClass('active');
-	$('.pay > .icon').removeClass('active');
-	$('.wrap > .icon').removeClass('active');
-	$('#line').addClass('one');
-	$('#line').removeClass('two');
-	$('#line').removeClass('three');
+    $('.choose').addClass('active');
+    $('.choose > .icon').addClass('active');
+    $('.pay').removeClass('active');
+    $('.wrap').removeClass('active');
+    $('.pay > .icon').removeClass('active');
+    $('.wrap > .icon').removeClass('active');
+    $('#line').addClass('one');
+    $('#line').removeClass('two');
+    $('#line').removeClass('three');
 });
 $('.pay').click(function () {
-	$('.pay').addClass('active');
-	$('.pay > .icon').addClass('active');
-	$('.choose').removeClass('active');
-	$('.wrap').removeClass('active');
-	$('.choose > .icon').removeClass('active');
-	$('.wrap > .icon').removeClass('active');
-	$('#line').addClass('two');
-	$('#line').removeClass('one');
-	$('#line').removeClass('three');
+    $('.pay').addClass('active');
+    $('.pay > .icon').addClass('active');
+    $('.choose').removeClass('active');
+    $('.wrap').removeClass('active');
+    $('.choose > .icon').removeClass('active');
+    $('.wrap > .icon').removeClass('active');
+    $('#line').addClass('two');
+    $('#line').removeClass('one');
+    $('#line').removeClass('three');
 });
 $('.wrap').click(function () {
-	$('.wrap').addClass('active');
-	$('.wrap > .icon').addClass('active');
-	$('.pay').removeClass('active');
-	$('.choose').removeClass('active');
-	$('.pay > .icon').removeClass('active');
-	$('.choose > .icon').removeClass('active');
-	$('#line').addClass('three');
-	$('#line').removeClass('two');
-	$('#line').removeClass('one');
+    $('.wrap').addClass('active');
+    $('.wrap > .icon').addClass('active');
+    $('.pay').removeClass('active');
+    $('.choose').removeClass('active');
+    $('.pay > .icon').removeClass('active');
+    $('.choose > .icon').removeClass('active');
+    $('#line').addClass('three');
+    $('#line').removeClass('two');
+    $('#line').removeClass('one');
 });
 $('.choose').click(function () {
-	$('#first').addClass('active');
-	$('#second').removeClass('active');
-	$('#third').removeClass('active');
+    $('#first').addClass('active');
+    $('#second').removeClass('active');
+    $('#third').removeClass('active');
 });
 $('.pay').click(function () {
-	$('#first').removeClass('active');
-	$('#second').addClass('active');
-	$('#third').removeClass('active');
+    $('#first').removeClass('active');
+    $('#second').addClass('active');
+    $('#third').removeClass('active');
 });
 $('.wrap').click(function () {
-	$('#first').removeClass('active');
-	$('#second').removeClass('active');
-	$('#third').addClass('active');
+    $('#first').removeClass('active');
+    $('#second').removeClass('active');
+    $('#third').addClass('active');
 });

+ 236 - 236
src/Masuit.MyBlogs.Core/wwwroot/Scripts/global/scripts.js

@@ -1,258 +1,258 @@
 ;(function($) {
-	$.fn.serializeObject = function() {
-		var o = {};
-		var a = this.serializeArray();
-		$.each(a, function() {
-				if (o[this.name]) {
-					if (!o[this.name].push) {
-						o[this.name] = [o[this.name]];
-					}
-					o[this.name].push(this.value || '');
-				} else {
-					o[this.name] = this.value || '';
-				}
-			});
-		return o;
-	}
+    $.fn.serializeObject = function() {
+        var o = {};
+        var a = this.serializeArray();
+        $.each(a, function() {
+                if (o[this.name]) {
+                    if (!o[this.name].push) {
+                        o[this.name] = [o[this.name]];
+                    }
+                    o[this.name].push(this.value || '');
+                } else {
+                    o[this.name] = this.value || '';
+                }
+            });
+        return o;
+    }
 })(jQuery);
 
 $(function() {
-	//$('.header').css({
-	//   'filter': 'unset',
-	//   '-webkit-filter' : 'unset',
-	//   '-moz-filter': 'unset',
-	//   '-o-filter': 'unset',
-	//   '-ms-filter': 'unset',
-	//   "transition": "all 0.1s ease-in-out"
-	//});
-	loadingDone();
-	
-	$(".notices").bootstrapNews({
-		newsPerPage: 4,
-		autoplay: true,
-		pauseOnHover: true,
-		navigation: false,
-		direction: 'down',
-		newsTickerInterval: 2500,
-		onToDo: function() {
-			//console.log(this);
-		}
-	});
+    //$('.header').css({
+    //   'filter': 'unset',
+    //   '-webkit-filter' : 'unset',
+    //   '-moz-filter': 'unset',
+    //   '-o-filter': 'unset',
+    //   '-ms-filter': 'unset',
+    //   "transition": "all 0.1s ease-in-out"
+    //});
+    loadingDone();
 
-	//全局加载动画
-	$("a[href]").click(function(e) {
-		if ($(this).attr("target") == "_blank") {
-			return;
-		}
-		if ($(this).attr("href").indexOf("#") >= 0 || $(this).attr("href").indexOf("javascript") >= 0) {
-			return;
-		}
-		loading();
-		setTimeout(function() {
-				loadingDone();
-				window.notie.alert({
-					type: 4,
-					text: "页面加载失败!",
-					time: 4
-				});
-			}, 60000);
-	});
+    $(".notices").bootstrapNews({
+        newsPerPage: 4,
+        autoplay: true,
+        pauseOnHover: true,
+        navigation: false,
+        direction: 'down',
+        newsTickerInterval: 2500,
+        onToDo: function() {
+            //console.log(this);
+        }
+    });
 
-	//new WOW().init();//滚动加载
-	var nav = $(".cd-main-header");
-	if (document.documentElement.scrollTop || document.body.scrollTop > 0) {
-		nav.css("background-color", "white");
-	} else {
-		nav.css("background-color", "transparent");
-	}
-	document.onscroll = function() {
-		if (document.documentElement.scrollTop || document.body.scrollTop > 10) {
-			nav.css({
-				"background-color": "white",
-				transition: "all 1s ease-in-out"
-			});
-		} else {
-			nav.css({
-				"background-color": "transparent",
-				transition: "all 1s ease-in-out"
-			});
-		}
-	}
-	
-	$(".btn").on("mousedown", function(e) { 
-		window.ripplet(e, {
-				color: null,
-				className: 'rainbow',
-				clearingDuration: '3s',
-				spreadingDuration: '1s'
-			});
-		});
-	if (!Object.prototype.hasOwnProperty.call(window, 'event')) {
-		['mousedown', 'mouseenter', 'onmouseleave'].forEach(function(eventType) {
-			window.addEventListener(eventType, function(event) {
-				window.event = event;
-			}, true);
-		});
-	}
-	
-	window.fetch("/notice/last").then(function(response) {
-		return response.json();
-	}).then(function(data) {
-		if (!data.Success) {
-			return ;
-		}
+    //全局加载动画
+    $("a[href]").click(function(e) {
+        if ($(this).attr("target") == "_blank") {
+            return;
+        }
+        if ($(this).attr("href").indexOf("#") >= 0 || $(this).attr("href").indexOf("javascript") >= 0) {
+            return;
+        }
+        loading();
+        setTimeout(function() {
+                loadingDone();
+                window.notie.alert({
+                    type: 4,
+                    text: "页面加载失败!",
+                    time: 4
+                });
+            }, 60000);
+    });
 
-		data = data.Data;
-		var nid = [].concat(JSON.parse(window.localStorage.getItem("notice") || '[]'));
-		if (data.StrongAlert && nid.indexOf(data.Id)==-1) {
-			//公告层
-			layer.open({
-				title: '网站公告:' + data.Title,
-				offset: (window.screen.width > 400 ? "100px" : "40px"),
-				area: (window.screen.width > 400 ? 400 : window.screen.width - 10) + 'px',
-				shade: 0.6,
-				closeBtn: true,
-				content: data.Content,
-				btn: ["查看详情", '知道了'],
-				btn1: function(layero) {
-					nid.push(data.Id);
-					window.localStorage.setItem("notice", JSON.stringify(nid));
-					window.location.href = "/notice/" + data.Id;
-					loading();
-				},
-				btn2: function(index) {
-					nid.push(data.Id);
-					window.localStorage.setItem("notice", JSON.stringify(nid));
-					layer.closeAll();
-				}
-			});
-		}
-	}).catch(function(e) {
-		console.log("Oops, error");
-	});
+    //new WOW().init();//滚动加载
+    var nav = $(".cd-main-header");
+    if (document.documentElement.scrollTop || document.body.scrollTop > 0) {
+        nav.css("background-color", "white");
+    } else {
+        nav.css("background-color", "transparent");
+    }
+    document.onscroll = function() {
+        if (document.documentElement.scrollTop || document.body.scrollTop > 10) {
+            nav.css({
+                "background-color": "white",
+                transition: "all 1s ease-in-out"
+            });
+        } else {
+            nav.css({
+                "background-color": "transparent",
+                transition: "all 1s ease-in-out"
+            });
+        }
+    }
 
-	setInterval(function() {
-		let timestamp = new Date().getTime();
-		DotNet.invokeMethodAsync('Masuit.MyBlogs.Core', 'Latency').then(data => {
-			$("#ping").text(new Date().getTime()-timestamp);
-		});
-	}, 2000);
+    $(".btn").on("mousedown", function(e) {
+        window.ripplet(e, {
+            color: null,
+            className: 'rainbow',
+            clearingDuration: '3s',
+            spreadingDuration: '1s'
+        });
+    });
+    if (!Object.prototype.hasOwnProperty.call(window, 'event')) {
+        ['mousedown', 'mouseenter', 'onmouseleave'].forEach(function(eventType) {
+            window.addEventListener(eventType, function(event) {
+                window.event = event;
+            }, true);
+        });
+    }
 
-	// 自动重试加载图片
-	$('img').on("error",function() {
-	   var that=$(this);
-	   var retry=that.attr("retry")||0;
-	   if(retry>10){
-		  return ;
-	   }else{
-		  retry++;
-		  that.attr("retry", retry);//重试次数+1
-		  that.attr('src', that.attr("src"));//继续刷新图片
-	   }
-	});
-	$('img').on("abort",function() {
-	   var that=$(this);
-	   var retry=that.attr("retry")||0;
-	   if(retry>10){
-		  return ;
-	   }else{
-		  retry++;
-		  that.attr("retry", retry);//重试次数+1
-		  that.attr('src', that.attr("src"));//继续刷新图片
-	   }
-	});
+    window.fetch("/notice/last").then(function(response) {
+        return response.json();
+    }).then(function(data) {
+        if (!data.Success) {
+            return;
+        }
+
+        data = data.Data;
+        var nid = [].concat(JSON.parse(window.localStorage.getItem("notice") || '[]'));
+        if (data.StrongAlert && nid.indexOf(data.Id) == -1) {
+            //公告层
+            layer.open({
+                title: '网站公告:' + data.Title,
+                offset: (window.screen.width > 400 ? "100px" : "40px"),
+                area: (window.screen.width > 400 ? 400 : window.screen.width - 10) + 'px',
+                shade: 0.6,
+                closeBtn: true,
+                content: data.Content,
+                btn: ["查看详情", '知道了'],
+                btn1: function(layero) {
+                    nid.push(data.Id);
+                    window.localStorage.setItem("notice", JSON.stringify(nid));
+                    window.location.href = "/notice/" + data.Id;
+                    loading();
+                },
+                btn2: function(index) {
+                    nid.push(data.Id);
+                    window.localStorage.setItem("notice", JSON.stringify(nid));
+                    layer.closeAll();
+                }
+            });
+        }
+    }).catch(function(e) {
+        console.log("Oops, error");
+    });
+
+    setInterval(function() {
+            let timestamp = new Date().getTime();
+            DotNet.invokeMethodAsync('Masuit.MyBlogs.Core', 'Latency').then(data => {
+                $("#ping").text(new Date().getTime() - timestamp);
+            });
+        }, 2000);
+
+    // 自动重试加载图片
+    $('img').on("error", function() {
+        var that = $(this);
+        var retry = that.attr("retry") || 0;
+        if (retry > 10) {
+            return;
+        } else {
+            retry++;
+            that.attr("retry", retry); //重试次数+1
+            that.attr('src', that.attr("src")); //继续刷新图片
+        }
+    });
+    $('img').on("abort", function() {
+        var that = $(this);
+        var retry = that.attr("retry") || 0;
+        if (retry > 10) {
+            return;
+        } else {
+            retry++;
+            that.attr("retry", retry); //重试次数+1
+            that.attr('src', that.attr("src")); //继续刷新图片
+        }
+    });
 });
 
 /**
  * 鼠标桃心
  */
 (function(window, document) {
-	var hearts = [];
-	window.requestAnimationFrame = (function() {
-		return window.requestAnimationFrame ||
-			window.webkitRequestAnimationFrame ||
-			window.mozRequestAnimationFrame ||
-			window.oRequestAnimationFrame ||
-			window.msRequestAnimationFrame ||
-			function(callback) {
-				setTimeout(callback, 1000 / 60);
-			}
-	})();
-	init();
+    var hearts = [];
+    window.requestAnimationFrame = (function() {
+        return window.requestAnimationFrame ||
+            window.webkitRequestAnimationFrame ||
+            window.mozRequestAnimationFrame ||
+            window.oRequestAnimationFrame ||
+            window.msRequestAnimationFrame ||
+            function(callback) {
+                setTimeout(callback, 1000 / 60);
+            }
+    })();
+    init();
 
-	function init() {
-		css(".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: absolute;}.heart:after{top: -5px;}.heart:before{left: -5px;}");
-		attachEvent();
-		gameloop();
-	}
+    function init() {
+        css(".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: absolute;}.heart:after{top: -5px;}.heart:before{left: -5px;}");
+        attachEvent();
+        gameloop();
+    }
 
-	function gameloop() {
-		for (var i = 0; i < hearts.length; i++) {
-			if (hearts[i].alpha <= 0) {
-				document.body.removeChild(hearts[i].el);
-				hearts.splice(i, 1);
-				continue;
-			}
-			hearts[i].y--;
-			hearts[i].scale += 0.004;
-			hearts[i].alpha -= 0.013;
-			hearts[i].el.style.cssText =
-				"left:" +
-				hearts[i].x +
-				"px;top:" +
-				hearts[i].y +
-				"px;opacity:" +
-				hearts[i].alpha +
-				";transform:scale(" +
-				hearts[i].scale +
-				"," +
-				hearts[i].scale +
-				") rotate(45deg);background:" +
-				hearts[i].color;
-		}
-		requestAnimationFrame(gameloop);
-	}
+    function gameloop() {
+        for (var i = 0; i < hearts.length; i++) {
+            if (hearts[i].alpha <= 0) {
+                document.body.removeChild(hearts[i].el);
+                hearts.splice(i, 1);
+                continue;
+            }
+            hearts[i].y--;
+            hearts[i].scale += 0.004;
+            hearts[i].alpha -= 0.013;
+            hearts[i].el.style.cssText =
+                "left:" +
+                hearts[i].x +
+                "px;top:" +
+                hearts[i].y +
+                "px;opacity:" +
+                hearts[i].alpha +
+                ";transform:scale(" +
+                hearts[i].scale +
+                "," +
+                hearts[i].scale +
+                ") rotate(45deg);background:" +
+                hearts[i].color;
+        }
+        requestAnimationFrame(gameloop);
+    }
 
-	function attachEvent() {
-		var old = typeof window.onclick === "function" && window.onclick;
-		window.onclick = function(event) {
-			old && old();
-			createHeart(event);
-		}
-	}
+    function attachEvent() {
+        var old = typeof window.onclick === "function" && window.onclick;
+        window.onclick = function(event) {
+            old && old();
+            createHeart(event);
+        }
+    }
 
-	function createHeart(event) {
-		var d = document.createElement("div");
-		d.className = "heart";
-		hearts.push({
-			el: d,
-			x: event.clientX - 5,
-			y: event.clientY - 5,
-			scale: 1,
-			alpha: 1,
-			color: randomColor()
-		});
-		document.body.appendChild(d);
-	}
+    function createHeart(event) {
+        var d = document.createElement("div");
+        d.className = "heart";
+        hearts.push({
+            el: d,
+            x: event.clientX - 5,
+            y: event.clientY - 5,
+            scale: 1,
+            alpha: 1,
+            color: randomColor()
+        });
+        document.body.appendChild(d);
+    }
 
-	function css(css) {
-		var style = document.createElement("style");
-		style.type = "text/css";
-		try {
-			style.appendChild(document.createTextNode(css));
-		} catch (ex) {
-			style.styleSheet.cssText = css;
-		}
-		document.getElementsByTagName('head')[0].appendChild(style);
-	}
+    function css(css) {
+        var style = document.createElement("style");
+        style.type = "text/css";
+        try {
+            style.appendChild(document.createTextNode(css));
+        } catch (ex) {
+            style.styleSheet.cssText = css;
+        }
+        document.getElementsByTagName('head')[0].appendChild(style);
+    }
 
-	function randomColor() {
-		return "rgb(" +
-			(~~(Math.random() * 255)) +
-			"," +
-			(~~(Math.random() * 255)) +
-			"," +
-			(~~(Math.random() * 255)) +
-			")";
-	}
+    function randomColor() {
+        return "rgb(" +
+            (~~(Math.random() * 255)) +
+            "," +
+            (~~(Math.random() * 255)) +
+            "," +
+            (~~(Math.random() * 255)) +
+            ")";
+    }
 })(window, document);

+ 2 - 2
src/Masuit.MyBlogs.Core/wwwroot/Scripts/platform.js

@@ -1,6 +1,6 @@
 ;var DeviceInfo = (function () {
-    var root = typeof self !== 'undefined' ? self : this
-    var _window = root || {}
+    var root = typeof self !== 'undefined' ? self : this;
+    var _window = root || {};
     // 变量库
     var VariableLibrary = {
         navigator: typeof root.navigator != 'undefined' ? root.navigator : {},

+ 1 - 0
src/Masuit.MyBlogs.Core/wwwroot/Scripts/publish/publish.js

@@ -17,6 +17,7 @@
 		if (!data.Success) {
 			return ;
 		}
+
 		data = data.Data.sort((a,b)=>(b.Id==1)-(a.Id==1));
 		window.categoryDropdown = xmSelect.render({
 			el: '#category',