BaseController.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. using AutoMapper;
  2. using AutoMapper.QueryableExtensions;
  3. using EFCoreSecondLevelCacheInterceptor;
  4. using Masuit.MyBlogs.Core.Common;
  5. using Masuit.MyBlogs.Core.Common.Mails;
  6. using Masuit.MyBlogs.Core.Configs;
  7. using Masuit.MyBlogs.Core.Extensions;
  8. using Masuit.MyBlogs.Core.Extensions.Firewall;
  9. using Masuit.MyBlogs.Core.Infrastructure.Services.Interface;
  10. using Masuit.MyBlogs.Core.Models.DTO;
  11. using Masuit.MyBlogs.Core.Models.Entity;
  12. using Masuit.MyBlogs.Core.Models.Enum;
  13. using Masuit.MyBlogs.Core.Models.ViewModel;
  14. using Masuit.Tools;
  15. using Masuit.Tools.Core.Net;
  16. using Masuit.Tools.Security;
  17. using Masuit.Tools.Strings;
  18. using Microsoft.AspNetCore.Http;
  19. using Microsoft.AspNetCore.Mvc;
  20. using Microsoft.AspNetCore.Mvc.Filters;
  21. using Microsoft.Net.Http.Headers;
  22. using System;
  23. using System.Collections.Generic;
  24. using System.Linq;
  25. using System.Net;
  26. using System.Text.RegularExpressions;
  27. using System.Threading.Tasks;
  28. using SameSiteMode = Microsoft.AspNetCore.Http.SameSiteMode;
  29. namespace Masuit.MyBlogs.Core.Controllers
  30. {
  31. /// <summary>
  32. /// 基本父控制器
  33. /// </summary>
  34. [ApiExplorerSettings(IgnoreApi = true), ServiceFilter(typeof(FirewallAttribute))]
  35. public class BaseController : Controller
  36. {
  37. /// <summary>
  38. /// UserInfoService
  39. /// </summary>
  40. public IUserInfoService UserInfoService { get; set; }
  41. /// <summary>
  42. /// MenuService
  43. /// </summary>
  44. public IMenuService MenuService { get; set; }
  45. /// <summary>
  46. /// LinksService
  47. /// </summary>
  48. public ILinksService LinksService { get; set; }
  49. public IAdvertisementService AdsService { get; set; }
  50. public IVariablesService VariablesService { get; set; }
  51. public UserInfoDto CurrentUser => HttpContext.Session.Get<UserInfoDto>(SessionKey.UserInfo) ?? new UserInfoDto();
  52. /// <summary>
  53. /// 客户端的真实IP
  54. /// </summary>
  55. public string ClientIP => HttpContext.Connection.RemoteIpAddress.ToString();
  56. /// <summary>
  57. /// 普通访客是否token合法
  58. /// </summary>
  59. public bool VisitorTokenValid => Request.Cookies["Email"].MDString3(AppConfig.BaiduAK).Equals(Request.Cookies["FullAccessToken"]);
  60. public IMapper Mapper { get; set; }
  61. public MapperConfiguration MapperConfig { get; set; }
  62. /// <summary>
  63. /// 响应数据
  64. /// </summary>
  65. /// <param name="data">数据</param>
  66. /// <param name="success">响应状态</param>
  67. /// <param name="message">响应消息</param>
  68. /// <param name="isLogin">登录状态</param>
  69. /// <param name="code">http响应码</param>
  70. /// <returns></returns>
  71. public ActionResult ResultData(object data, bool success = true, string message = "", bool isLogin = true, HttpStatusCode code = HttpStatusCode.OK)
  72. {
  73. return Ok(new
  74. {
  75. IsLogin = isLogin,
  76. Success = success,
  77. Message = message,
  78. Data = data,
  79. code
  80. });
  81. }
  82. protected string ReplaceVariables(string text)
  83. {
  84. if (string.IsNullOrEmpty(text))
  85. {
  86. return text;
  87. }
  88. var keys = Regex.Matches(text, @"\{\{[\w._-]+\}\}").Select(m => m.Value).Join(",");
  89. if (!string.IsNullOrEmpty(keys))
  90. {
  91. var dic = VariablesService.GetQueryFromCache(v => keys.Contains(v.Key)).ToDictionary(v => v.Key, v => v.Value);
  92. var template = Template.Create(text);
  93. foreach (var (key, value) in dic)
  94. {
  95. template.Set(key, value);
  96. }
  97. return template.Render();
  98. }
  99. return text;
  100. }
  101. /// <summary>在调用操作方法前调用。</summary>
  102. /// <param name="filterContext">有关当前请求和操作的信息。</param>
  103. public override void OnActionExecuting(ActionExecutingContext filterContext)
  104. {
  105. ViewBag.Desc = CommonHelper.SystemSettings["Description"];
  106. var user = filterContext.HttpContext.Session.Get<UserInfoDto>(SessionKey.UserInfo);
  107. #if DEBUG
  108. user = UserInfoService.GetByUsername("masuit").Mapper<UserInfoDto>();
  109. filterContext.HttpContext.Session.Set(SessionKey.UserInfo, user);
  110. #endif
  111. if (CommonHelper.SystemSettings.GetOrAdd("CloseSite", "false") == "true" && user?.IsAdmin != true)
  112. {
  113. filterContext.Result = RedirectToAction("ComingSoon", "Error");
  114. }
  115. 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))))
  116. {
  117. filterContext.Result = ResultData("网站当前处于数据写保护状态,无法提交任何数据,如有疑问请联系网站管理员!", false, "网站当前处于数据写保护状态,无法提交任何数据,如有疑问请联系网站管理员!", user != null, HttpStatusCode.BadRequest);
  118. }
  119. if (user == null && Request.Cookies.Any(x => x.Key == "username" || x.Key == "password")) //执行自动登录
  120. {
  121. string name = Request.Cookies["username"];
  122. string pwd = Request.Cookies["password"]?.DesDecrypt(AppConfig.BaiduAK);
  123. var userInfo = UserInfoService.Login(name, pwd);
  124. if (userInfo != null)
  125. {
  126. Response.Cookies.Append("username", name, new CookieOptions
  127. {
  128. Expires = DateTime.Now.AddYears(1),
  129. SameSite = SameSiteMode.Lax
  130. });
  131. Response.Cookies.Append("password", Request.Cookies["password"], new CookieOptions
  132. {
  133. Expires = DateTime.Now.AddYears(1),
  134. SameSite = SameSiteMode.Lax
  135. });
  136. filterContext.HttpContext.Session.Set(SessionKey.UserInfo, userInfo);
  137. }
  138. }
  139. if (ModelState.IsValid) return;
  140. var errmsgs = ModelState.SelectMany(kv => kv.Value.Errors.Select(e => e.ErrorMessage)).ToList();
  141. if (errmsgs.Any())
  142. {
  143. for (var i = 0; i < errmsgs.Count; i++)
  144. {
  145. errmsgs[i] = i + 1 + ". " + errmsgs[i];
  146. }
  147. }
  148. filterContext.Result = ResultData(errmsgs, false, "数据校验失败,错误信息:" + errmsgs.Join(" | "), user != null, HttpStatusCode.BadRequest);
  149. }
  150. /// <summary>在调用操作方法后调用。</summary>
  151. /// <param name="filterContext">有关当前请求和操作的信息。</param>
  152. public override void OnActionExecuted(ActionExecutedContext filterContext)
  153. {
  154. if (filterContext.Result is ViewResult)
  155. {
  156. ViewBag.menus = MenuService.GetQueryFromCache(m => m.ParentId == null && m.Status == Status.Available).OrderBy(m => m.Sort).ToList(); //菜单
  157. var model = new PageFootViewModel //页脚
  158. {
  159. Links = LinksService.GetQuery(l => l.Status == Status.Available).OrderByDescending(l => l.Recommend).ThenByDescending(l => l.Loopbacks.Count).Take(30).ProjectTo<LinksDto>(MapperConfig).Cacheable().ToList()
  160. };
  161. ViewBag.Footer = model;
  162. }
  163. }
  164. /// <summary>
  165. /// 验证邮箱验证码
  166. /// </summary>
  167. /// <param name="mailSender"></param>
  168. /// <param name="email">邮箱地址</param>
  169. /// <param name="code">验证码</param>
  170. /// <returns></returns>
  171. internal async Task<string> ValidateEmailCode(IMailSender mailSender, string email, string code)
  172. {
  173. if (CurrentUser.IsAdmin)
  174. {
  175. return string.Empty; ;
  176. }
  177. if (string.IsNullOrEmpty(Request.Cookies["ValidateKey"]))
  178. {
  179. if (string.IsNullOrEmpty(code))
  180. {
  181. return "请输入验证码!";
  182. }
  183. if (await RedisHelper.GetAsync("code:" + email) != code)
  184. {
  185. return "验证码错误!";
  186. }
  187. }
  188. else if (Request.Cookies["ValidateKey"].DesDecrypt(AppConfig.BaiduAK) != email)
  189. {
  190. Response.Cookies.Delete("Email");
  191. Response.Cookies.Delete("NickName");
  192. Response.Cookies.Delete("ValidateKey");
  193. return "邮箱验证信息已失效,请刷新页面后重新评论!";
  194. }
  195. if (mailSender.HasBounced(email))
  196. {
  197. Response.Cookies.Delete("Email");
  198. Response.Cookies.Delete("NickName");
  199. Response.Cookies.Delete("ValidateKey");
  200. return "邮箱地址错误,请刷新页面后重新使用有效的邮箱地址!";
  201. }
  202. return string.Empty;
  203. }
  204. internal void WriteEmailKeyCookie(string email)
  205. {
  206. Response.Cookies.Append("Email", email, new CookieOptions()
  207. {
  208. Expires = DateTimeOffset.Now.AddYears(1),
  209. SameSite = SameSiteMode.Lax
  210. });
  211. Response.Cookies.Append("ValidateKey", email.DesEncrypt(AppConfig.BaiduAK), new CookieOptions()
  212. {
  213. Expires = DateTimeOffset.Now.AddYears(1),
  214. SameSite = SameSiteMode.Lax
  215. });
  216. }
  217. protected void CheckPermission(List<PostDto> posts)
  218. {
  219. if (CurrentUser.IsAdmin || VisitorTokenValid || Request.IsRobot())
  220. {
  221. return;
  222. }
  223. var location = Request.Location() + "|" + string.Join("", Request.Headers.Values);
  224. posts.RemoveAll(p =>
  225. {
  226. switch (p.LimitMode)
  227. {
  228. case RegionLimitMode.AllowRegion:
  229. return !location.Contains(p.Regions.Split(',', StringSplitOptions.RemoveEmptyEntries));
  230. case RegionLimitMode.ForbidRegion:
  231. return location.Contains(p.Regions.Split(',', StringSplitOptions.RemoveEmptyEntries));
  232. case RegionLimitMode.AllowRegionExceptForbidRegion:
  233. if (location.Contains(p.ExceptRegions.Split(',', StringSplitOptions.RemoveEmptyEntries)))
  234. {
  235. return true;
  236. }
  237. goto case RegionLimitMode.AllowRegion;
  238. case RegionLimitMode.ForbidRegionExceptAllowRegion:
  239. if (location.Contains(p.ExceptRegions.Split(',', StringSplitOptions.RemoveEmptyEntries)))
  240. {
  241. return false;
  242. }
  243. goto case RegionLimitMode.ForbidRegion;
  244. default:
  245. return false;
  246. }
  247. });
  248. }
  249. protected void CheckPermission(Post post)
  250. {
  251. if (CurrentUser.IsAdmin || VisitorTokenValid || Request.IsRobot())
  252. {
  253. return;
  254. }
  255. var location = Request.Location() + "|" + string.Join("", Request.Headers.Values);
  256. switch (post.LimitMode)
  257. {
  258. case RegionLimitMode.AllowRegion:
  259. if (!location.Contains(post.Regions.Split(',', StringSplitOptions.RemoveEmptyEntries)))
  260. {
  261. Disallow(post);
  262. }
  263. break;
  264. case RegionLimitMode.ForbidRegion:
  265. if (location.Contains(post.Regions.Split(',', StringSplitOptions.RemoveEmptyEntries)))
  266. {
  267. Disallow(post);
  268. }
  269. break;
  270. case RegionLimitMode.AllowRegionExceptForbidRegion:
  271. if (location.Contains(post.ExceptRegions.Split(',', StringSplitOptions.RemoveEmptyEntries)))
  272. {
  273. Disallow(post);
  274. }
  275. goto case RegionLimitMode.AllowRegion;
  276. case RegionLimitMode.ForbidRegionExceptAllowRegion:
  277. if (location.Contains(post.ExceptRegions.Split(',', StringSplitOptions.RemoveEmptyEntries)))
  278. {
  279. break;
  280. }
  281. goto case RegionLimitMode.ForbidRegion;
  282. }
  283. }
  284. private void Disallow(Post post)
  285. {
  286. RedisHelper.IncrBy("interceptCount");
  287. RedisHelper.LPush("intercept", new IpIntercepter()
  288. {
  289. IP = ClientIP,
  290. RequestUrl = $"//{Request.Host}/{post.Id}",
  291. Referer = Request.Headers[HeaderNames.Referer],
  292. Time = DateTime.Now,
  293. UserAgent = Request.Headers[HeaderNames.UserAgent],
  294. Remark = "无权限查看该文章",
  295. Address = Request.Location(),
  296. HttpVersion = Request.Protocol,
  297. Headers = Request.Headers.ToJsonString()
  298. });
  299. throw new NotFoundException("文章未找到");
  300. }
  301. }
  302. }