diff --git a/.gitignore b/.gitignore index 97072da3..99804f89 100644 --- a/.gitignore +++ b/.gitignore @@ -359,3 +359,4 @@ Blog.Core.Api/Logs *.db /Blog.Core.Api/WMBlog.db-journal .docs/.vuepress/dist/ +Blog.Core.Api/wwwroot/Temp/Sessions diff --git a/Blog.Core.Api/Blog.Core.Api.csproj b/Blog.Core.Api/Blog.Core.Api.csproj index 92a680da..8bc327ab 100644 --- a/Blog.Core.Api/Blog.Core.Api.csproj +++ b/Blog.Core.Api/Blog.Core.Api.csproj @@ -1,13 +1,12 @@  - Exe enable Linux true - + default @@ -47,6 +46,10 @@ + + + + @@ -92,6 +95,9 @@ Always + + PreserveNewest + diff --git a/Blog.Core.Api/Blog.Core.Model.xml b/Blog.Core.Api/Blog.Core.Model.xml index 8de49a7b..00b4c5be 100644 --- a/Blog.Core.Api/Blog.Core.Model.xml +++ b/Blog.Core.Api/Blog.Core.Model.xml @@ -676,6 +676,11 @@ 菜单图标 + + + 菜单图标新 + + 菜单描述 diff --git a/Blog.Core.Api/Blog.Core.xml b/Blog.Core.Api/Blog.Core.xml index dfe9950a..0ed61c91 100644 --- a/Blog.Core.Api/Blog.Core.xml +++ b/Blog.Core.Api/Blog.Core.xml @@ -255,7 +255,7 @@ 接口管理 - + 获取全部接口api @@ -758,21 +758,6 @@ Values控制器 - - - ValuesController - - - - - - - - - - - - 测试Rabbit消息队列发送 @@ -894,6 +879,25 @@ 通过此处的key格式为 xx:xx:x + + + 获取雪花Id + + + + + + 测试缓存 + + + + + + 雪花Id To DateTime + + + + WeChatCompanyController @@ -1321,11 +1325,32 @@ + + + SqlSugar 相关测试 + + + + + SqlSugar 相关测试 + + + + + 测试建表后,SqlSugar缓存 + + + 缓存管理 + + + 缓存管理 + + 获取全部缓存 @@ -1520,28 +1545,54 @@ - + - 自定义路由 /api/{version}/[controler]/[action] + 枚举测试 - + - 分组名称,是来实现接口 IApiDescriptionGroupNameProvider + 获取学生信息 + 学生类型 + + + 学生信息 - + - 自定义路由构造函数,继承基类路由 + 学生类型 - - + - 自定义版本+路由构造函数,继承基类路由 + 小学生 + + + + + 中学生 + + + + + 大学生 + + + + + 学生姓名 + + + + + 学生年龄 + + + + + 学生类型 - - @@ -1584,5 +1635,28 @@ 全局路由前缀公约 + + + 自定义路由 /api/{version}/[controler]/[action] + + + + + 分组名称,是来实现接口 IApiDescriptionGroupNameProvider + + + + + 自定义路由构造函数,继承基类路由 + + + + + + 自定义版本+路由构造函数,继承基类路由 + + + + diff --git a/Blog.Core.Api/Controllers/DbFirst/DbFirstController.cs b/Blog.Core.Api/Controllers/DbFirst/DbFirstController.cs index 83b0beb5..553dee75 100644 --- a/Blog.Core.Api/Controllers/DbFirst/DbFirstController.cs +++ b/Blog.Core.Api/Controllers/DbFirst/DbFirstController.cs @@ -43,12 +43,12 @@ public MessageModel GetFrameFiles() BaseDBConfig.ValidConfig.ForEach(m => { - _sqlSugarClient.ChangeDatabase(m.ConfigId.ToLower()); - data.response += $"库{m.ConfigId}-Model层生成:{FrameSeed.CreateModels(_sqlSugarClient, m.ConfigId, isMuti)} || "; - data.response += $"库{m.ConfigId}-IRepositorys层生成:{FrameSeed.CreateIRepositorys(_sqlSugarClient, m.ConfigId, isMuti)} || "; - data.response += $"库{m.ConfigId}-IServices层生成:{FrameSeed.CreateIServices(_sqlSugarClient, m.ConfigId, isMuti)} || "; - data.response += $"库{m.ConfigId}-Repository层生成:{FrameSeed.CreateRepository(_sqlSugarClient, m.ConfigId, isMuti)} || "; - data.response += $"库{m.ConfigId}-Services层生成:{FrameSeed.CreateServices(_sqlSugarClient, m.ConfigId, isMuti)} || "; + _sqlSugarClient.ChangeDatabase(m.ConfigId.ToString().ToLower()); + data.response += $"库{m.ConfigId}-Model层生成:{FrameSeed.CreateModels(_sqlSugarClient, m.ConfigId.ToString(), isMuti)} || "; + data.response += $"库{m.ConfigId}-IRepositorys层生成:{FrameSeed.CreateIRepositorys(_sqlSugarClient, m.ConfigId.ToString(), isMuti)} || "; + data.response += $"库{m.ConfigId}-IServices层生成:{FrameSeed.CreateIServices(_sqlSugarClient, m.ConfigId.ToString(), isMuti)} || "; + data.response += $"库{m.ConfigId}-Repository层生成:{FrameSeed.CreateRepository(_sqlSugarClient, m.ConfigId.ToString(), isMuti)} || "; + data.response += $"库{m.ConfigId}-Services层生成:{FrameSeed.CreateServices(_sqlSugarClient, m.ConfigId.ToString(), isMuti)} || "; }); // 切回主库 diff --git a/Blog.Core.Api/Controllers/LoginController.cs b/Blog.Core.Api/Controllers/LoginController.cs index f3e9accc..5b40773d 100644 --- a/Blog.Core.Api/Controllers/LoginController.cs +++ b/Blog.Core.Api/Controllers/LoginController.cs @@ -65,7 +65,7 @@ public async Task> GetJwtStr(string name, string pass) { string jwtStr = string.Empty; bool suc = false; - //这里就是用户登陆以后,通过数据库去调取数据,分配权限的操作 + //这里就是用户登录以后,通过数据库去调取数据,分配权限的操作 var user = await _sysUserInfoServices.GetUserRoleNameStr(name, MD5Helper.MD5Encrypt32(pass)); if (user != null) @@ -101,7 +101,7 @@ public MessageModel GetJwtStrForNuxt(string name, string pass) { string jwtStr = string.Empty; bool suc = false; - //这里就是用户登陆以后,通过数据库去调取数据,分配权限的操作 + //这里就是用户登录以后,通过数据库去调取数据,分配权限的操作 //这里直接写死了 if (name == "admins" && pass == "admins") { diff --git a/Blog.Core.Api/Controllers/ModuleController.cs b/Blog.Core.Api/Controllers/ModuleController.cs index 27e6f4db..3b286808 100644 --- a/Blog.Core.Api/Controllers/ModuleController.cs +++ b/Blog.Core.Api/Controllers/ModuleController.cs @@ -34,15 +34,14 @@ public ModuleController(IModuleServices moduleServices, IUser user) /// // GET: api/User [HttpGet] - public async Task>> Get(int page = 1, string key = "") + public async Task>> Get(int page = 1, string key = "", int pageSize = 50) { if (string.IsNullOrEmpty(key) || string.IsNullOrWhiteSpace(key)) { key = ""; } - int intPageSize = 50; - Expression> whereExpression = a => a.IsDeleted != true && (a.Name != null && a.Name.Contains(key)); + Expression> whereExpression = a => a.IsDeleted != true && ((a.Name != null && a.Name.Contains(key) || (a.LinkUrl != null && a.LinkUrl.Contains(key)))); PageModel data = new PageModel(); @@ -53,7 +52,7 @@ public async Task>> Get(int page = 1, string key } else { - data = await _moduleServices.QueryPage(whereExpression, page, intPageSize, " Id desc "); + data = await _moduleServices.QueryPage(whereExpression, page, pageSize, " Id desc "); } diff --git a/Blog.Core.Api/Controllers/MonitorController.cs b/Blog.Core.Api/Controllers/MonitorController.cs index 9ed96672..d707d497 100644 --- a/Blog.Core.Api/Controllers/MonitorController.cs +++ b/Blog.Core.Api/Controllers/MonitorController.cs @@ -26,12 +26,13 @@ public class MonitorController : BaseApiController private readonly IApplicationUserServices _applicationUserServices; private readonly ILogger _logger; - public MonitorController(IHubContext hubContext, IWebHostEnvironment env, IApplicationUserServices applicationUserServices, ILogger logger) + public MonitorController(IHubContext hubContext, IWebHostEnvironment env, + IApplicationUserServices applicationUserServices, ILogger logger) { - _hubContext = hubContext; - _env = env; + _hubContext = hubContext; + _env = env; _applicationUserServices = applicationUserServices; - _logger = logger; + _logger = logger; } /// @@ -43,13 +44,13 @@ public MessageModel Server() { return Success(new ServerViewModel() { - EnvironmentName = _env.EnvironmentName, - OSArchitecture = RuntimeInformation.OSArchitecture.ObjToString(), - ContentRootPath = _env.ContentRootPath, - WebRootPath = _env.WebRootPath, + EnvironmentName = _env.EnvironmentName, + OSArchitecture = RuntimeInformation.OSArchitecture.ObjToString(), + ContentRootPath = _env.ContentRootPath, + WebRootPath = _env.WebRootPath, FrameworkDescription = RuntimeInformation.FrameworkDescription, - MemoryFootprint = (Process.GetCurrentProcess().WorkingSet64 / 1048576).ToString("N2") + " MB", - WorkingTime = DateHelper.TimeSubTract(DateTime.Now, Process.GetCurrentProcess().StartTime) + MemoryFootprint = (Process.GetCurrentProcess().WorkingSet64 / 1048576).ToString("N2") + " MB", + WorkingTime = DateHelper.TimeSubTract(DateTime.Now, Process.GetCurrentProcess().StartTime) }, "获取服务器配置信息成功"); } @@ -64,17 +65,18 @@ public MessageModel> Get() { if (AppSettings.app(new string[] { "Middleware", "SignalRSendLog", "Enabled" }).ObjToBool()) { - _hubContext.Clients.All.SendAsync("ReceiveUpdate", LogLock.GetLogData()).Wait(); + _hubContext.Clients.All.SendAsync("ReceiveUpdate", "执行成功").Wait(); } + return Success>(null, "执行成功"); } - [HttpGet] public MessageModel GetRequestApiinfoByWeek() { - return Success(LogLock.RequestApiinfoByWeek(), "成功"); + //后续补充扩展Log + return Success(new RequestApiWeekView(), "成功"); } [HttpGet] @@ -87,7 +89,8 @@ public MessageModel GetAccessApiByDate() // response = LogLock.AccessApiByDate() //}; - return Success(LogLock.AccessApiByDate(), "获取成功"); + //后续补充扩展Log + return Success(new AccessApiDateView(), "获取成功"); } [HttpGet] @@ -100,70 +103,74 @@ public MessageModel GetAccessApiByHour() // response = LogLock.AccessApiByHour() //}; - return Success(LogLock.AccessApiByHour(), "获取成功"); + //后续补充扩展Log + return Success(new AccessApiDateView(), "获取成功"); } private List GetAccessLogsToday(IWebHostEnvironment environment) { List userAccessModels = new(); - var accessLogs = LogLock.ReadLog( - Path.Combine(environment.ContentRootPath, "Log"), "RecordAccessLogs_", Encoding.UTF8, ReadType.PrefixLatest - ).ObjToString(); - try - { - return JsonConvert.DeserializeObject>("[" + accessLogs + "]"); - } - catch (Exception) - { - var accLogArr = accessLogs.Split("\n"); - foreach (var item in accLogArr) - { - if (item.ObjToString() != "") - { - try - { - var accItem = JsonConvert.DeserializeObject(item.TrimEnd(',')); - userAccessModels.Add(accItem); - } - catch (Exception) - { - } - } - } - - } + //后续补充扩展Log + // var accessLogs = LogLock.ReadLog( + // Path.Combine(environment.ContentRootPath, "Log"), "RecordAccessLogs_", Encoding.UTF8, + // ReadType.PrefixLatest + // ).ObjToString(); + // try + // { + // return JsonConvert.DeserializeObject>("[" + accessLogs + "]"); + // } + // catch (Exception) + // { + // var accLogArr = accessLogs.Split("\n"); + // foreach (var item in accLogArr) + // { + // if (item.ObjToString() != "") + // { + // try + // { + // var accItem = JsonConvert.DeserializeObject(item.TrimEnd(',')); + // userAccessModels.Add(accItem); + // } + // catch (Exception) + // { + // } + // } + // } + // } return userAccessModels; } + private List GetAccessLogsTrend(IWebHostEnvironment environment) { List userAccessModels = new(); - var accessLogs = LogLock.ReadLog( - Path.Combine(environment.ContentRootPath, "Log"), "ACCESSTRENDLOG_", Encoding.UTF8, ReadType.PrefixLatest - ).ObjToString(); - try - { - return JsonConvert.DeserializeObject>(accessLogs); - } - catch (Exception) - { - var accLogArr = accessLogs.Split("\n"); - foreach (var item in accLogArr) - { - if (item.ObjToString() != "") - { - try - { - var accItem = JsonConvert.DeserializeObject(item.TrimStart('[').TrimEnd(']')); - userAccessModels.Add(accItem); - } - catch (Exception) - { - } - } - } - - } + //后续补充扩展Log + // var accessLogs = LogLock.ReadLog( + // Path.Combine(environment.ContentRootPath, "Log"), "ACCESSTRENDLOG_", Encoding.UTF8, + // ReadType.PrefixLatest + // ).ObjToString(); + // try + // { + // return JsonConvert.DeserializeObject>(accessLogs); + // } + // catch (Exception) + // { + // var accLogArr = accessLogs.Split("\n"); + // foreach (var item in accLogArr) + // { + // if (item.ObjToString() != "") + // { + // try + // { + // var accItem = JsonConvert.DeserializeObject(item.TrimStart('[').TrimEnd(']')); + // userAccessModels.Add(accItem); + // } + // catch (Exception) + // { + // } + // } + // } + // } return userAccessModels; } @@ -175,17 +182,16 @@ public MessageModel GetActiveUsers([FromServices] IWebHostEnvir var Logs = accessLogsToday.OrderByDescending(d => d.BeginTime).Take(50).ToList(); - var errorCountToday = LogLock.GetLogData().Where(d => d.Import == 9).Count(); - accessLogsToday = accessLogsToday.Where(d => d.User != "").ToList(); var activeUsers = (from n in accessLogsToday - group n by new { n.User } into g - select new ActiveUserVM - { - user = g.Key.User, - count = g.Count(), - }).ToList(); + group n by new { n.User } + into g + select new ActiveUserVM + { + user = g.Key.User, + count = g.Count(), + }).ToList(); int activeUsersCount = activeUsers.Count; activeUsers = activeUsers.OrderByDescending(d => d.count).Take(10).ToList(); @@ -206,11 +212,11 @@ public MessageModel GetActiveUsers([FromServices] IWebHostEnvir return Success(new WelcomeInitData() { - activeUsers = activeUsers, + activeUsers = activeUsers, activeUserCount = activeUsersCount, - errorCount = errorCountToday, - logs = Logs, - activeCount = GetAccessLogsTrend(environment) + errorCount = default, + logs = Logs, + activeCount = GetAccessLogsTrend(environment) }, "获取成功"); } @@ -224,12 +230,13 @@ public async Task> GetIds4Users() var users = await _applicationUserServices.Query(d => d.tdIsDelete == false); apiDates = (from n in users - group n by new { n.birth.Date } into g - select new ApiDate - { - date = g.Key?.Date.ToString("yyyy-MM-dd"), - count = g.Count(), - }).ToList(); + group n by new { n.birth.Date } + into g + select new ApiDate + { + date = g.Key?.Date.ToString("yyyy-MM-dd"), + count = g.Count(), + }).ToList(); apiDates = apiDates.OrderByDescending(d => d.date).Take(30).ToList(); } @@ -239,7 +246,7 @@ public async Task> GetIds4Users() { apiDates.Add(new ApiDate() { - date = "没数据,或未开启相应接口服务", + date = "没数据,或未开启相应接口服务", count = 0 }); } @@ -257,10 +264,9 @@ public async Task> GetIds4Users() return Success(new AccessApiDateView { columns = new string[] { "date", "count" }, - rows = apiDates.OrderBy(d => d.date).ToList(), + rows = apiDates.OrderBy(d => d.date).ToList(), }, "获取成功"); } - } public class WelcomeInitData @@ -271,5 +277,4 @@ public class WelcomeInitData public int errorCount { get; set; } public List activeCount { get; set; } } - -} +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/PermissionController.cs b/Blog.Core.Api/Controllers/PermissionController.cs index 13f8125a..ccdb3dc4 100644 --- a/Blog.Core.Api/Controllers/PermissionController.cs +++ b/Blog.Core.Api/Controllers/PermissionController.cs @@ -398,6 +398,7 @@ orderby child.Id IsButton = child.IsButton.ObjToBool(), meta = new NavigationBarMeta { + icon = child.IconNew, requireAuth = true, title = child.Name, NoTabPage = child.IsHide.ObjToBool(), diff --git a/Blog.Core.Api/Controllers/SqlSugarTestController.cs b/Blog.Core.Api/Controllers/SqlSugarTestController.cs new file mode 100644 index 00000000..f43e32bf --- /dev/null +++ b/Blog.Core.Api/Controllers/SqlSugarTestController.cs @@ -0,0 +1,61 @@ +using System.Text; +using Blog.Core.Common.DB.Extension; +using Blog.Core.Controllers; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using SqlSugar; + +namespace Blog.Core.Api.Controllers; + +/// +/// SqlSugar 相关测试 +/// +[Route("api/[controller]/[action]")] +[ApiController] +[AllowAnonymous] +public class SqlSugarTestController(ISqlSugarClient db) : BaseApiController +{ + /// + /// 测试建表后,SqlSugar缓存 + /// + /// + [HttpGet] + public MessageModel ClearDbTableCache() + { + var tableName = "BlogArticle_Test"; + + //先删除表 + try + { + db.DbMaintenance.DropTable(tableName); + db.ClearDbTableCache(); + } + catch + { + //Ignore + } + + StringBuilder sb = new StringBuilder(); + + //提前检查表是否存在,测试缓存 + sb.AppendLine($"表{tableName} 是否存在:{db.DbMaintenance.IsAnyTable(tableName)}"); + + //创建表 + db.CodeFirst.As(tableName).InitTables(); + sb.AppendLine($"表{tableName} 创建成功"); + + //检查表是否存在 + sb.AppendLine($"表{tableName} 是否存在:{db.DbMaintenance.IsAnyTable(tableName)}"); + + //清除缓存 + db.ClearDbTableCache(); + sb.AppendLine($"清除缓存后"); + + //检查表是否存在 + sb.AppendLine($"表{tableName} 是否存在:{db.DbMaintenance.IsAnyTable(tableName)}"); + + return Success(sb.ToString()); + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/Systems/CacheManageController.cs b/Blog.Core.Api/Controllers/Systems/CacheManageController.cs index 4f400e8f..ef276c17 100644 --- a/Blog.Core.Api/Controllers/Systems/CacheManageController.cs +++ b/Blog.Core.Api/Controllers/Systems/CacheManageController.cs @@ -1,4 +1,5 @@ using Blog.Core.Common.Caches; +using Blog.Core.Common.Caches.Interface; using Blog.Core.Controllers; using Blog.Core.Model; using Microsoft.AspNetCore.Authorization; @@ -12,70 +13,63 @@ namespace Blog.Core.Api.Controllers.Systems; [Route("api/Systems/[controller]")] [ApiController] [Authorize(Permissions.Name)] -public class CacheManageController : BaseApiController +public class CacheManageController(ICaching caching) : BaseApiController { - private readonly ICaching _caching; + /// + /// 获取全部缓存 + /// + /// + [HttpGet] + public MessageModel> Get() + { + return Success(caching.GetAllCacheKeys()); + } - public CacheManageController(ICaching caching) - { - _caching = caching; - } + /// + /// 获取缓存 + /// + /// + [HttpGet("{key}")] + public async Task> Get(string key) + { + return Success(await caching.GetStringAsync(key)); + } - /// - /// 获取全部缓存 - /// - /// - [HttpGet] - public async Task>> Get() - { - return Success(await _caching.GetAllCacheKeysAsync()); - } + /// + /// 新增 + /// + /// + [HttpPost] + public async Task Post([FromQuery] string key, [FromQuery] string value, [FromQuery] int? expire) + { + if (expire.HasValue) + await caching.SetStringAsync(key, value, TimeSpan.FromMilliseconds(expire.Value)); + else + await caching.SetStringAsync(key, value); - /// - /// 获取缓存 - /// - /// - [HttpGet("{key}")] - public async Task> Get(string key) - { - return Success(await _caching.GetStringAsync(key)); - } + return Success(); + } - /// - /// 新增 - /// - /// - [HttpPost] - public async Task Post([FromQuery] string key, [FromQuery] string value, [FromQuery] int? expire) - { - if (expire.HasValue) - await _caching.SetStringAsync(key, value, TimeSpan.FromMilliseconds(expire.Value)); - else - await _caching.SetStringAsync(key, value); + /// + /// 删除全部缓存 + /// + /// + [HttpDelete] + public MessageModel Delete() + { + caching.RemoveAll(); + return Success(); + } - return Success(); - } - - /// - /// 删除全部缓存 - /// - /// - [HttpDelete] - public async Task Delete() - { - await _caching.RemoveAllAsync(); - return Success(); - } - - /// - /// 删除缓存 - /// - /// - [Route("{key}")] - [HttpDelete] - public async Task Delete(string key) - { - await _caching.RemoveAsync(key); - return Success(); - } + /// + /// 删除缓存 + /// + /// + [Route("{key}")] + [HttpDelete] + public async Task Delete(string key) + { + await caching.RemoveAsync(key); + return Success(); + } } \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/Test/EnumTestController.cs b/Blog.Core.Api/Controllers/Test/EnumTestController.cs new file mode 100644 index 00000000..29a85ac6 --- /dev/null +++ b/Blog.Core.Api/Controllers/Test/EnumTestController.cs @@ -0,0 +1,75 @@ +using System.ComponentModel; +using Blog.Core.Controllers; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Blog.Core.Api.Controllers.Test; + +/// +/// 枚举测试 +/// +[Route("api/[Controller]/[Action]")] +[AllowAnonymous] +public class EnumTestController : BaseApiController +{ + /// + /// 获取学生信息 + /// + /// 学生类型 + /// + /// + /// 学生信息 + [HttpGet] + public Student GetStudent( StudentType studentType, StudentType? studentType2, List studentTypes) + { + return new Student + { + Name = "张三", + Age = 20, + Type = studentType + }; + } +} + +/// +/// 学生类型 +/// +[Description("学生类型")] +public enum StudentType +{ + /// + /// 小学生 + /// + [Description("小学生")] + PrimarySchool = 1, + + /// + /// 中学生 + /// + [Description("中学生")] + MiddleSchool = 2, + + /// + /// 大学生 + /// + [Description("大学生")] + University = 3 +} + +public class Student +{ + /// + /// 学生姓名 + /// + public string Name { get; set; } + + /// + /// 学生年龄 + /// + public int Age { get; set; } + + /// + /// 学生类型 + /// + public StudentType Type { get; set; } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/Test/SqlsugarTestController.cs b/Blog.Core.Api/Controllers/Test/SqlsugarTestController.cs new file mode 100644 index 00000000..774a9b12 --- /dev/null +++ b/Blog.Core.Api/Controllers/Test/SqlsugarTestController.cs @@ -0,0 +1,29 @@ +using Blog.Core.Common; +using Blog.Core.Controllers; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using SqlSugar; + +namespace Blog.Core.Api.Controllers.Test; + +[Route("api/[Controller]/[Action]")] +[AllowAnonymous] +public class SqlsugarTestController : BaseApiController +{ + private readonly SqlSugarScope _db; + + public SqlsugarTestController(SqlSugarScope db) + { + _db = db; + } + + [HttpGet] + public async Task Get() + { + Console.WriteLine(App.HttpContext.Request.Path); + Console.WriteLine(App.HttpContext.RequestServices.ToString()); + Console.WriteLine(App.User?.ID); + await Task.CompletedTask; + return Ok(); + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/ValuesController.cs b/Blog.Core.Api/Controllers/ValuesController.cs index 9d0c1558..22d663e9 100644 --- a/Blog.Core.Api/Controllers/ValuesController.cs +++ b/Blog.Core.Api/Controllers/ValuesController.cs @@ -19,6 +19,8 @@ using System.ComponentModel.DataAnnotations; using System.Linq.Expressions; using System.Text; +using Blog.Core.Common.Caches.Interface; +using Blog.Core.Common.Utility; namespace Blog.Core.Controllers { @@ -44,34 +46,19 @@ public class ValuesController : BaseApiController private readonly IHttpPollyHelper _httpPollyHelper; private readonly IRabbitMQPersistentConnection _persistentConnection; private readonly SeqOptions _seqOptions; - - /// - /// ValuesController - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public ValuesController(IBlogArticleServices blogArticleServices - , IMapper mapper - , IAdvertisementServices advertisementServices - , Love love - , IRoleModulePermissionServices roleModulePermissionServices - , IUser user, IPasswordLibServices passwordLibServices - , IHttpPollyHelper httpPollyHelper - , IRabbitMQPersistentConnection persistentConnection - , IOptions seqOptions) + private readonly ICaching _cache; + + public ValuesController(IBlogArticleServices blogArticleServices, IMapper mapper, + IAdvertisementServices advertisementServices, Love love, + IRoleModulePermissionServices roleModulePermissionServices, IUser user, + IPasswordLibServices passwordLibServices, + IHttpPollyHelper httpPollyHelper, IRabbitMQPersistentConnection persistentConnection, + IOptions seqOptions, ICaching caching) { // 测试 Authorize 和 mapper - _mapper = mapper; - _advertisementServices = advertisementServices; - _love = love; + _mapper = mapper; + _advertisementServices = advertisementServices; + _love = love; _roleModulePermissionServices = roleModulePermissionServices; // 测试 Httpcontext _user = user; @@ -82,9 +69,10 @@ public ValuesController(IBlogArticleServices blogArticleServices // 测试redis消息队列 _blogArticleServices = blogArticleServices; // httpPolly - _httpPollyHelper = httpPollyHelper; + _httpPollyHelper = httpPollyHelper; _persistentConnection = persistentConnection; - _seqOptions = seqOptions.Value; + _cache = caching; + _seqOptions = seqOptions.Value; } /// @@ -98,7 +86,9 @@ public IActionResult TestRabbitMqPublish() { _persistentConnection.TryConnect(); } - _persistentConnection.PublishMessage("Hello, RabbitMQ!", exchangeName: "blogcore", routingKey: "myRoutingKey"); + + _persistentConnection.PublishMessage("Hello, RabbitMQ!", exchangeName: "blogcore", + routingKey: "myRoutingKey"); return Ok(); } @@ -118,7 +108,8 @@ public IActionResult TestRabbitMqSubscribe() return Ok(); } - private async Task Dealer(string exchange, string routingKey, byte[] msgBody, IDictionary headers) + private async Task Dealer(string exchange, string routingKey, byte[] msgBody, + IDictionary headers) { await Task.CompletedTask; Console.WriteLine("我是消费者,这里消费了一条信息是:" + Encoding.UTF8.GetString(msgBody)); @@ -134,7 +125,7 @@ public MessageModel> MyClaims() response = (_user.GetClaimsIdentity().ToList()).Select(d => new ClaimDto { - Type = d.Type, + Type = d.Type, Value = d.Value } ).ToList() @@ -176,18 +167,18 @@ await _blogArticleServices.QuerySql( * 测试按照指定列查询 */ var queryByColums = await _blogArticleServices - .Query(it => new BlogViewModels() { btitle = it.btitle }); + .Query(it => new BlogViewModels() { btitle = it.btitle }); /* - * 测试按照指定列查询带多条件和排序方法 - */ + * 测试按照指定列查询带多条件和排序方法 + */ Expression> registerInfoWhere = a => a.btitle == "xxx" && a.bRemark == "XXX"; var queryByColumsByMultiTerms = await _blogArticleServices - .Query(it => new BlogArticle() { btitle = it.btitle }, registerInfoWhere, "bID Desc"); + .Query(it => new BlogArticle() { btitle = it.btitle }, registerInfoWhere, "bID Desc"); /* * 测试 sql 更新 - * + * * 【SQL参数】:@bID:5 * @bsubmitter:laozhang619 * @IsDeleted:False @@ -195,7 +186,7 @@ await _blogArticleServices.QuerySql( * `bsubmitter`=@bsubmitter,`IsDeleted`=@IsDeleted WHERE `bID`=@bID */ var updateSql = await _blogArticleServices.Update(new - { bsubmitter = $"laozhang{DateTime.Now.Millisecond}", IsDeleted = false, bID = 5 }); + { bsubmitter = $"laozhang{DateTime.Now.Millisecond}", IsDeleted = false, bID = 5 }); // 测试 AOP 缓存 @@ -208,9 +199,9 @@ await _blogArticleServices.QuerySql( // 测试多个异步执行时间 var roleModuleTask = _roleModulePermissionServices.Query(); - var listTask = _advertisementServices.Query(); - var ad = await roleModuleTask; - var list = await listTask; + var listTask = _advertisementServices.Query(); + var ad = await roleModuleTask; + var list = await listTask; // 测试service层返回异常 @@ -304,8 +295,8 @@ public MessageModel> GetUserInfo(string ClaimType = "jti") var getUserInfoByToken = _user.GetUserInfoFromToken(ClaimType); return new MessageModel>() { - success = _user.IsAuthenticated(), - msg = _user.IsAuthenticated() ? _user.Name.ObjToString() : "未登录", + success = _user.IsAuthenticated(), + msg = _user.IsAuthenticated() ? _user.Name.ObjToString() : "未登录", response = _user.GetClaimValueByType(ClaimType) }; } @@ -367,11 +358,11 @@ public object TestPostPara(string name) public async Task TestMutiDBAPI() { // 从主库中,操作blogs - var blogs = await _blogArticleServices.Query(d => d.bID == 1); + var blogs = await _blogArticleServices.Query(d => d.bID == 1); var addBlog = await _blogArticleServices.Add(new BlogArticle() { }); // 从从库中,操作pwds - var pwds = await _passwordLibServices.Query(d => d.PLID > 0); + var pwds = await _passwordLibServices.Query(d => d.PLID > 0); var addPwd = await _passwordLibServices.Add(new PasswordLib() { }); return new @@ -472,6 +463,54 @@ public string TestOption() { return _seqOptions.ToJson(); } + + /// + /// 获取雪花Id + /// + /// + [HttpGet] + [AllowAnonymous] + public long GetSnowflakeId() + { + return IdGeneratorUtility.NextId(); + } + + /// + /// 测试缓存 + /// + /// + [HttpGet] + [AllowAnonymous] + public async Task> TestCacheAsync() + { + await _cache.SetAsync("test", "test", new TimeSpan(0, 10, 0)); + + var result = await _cache.GetAsync("test"); + if (!"test".Equals(result)) + { + return Failed("缓存失败,值不一样"); + } + + var count = _cache.GetAllCacheKeys().Count; + if (count <= 0) + { + return Failed("缓存失败,数量不对"); + } + + return Success(""); + } + + /// + /// 雪花Id To DateTime + /// + /// + /// + [HttpGet] + [AllowAnonymous] + public DateTime SnowflakeIdToDateTime(long id) + { + return YitterSnowflakeHelper.GetDateTime(IdGeneratorUtility.GetOptions(), id); + } } public class ClaimDto diff --git a/Blog.Core.Api/Dockerfile b/Blog.Core.Api/Dockerfile index 1eb0572a..afb398c1 100644 --- a/Blog.Core.Api/Dockerfile +++ b/Blog.Core.Api/Dockerfile @@ -3,7 +3,7 @@ #FROM swr.cn-south-1.myhuaweicloud.com/mcr/aspnet:5.0-alpine #FROM mcr.microsoft.com/dotnet/core/aspnet:5.0-buster-slim -FROM mcr.microsoft.com/dotnet/aspnet:6.0-bullseye-slim +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo 'Asia/Shanghai' >/etc/timezone diff --git a/Blog.Core.Api/Filter/GlobalExceptionFilter.cs b/Blog.Core.Api/Filter/GlobalExceptionFilter.cs index 44c6124c..e11d8dbd 100644 --- a/Blog.Core.Api/Filter/GlobalExceptionFilter.cs +++ b/Blog.Core.Api/Filter/GlobalExceptionFilter.cs @@ -19,19 +19,20 @@ public class GlobalExceptionsFilter : IExceptionFilter private readonly IHubContext _hubContext; private readonly ILogger _loggerHelper; - public GlobalExceptionsFilter(IWebHostEnvironment env, ILogger loggerHelper, IHubContext hubContext) + public GlobalExceptionsFilter(IWebHostEnvironment env, ILogger loggerHelper, + IHubContext hubContext) { - _env = env; + _env = env; _loggerHelper = loggerHelper; - _hubContext = hubContext; + _hubContext = hubContext; } public void OnException(ExceptionContext context) { var json = new MessageModel(); - json.msg = context.Exception.Message;//错误信息 - json.status = 500;//500异常 + json.msg = context.Exception.Message; //错误信息 + json.status = 500; //500异常 var errorAudit = "Unable to resolve service for"; if (!string.IsNullOrEmpty(json.msg) && json.msg.Contains(errorAudit)) { @@ -40,8 +41,9 @@ public void OnException(ExceptionContext context) if (_env.EnvironmentName.ObjToString().Equals("Development")) { - json.msgDev = context.Exception.StackTrace;//堆栈信息 + json.msgDev = context.Exception.StackTrace; //堆栈信息 } + var res = new ContentResult(); res.Content = JsonHelper.GetJSON>(json); @@ -54,10 +56,8 @@ public void OnException(ExceptionContext context) _loggerHelper.LogError(json.msg + WriteLog(json.msg, context.Exception)); if (AppSettings.app(new string[] { "Middleware", "SignalRSendLog", "Enabled" }).ObjToBool()) { - _hubContext.Clients.All.SendAsync("ReceiveUpdate", LogLock.GetLogData()).Wait(); + _hubContext.Clients.All.SendAsync("ReceiveUpdate", json.msg).Wait(); } - - } /// @@ -68,11 +68,14 @@ public void OnException(ExceptionContext context) /// public string WriteLog(string throwMsg, Exception ex) { - return string.Format("\r\n【自定义错误】:{0} \r\n【异常类型】:{1} \r\n【异常信息】:{2} \r\n【堆栈调用】:{3}", new object[] { throwMsg, - ex.GetType().Name, ex.Message, ex.StackTrace }); + return string.Format("\r\n【自定义错误】:{0} \r\n【异常类型】:{1} \r\n【异常信息】:{2} \r\n【堆栈调用】:{3}", new object[] + { + throwMsg, + ex.GetType().Name, ex.Message, ex.StackTrace + }); } - } + public class InternalServerErrorObjectResult : ObjectResult { public InternalServerErrorObjectResult(object value) : base(value) @@ -80,6 +83,7 @@ public InternalServerErrorObjectResult(object value) : base(value) StatusCode = StatusCodes.Status500InternalServerError; } } + //返回错误信息 public class JsonErrorResponse { @@ -87,10 +91,10 @@ public class JsonErrorResponse /// 生产环境的消息 /// public string Message { get; set; } + /// /// 开发环境的消息 /// public string DevelopmentMessage { get; set; } } - -} +} \ No newline at end of file diff --git a/Blog.Core.Api/Program.cs b/Blog.Core.Api/Program.cs index 0b9dfd3f..bc2b51cc 100644 --- a/Blog.Core.Api/Program.cs +++ b/Blog.Core.Api/Program.cs @@ -97,6 +97,7 @@ .Configure(x => x.AllowSynchronousIO = true); builder.Services.AddSession(); +builder.Services.AddDataProtectionSetup(); builder.Services.AddControllers(o => { o.Filters.Add(typeof(GlobalExceptionsFilter)); diff --git a/Blog.Core.Api/appsettings.json b/Blog.Core.Api/appsettings.json index 673340be..d62aae9e 100644 --- a/Blog.Core.Api/appsettings.json +++ b/Blog.Core.Api/appsettings.json @@ -2,7 +2,7 @@ "urls": "http://*:9291", //web服务端口,如果用IIS部署,把这个去掉 "Serilog": { "MinimumLevel": { - "Default": "Debug", + "Default": "Information", //关闭日志1:修改Serilog的最低日志级别,比如Warning "Override": { "Microsoft": "Information", "Microsoft.AspNetCore": "Warning", @@ -19,7 +19,7 @@ "AllowedHosts": "*", "Redis": { "Enable": false, - "ConnectionString": "127.0.0.1:6379", + "ConnectionString": "127.0.0.1:6379,allowAdmin=true", "InstanceName": "" //前缀 }, "RabbitMQ": { @@ -62,7 +62,7 @@ "Enabled": false }, "SqlAOP": { - "Enabled": true, + "Enabled": true, //关闭日志2:修改Sql日志是否显示(也可以精准配置,是否生成到文件、数据库、控制台) "LogToFile": { "Enabled": true }, @@ -78,7 +78,8 @@ "SeedDBDataEnabled": true, //生成表,并初始化数据 "Author": "Blog.Core", "SvcName": "", // /svc/blog - "UseLoadTest": false + "UseLoadTest": false, + "CacheDbEnabled": false }, //优化DB配置、不会再区分单库多库 @@ -118,7 +119,7 @@ { "ConnId": "Main2", "DBType": 2, - "Enabled": true, + "Enabled": false, "Connection": "WMBlog3.db", //sqlite只写数据库名就行 "Slaves": [ { @@ -217,7 +218,7 @@ "Enabled": false //redis 消息队列 }, "MiniProfiler": { - "Enabled": false //性能分析开启 + "Enabled": true //性能分析开启 }, "Nacos": { "Enabled": false //Nacos注册中心 @@ -356,7 +357,7 @@ ] }, "Seq": { - "Enabled": true, + "Enabled": false, "Address": "http://localhost:5341/", "ApiKey": "" } diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.json/Permission.tsv b/Blog.Core.Api/wwwroot/BlogCore.Data.json/Permission.tsv index c026f01e..66b4313e 100644 --- a/Blog.Core.Api/wwwroot/BlogCore.Data.json/Permission.tsv +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/Permission.tsv @@ -7,6 +7,7 @@ "Mid": 0, "OrderSort": -90, "Icon": "fa-home", + "IconNew": "HomeFilled", "Description": "33", "Enabled": 1, "CreateId": 18, @@ -27,6 +28,7 @@ "Mid": 0, "OrderSort": 0, "Icon": "fa-users", + "IconNew": "UserFilled", "Description": "11", "Enabled": 1, "CreateId": 18, @@ -47,6 +49,7 @@ "Mid": 22, "OrderSort": 0, "Icon": null, + "IconNew": "Menu", "Description": null, "Enabled": 1, "CreateId": 18, @@ -67,6 +70,7 @@ "Mid": 7, "OrderSort": 0, "Icon": null, + "IconNew": "Menu", "Description": null, "Enabled": 1, "CreateId": 18, @@ -87,6 +91,7 @@ "Mid": 0, "OrderSort": 0, "Icon": "fa-sitemap", + "IconNew": "Lock", "Description": null, "Enabled": 1, "CreateId": 18, @@ -169,7 +174,7 @@ "Icon": null, "Description": "这个用户页的查询按钮", "Enabled": 1, - "Func": "getUsers", + "Func": "handleQuery", "CreateId": 18, "CreateBy": "提bug账号", "CreateTime": "\/Date(1546272000000+0800)\/", @@ -188,6 +193,7 @@ "Mid": 0, "OrderSort": 0, "Icon": "fa-line-chart", + "IconNew": "Histogram", "Description": null, "Enabled": 1, "CreateId": 18, @@ -319,7 +325,7 @@ "ModifyId": null, "ModifyBy": null, "ModifyTime": "\/Date(1546272000000+0800)\/", - "Func": "getRoles", + "Func": "handleQuery", "IsDeleted": 0, "Id": 16, "IsHide": 0 @@ -396,7 +402,7 @@ "OrderSort": 0, "Icon": null, "Description": "查询 接口", - "Func": "getModules", + "Func": "handleQuery", "Enabled": 1, "CreateId": 18, "CreateBy": "提bug账号", @@ -480,7 +486,7 @@ "OrderSort": 0, "Icon": null, "Description": "查询 菜单", - "Func": "getPermissions", + "Func": "handleQuery", "Enabled": 1, "CreateId": 18, "CreateBy": "提bug账号", @@ -583,6 +589,7 @@ "Mid": 0, "OrderSort": 0, "Icon": "fa-file-word-o", + "IconNew": "List", "Description": null, "Enabled": 1, "CreateId": 18, @@ -651,7 +658,7 @@ "CreateBy": "后台总管理员", "CreateTime": "\/Date(1546272000000+0800)\/", "ModifyId": null, - "Func": "getBugs", + "Func": "handleQuery", "ModifyBy": null, "ModifyTime": "\/Date(1546272000000+0800)\/", "IsDeleted": 0, @@ -746,6 +753,7 @@ "Mid": 0, "OrderSort": 0, "Icon": "fa-flask", + "IconNew": "WarningFilled", "Description": null, "Enabled": 1, "CreateId": 23, @@ -826,6 +834,7 @@ "Mid": 0, "OrderSort": 0, "Icon": "fa-language", + "IconNew": "HelpFilled", "Description": null, "Enabled": 1, "CreateId": 23, @@ -846,6 +855,7 @@ "Mid": 0, "OrderSort": 0, "Icon": "fa-bug", + "IconNew": "Flag", "Description": null, "Enabled": 1, "CreateId": 23, @@ -886,6 +896,7 @@ "Mid": 0, "OrderSort": 0, "Icon": "fa-sort-amount-asc", + "IconNew": "ChromeFilled", "Description": null, "Enabled": 1, "CreateId": 23, @@ -1307,6 +1318,7 @@ "Mid": 0, "OrderSort": 0, "Icon": "fa-diamond", + "IconNew": "Stamp", "Description": null, "Enabled": 1, "CreateId": 12, @@ -1367,6 +1379,7 @@ "Mid": 0, "OrderSort": 1, "Icon": "el-icon-s-operation", + "IconNew": "Tools", "Description": null, "Enabled": 1, "CreateId": 12, @@ -1471,7 +1484,7 @@ "Enabled": 1, "CreateId": 12, "CreateBy": "后台总管理员", - "Func": "getBlogs", + "Func": "handleQuery", "CreateTime": "\/Date(1546272000000+0800)\/", "ModifyId": null, "ModifyBy": null, @@ -1509,6 +1522,7 @@ "Mid": 0, "OrderSort": 1, "Icon": "fa-history", + "IconNew": "Ticket", "Description": null, "Enabled": 1, "CreateId": 12, @@ -1553,7 +1567,7 @@ "Enabled": 1, "CreateId": 12, "CreateBy": "后台总管理员", - "Func": "getTasks", + "Func": "handleQuery", "CreateTime": "\/Date(1546272000000+0800)\/", "ModifyId": null, "ModifyBy": null, @@ -1760,6 +1774,7 @@ "IskeepAlive": 0, "Func": null, "OrderSort": 3, + "IconNew": "WalletFilled", "Icon": "fa-weixin", "Description": null, "Enabled": 1, @@ -2353,6 +2368,7 @@ "IskeepAlive": false, "OrderSort": -10, "Icon": "fa-address-book", + "IconNew": "Briefcase", "Description": "", "Enabled": true, "CreateId": 12, @@ -2379,6 +2395,7 @@ "IskeepAlive": false, "OrderSort": 0, "Icon": "", + "IconNew": "Collection", "Description": "", "Enabled": true, "CreateId": 12, diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.json/TasksQz.tsv b/Blog.Core.Api/wwwroot/BlogCore.Data.json/TasksQz.tsv index 2fa2c648..5aaef106 100644 --- a/Blog.Core.Api/wwwroot/BlogCore.Data.json/TasksQz.tsv +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/TasksQz.tsv @@ -3,7 +3,7 @@ "Name": "博客管理", "JobGroup": "博客测试组", "TriggerType": 1, - "Cron": "0 */5 * * * ?", + "Cron": "0 */1 * * * ?", "AssemblyName": "Blog.Core.Tasks", "ClassName": "Job_Blogs_Quartz", "Remark": "", diff --git a/Blog.Core.Common/App.cs b/Blog.Core.Common/App.cs index ecdb1b49..95481ecf 100644 --- a/Blog.Core.Common/App.cs +++ b/Blog.Core.Common/App.cs @@ -67,22 +67,21 @@ public static bool IsRun /// public static IServiceProvider GetServiceProvider(Type serviceType, bool mustBuild = false, bool throwException = true) { - if (App.HostEnvironment == null || App.RootServices != null && - InternalApp.InternalServices - .Where((u => - u.ServiceType == - (serviceType.IsGenericType ? serviceType.GetGenericTypeDefinition() : serviceType))) - .Any((u => u.Lifetime == ServiceLifetime.Singleton))) - return App.RootServices; - //获取请求生存周期的服务 if (HttpContext?.RequestServices != null) return HttpContext.RequestServices; if (App.RootServices != null) { - IServiceScope scope = RootServices.CreateScope(); - return scope.ServiceProvider; + return RootServices; + } + + //单例 + if (InternalApp.InternalServices + .Where(u => u.ServiceType == (serviceType.IsGenericType ? serviceType.GetGenericTypeDefinition() : serviceType)) + .Any(u => u.Lifetime == ServiceLifetime.Singleton)) + { + return RootServices ?? InternalApp.InternalServices.BuildServiceProvider(); } if (mustBuild) @@ -151,8 +150,8 @@ public static TOptions GetConfig() where TOptions : class, IConfigurableOptions { TOptions instance = App.Configuration - .GetSection(ConfigurableOptions.GetConfigurationPath(typeof(TOptions))) - .Get(); + .GetSection(ConfigurableOptions.GetConfigurationPath(typeof(TOptions))) + .Get(); return instance; } diff --git a/Blog.Core.Common/Blog.Core.Common.csproj b/Blog.Core.Common/Blog.Core.Common.csproj index aab4f0e8..6e1624a3 100644 --- a/Blog.Core.Common/Blog.Core.Common.csproj +++ b/Blog.Core.Common/Blog.Core.Common.csproj @@ -1,6 +1,5 @@  - @@ -20,23 +19,28 @@ - - + + - + - + + + + + + diff --git a/Blog.Core.Common/Caches/Caching.cs b/Blog.Core.Common/Caches/Caching.cs index 36e4ce3a..67f8ac18 100644 --- a/Blog.Core.Common/Caches/Caching.cs +++ b/Blog.Core.Common/Caches/Caching.cs @@ -1,346 +1,260 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Concurrent; using System.Text; -using System.Threading.Tasks; -using Blog.Core.Common.Const; +using Blog.Core.Common.Caches.Interface; +using Blog.Core.Common.Extensions; +using Blog.Core.Common.Option; using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Newtonsoft.Json; +using StackExchange.Redis; namespace Blog.Core.Common.Caches; -public class Caching : ICaching +public class Caching( + ILogger logger, + IDistributedCache cache, + IOptions redisOptions) + : ICaching { - private readonly IDistributedCache _cache; - - public Caching(IDistributedCache cache) - { - _cache = cache; - } - - private byte[] GetBytes(T source) - { - return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(source)); - } - - public IDistributedCache Cache => _cache; - - public void AddCacheKey(string cacheKey) - { - var res = _cache.GetString(CacheConst.KeyAll); - var allkeys = string.IsNullOrWhiteSpace(res) ? new List() : JsonConvert.DeserializeObject>(res); - if (!allkeys.Any(m => m == cacheKey)) - { - allkeys.Add(cacheKey); - _cache.SetString(CacheConst.KeyAll, JsonConvert.SerializeObject(allkeys)); - } - } - - /// - /// 增加缓存Key - /// - /// - /// - public async Task AddCacheKeyAsync(string cacheKey) - { - var res = await _cache.GetStringAsync(CacheConst.KeyAll); - var allkeys = string.IsNullOrWhiteSpace(res) ? new List() : JsonConvert.DeserializeObject>(res); - if (!allkeys.Any(m => m == cacheKey)) - { - allkeys.Add(cacheKey); - await _cache.SetStringAsync(CacheConst.KeyAll, JsonConvert.SerializeObject(allkeys)); - } - } - - public void DelByPattern(string key) - { - var allkeys = GetAllCacheKeys(); - if (allkeys == null) return; - - var delAllkeys = allkeys.Where(u => u.Contains(key)).ToList(); - delAllkeys.ForEach(u => { _cache.Remove(u); }); - - // 更新所有缓存键 - allkeys = allkeys.Where(u => !u.Contains(key)).ToList(); - _cache.SetString(CacheConst.KeyAll, JsonConvert.SerializeObject(allkeys)); - } - - /// - /// 删除某特征关键字缓存 - /// - /// - /// - public async Task DelByPatternAsync(string key) - { - var allkeys = await GetAllCacheKeysAsync(); - if (allkeys == null) return; - - var delAllkeys = allkeys.Where(u => u.Contains(key)).ToList(); - delAllkeys.ForEach(u => { _cache.Remove(u); }); - - // 更新所有缓存键 - allkeys = allkeys.Where(u => !u.Contains(key)).ToList(); - await _cache.SetStringAsync(CacheConst.KeyAll, JsonConvert.SerializeObject(allkeys)); - } - - public void DelCacheKey(string cacheKey) - { - var res = _cache.GetString(CacheConst.KeyAll); - var allkeys = string.IsNullOrWhiteSpace(res) ? new List() : JsonConvert.DeserializeObject>(res); - if (allkeys.Any(m => m == cacheKey)) - { - allkeys.Remove(cacheKey); - _cache.SetString(CacheConst.KeyAll, JsonConvert.SerializeObject(allkeys)); - } - } - - /// - /// 删除缓存 - /// - /// - /// - public async Task DelCacheKeyAsync(string cacheKey) - { - var res = await _cache.GetStringAsync(CacheConst.KeyAll); - var allkeys = string.IsNullOrWhiteSpace(res) ? new List() : JsonConvert.DeserializeObject>(res); - if (allkeys.Any(m => m == cacheKey)) - { - allkeys.Remove(cacheKey); - await _cache.SetStringAsync(CacheConst.KeyAll, JsonConvert.SerializeObject(allkeys)); - } - } - - public bool Exists(string cacheKey) - { - var res = _cache.Get(cacheKey); - return res != null; - } - - /// - /// 检查给定 key 是否存在 - /// - /// 键 - /// - public async Task ExistsAsync(string cacheKey) - { - var res = await _cache.GetAsync(cacheKey); - return res != null; - } - - public List GetAllCacheKeys() - { - var res = _cache.GetString(CacheConst.KeyAll); - return string.IsNullOrWhiteSpace(res) ? null : JsonConvert.DeserializeObject>(res); - } - - /// - /// 获取所有缓存列表 - /// - /// - public async Task> GetAllCacheKeysAsync() - { - var res = await _cache.GetStringAsync(CacheConst.KeyAll); - return string.IsNullOrWhiteSpace(res) ? null : JsonConvert.DeserializeObject>(res); - } - - public T Get(string cacheKey) - { - var res = _cache.Get(cacheKey); - return res == null ? default : JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res)); - } - - /// - /// 获取缓存 - /// - /// - /// - /// - public async Task GetAsync(string cacheKey) - { - var res = await _cache.GetAsync(cacheKey); - return res == null ? default : JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res)); - } - - public object Get(Type type, string cacheKey) - { - var res = _cache.Get(cacheKey); - return res == null ? default : JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res), type); - } - - public async Task GetAsync(Type type, string cacheKey) - { - var res = await _cache.GetAsync(cacheKey); - return res == null ? default : JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res), type); - } - - public string GetString(string cacheKey) - { - return _cache.GetString(cacheKey); - } - - /// - /// 获取缓存 - /// - /// - /// - public async Task GetStringAsync(string cacheKey) - { - return await _cache.GetStringAsync(cacheKey); - } - - public void Remove(string key) - { - _cache.Remove(key); - DelCacheKey(key); - } - - /// - /// 删除缓存 - /// - /// - /// - public async Task RemoveAsync(string key) - { - await _cache.RemoveAsync(key); - await DelCacheKeyAsync(key); - } - - public void RemoveAll() - { - var catches = GetAllCacheKeys(); - foreach (var @catch in catches) Remove(@catch); - - catches.Clear(); - _cache.SetString(CacheConst.KeyAll, JsonConvert.SerializeObject(catches)); - } - - public async Task RemoveAllAsync() - { - var catches = await GetAllCacheKeysAsync(); - foreach (var @catch in catches) await RemoveAsync(@catch); - - catches.Clear(); - await _cache.SetStringAsync(CacheConst.KeyAll, JsonConvert.SerializeObject(catches)); - } - - - public void Set(string cacheKey, T value, TimeSpan? expire = null) - { - _cache.Set(cacheKey, GetBytes(value), - expire == null - ? new DistributedCacheEntryOptions() {AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6)} - : new DistributedCacheEntryOptions() {AbsoluteExpirationRelativeToNow = expire}); - - AddCacheKey(cacheKey); - } - - /// - /// 增加对象缓存 - /// - /// - /// - /// - public async Task SetAsync(string cacheKey, T value) - { - await _cache.SetAsync(cacheKey, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)), - new DistributedCacheEntryOptions() {AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6)}); - - await AddCacheKeyAsync(cacheKey); - } - - /// - /// 增加对象缓存,并设置过期时间 - /// - /// - /// - /// - /// - public async Task SetAsync(string cacheKey, T value, TimeSpan expire) - { - await _cache.SetAsync(cacheKey, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)), - new DistributedCacheEntryOptions() {AbsoluteExpirationRelativeToNow = expire}); - - await AddCacheKeyAsync(cacheKey); - } - - public void SetPermanent(string cacheKey, T value) - { - _cache.Set(cacheKey, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value))); - AddCacheKey(cacheKey); - } - - public async Task SetPermanentAsync(string cacheKey, T value) - { - await _cache.SetAsync(cacheKey, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value))); - await AddCacheKeyAsync(cacheKey); - } - - public void SetString(string cacheKey, string value, TimeSpan? expire = null) - { - if (expire == null) - _cache.SetString(cacheKey, value, new DistributedCacheEntryOptions() {AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6)}); - else - _cache.SetString(cacheKey, value, new DistributedCacheEntryOptions() {AbsoluteExpirationRelativeToNow = expire}); - - AddCacheKey(cacheKey); - } - - /// - /// 增加字符串缓存 - /// - /// - /// - /// - public async Task SetStringAsync(string cacheKey, string value) - { - await _cache.SetStringAsync(cacheKey, value, new DistributedCacheEntryOptions() {AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6)}); - - await AddCacheKeyAsync(cacheKey); - } - - /// - /// 增加字符串缓存,并设置过期时间 - /// - /// - /// - /// - /// - public async Task SetStringAsync(string cacheKey, string value, TimeSpan expire) - { - await _cache.SetStringAsync(cacheKey, value, new DistributedCacheEntryOptions() {AbsoluteExpirationRelativeToNow = expire}); - - await AddCacheKeyAsync(cacheKey); - } - - - /// - /// 缓存最大角色数据范围 - /// - /// - /// - /// - public async Task SetMaxDataScopeType(long userId, int dataScopeType) - { - var cacheKey = CacheConst.KeyMaxDataScopeType + userId; - await SetStringAsync(cacheKey, dataScopeType.ToString()); - - await AddCacheKeyAsync(cacheKey); - } - - /// - /// 根据父键清空 - /// - /// - /// - public async Task DelByParentKeyAsync(string key) - { - var allkeys = await GetAllCacheKeysAsync(); - if (allkeys == null) return; - - var delAllkeys = allkeys.Where(u => u.StartsWith(key)).ToList(); - delAllkeys.ForEach(Remove); - // 更新所有缓存键 - allkeys = allkeys.Where(u => !u.StartsWith(key)).ToList(); - await SetStringAsync(CacheConst.KeyAll, JsonConvert.SerializeObject(allkeys)); - } + private static readonly ConcurrentDictionary _loggedWarnings = new(); + private readonly RedisOptions _redisOptions = redisOptions.Value; + private const string WarningMessage = "注入的缓存服务不是MemoryCacheManager,请检查注册配置,无法获取所有KEY"; + public IDistributedCache Cache => cache; + + public void DelByPattern(string key) + { + var allkeys = GetAllCacheKeys(key); + if (allkeys == null) return; + + foreach (var u in allkeys) + { + cache.Remove(u); + } + } + + /// + /// 删除某特征关键字缓存 + /// + /// + /// + public async Task DelByPatternAsync(string key) + { + var allkeys = GetAllCacheKeys(key); + if (allkeys == null) return; + + foreach (var s in allkeys) await cache.RemoveAsync(s); + } + + public bool Exists(string cacheKey) + { + var res = cache.Get(cacheKey); + return res != null; + } + + /// + /// 检查给定 key 是否存在 + /// + /// 键 + /// + public async Task ExistsAsync(string cacheKey) + { + var res = await cache.GetAsync(cacheKey); + return res != null; + } + + public List GetAllCacheKeys(string pattern = default) + { + if (_redisOptions.Enable) + { + var redis = App.GetService(false); + var endpoints = redis.GetEndPoints(); + var server = redis.GetServer(endpoints[0]); + + // 使用 SCAN 命令来增量获取符合条件的键,避免 KEYS 的性能问题 + return server.Keys(pattern: pattern, pageSize: 100).Select(key => key.ToString()).ToList(); + } + + var memoryCache = App.GetService(); + if (memoryCache is not MemoryCacheManager memoryCacheManager) + { + if (_loggedWarnings.TryAdd(WarningMessage, true)) + { + logger.LogWarning(WarningMessage); + } + + return []; + } + + return memoryCacheManager.GetAllKeys().WhereIf(!pattern.IsNullOrEmpty(), s => s.StartsWith(pattern!)).ToList(); + } + + public T Get(string cacheKey) + { + var res = cache.Get(cacheKey); + return res == null ? default : JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res)); + } + + /// + /// 获取缓存 + /// + /// + /// + /// + public async Task GetAsync(string cacheKey) + { + var res = await cache.GetAsync(cacheKey); + return res == null ? default : JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res)); + } + + public object Get(Type type, string cacheKey) + { + var res = cache.Get(cacheKey); + return res == null ? default : JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res), type); + } + + public async Task GetAsync(Type type, string cacheKey) + { + var res = await cache.GetAsync(cacheKey); + return res == null ? default : JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res), type); + } + + public string GetString(string cacheKey) + { + return cache.GetString(cacheKey); + } + + /// + /// 获取缓存 + /// + /// + /// + public async Task GetStringAsync(string cacheKey) + { + return await cache.GetStringAsync(cacheKey); + } + + public void Remove(string key) + { + cache.Remove(key); + } + + /// + /// 删除缓存 + /// + /// + /// + public async Task RemoveAsync(string key) + { + await cache.RemoveAsync(key); + } + + public void RemoveAll() + { + if (_redisOptions.Enable) + { + var redis = App.GetService(false); + var endpoints = redis.GetEndPoints(); + var server = redis.GetServer(endpoints[0]); + server.FlushDatabase(); + } + else + { + var manage = App.GetService(false); + manage.Reset(); + } + } + + public void Set(string cacheKey, T value, TimeSpan? expire = null) + { + cache.Set(cacheKey, GetBytes(value), + expire == null + ? new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6) } + : new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = expire }); + } + + public void SetPermanent(string cacheKey, T value) + { + cache.Set(cacheKey, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value))); + } + + /// + /// 增加对象缓存 + /// + /// + /// + /// + public async Task SetAsync(string cacheKey, T value) + { + await cache.SetAsync(cacheKey, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)), + new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6) }); + } + + /// + /// 增加对象缓存,并设置过期时间 + /// + /// + /// + /// + /// + public async Task SetAsync(string cacheKey, T value, TimeSpan expire) + { + await cache.SetAsync(cacheKey, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)), + new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = expire }); + } + + public async Task SetPermanentAsync(string cacheKey, T value) + { + await cache.SetAsync(cacheKey, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value))); + } + + public void SetString(string cacheKey, string value, TimeSpan? expire = null) + { + cache.SetString(cacheKey, value, + expire == null + ? new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6) } + : new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = expire }); + } + + /// + /// 增加字符串缓存 + /// + /// + /// + /// + public async Task SetStringAsync(string cacheKey, string value) + { + await cache.SetStringAsync(cacheKey, value, new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6) }); + } + + /// + /// 增加字符串缓存,并设置过期时间 + /// + /// + /// + /// + /// + public async Task SetStringAsync(string cacheKey, string value, TimeSpan expire) + { + await cache.SetStringAsync(cacheKey, value, new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = expire }); + } + + /// + /// 根据父键清空 + /// + /// + /// + public async Task DelByParentKeyAsync(string key) + { + var allkeys = GetAllCacheKeys(key); + if (allkeys == null) return; + + foreach (var s in allkeys) await cache.RemoveAsync(s); + } + + private byte[] GetBytes(T source) + { + return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(source)); + } } \ No newline at end of file diff --git a/Blog.Core.Common/Caches/Distributed/CommonMemoryDistributedCache.cs b/Blog.Core.Common/Caches/Distributed/CommonMemoryDistributedCache.cs new file mode 100644 index 00000000..65ff4503 --- /dev/null +++ b/Blog.Core.Common/Caches/Distributed/CommonMemoryDistributedCache.cs @@ -0,0 +1,103 @@ +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; + +namespace Blog.Core.Common.Caches.Distributed; + +/// +/// A common memory distributed cache.
+/// 因为微软的MemoryDistributedCache内部自己实例化MemoryCache,而不是使用IMemoryCache接口 +///
+public class CommonMemoryDistributedCache : IDistributedCache +{ + private readonly IMemoryCache _memCache; + + public CommonMemoryDistributedCache(IOptions optionsAccessor, IMemoryCache memoryCache) + : this(optionsAccessor, NullLoggerFactory.Instance, memoryCache) + { + } + + public CommonMemoryDistributedCache(IOptions optionsAccessor, ILoggerFactory loggerFactory, + IMemoryCache memoryCache) + { + ArgumentNullException.ThrowIfNull(optionsAccessor); + ArgumentNullException.ThrowIfNull(loggerFactory); + + _memCache = memoryCache ?? new MemoryCache(optionsAccessor.Value, loggerFactory); + } + + public byte[] Get(string key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + return (byte[])_memCache.Get(key); + } + + public Task GetAsync(string key, CancellationToken token = default(CancellationToken)) + { + ArgumentNullException.ThrowIfNull(key); + return Task.FromResult(Get(key)); + } + + public void Set(string key, byte[] value, DistributedCacheEntryOptions options) + { + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(value); + ArgumentNullException.ThrowIfNull(options); + + var memoryCacheEntryOptions = new MemoryCacheEntryOptions + { + AbsoluteExpiration = options.AbsoluteExpiration, + AbsoluteExpirationRelativeToNow = options.AbsoluteExpirationRelativeToNow, + SlidingExpiration = options.SlidingExpiration, + Size = value.Length + }; + + _memCache.Set(key, value, memoryCacheEntryOptions); + } + + public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken)) + { + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(value); + ArgumentNullException.ThrowIfNull(options); + + Set(key, value, options); + return Task.CompletedTask; + } + + public void Refresh(string key) + { + ArgumentNullException.ThrowIfNull(key); + + _memCache.TryGetValue(key, out object value); + } + + public Task RefreshAsync(string key, CancellationToken token = default(CancellationToken)) + { + ArgumentNullException.ThrowIfNull(key); + + Refresh(key); + return Task.CompletedTask; + } + + public void Remove(string key) + { + ArgumentNullException.ThrowIfNull(key); + + _memCache.Remove(key); + } + + public Task RemoveAsync(string key, CancellationToken token = default(CancellationToken)) + { + ArgumentNullException.ThrowIfNull(key); + + Remove(key); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Caches/Extensions/MemoryCacheExtensions.cs b/Blog.Core.Common/Caches/Extensions/MemoryCacheExtensions.cs new file mode 100644 index 00000000..380fbc84 --- /dev/null +++ b/Blog.Core.Common/Caches/Extensions/MemoryCacheExtensions.cs @@ -0,0 +1,80 @@ +using System.Collections; +using System.Reflection; +using System.Reflection.Emit; +using Microsoft.Extensions.Caching.Memory; + +namespace Blog.Core.Common.Caches.Extensions; + +public static class MemoryCacheExtensions +{ + #region Microsoft.Extensions.Caching.Memory_6_OR_OLDER + + /// + /// 6.x
+ /// 6.0.2 调整了字段名,使用 StringKeyEntriesCollection + ///
+ private static readonly Lazy> GetEntries6 = new(() => + (Func)Delegate.CreateDelegate(typeof(Func), + typeof(MemoryCache).GetProperty("EntriesCollection", BindingFlags.NonPublic | BindingFlags.Instance)?.GetGetMethod(true) + ?? typeof(MemoryCache).GetProperty("StringKeyEntriesCollection", BindingFlags.NonPublic | BindingFlags.Instance)?.GetGetMethod(true) + ?? throw new InvalidOperationException("Cannot find property 'EntriesCollection' or 'StringKeyEntriesCollection' on MemoryCache."), + true)); + + #endregion + + #region Microsoft.Extensions.Caching.Memory_7_OR_NEWER + + private static readonly Lazy> GetCoherentState7 = new(() => + CreateGetter(typeof(MemoryCache) + .GetField("_coherentState", BindingFlags.NonPublic | BindingFlags.Instance))); + + private static readonly Lazy> GetEntries7 = new(() => + CreateGetter(typeof(MemoryCache) + .GetNestedType("CoherentState", BindingFlags.NonPublic)? + .GetField("_entries", BindingFlags.NonPublic | BindingFlags.Instance))); + + #endregion + + #region Microsoft.Extensions.Caching.Memory_8_OR_NEWER + + private static readonly Lazy> GetCoherentState8 = new(() => + CreateGetter(typeof(MemoryCache) + .GetField("_coherentState", BindingFlags.NonPublic | BindingFlags.Instance))); + + private static readonly Lazy> GetEntries8 = new(() => + CreateGetter(typeof(MemoryCache) + .GetNestedType("CoherentState", BindingFlags.NonPublic)? + .GetField("_stringEntries", BindingFlags.NonPublic | BindingFlags.Instance))); + + #endregion + + private static Func CreateGetter(FieldInfo field) + { + if (field == null) + { + throw new ArgumentNullException(nameof(field), "Field cannot be null."); + } + + var methodName = $"{field.ReflectedType!.FullName}.get_{field.Name}"; + var method = new DynamicMethod(methodName, typeof(TReturn), new[] { typeof(TParam) }, typeof(TParam), true); + var ilGen = method.GetILGenerator(); + ilGen.Emit(OpCodes.Ldarg_0); + ilGen.Emit(OpCodes.Ldfld, field); + ilGen.Emit(OpCodes.Ret); + return (Func)method.CreateDelegate(typeof(Func)); + } + + private static readonly Func GetEntries = + Assembly.GetAssembly(typeof(MemoryCache))?.GetName().Version?.Major switch + { + < 7 => cache => (IDictionary)GetEntries6.Value(cache), + 7 => cache => GetEntries7.Value(GetCoherentState7.Value(cache)), + _ => cache => GetEntries8.Value(GetCoherentState8.Value(cache)), + }; + + public static ICollection GetKeys(this IMemoryCache memoryCache) => + GetEntries((MemoryCache)memoryCache).Keys; + + public static IEnumerable GetKeys(this IMemoryCache memoryCache) => + memoryCache.GetKeys().OfType(); +} \ No newline at end of file diff --git a/Blog.Core.Common/Caches/ICaching.cs b/Blog.Core.Common/Caches/Interface/ICaching.cs similarity index 73% rename from Blog.Core.Common/Caches/ICaching.cs rename to Blog.Core.Common/Caches/Interface/ICaching.cs index 9ce92632..836e11c0 100644 --- a/Blog.Core.Common/Caches/ICaching.cs +++ b/Blog.Core.Common/Caches/Interface/ICaching.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Distributed; -namespace Blog.Core.Common.Caches; +namespace Blog.Core.Common.Caches.Interface; /// /// 缓存抽象接口,基于IDistributedCache封装 @@ -11,20 +8,14 @@ namespace Blog.Core.Common.Caches; public interface ICaching { public IDistributedCache Cache { get; } - void AddCacheKey(string cacheKey); - Task AddCacheKeyAsync(string cacheKey); void DelByPattern(string key); Task DelByPatternAsync(string key); - void DelCacheKey(string cacheKey); - Task DelCacheKeyAsync(string cacheKey); - bool Exists(string cacheKey); Task ExistsAsync(string cacheKey); - List GetAllCacheKeys(); - Task> GetAllCacheKeysAsync(); + List GetAllCacheKeys(string pattern = default); T Get(string cacheKey); Task GetAsync(string cacheKey); @@ -39,7 +30,6 @@ public interface ICaching Task RemoveAsync(string key); void RemoveAll(); - Task RemoveAllAsync(); void Set(string cacheKey, T value, TimeSpan? expire = null); Task SetAsync(string cacheKey, T value); diff --git a/Blog.Core.Common/Caches/MemoryCacheManager.cs b/Blog.Core.Common/Caches/MemoryCacheManager.cs new file mode 100644 index 00000000..5f0a8861 --- /dev/null +++ b/Blog.Core.Common/Caches/MemoryCacheManager.cs @@ -0,0 +1,43 @@ +using System.Collections; +using System.Reflection; +using Blog.Core.Common.Caches.Extensions; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; + +namespace Blog.Core.Common.Caches; + +public class MemoryCacheManager : IMemoryCache +{ + private readonly IOptions _optionsAccessor; + + private MemoryCache _inner; + + public MemoryCacheManager(IOptions optionsAccessor) + { + _optionsAccessor = optionsAccessor; + _inner = new MemoryCache(_optionsAccessor); + } + + public void Dispose() => _inner.Dispose(); + + public ICacheEntry CreateEntry(object key) => _inner.CreateEntry(key); + + public void Remove(object key) => _inner.Remove(key); + + public bool TryGetValue(object key, out object value) => _inner.TryGetValue(key, out value); + + public void Reset() + { + lock (_optionsAccessor) + { + var old = _inner; + _inner = new MemoryCache(_optionsAccessor); + old.Dispose(); + } + } + + public IEnumerable GetAllKeys() + { + return _inner.GetKeys(); + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Caches/SqlSugarCacheService.cs b/Blog.Core.Common/Caches/SqlSugarCacheService.cs index c26a164d..d94eec54 100644 --- a/Blog.Core.Common/Caches/SqlSugarCacheService.cs +++ b/Blog.Core.Common/Caches/SqlSugarCacheService.cs @@ -1,62 +1,110 @@ using System; using System.Collections.Generic; +using Blog.Core.Common.Caches.Interface; +using Blog.Core.Common.Option; using SqlSugar; namespace Blog.Core.Common.Caches; /// -/// 实现SqlSugar的ICacheService接口 +/// 实现SqlSugar的ICacheService接口
+///
+/// 建议自己实现业务缓存,注入ICaching直接用即可
+///
+/// 不建议使用SqlSugar缓存,性能有很大问题,会导致redis堆积
+/// 核心问题在于SqlSugar,每次query(注意:不管你有没有启用,所有表的查询)都会查缓存, insert\update\delete,又会频繁GetAllKey,导致性能特别低
///
public class SqlSugarCacheService : ICacheService { - private readonly Lazy _caching = new(() => App.GetService(false)); - private ICaching Caching => _caching.Value; - - public void Add(string key, V value) - { - Caching.Set(key, value); - } - - public void Add(string key, V value, int cacheDurationInSeconds) - { - Caching.Set(key, value, TimeSpan.FromSeconds(cacheDurationInSeconds)); - } - - public bool ContainsKey(string key) - { - return Caching.Exists(key); - } - - public V Get(string key) - { - return Caching.Get(key); - } - - public IEnumerable GetAllKey() - { - return Caching.GetAllCacheKeys(); - } - - public V GetOrCreate(string cacheKey, Func create, int cacheDurationInSeconds = int.MaxValue) - { - if (!ContainsKey(cacheKey)) - { - var value = create(); - Caching.Set(cacheKey, value, TimeSpan.FromSeconds(cacheDurationInSeconds)); - return value; - } - - return Caching.Get(cacheKey); - } - - public void Remove(string key) - { - Caching.Remove(key); - } - - public bool RemoveAll() - { - Caching.RemoveAll(); - return true; - } + private readonly Lazy _caching = new(() => App.GetService(false)); + private ICaching Caching => _caching.Value; + private readonly AppSettingsOption _options = App.GetOptions(); + + public void Add(string key, V value) + { + if (!_options.CacheDbEnabled) + { + return; + } + + Caching.Set(key, value); + } + + public void Add(string key, V value, int cacheDurationInSeconds) + { + if (!_options.CacheDbEnabled) + { + return; + } + + Caching.Set(key, value, TimeSpan.FromSeconds(cacheDurationInSeconds)); + } + + public bool ContainsKey(string key) + { + if (!_options.CacheDbEnabled) + { + return default; + } + + return Caching.Exists(key); + } + + public V Get(string key) + { + if (!_options.CacheDbEnabled) + { + return default; + } + + return Caching.Get(key); + } + + public IEnumerable GetAllKey() + { + if (!_options.CacheDbEnabled) + { + return default; + } + + return Caching.GetAllCacheKeys(); + } + + public V GetOrCreate(string cacheKey, Func create, int cacheDurationInSeconds = int.MaxValue) + { + if (!_options.CacheDbEnabled) + { + return create(); + } + + if (!ContainsKey(cacheKey)) + { + var value = create(); + Caching.Set(cacheKey, value, TimeSpan.FromSeconds(cacheDurationInSeconds)); + return value; + } + + return Caching.Get(cacheKey); + } + + public void Remove(string key) + { + if (!_options.CacheDbEnabled) + { + return; + } + + Caching.Remove(key); + } + + public bool RemoveAll() + { + if (!_options.CacheDbEnabled) + { + return true; + } + + Caching.RemoveAll(); + return true; + } } \ No newline at end of file diff --git a/Blog.Core.Common/Const/CacheConst.cs b/Blog.Core.Common/Const/CacheConst.cs index 92f0e633..73e0d048 100644 --- a/Blog.Core.Common/Const/CacheConst.cs +++ b/Blog.Core.Common/Const/CacheConst.cs @@ -60,11 +60,6 @@ public class CacheConst ///
public const string KeyVerCode = "verCode:"; - /// - /// 所有缓存关键字集合 - /// - public const string KeyAll = "keys"; - /// /// 定时任务缓存 /// diff --git a/Blog.Core.Common/Core/InternalApp.cs b/Blog.Core.Common/Core/InternalApp.cs index b8a7736a..5cf3a97b 100644 --- a/Blog.Core.Common/Core/InternalApp.cs +++ b/Blog.Core.Common/Core/InternalApp.cs @@ -42,4 +42,14 @@ public static void ConfigureApplication(this IHost app) { RootServices = app.Services; } + + public static void ConfigureApplication(this IServiceCollection services) + { + InternalServices = services; + } + + public static void ConfigureApplication(this IServiceProvider services) + { + RootServices = services; + } } \ No newline at end of file diff --git a/Blog.Core.Common/DB/Aop/SqlsugarAop.cs b/Blog.Core.Common/DB/Aop/SqlsugarAop.cs index 826984ed..7d2031ad 100644 --- a/Blog.Core.Common/DB/Aop/SqlsugarAop.cs +++ b/Blog.Core.Common/DB/Aop/SqlsugarAop.cs @@ -5,13 +5,15 @@ using System; using Serilog; using Blog.Core.Common.LogHelper; +using Blog.Core.Common.Utility; using Blog.Core.Model; namespace Blog.Core.Common.DB.Aop; public static class SqlSugarAop { - public static void OnLogExecuting(ISqlSugarClient sqlSugarScopeProvider, string user, string table, string operate, string sql, SugarParameter[] p, ConnectionConfig config) + public static void OnLogExecuting(ISqlSugarClient sqlSugarScopeProvider, string user, string table, string operate, string sql, + SugarParameter[] p, ConnectionConfig config) { try { @@ -25,7 +27,8 @@ public static void OnLogExecuting(ISqlSugarClient sqlSugarScopeProvider, string { using (LogContextExtension.Create.SqlAopPushProperty(sqlSugarScopeProvider)) { - Log.Information("------------------ \r\n User:[{User}] Table:[{Table}] Operate:[{Operate}] ConnId:[{ConnId}]【SQL语句】: \r\n {Sql}", + Log.Information( + "------------------ \r\n User:[{User}] Table:[{Table}] Operate:[{Operate}] ConnId:[{ConnId}]【SQL语句】: \r\n {Sql}", user, table, operate, config.ConfigId, UtilMethods.GetNativeSql(sql, p)); } } @@ -42,7 +45,7 @@ public static void DataExecuting(object oldValue, DataFilterModel entityInfo) { if (rootEntity.Id == 0) { - rootEntity.Id = SnowFlakeSingle.Instance.NextId(); + rootEntity.Id = IdGeneratorUtility.NextId(); } } @@ -113,7 +116,8 @@ public static void DataExecuting(object oldValue, DataFilterModel entityInfo) if (App.User?.ID > 0 && dyCreateId != null && dyCreateId.GetValue(entityInfo.EntityValue) == null) dyCreateId.SetValue(entityInfo.EntityValue, App.User.ID); - if (dyCreateTime != null && dyCreateTime.GetValue(entityInfo.EntityValue) != null && (DateTime)dyCreateTime.GetValue(entityInfo.EntityValue) == DateTime.MinValue) + if (dyCreateTime != null && dyCreateTime.GetValue(entityInfo.EntityValue) != null && + (DateTime)dyCreateTime.GetValue(entityInfo.EntityValue) == DateTime.MinValue) dyCreateTime.SetValue(entityInfo.EntityValue, DateTime.Now); break; @@ -155,4 +159,4 @@ private static string GetParas(SugarParameter[] pars) return key; } -} +} \ No newline at end of file diff --git a/Blog.Core.Common/DB/Extension/SqlSugarDbMaintenanceExtension.cs b/Blog.Core.Common/DB/Extension/SqlSugarDbMaintenanceExtension.cs new file mode 100644 index 00000000..a316a2e7 --- /dev/null +++ b/Blog.Core.Common/DB/Extension/SqlSugarDbMaintenanceExtension.cs @@ -0,0 +1,38 @@ +using SqlSugar; + +namespace Blog.Core.Common.DB.Extension; + +/// +/// SqlSugar DbMaintenance 扩展 +/// +public static class SqlSugarDbMaintenanceExtension +{ + /// + /// 清除 SqlSugar 中表信息的缓存。 + /// 使用场景: + /// + /// + /// + /// 调用 时会缓存表的元数据信息, + /// SqlSugar 未提供显式清理该缓存的方法。 + /// + /// + /// + /// + /// 如果通过 DbMaintenance.IsAnyTable(tableName, false) 绕过缓存, + /// 则每次调用都会重新查询所有表信息,影响性能。 + /// + /// + /// + /// 建议在InitTable后进行清理缓存. + /// + /// SqlSugar的数据库上下文实例。 + public static void ClearDbTableCache(this ISqlSugarClient context) + { + var fullCacheKey = GetDbTableCacheKey(context, "DbMaintenanceProvider.GetTableInfoList" + context.CurrentConnectionConfig.ConfigId); + context.CurrentConnectionConfig.ConfigureExternalServices.ReflectionInoCacheService.Remove>(fullCacheKey); + } + + private static string GetDbTableCacheKey(ISqlSugarClient context, string cacheKey) => + $"{context.CurrentConnectionConfig.DbType}.{context.Ado.Connection.Database}.{cacheKey}"; +} \ No newline at end of file diff --git a/Blog.Core.Common/Extensions/ExpressionExtensions.cs b/Blog.Core.Common/Extensions/ExpressionExtensions.cs index 6591923f..76179d5d 100644 --- a/Blog.Core.Common/Extensions/ExpressionExtensions.cs +++ b/Blog.Core.Common/Extensions/ExpressionExtensions.cs @@ -5,6 +5,7 @@ using System.Linq.Expressions; using System.Threading.Tasks; using Blog.Core.Common.Caches; +using Blog.Core.Common.Caches.Interface; namespace Blog.Core.Common.Helper { diff --git a/Blog.Core.Common/Extensions/RuntimeExtension.cs b/Blog.Core.Common/Extensions/RuntimeExtension.cs index d66f1183..7d58f0a8 100644 --- a/Blog.Core.Common/Extensions/RuntimeExtension.cs +++ b/Blog.Core.Common/Extensions/RuntimeExtension.cs @@ -10,77 +10,83 @@ namespace Blog.Core.Common.Extensions; public static class RuntimeExtension { - /// - /// 获取项目程序集,排除所有的系统程序集(Microsoft.***、System.***等)、Nuget下载包 - /// - /// - public static IList GetAllAssemblies() - { - var list = new List(); - var deps = DependencyContext.Default; - //只加载项目中的程序集 - var libs = deps.CompileLibraries.Where(lib => !lib.Serviceable && lib.Type == "project"); //排除所有的系统程序集、Nuget下载包 - foreach (var lib in libs) - { - try - { - var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(lib.Name)); - list.Add(assembly); - } - catch (Exception e) - { - Log.Debug(e, "GetAllAssemblies Exception:{ex}", e.Message); - } - } + private static readonly List ProjectAssemblies = + [ + "Blog.Core" + ]; - return list; - } + /// + /// 获取项目程序集,排除所有的系统程序集(Microsoft.***、System.***等)、Nuget下载包 + /// + /// + public static IList GetAllAssemblies() + { + var assemblies = new List(); + var dllFiles = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll"); - public static Assembly GetAssembly(string assemblyName) - { - return GetAllAssemblies().FirstOrDefault(assembly => assembly.FullName.Contains(assemblyName)); - } + foreach (var dllFile in dllFiles) + { + var fileName = Path.GetFileNameWithoutExtension(dllFile); + if (!ProjectAssemblies.Any(s => fileName.StartsWith(s))) continue; + try + { + var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(dllFile); + assemblies.Add(assembly); + } + catch (Exception e) + { + Console.WriteLine($"Failed to load assembly: {fileName}. Error: {e.Message}"); + } + } - public static IList GetAllTypes() - { - var list = new List(); - foreach (var assembly in GetAllAssemblies()) - { - var typeInfos = assembly.DefinedTypes; - foreach (var typeInfo in typeInfos) - { - list.Add(typeInfo.AsType()); - } - } + return assemblies; + } - return list; - } + public static Assembly GetAssembly(string assemblyName) + { + return GetAllAssemblies().FirstOrDefault(assembly => assembly.FullName.Contains(assemblyName)); + } - public static IList GetTypesByAssembly(string assemblyName) - { - var list = new List(); - var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(assemblyName)); - var typeInfos = assembly.DefinedTypes; - foreach (var typeInfo in typeInfos) - { - list.Add(typeInfo.AsType()); - } + public static IList GetAllTypes() + { + var list = new List(); + foreach (var assembly in GetAllAssemblies()) + { + var typeInfos = assembly.DefinedTypes; + foreach (var typeInfo in typeInfos) + { + list.Add(typeInfo.AsType()); + } + } - return list; - } + return list; + } - public static Type GetImplementType(string typeName, Type baseInterfaceType) - { - return GetAllTypes().FirstOrDefault(t => - { - if (t.Name == typeName && - t.GetTypeInfo().GetInterfaces().Any(b => b.Name == baseInterfaceType.Name)) - { - var typeInfo = t.GetTypeInfo(); - return typeInfo.IsClass && !typeInfo.IsAbstract && !typeInfo.IsGenericType; - } + public static IList GetTypesByAssembly(string assemblyName) + { + var list = new List(); + var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(assemblyName)); + var typeInfos = assembly.DefinedTypes; + foreach (var typeInfo in typeInfos) + { + list.Add(typeInfo.AsType()); + } - return false; - }); - } + return list; + } + + public static Type GetImplementType(string typeName, Type baseInterfaceType) + { + return GetAllTypes().FirstOrDefault(t => + { + if (t.Name == typeName && + t.GetTypeInfo().GetInterfaces().Any(b => b.Name == baseInterfaceType.Name)) + { + var typeInfo = t.GetTypeInfo(); + return typeInfo.IsClass && !typeInfo.IsAbstract && !typeInfo.IsGenericType; + } + + return false; + }); + } } \ No newline at end of file diff --git a/Blog.Core.Common/Extensions/UntilExtensions.cs b/Blog.Core.Common/Extensions/UntilExtensions.cs index 1ae503b2..efb17dc8 100644 --- a/Blog.Core.Common/Extensions/UntilExtensions.cs +++ b/Blog.Core.Common/Extensions/UntilExtensions.cs @@ -15,4 +15,9 @@ public static void AddOrModify(this IDictionary dic, dic.Add(key, value); } } + + public static IEnumerable WhereIf(this IEnumerable source, bool condition, Func predicate) + { + return condition ? source.Where(predicate) : source; + } } \ No newline at end of file diff --git a/Blog.Core.Common/Helper/RecursionHelper.cs b/Blog.Core.Common/Helper/RecursionHelper.cs index f223cd71..8b9874db 100644 --- a/Blog.Core.Common/Helper/RecursionHelper.cs +++ b/Blog.Core.Common/Helper/RecursionHelper.cs @@ -192,6 +192,7 @@ public class NavigationBarMeta public bool requireAuth { get; set; } = true; public bool NoTabPage { get; set; } = false; public bool keepAlive { get; set; } = false; + public string icon { get; set; } } diff --git a/Blog.Core.Common/Hubs/ChatHub.cs b/Blog.Core.Common/Hubs/ChatHub.cs index f30e6b7b..63803d2f 100644 --- a/Blog.Core.Common/Hubs/ChatHub.cs +++ b/Blog.Core.Common/Hubs/ChatHub.cs @@ -96,10 +96,10 @@ public async Task SendMessage(string user, string message) public async Task GetLatestCount(string random) { //2、服务端主动向客户端发送数据,名字千万不能错 - if (AppSettings.app(new string[] {"Middleware", "SignalRSendLog", "Enabled"}).ObjToBool()) + if (AppSettings.app(new string[] { "Middleware", "SignalRSendLog", "Enabled" }).ObjToBool()) { //TODO 主动发送错误消息 - await Clients.All.ReceiveUpdate(LogLock.GetLogData()); + await Clients.All.ReceiveUpdate("这是一个LOG"); } diff --git a/Blog.Core.Common/LogHelper/LogLock.cs b/Blog.Core.Common/LogHelper/LogLock.cs deleted file mode 100644 index e03c6366..00000000 --- a/Blog.Core.Common/LogHelper/LogLock.cs +++ /dev/null @@ -1,614 +0,0 @@ -using Blog.Core.Common.Helper; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using Serilog; - -namespace Blog.Core.Common.LogHelper -{ - public class LogLock - { - static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim(); - static int WritedCount = 0; - static int FailedCount = 0; - static string _contentRoot = string.Empty; - - public LogLock(string contentPath) - { - _contentRoot = contentPath; - } - - public static void OutLogAOP(string prefix, string traceId, string[] dataParas, bool IsHeader = true) - { - string AppSetingNodeName = "AppSettings"; - string AppSetingName = "LogAOP"; - switch (prefix) - { - case "AOPLog": - AppSetingName = "LogAOP"; - break; - case "AOPLogEx": - AppSetingName = "LogAOP"; - break; - case "RequestIpInfoLog": - AppSetingNodeName = "Middleware"; - AppSetingName = "IPLog"; - break; - case "RecordAccessLogs": - AppSetingNodeName = "Middleware"; - AppSetingName = "RecordAccessLogs"; - break; - case "SqlLog": - AppSetingName = "SqlAOP"; - break; - case "RequestResponseLog": - AppSetingNodeName = "Middleware"; - AppSetingName = "RequestResponseLog"; - break; - default: - break; - } - - if (AppSettings.app(new string[] { AppSetingNodeName, AppSetingName, "Enabled" }).ObjToBool()) - { - if (AppSettings.app(new string[] { AppSetingNodeName, AppSetingName, "LogToDB", "Enabled" }).ObjToBool()) - { - OutSql2LogToDB(prefix, traceId, dataParas, IsHeader); - } - - if (AppSettings.app(new string[] { AppSetingNodeName, AppSetingName, "LogToFile", "Enabled" }).ObjToBool()) - { - OutSql2LogToFile(prefix, traceId, dataParas, IsHeader); - } - } - - //if (AppSettings.app(new string[] { "AppSettings", "LogFile", "Enabled" }).ObjToBool()) - //{ - // OutSql2LogFile(prefix, dataParas, IsHeader); - //} - //else - //{ - // OutSql2Log(prefix, dataParas, IsHeader); - //} - } - - public static void OutSql2LogToFile(string prefix, string traceId, string[] dataParas, bool IsHeader = true, bool isWrt = false) - { - try - { - //设置读写锁为写入模式独占资源,其他写入请求需要等待本次写入结束之后才能继续写入 - //注意:长时间持有读线程锁或写线程锁会使其他线程发生饥饿 (starve)。 为了得到最好的性能,需要考虑重新构造应用程序以将写访问的持续时间减少到最小。 - // 从性能方面考虑,请求进入写入模式应该紧跟文件操作之前,在此处进入写入模式仅是为了降低代码复杂度 - // 因进入与退出写入模式应在同一个try finally语句块内,所以在请求进入写入模式之前不能触发异常,否则释放次数大于请求次数将会触发异常 - LogWriteLock.EnterWriteLock(); - - var folderPath = Path.Combine(_contentRoot, "Log"); - if (!Directory.Exists(folderPath)) - { - Directory.CreateDirectory(folderPath); - } - - //string logFilePath = Path.Combine(path, $@"{filename}.log"); - var logFilePath = FileHelper.GetAvailableFileWithPrefixOrderSize(folderPath, prefix); - switch (prefix) - { - case "AOPLog": - AOPLogInfo apiLogAopInfo = JsonConvert.DeserializeObject(dataParas[1]); - //记录被拦截方法信息的日志信息 - var dataIntercept = "" + - $"【操作时间】:{apiLogAopInfo.RequestTime}\r\n" + - $"【当前操作用户】:{apiLogAopInfo.OpUserName} \r\n" + - $"【当前执行方法】:{apiLogAopInfo.RequestMethodName} \r\n" + - $"【携带的参数有】: {apiLogAopInfo.RequestParamsName} \r\n" + - $"【携带的参数JSON】: {apiLogAopInfo.RequestParamsData} \r\n" + - $"【响应时间】:{apiLogAopInfo.ResponseIntervalTime}\r\n" + - $"【执行完成时间】:{apiLogAopInfo.ResponseTime}\r\n" + - $"【执行完成结果】:{apiLogAopInfo.ResponseJsonData}\r\n"; - dataParas = new string[] { dataIntercept }; - break; - case "AOPLogEx": - AOPLogExInfo apiLogAopExInfo = JsonConvert.DeserializeObject(dataParas[1]); - var dataInterceptEx = "" + - $"【操作时间】:{apiLogAopExInfo.ApiLogAopInfo.RequestTime}\r\n" + - $"【当前操作用户】:{apiLogAopExInfo.ApiLogAopInfo.OpUserName} \r\n" + - $"【当前执行方法】:{apiLogAopExInfo.ApiLogAopInfo.RequestMethodName} \r\n" + - $"【携带的参数有】: {apiLogAopExInfo.ApiLogAopInfo.RequestParamsName} \r\n" + - $"【携带的参数JSON】: {apiLogAopExInfo.ApiLogAopInfo.RequestParamsData} \r\n" + - $"【响应时间】:{apiLogAopExInfo.ApiLogAopInfo.ResponseIntervalTime}\r\n" + - $"【执行完成时间】:{apiLogAopExInfo.ApiLogAopInfo.ResponseTime}\r\n" + - $"【执行完成结果】:{apiLogAopExInfo.ApiLogAopInfo.ResponseJsonData}\r\n" + - $"【执行完成异常信息】:方法中出现异常:{apiLogAopExInfo.ExMessage}\r\n" + - $"【执行完成异常】:方法中出现异常:{apiLogAopExInfo.InnerException}\r\n"; - dataParas = new string[] { dataInterceptEx }; - break; - } - - var now = DateTime.Now; - string logContent = String.Join("\r\n", dataParas); - if (IsHeader) - { - logContent = ( - "--------------------------------\r\n" + - DateTime.Now + "|\r\n" + - String.Join("\r\n", dataParas) + "\r\n" - ); - } - else - { - logContent = ( - dataParas[1] + ",\r\n" - ); - } - - //if (logContent.IsNotEmptyOrNull() && logContent.Length > 500) - //{ - // logContent = logContent.Substring(0, 500) + "\r\n"; - //} - if (isWrt) - { - File.WriteAllText(logFilePath, logContent); - } - else - { - File.AppendAllText(logFilePath, logContent); - } - - WritedCount++; - } - catch (Exception e) - { - Console.Write(e.Message); - FailedCount++; - } - finally - { - //退出写入模式,释放资源占用 - //注意:一次请求对应一次释放 - // 若释放次数大于请求次数将会触发异常[写入锁定未经保持即被释放] - // 若请求处理完成后未释放将会触发异常[此模式不下允许以递归方式获取写入锁定] - LogWriteLock.ExitWriteLock(); - } - } - - public static void OutSql2LogToDB(string prefix, string traceId, string[] dataParas, bool IsHeader = true) - { - //log4net.LogicalThreadContext.Properties["LogType"] = prefix; - //log4net.LogicalThreadContext.Properties["TraceId"] = traceId; - //if (dataParas.Length >= 2) - //{ - // log4net.LogicalThreadContext.Properties["DataType"] = dataParas[0]; - //} - - dataParas = dataParas.Skip(1).ToArray(); - - string logContent = String.Join("", dataParas); - if (IsHeader) - { - logContent = (String.Join("", dataParas)); - } - - switch (prefix) - { - //DEBUG | INFO | WARN | ERROR | FATAL - case "AOPLog": - Log.Information(logContent); - break; - case "AOPLogEx": - Log.Error(logContent); - break; - case "RequestIpInfoLog": - //TODO 是否需要Debug输出? - Log.Information(logContent); - break; - case "RecordAccessLogs": - //TODO 是否需要Debug输出? - Log.Information(logContent); - break; - case "SqlLog": - Log.Information(logContent); - break; - case "RequestResponseLog": - //TODO 是否需要Debug输出? - Log.Information(logContent); - break; - default: - break; - } - } - - /// - /// 读取文件内容 - /// - /// 文件夹路径 - /// 文件名 - /// 编码 - /// 读取类型(0:精准,1:前缀模糊) - /// - public static string ReadLog(string folderPath, string fileName, Encoding encode, ReadType readType = ReadType.Accurate, int takeOnlyTop = -1) - { - string s = ""; - try - { - LogWriteLock.EnterReadLock(); - - // 根据文件名读取当前文件内容 - if (readType == ReadType.Accurate) - { - var filePath = Path.Combine(folderPath, fileName); - if (!File.Exists(filePath)) - { - s = null; - } - else - { - StreamReader f2 = new StreamReader(filePath, encode); - s = f2.ReadToEnd(); - f2.Close(); - f2.Dispose(); - } - } - - // 根据前缀读取所有文件内容 - if (readType == ReadType.Prefix) - { - var allFiles = new DirectoryInfo(folderPath); - var selectFiles = allFiles.GetFiles().Where(fi => fi.Name.ToLower().Contains(fileName.ToLower())).ToList(); - - selectFiles = takeOnlyTop > 0 ? selectFiles.OrderByDescending(d => d.Name).Take(takeOnlyTop).ToList() : selectFiles; - - foreach (var item in selectFiles) - { - if (File.Exists(item.FullName)) - { - StreamReader f2 = new StreamReader(item.FullName, encode); - s += f2.ReadToEnd(); - f2.Close(); - f2.Dispose(); - } - } - } - - // 根据前缀读取 最新文件 时间倒叙 - if (readType == ReadType.PrefixLatest) - { - var allFiles = new DirectoryInfo(folderPath); - var selectLastestFile = allFiles.GetFiles().Where(fi => fi.Name.ToLower().Contains(fileName.ToLower())).OrderByDescending(d => d.Name).FirstOrDefault(); - - if (selectLastestFile != null && File.Exists(selectLastestFile.FullName)) - { - StreamReader f2 = new StreamReader(selectLastestFile.FullName, encode); - s = f2.ReadToEnd(); - f2.Close(); - f2.Dispose(); - } - } - } - catch (Exception) - { - FailedCount++; - } - finally - { - LogWriteLock.ExitReadLock(); - } - - return s; - } - - private static List GetRequestInfo(ReadType readType) - { - List requestInfos = new(); - var accessLogs = ReadLog(Path.Combine(_contentRoot, "Log"), "RequestIpInfoLog_", Encoding.UTF8, readType).ObjToString(); - try - { - return JsonConvert.DeserializeObject>("[" + accessLogs + "]"); - } - catch (Exception) - { - var accLogArr = accessLogs.Split("\r\n"); - foreach (var item in accLogArr) - { - if (item.ObjToString() != "") - { - try - { - var accItem = JsonConvert.DeserializeObject(item.TrimEnd(',')); - requestInfos.Add(accItem); - } - catch (Exception) - { - } - } - } - } - - return requestInfos; - } - - - public static List GetLogData() - { - List aopLogs = new List(); - List excLogs = new List(); - List sqlLogs = new List(); - List reqresLogs = new List(); - - try - { - var aoplogContent = ReadLog(Path.Combine(_contentRoot, "Log"), "AOPLog_", Encoding.UTF8, ReadType.Prefix); - - if (!string.IsNullOrEmpty(aoplogContent)) - { - aopLogs = aoplogContent.Split("--------------------------------") - .Where(d => !string.IsNullOrEmpty(d) && d != "\n" && d != "\r\n") - .Select(d => new LogInfo - { - Datetime = d.Split("|")[0].ObjToDate(), - Content = d.Split("|")[1]?.Replace("\r\n", "
"), - LogColor = "AOP", - }).ToList(); - } - } - catch (Exception) - { - } - - try - { - var exclogContent = ReadLog(Path.Combine(_contentRoot, "Log"), $"GlobalExceptionLogs_{DateTime.Now.ToString("yyyMMdd")}.log", Encoding.UTF8); - - if (!string.IsNullOrEmpty(exclogContent)) - { - excLogs = exclogContent.Split("--------------------------------") - .Where(d => !string.IsNullOrEmpty(d) && d != "\n" && d != "\r\n") - .Select(d => new LogInfo - { - Datetime = (d.Split("|")[0]).Split(',')[0].ObjToDate(), - Content = d.Split("|")[1]?.Replace("\r\n", "
"), - LogColor = "EXC", - Import = 9, - }).ToList(); - } - } - catch (Exception) - { - } - - - try - { - var sqllogContent = ReadLog(Path.Combine(_contentRoot, "Log"), "SqlLog_", Encoding.UTF8, ReadType.PrefixLatest); - - if (!string.IsNullOrEmpty(sqllogContent)) - { - sqlLogs = sqllogContent.Split("--------------------------------") - .Where(d => !string.IsNullOrEmpty(d) && d != "\n" && d != "\r\n") - .Select(d => new LogInfo - { - Datetime = d.Split("|")[0].ObjToDate(), - Content = d.Split("|")[1]?.Replace("\r\n", "
"), - LogColor = "SQL", - }).ToList(); - } - } - catch (Exception) - { - } - - //try - //{ - // reqresLogs = ReadLog(Path.Combine(_contentRoot, "Log", "RequestResponseLog.log"), Encoding.UTF8)? - // .Split("--------------------------------") - // .Where(d => !string.IsNullOrEmpty(d) && d != "\n" && d != "\r\n") - // .Select(d => new LogInfo - // { - // Datetime = d.Split("|")[0].ObjToDate(), - // Content = d.Split("|")[1]?.Replace("\r\n", "
"), - // LogColor = "ReqRes", - // }).ToList(); - //} - //catch (Exception) - //{ - //} - - try - { - var Logs = GetRequestInfo(ReadType.PrefixLatest); - - Logs = Logs.Where(d => d.Datetime.ObjToDate() >= DateTime.Today).ToList(); - - reqresLogs = Logs.Select(d => new LogInfo - { - Datetime = d.Datetime.ObjToDate(), - Content = $"IP:{d.Ip}
{d.Url}", - LogColor = "ReqRes", - }).ToList(); - } - catch (Exception) - { - } - - if (excLogs != null) - { - aopLogs.AddRange(excLogs); - } - - if (sqlLogs != null) - { - aopLogs.AddRange(sqlLogs); - } - - if (reqresLogs != null) - { - aopLogs.AddRange(reqresLogs); - } - - aopLogs = aopLogs.OrderByDescending(d => d.Import).ThenByDescending(d => d.Datetime).Take(100).ToList(); - - return aopLogs; - } - - - public static RequestApiWeekView RequestApiinfoByWeek() - { - List Logs = new List(); - List apiWeeks = new List(); - string apiWeeksJson = string.Empty; - List columns = new List(); - columns.Add("日期"); - - - try - { - Logs = GetRequestInfo(ReadType.Prefix); - - apiWeeks = (from n in Logs - group n by new { n.Week, n.Url } - into g - select new ApiWeek - { - week = g.Key.Week, - url = g.Key.Url, - count = g.Count(), - }).ToList(); - - //apiWeeks = apiWeeks.OrderByDescending(d => d.count).Take(8).ToList(); - } - catch (Exception) - { - } - - StringBuilder jsonBuilder = new StringBuilder(); - jsonBuilder.Append("["); - - var weeks = apiWeeks.GroupBy(x => new { x.week }).Select(s => s.First()).ToList(); - foreach (var week in weeks) - { - var apiweeksCurrentWeek = apiWeeks.Where(d => d.week == week.week).OrderByDescending(d => d.count).Take(5).ToList(); - jsonBuilder.Append("{"); - - jsonBuilder.Append("\""); - jsonBuilder.Append("日期"); - jsonBuilder.Append("\":\""); - jsonBuilder.Append(week.week); - jsonBuilder.Append("\","); - - foreach (var item in apiweeksCurrentWeek) - { - columns.Add(item.url); - jsonBuilder.Append("\""); - jsonBuilder.Append(item.url); - jsonBuilder.Append("\":\""); - jsonBuilder.Append(item.count); - jsonBuilder.Append("\","); - } - - if (apiweeksCurrentWeek.Count > 0) - { - jsonBuilder.Remove(jsonBuilder.Length - 1, 1); - } - - jsonBuilder.Append("},"); - } - - if (weeks.Count > 0) - { - jsonBuilder.Remove(jsonBuilder.Length - 1, 1); - } - - jsonBuilder.Append("]"); - - //columns.AddRange(apiWeeks.OrderByDescending(d => d.count).Take(8).Select(d => d.url).ToList()); - columns = columns.Distinct().ToList(); - - return new RequestApiWeekView() - { - columns = columns, - rows = jsonBuilder.ToString(), - }; - } - - public static AccessApiDateView AccessApiByDate() - { - List Logs = new List(); - List apiDates = new List(); - try - { - Logs = GetRequestInfo(ReadType.Prefix); - - apiDates = (from n in Logs - group n by new { n.Date } - into g - select new ApiDate - { - date = g.Key.Date, - count = g.Count(), - }).ToList(); - - apiDates = apiDates.OrderByDescending(d => d.date).Take(7).ToList(); - } - catch (Exception) - { - } - - return new AccessApiDateView() - { - columns = new string[] { "date", "count" }, - rows = apiDates.OrderBy(d => d.date).ToList(), - }; - } - - public static AccessApiDateView AccessApiByHour() - { - List Logs = new List(); - List apiDates = new List(); - try - { - Logs = GetRequestInfo(ReadType.Prefix); - - apiDates = (from n in Logs - where n.Datetime.ObjToDate() >= DateTime.Today - group n by new { hour = n.Datetime.ObjToDate().Hour } - into g - select new ApiDate - { - date = g.Key.hour.ToString("00"), - count = g.Count(), - }).ToList(); - - apiDates = apiDates.OrderBy(d => d.date).Take(24).ToList(); - } - catch (Exception) - { - } - - return new AccessApiDateView() - { - columns = new string[] { "date", "count" }, - rows = apiDates, - }; - } - } - - public enum ReadType - { - /// - /// 精确查找一个 - /// - Accurate, - - /// - /// 指定前缀,模糊查找全部 - /// - Prefix, - - /// - /// 指定前缀,最新一个文件 - /// - PrefixLatest - } -} \ No newline at end of file diff --git a/Blog.Core.Common/Option/AppSettingsOption.cs b/Blog.Core.Common/Option/AppSettingsOption.cs new file mode 100644 index 00000000..bd8770e2 --- /dev/null +++ b/Blog.Core.Common/Option/AppSettingsOption.cs @@ -0,0 +1,28 @@ +using Blog.Core.Common.Option.Core; + +namespace Blog.Core.Common.Option; + +public class AppSettingsOption : IConfigurableOptions +{ + /// + /// 迁移表结构 + /// + public bool MigrateDBEnabled { get; set; } + + /// + /// 初始化数据 + /// + public bool SeedDBEnabled { get; set; } + + /// + /// 生成测试数据 + /// + public bool TestSeedDbEnabled { get; set; } + + public string Author { get; set; } + + /// + /// 是否启用数据库二级缓存 + /// + public bool CacheDbEnabled { get; set; } = false; +} \ No newline at end of file diff --git a/Blog.Core.Common/Seed/DBSeed.cs b/Blog.Core.Common/Seed/DBSeed.cs index f3f023a0..29222802 100644 --- a/Blog.Core.Common/Seed/DBSeed.cs +++ b/Blog.Core.Common/Seed/DBSeed.cs @@ -10,6 +10,7 @@ using System.Reflection; using System.Text; using Blog.Core.Common.Const; +using Microsoft.Data.SqlClient; namespace Blog.Core.Common.Seed { @@ -72,6 +73,7 @@ public static async Task SeedAsync(MyContext myContext, string WebRootPath) if (MyContext.DbType != SqlSugar.DbType.Oracle && MyContext.DbType != SqlSugar.DbType.Dm) { myContext.Db.DbMaintenance.CreateDatabase(); + SqlConnection.ClearAllPools(); ConsoleHelper.WriteSuccessLine($"Database created successfully!"); } else @@ -86,13 +88,13 @@ public static async Task SeedAsync(MyContext myContext, string WebRootPath) var path = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory; var referencedAssemblies = System.IO.Directory.GetFiles(path, "Blog.Core.Model.dll") - .Select(Assembly.LoadFrom).ToArray(); + .Select(Assembly.LoadFrom).ToArray(); var modelTypes = referencedAssemblies - .SelectMany(a => a.DefinedTypes) - .Select(type => type.AsType()) - .Where(x => x.IsClass && x.Namespace is "Blog.Core.Model.Models") - .Where(s => !s.IsDefined(typeof(MultiTenantAttribute), false)) - .ToList(); + .SelectMany(a => a.DefinedTypes) + .Select(type => type.AsType()) + .Where(x => x.IsClass && x.Namespace is "Blog.Core.Model.Models") + .Where(s => !s.IsDefined(typeof(MultiTenantAttribute), false)) + .ToList(); modelTypes.ForEach(t => { // 这里只支持添加表,不支持删除 @@ -106,7 +108,7 @@ public static async Task SeedAsync(MyContext myContext, string WebRootPath) ConsoleHelper.WriteSuccessLine($"Tables created successfully!"); Console.WriteLine(); - if (AppSettings.app(new string[] {"AppSettings", "SeedDBDataEnabled"}).ObjToBool()) + if (AppSettings.app(new string[] { "AppSettings", "SeedDBDataEnabled" }).ObjToBool()) { JsonSerializerSettings setting = new JsonSerializerSettings(); JsonConvert.DefaultSettings = new Func(() => @@ -366,11 +368,11 @@ private static async Task SeedDataAsync(ISqlSugarClient db) { // 获取所有种子配置-初始化数据 var seedDataTypes = AssemblysExtensions.GetAllAssemblies().SelectMany(s => s.DefinedTypes) - .Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass) - .Where(u => + .Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass) + .Where(u => { var esd = u.GetInterfaces() - .FirstOrDefault(i => i.HasImplementedRawGeneric(typeof(IEntitySeedData<>))); + .FirstOrDefault(i => i.HasImplementedRawGeneric(typeof(IEntitySeedData<>))); if (esd is null) { return false; @@ -445,12 +447,12 @@ public static void MigrationLogs(MyContext myContext) ConsoleHelper.WriteSuccessLine($"Log Database created successfully!"); var path = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory; var referencedAssemblies = System.IO.Directory.GetFiles(path, "Blog.Core.Model.dll") - .Select(Assembly.LoadFrom).ToArray(); + .Select(Assembly.LoadFrom).ToArray(); var modelTypes = referencedAssemblies - .SelectMany(a => a.DefinedTypes) - .Select(type => type.AsType()) - .Where(x => x.IsClass && x.Namespace != null && x.Namespace.StartsWith("Blog.Core.Model.Logs")) - .ToList(); + .SelectMany(a => a.DefinedTypes) + .Select(type => type.AsType()) + .Where(x => x.IsClass && x.Namespace != null && x.Namespace.StartsWith("Blog.Core.Model.Logs")) + .ToList(); Stopwatch sw = Stopwatch.StartNew(); var tables = logDb.DbMaintenance.GetTableInfoList(); @@ -488,7 +490,7 @@ public static void MigrationLogs(MyContext myContext) public static async Task TenantSeedAsync(MyContext myContext) { var tenants = await myContext.Db.Queryable().Where(s => s.TenantType == TenantTypeEnum.Db) - .ToListAsync(); + .ToListAsync(); if (tenants.Any()) { Console.WriteLine($@"Init Multi Tenant Db"); @@ -500,7 +502,7 @@ public static async Task TenantSeedAsync(MyContext myContext) } tenants = await myContext.Db.Queryable().Where(s => s.TenantType == TenantTypeEnum.Tables) - .ToListAsync(); + .ToListAsync(); if (tenants.Any()) { await InitTenantSeedAsync(myContext, tenants); @@ -522,8 +524,8 @@ private static async Task InitTenantSeedAsync(MyContext myContext, List s.DefinedTypes) - .Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass) - .Where(u => + .Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass) + .Where(u => { var esd = u.GetInterfaces() - .FirstOrDefault(i => i.HasImplementedRawGeneric(typeof(IEntitySeedData<>))); + .FirstOrDefault(i => i.HasImplementedRawGeneric(typeof(IEntitySeedData<>))); if (esd is null) { return false; diff --git a/Blog.Core.Common/Seed/SeedData/SubBusinessDataSeedData.cs b/Blog.Core.Common/Seed/SeedData/SubBusinessDataSeedData.cs index be8462e8..3d7b8937 100644 --- a/Blog.Core.Common/Seed/SeedData/SubBusinessDataSeedData.cs +++ b/Blog.Core.Common/Seed/SeedData/SubBusinessDataSeedData.cs @@ -2,6 +2,7 @@ using SqlSugar; using System.Collections.Generic; using System.Threading.Tasks; +using Blog.Core.Common.Utility; namespace Blog.Core.Common.Seed.SeedData; @@ -29,13 +30,13 @@ await db.Insertable(new List() { new() { - Id = SnowFlakeSingle.Instance.NextId(), + Id = IdGeneratorUtility.NextId(), Name = "王五业务数据1", Amount = 100, }, new() { - Id = SnowFlakeSingle.Instance.NextId(), + Id = IdGeneratorUtility.NextId(), Name = "王五业务数据2", Amount = 1000, }, @@ -50,13 +51,13 @@ await db.Insertable(new List() { new() { - Id = SnowFlakeSingle.Instance.NextId(), + Id = IdGeneratorUtility.NextId(), Name = "赵六业务数据1", Amount = 50, }, new() { - Id = SnowFlakeSingle.Instance.NextId(), + Id = IdGeneratorUtility.NextId(), Name = "赵六业务数据2", Amount = 60, }, diff --git a/Blog.Core.Common/Seed/SeedData/UserInfoSeedData.cs b/Blog.Core.Common/Seed/SeedData/UserInfoSeedData.cs index 414a506b..900ca79e 100644 --- a/Blog.Core.Common/Seed/SeedData/UserInfoSeedData.cs +++ b/Blog.Core.Common/Seed/SeedData/UserInfoSeedData.cs @@ -72,9 +72,10 @@ public async Task CustomizeSeedData(ISqlSugarClient db) var sysUserInfos = data.Where(s => !names.Contains(s.LoginName)).ToList(); if (sysUserInfos.Any()) { - await db.Insertable(sysUserInfos).ExecuteReturnIdentityAsync(); + //await db.Insertable(sysUserInfos).ExecuteReturnIdentityAsync();//postgresql这句会报错 + await db.Insertable(sysUserInfos).ExecuteCommandAsync(); } await Task.CompletedTask; } -} \ No newline at end of file +} diff --git a/Blog.Core.Common/Swagger/Filter/EnumSchemaFilter.cs b/Blog.Core.Common/Swagger/Filter/EnumSchemaFilter.cs new file mode 100644 index 00000000..a20f8544 --- /dev/null +++ b/Blog.Core.Common/Swagger/Filter/EnumSchemaFilter.cs @@ -0,0 +1,35 @@ +using System.ComponentModel; +using System.Reflection; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Blog.Core.Common.Swagger.Filter; + +/// +/// Enum 转换 +/// +public class EnumSchemaFilter : ISchemaFilter +{ + public void Apply(OpenApiSchema schema, SchemaFilterContext context) + { + if (schema.Enum == null || schema.Enum.Count == 0 || + context.Type == null || !context.Type.IsEnum) + return; + + schema.Description += "

枚举说明:

    "; + + var enumMembers = context.Type.GetFields(BindingFlags.Public | BindingFlags.Static); + + foreach (var enumMember in enumMembers) + { + var enumValue = Convert.ToInt64(Enum.Parse(context.Type, enumMember.Name)); + + var descriptionAttribute = enumMember.GetCustomAttribute(); + var description = descriptionAttribute != null ? descriptionAttribute.Description : enumMember.Name; + + schema.Description += $"
  • {enumValue}:{enumMember.Name} - {description}
  • "; + } + + schema.Description += "
"; + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Swagger/Filter/EnumTypesDocumentFilter.cs b/Blog.Core.Common/Swagger/Filter/EnumTypesDocumentFilter.cs new file mode 100644 index 00000000..b0e73b5f --- /dev/null +++ b/Blog.Core.Common/Swagger/Filter/EnumTypesDocumentFilter.cs @@ -0,0 +1,107 @@ +using System.Reflection; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Blog.Core.Common.Swagger.Filter; + +public class EnumTypesDocumentFilter : IDocumentFilter +{ + private static Type UnwrapEnumType(Type type) + { + if (type == null) + return null; + + // Nullable + if (Nullable.GetUnderlyingType(type)?.IsEnum == true) + return Nullable.GetUnderlyingType(type); + + // 直接是 Enum + if (type.IsEnum) + return type; + + // List / IEnumerable + if (type.IsGenericType && + (type.GetGenericTypeDefinition() == typeof(IEnumerable<>) || + type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)))) + { + var itemType = type.GetGenericArguments().FirstOrDefault(); + return UnwrapEnumType(itemType); + } + + // Enum[] + if (type.IsArray) + return UnwrapEnumType(type.GetElementType()); + + return null; + } + + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + foreach (var path in swaggerDoc.Paths.Values) + { + foreach (var operation in path.Operations.Values) + { + foreach (var parameter in operation.Parameters) + { + OpenApiSchema schema = null; + if (parameter.Schema != null && !string.IsNullOrWhiteSpace(parameter.Description) && parameter.Description.Contains("
    ")) + { + schema = parameter.Schema; + } + else if (parameter.Schema is { Reference: not null }) + { + // 引用类型Enum + schema = context.SchemaRepository.Schemas[parameter.Schema.Reference.Id]; + } + else if (parameter.Schema.Type == "array" && parameter.Schema.Items.Reference != null) + { + // 数组类型Enum + schema = context.SchemaRepository.Schemas[parameter.Schema.Items.Reference.Id]; + } + else if (parameter.Schema is { Items: { Enum: { Count: > 0 } } }) + { + schema = parameter.Schema.Items; + } + else if (parameter.Schema.Enum is { Count: > 0 }) + { + // 基础类型Enum (integer) + var apiParam = context.ApiDescriptions + .SelectMany(desc => desc.ParameterDescriptions) + .FirstOrDefault(desc => desc.Name == parameter.Name); + + Type paramType = apiParam?.ParameterDescriptor?.ParameterType; + Type enumType = null; + + if (paramType != null) + { + // 如果是 DTO 类型,从其属性中反查当前参数名 + if (!paramType.IsEnum && !paramType.IsValueType && !paramType.IsPrimitive && paramType != typeof(string)) + { + var dtoProp = paramType.GetProperty(apiParam.Name, + BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); + if (dtoProp != null) + paramType = dtoProp.PropertyType; + } + + enumType = UnwrapEnumType(paramType); + + if (enumType?.IsEnum == true) + { + schema = context.SchemaGenerator.GenerateSchema(enumType, context.SchemaRepository); + } + } + } + + if (schema == null || schema.Description == null) continue; + + var cutStart = schema.Description.IndexOf("
      "); + var cutEnd = schema.Description.IndexOf("
    ") + 5; + + if (cutStart < 0 || cutEnd <= cutStart) continue; + + parameter.Description += "

    说明:

    " + schema.Description.Substring(cutStart, cutEnd - cutStart); + } + } + } + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Utility/IdGeneratorUtility.cs b/Blog.Core.Common/Utility/IdGeneratorUtility.cs new file mode 100644 index 00000000..dcc308b1 --- /dev/null +++ b/Blog.Core.Common/Utility/IdGeneratorUtility.cs @@ -0,0 +1,78 @@ +using Blog.Core.Common.DB; +using Blog.Core.Common.Option; +using Microsoft.Extensions.Hosting; +using Serilog; +using SnowflakeId.AutoRegister.Builder; +using SnowflakeId.AutoRegister.Interfaces; +using SqlSugar; +using Yitter.IdGenerator; + +namespace Blog.Core.Common.Utility; + +public class IdGeneratorUtility +{ + private static IdGeneratorOptions _options; + + private static readonly Lazy AutoRegister = new(() => + { + var builder = new AutoRegisterBuilder() + // Register Option + // Use the following line to set the identifier. + // Recommended setting to distinguish multiple applications on a single machine + .SetExtraIdentifier(App.Configuration["urls"] ?? string.Empty) + // Use the following line to set the WorkerId scope. + .SetWorkerIdScope(1, 30) + // Use the following line to set the register option. + // .SetRegisterOption(option => {}) + ; + var redisOptions = App.GetOptions(); + if (redisOptions.Enable) + // Use the following line to use the Redis store. + builder.UseRedisStore(redisOptions.ConnectionString); + else if (BaseDBConfig.LogConfig != null && BaseDBConfig.LogConfig.DbType == DbType.SqlServer) + // Use the following line to use the SQL Server store. + builder.UseSqlServerStore(BaseDBConfig.LogConfig.ConnectionString); + else + // Use the following line to use the default store. + // Only suitable for standalone use, local testing, etc. + builder.UseDefaultStore(); + + App.GetService(false).ApplicationStopping.Register(UnRegister); + return builder.Build(); + }); + + private static readonly Lazy _idGenInstance = new(() => + { + var config = AutoRegister.Value.Register(); + + //WorkerId DataCenterId 取值 1-31 + var options = GetOptions(); + options.WorkerId = (ushort)config.WorkerId; + IIdGenerator idGenInstance = new DefaultIdGenerator(options); + return idGenInstance; + }); + + private static IIdGenerator IdGenInstance => _idGenInstance.Value; + + public static IdGeneratorOptions GetOptions() + { + _options ??= new IdGeneratorOptions + { + }; + + return _options; + } + + public static long NextId() + { + return IdGenInstance.NewLong(); + } + + public static void UnRegister() + { + if (!AutoRegister.IsValueCreated) return; + + AutoRegister.Value.UnRegister(); + Log.Information("Snowflake Id Unregistered"); + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Utility/SqlSugarSnowflakeHelper.cs b/Blog.Core.Common/Utility/SqlSugarSnowflakeHelper.cs new file mode 100644 index 00000000..4fc7a9ca --- /dev/null +++ b/Blog.Core.Common/Utility/SqlSugarSnowflakeHelper.cs @@ -0,0 +1,31 @@ +namespace Blog.Core.Common.Utility; + +/// +/// SqlSugar 雪花算法 工具类 +/// +public class SqlSugarSnowflakeHelper +{ + private const long Twepoch = 1288834974657L; + private const int TimestampLeftShift = 22; + private const int DatacenterIdShift = 17; + private const int WorkerIdShift = 12; + private const long SequenceMask = 0xFFF; // 4095 + private const long WorkerMask = 0x1F; // 31 + private const long DatacenterMask = 0x1F; // 31 + + public static DateTime GetDateTime(long id) + { + long timestamp = (id >> TimestampLeftShift) + Twepoch; + return DateTimeOffset.FromUnixTimeMilliseconds(timestamp).ToLocalTime().DateTime; + } + + public static (DateTime time, long datacenterId, long workerId, long sequence) Decode(long id) + { + long timestamp = (id >> TimestampLeftShift) + Twepoch; + long datacenterId = (id >> DatacenterIdShift) & DatacenterMask; + long workerId = (id >> WorkerIdShift) & WorkerMask; + long sequence = id & SequenceMask; + var time = DateTimeOffset.FromUnixTimeMilliseconds(timestamp).ToLocalTime().DateTime; + return (time, datacenterId, workerId, sequence); + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Utility/YitterSnowflakeHelper.cs b/Blog.Core.Common/Utility/YitterSnowflakeHelper.cs new file mode 100644 index 00000000..122e8927 --- /dev/null +++ b/Blog.Core.Common/Utility/YitterSnowflakeHelper.cs @@ -0,0 +1,48 @@ +using Yitter.IdGenerator; + +namespace Blog.Core.Common.Utility; + +/// +/// Yitter 雪花算法 工具类 +/// +public class YitterSnowflakeHelper +{ + /// + /// 从ID中解析时间 + /// + public static DateTime GetDateTime(IdGeneratorOptions options, long id) + { + int shift = options.SeqBitLength + options.WorkerIdBitLength + options.DataCenterIdBitLength; + long timeDiff = id >> shift; + + DateTime utcTime = options.TimestampType == 1 + ? options.BaseTime.AddSeconds(timeDiff) + : options.BaseTime.AddMilliseconds(timeDiff); + + return DateTime.SpecifyKind(utcTime, DateTimeKind.Utc).ToLocalTime(); + } + + /// + /// 从ID中解析时间、WorkerId、序列号 + /// + public static (DateTime time, long workerId, long sequence, long datacenterId) Decode(IdGeneratorOptions options, + long id) + { + int seqBits = options.SeqBitLength; + int workerBits = options.WorkerIdBitLength; + int datacenterBits = options.DataCenterIdBitLength; + + long sequence = id & ((1L << seqBits) - 1); + long workerId = (id >> seqBits) & ((1L << workerBits) - 1); + long datacenterId = datacenterBits == 0 ? 0 : (id >> (seqBits + workerBits)) & ((1L << datacenterBits) - 1); + long timeDiff = id >> (seqBits + workerBits + datacenterBits); + + DateTime utcTime = options.TimestampType == 1 + ? options.BaseTime.AddSeconds(timeDiff) + : options.BaseTime.AddMilliseconds(timeDiff); + + DateTime localTime = DateTime.SpecifyKind(utcTime, DateTimeKind.Utc).ToLocalTime(); + + return (localTime, workerId, sequence, datacenterId); + } +} \ No newline at end of file diff --git a/Blog.Core.EventBus/Blog.Core.EventBus.csproj b/Blog.Core.EventBus/Blog.Core.EventBus.csproj index 17bebf20..622856f5 100644 --- a/Blog.Core.EventBus/Blog.Core.EventBus.csproj +++ b/Blog.Core.EventBus/Blog.Core.EventBus.csproj @@ -1,6 +1,5 @@ - @@ -8,7 +7,7 @@ - + diff --git a/Blog.Core.Extensions/AOP/BlogCacheAOP.cs b/Blog.Core.Extensions/AOP/BlogCacheAOP.cs index 1d52a2de..2fb8f7bd 100644 --- a/Blog.Core.Extensions/AOP/BlogCacheAOP.cs +++ b/Blog.Core.Extensions/AOP/BlogCacheAOP.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Blog.Core.Common.Caches; +using Blog.Core.Common.Caches.Interface; namespace Blog.Core.AOP { diff --git a/Blog.Core.Extensions/AOP/BlogLogAOP.cs b/Blog.Core.Extensions/AOP/BlogLogAOP.cs index 09d798bf..a52d3ad8 100644 --- a/Blog.Core.Extensions/AOP/BlogLogAOP.cs +++ b/Blog.Core.Extensions/AOP/BlogLogAOP.cs @@ -10,282 +10,270 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; namespace Blog.Core.AOP { - /// - /// 拦截器BlogLogAOP 继承IInterceptor接口 - /// - public class BlogLogAOP : IInterceptor - { - private readonly IHubContext _hubContext; - private readonly IHttpContextAccessor _accessor; + /// + /// 拦截器BlogLogAOP 继承IInterceptor接口 + /// + public class BlogLogAOP : IInterceptor + { + private readonly IHubContext _hubContext; + private readonly IHttpContextAccessor _accessor; + private readonly ILogger _logger; - public BlogLogAOP(IHubContext hubContext, IHttpContextAccessor accessor) - { - _hubContext = hubContext; - _accessor = accessor; - } + public BlogLogAOP(IHubContext hubContext, IHttpContextAccessor accessor, ILogger logger) + { + _hubContext = hubContext; + _accessor = accessor; + _logger = logger; + } - /// - /// 实例化IInterceptor唯一方法 - /// - /// 包含被拦截方法的信息 - public void Intercept(IInvocation invocation) - { - string UserName = _accessor.HttpContext?.User?.Identity?.Name; - string json; - try - { - json = JsonConvert.SerializeObject(invocation.Arguments); - } - catch (Exception ex) - { - json = "无法序列化,可能是兰姆达表达式等原因造成,按照框架优化代码" + ex.ToString(); - } + /// + /// 实例化IInterceptor唯一方法 + /// + /// 包含被拦截方法的信息 + public void Intercept(IInvocation invocation) + { + string UserName = _accessor.HttpContext?.User?.Identity?.Name; + string json; + try + { + json = JsonConvert.SerializeObject(invocation.Arguments); + } + catch (Exception ex) + { + json = "无法序列化,可能是兰姆达表达式等原因造成,按照框架优化代码" + ex.ToString(); + } - DateTime startTime = DateTime.Now; - AOPLogInfo apiLogAopInfo = new AOPLogInfo - { - RequestTime = startTime.ToString("yyyy-MM-dd hh:mm:ss fff"), - OpUserName = UserName, - RequestMethodName = invocation.Method.Name, - RequestParamsName = string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray()), - ResponseJsonData = json - }; + DateTime startTime = DateTime.Now; + AOPLogInfo apiLogAopInfo = new AOPLogInfo + { + RequestTime = startTime.ToString("yyyy-MM-dd hh:mm:ss fff"), + OpUserName = UserName, + RequestMethodName = invocation.Method.Name, + RequestParamsName = string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray()), + ResponseJsonData = json + }; - //测试异常记录 - //Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss fff")); + //测试异常记录 + //Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss fff")); - //记录被拦截方法信息的日志信息 - //var dataIntercept = "" + - // $"【当前操作用户】:{ UserName} \r\n" + - // $"【当前执行方法】:{ invocation.Method.Name} \r\n" + - // $"【携带的参数有】: {string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray())} \r\n"; + //记录被拦截方法信息的日志信息 + //var dataIntercept = "" + + // $"【当前操作用户】:{ UserName} \r\n" + + // $"【当前执行方法】:{ invocation.Method.Name} \r\n" + + // $"【携带的参数有】: {string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray())} \r\n"; - try - { - MiniProfiler.Current.Step($"执行Service方法:{invocation.Method.Name}() -> "); - //在被拦截的方法执行完毕后 继续执行当前方法,注意是被拦截的是异步的 - invocation.Proceed(); + try + { + MiniProfiler.Current.Step($"执行Service方法:{invocation.Method.Name}() -> "); + //在被拦截的方法执行完毕后 继续执行当前方法,注意是被拦截的是异步的 + invocation.Proceed(); - // 异步获取异常,先执行 - if (IsAsyncMethod(invocation.Method)) - { - #region 方案一 + // 异步获取异常,先执行 + if (IsAsyncMethod(invocation.Method)) + { + #region 方案一 - //Wait task execution and modify return value - if (invocation.Method.ReturnType == typeof(Task)) - { - invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally( - (Task) invocation.ReturnValue, - async () => await SuccessAction(invocation, apiLogAopInfo, startTime), /*成功时执行*/ - ex => - { - LogEx(ex, apiLogAopInfo); - }); - } - //Task - else - { - invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult( - invocation.Method.ReturnType.GenericTypeArguments[0], - invocation.ReturnValue, - //async () => await SuccessAction(invocation, dataIntercept),/*成功时执行*/ - async (o) => await SuccessAction(invocation, apiLogAopInfo, startTime, o), /*成功时执行*/ - ex => - { - LogEx(ex, apiLogAopInfo); - }); - } + //Wait task execution and modify return value + if (invocation.Method.ReturnType == typeof(Task)) + { + invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally( + (Task)invocation.ReturnValue, + async () => await SuccessAction(invocation, apiLogAopInfo, startTime), /*成功时执行*/ + ex => { LogEx(ex, apiLogAopInfo); }); + } + //Task + else + { + invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult( + invocation.Method.ReturnType.GenericTypeArguments[0], + invocation.ReturnValue, + //async () => await SuccessAction(invocation, dataIntercept),/*成功时执行*/ + async (o) => await SuccessAction(invocation, apiLogAopInfo, startTime, o), /*成功时执行*/ + ex => { LogEx(ex, apiLogAopInfo); }); + } - #endregion + #endregion - // 如果方案一不行,试试这个方案 - //#region 方案二 + // 如果方案一不行,试试这个方案 + //#region 方案二 - //var type = invocation.Method.ReturnType; - //var resultProperty = type.GetProperty("Result"); - //DateTime endTime = DateTime.Now; - //string ResponseTime = (endTime - startTime).Milliseconds.ToString(); - //apiLogAopInfo.ResponseTime = endTime.ToString("yyyy-MM-dd hh:mm:ss fff"); - //apiLogAopInfo.ResponseIntervalTime = ResponseTime + "ms"; - //apiLogAopInfo.ResponseJsonData = JsonConvert.SerializeObject(resultProperty.GetValue(invocation.ReturnValue)); + //var type = invocation.Method.ReturnType; + //var resultProperty = type.GetProperty("Result"); + //DateTime endTime = DateTime.Now; + //string ResponseTime = (endTime - startTime).Milliseconds.ToString(); + //apiLogAopInfo.ResponseTime = endTime.ToString("yyyy-MM-dd hh:mm:ss fff"); + //apiLogAopInfo.ResponseIntervalTime = ResponseTime + "ms"; + //apiLogAopInfo.ResponseJsonData = JsonConvert.SerializeObject(resultProperty.GetValue(invocation.ReturnValue)); - ////dataIntercept += ($"【响应时间】:{ResponseTime}ms\r\n"); - ////dataIntercept += ($"【执行完成时间】:{endTime.ToString("yyyy-MM-dd hh:mm:ss fff")}\r\n"); - ////dataIntercept += ($"【执行完成结果】:{JsonConvert.SerializeObject(resultProperty.GetValue(invocation.ReturnValue))}\r\n"); + ////dataIntercept += ($"【响应时间】:{ResponseTime}ms\r\n"); + ////dataIntercept += ($"【执行完成时间】:{endTime.ToString("yyyy-MM-dd hh:mm:ss fff")}\r\n"); + ////dataIntercept += ($"【执行完成结果】:{JsonConvert.SerializeObject(resultProperty.GetValue(invocation.ReturnValue))}\r\n"); - //Parallel.For(0, 1, e => - //{ - // //LogLock.OutLogAOP("AOPLog", new string[] { dataIntercept }); - // LogLock.OutLogAOP("AOPLog", new string[] { apiLogAopInfo.GetType().ToString() + " - ResponseJsonDataType:" + type, JsonConvert.SerializeObject(apiLogAopInfo) }); - //}); + //Parallel.For(0, 1, e => + //{ + // //LogLock.OutLogAOP("AOPLog", new string[] { dataIntercept }); + // LogLock.OutLogAOP("AOPLog", new string[] { apiLogAopInfo.GetType().ToString() + " - ResponseJsonDataType:" + type, JsonConvert.SerializeObject(apiLogAopInfo) }); + //}); - //#endregion - } - else - { - // 同步1 - string jsonResult; - try - { - jsonResult = JsonConvert.SerializeObject(invocation.ReturnValue); - } - catch (Exception ex) - { - jsonResult = "无法序列化,可能是兰姆达表达式等原因造成,按照框架优化代码" + ex.ToString(); - } + //#endregion + } + else + { + // 同步1 + string jsonResult; + try + { + jsonResult = JsonConvert.SerializeObject(invocation.ReturnValue); + } + catch (Exception ex) + { + jsonResult = "无法序列化,可能是兰姆达表达式等原因造成,按照框架优化代码" + ex.ToString(); + } - var type = invocation.Method.ReturnType; - var resultProperty = type.GetProperty("Result"); - DateTime endTime = DateTime.Now; - string ResponseTime = (endTime - startTime).Milliseconds.ToString(); - apiLogAopInfo.ResponseTime = endTime.ToString("yyyy-MM-dd hh:mm:ss fff"); - apiLogAopInfo.ResponseIntervalTime = ResponseTime + "ms"; - //apiLogAopInfo.ResponseJsonData = JsonConvert.SerializeObject(resultProperty.GetValue(invocation.ReturnValue)); - apiLogAopInfo.ResponseJsonData = jsonResult; - //dataIntercept += ($"【执行完成结果】:{jsonResult}"); - Parallel.For(0, 1, e => - { - //LogLock.OutLogAOP("AOPLog", new string[] { dataIntercept }); - LogLock.OutLogAOP("AOPLog", _accessor.HttpContext?.TraceIdentifier, - new string[] {apiLogAopInfo.GetType().ToString(), JsonConvert.SerializeObject(apiLogAopInfo)}); - }); - } - } - catch (Exception ex) // 同步2 - { - LogEx(ex, apiLogAopInfo); - throw; - } + var type = invocation.Method.ReturnType; + var resultProperty = type.GetProperty("Result"); + DateTime endTime = DateTime.Now; + string ResponseTime = (endTime - startTime).Milliseconds.ToString(); + apiLogAopInfo.ResponseTime = endTime.ToString("yyyy-MM-dd hh:mm:ss fff"); + apiLogAopInfo.ResponseIntervalTime = ResponseTime + "ms"; + //apiLogAopInfo.ResponseJsonData = JsonConvert.SerializeObject(resultProperty.GetValue(invocation.ReturnValue)); + apiLogAopInfo.ResponseJsonData = jsonResult; + //dataIntercept += ($"【执行完成结果】:{jsonResult}"); - if (AppSettings.app(new string[] {"Middleware", "SignalRSendLog", "Enabled"}).ObjToBool()) - { - _hubContext.Clients.All.SendAsync("ReceiveUpdate", LogLock.GetLogData()).Wait(); - } - } + _logger.LogInformation("AOPLog,{TraceIdentifier}: {ApiLogAopInfo}", + _accessor.HttpContext?.TraceIdentifier, JsonConvert.SerializeObject(apiLogAopInfo)); + } + } + catch (Exception ex) // 同步2 + { + LogEx(ex, apiLogAopInfo); + throw; + } - private async Task SuccessAction(IInvocation invocation, AOPLogInfo apiLogAopInfo, DateTime startTime, object o = null) - { - //invocation.ReturnValue = o; - //var type = invocation.Method.ReturnType; - //if (typeof(Task).IsAssignableFrom(type)) - //{ - // //var resultProperty = type.GetProperty("Result"); - // //类型错误 都可以不要invocation参数,直接将o系列化保存到日记中 - // dataIntercept += ($"【执行完成结果】:{JsonConvert.SerializeObject(invocation.ReturnValue)}"); - //} - //else - //{ - // dataIntercept += ($"【执行完成结果】:{invocation.ReturnValue}"); - //} - DateTime endTime = DateTime.Now; - string ResponseTime = (endTime - startTime).Milliseconds.ToString(); - apiLogAopInfo.ResponseTime = endTime.ToString("yyyy-MM-dd hh:mm:ss fff"); - apiLogAopInfo.ResponseIntervalTime = ResponseTime + "ms"; - apiLogAopInfo.ResponseJsonData = JsonConvert.SerializeObject(o); + if (AppSettings.app(new string[] { "Middleware", "SignalRSendLog", "Enabled" }).ObjToBool()) + { + //发送日志 + _hubContext.Clients.All.SendAsync("ReceiveUpdate", JsonConvert.SerializeObject(apiLogAopInfo)).Wait(); + } + } + private async Task SuccessAction(IInvocation invocation, AOPLogInfo apiLogAopInfo, DateTime startTime, + object o = null) + { + //invocation.ReturnValue = o; + //var type = invocation.Method.ReturnType; + //if (typeof(Task).IsAssignableFrom(type)) + //{ + // //var resultProperty = type.GetProperty("Result"); + // //类型错误 都可以不要invocation参数,直接将o系列化保存到日记中 + // dataIntercept += ($"【执行完成结果】:{JsonConvert.SerializeObject(invocation.ReturnValue)}"); + //} + //else + //{ + // dataIntercept += ($"【执行完成结果】:{invocation.ReturnValue}"); + //} + DateTime endTime = DateTime.Now; + string ResponseTime = (endTime - startTime).Milliseconds.ToString(); + apiLogAopInfo.ResponseTime = endTime.ToString("yyyy-MM-dd hh:mm:ss fff"); + apiLogAopInfo.ResponseIntervalTime = ResponseTime + "ms"; + apiLogAopInfo.ResponseJsonData = JsonConvert.SerializeObject(o); - await Task.Run(() => - { - Parallel.For(0, 1, e => - { - //LogLock.OutSql2Log("AOPLog", new string[] { JsonConvert.SerializeObject(apiLogAopInfo) }); - LogLock.OutLogAOP("AOPLog", _accessor.HttpContext?.TraceIdentifier, - new string[] {apiLogAopInfo.GetType().ToString(), JsonConvert.SerializeObject(apiLogAopInfo)}); - }); - }); - } + _logger.LogInformation("AOPLog,{TraceIdentifier}: {ApiLogAopInfo}", + _accessor.HttpContext?.TraceIdentifier, JsonConvert.SerializeObject(apiLogAopInfo)); + } - private void LogEx(Exception ex, AOPLogInfo dataIntercept) - { - if (ex != null) - { - //执行的 service 中,收录异常 - MiniProfiler.Current.CustomTiming("Errors:", ex.Message); - //执行的 service 中,捕获异常 - //dataIntercept += ($"【执行完成结果】:方法中出现异常:{ex.Message + ex.InnerException}\r\n"); - AOPLogExInfo apiLogAopExInfo = new AOPLogExInfo - { - ExMessage = ex.Message, - InnerException = "InnerException-内部异常:\r\n" + (ex.InnerException == null ? "" : ex.InnerException.InnerException.ToString()) + - ("\r\nStackTrace-堆栈跟踪:\r\n") + (ex.StackTrace == null ? "" : ex.StackTrace.ToString()), - ApiLogAopInfo = dataIntercept - }; - // 异常日志里有详细的堆栈信息 - Parallel.For(0, 1, e => - { - //LogLock.OutLogAOP("AOPLogEx", new string[] { dataIntercept }); - LogLock.OutLogAOP("AOPLogEx", _accessor.HttpContext?.TraceIdentifier, - new string[] {apiLogAopExInfo.GetType().ToString(), JsonConvert.SerializeObject(apiLogAopExInfo)}); - }); - } - } + private void LogEx(Exception ex, AOPLogInfo dataIntercept) + { + if (ex != null) + { + //执行的 service 中,收录异常 + MiniProfiler.Current.CustomTiming("Errors:", ex.Message); + //执行的 service 中,捕获异常 + //dataIntercept += ($"【执行完成结果】:方法中出现异常:{ex.Message + ex.InnerException}\r\n"); + AOPLogExInfo apiLogAopExInfo = new AOPLogExInfo + { + ExMessage = ex.Message, + InnerException = "InnerException-内部异常:\r\n" + + (ex.InnerException == null ? "" : ex.InnerException.InnerException.ToString()) + + ("\r\nStackTrace-堆栈跟踪:\r\n") + (ex.StackTrace == null ? "" : ex.StackTrace.ToString()), + ApiLogAopInfo = dataIntercept + }; + // 异常日志里有详细的堆栈信息 + _logger.LogError(ex, "AOPLog,{TraceIdentifier}: {ApiLogAopInfo}", + _accessor.HttpContext?.TraceIdentifier, JsonConvert.SerializeObject(apiLogAopExInfo)); + } + } - public static bool IsAsyncMethod(MethodInfo method) - { - return ( - method.ReturnType == typeof(Task) || - (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)) - ); - } - } + public static bool IsAsyncMethod(MethodInfo method) + { + return ( + method.ReturnType == typeof(Task) || + (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)) + ); + } + } - internal static class InternalAsyncHelper - { - public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func postAction, Action finalAction) - { - Exception exception = null; + internal static class InternalAsyncHelper + { + public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func postAction, + Action finalAction) + { + Exception exception = null; - try - { - await actualReturnValue; - await postAction(); - } - catch (Exception ex) - { - exception = ex; - } - finally - { - finalAction(exception); - } - } + try + { + await actualReturnValue; + await postAction(); + } + catch (Exception ex) + { + exception = ex; + } + finally + { + finalAction(exception); + } + } - public static async Task AwaitTaskWithPostActionAndFinallyAndGetResult(Task actualReturnValue, Func postAction, - Action finalAction) - { - Exception exception = null; - try - { - var result = await actualReturnValue; - await postAction(result); - return result; - } - catch (Exception ex) - { - exception = ex; - throw; - } - finally - { - finalAction(exception); - } - } + public static async Task AwaitTaskWithPostActionAndFinallyAndGetResult(Task actualReturnValue, + Func postAction, + Action finalAction) + { + Exception exception = null; + try + { + var result = await actualReturnValue; + await postAction(result); + return result; + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + finalAction(exception); + } + } - public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, object actualReturnValue, - Func action, Action finalAction) - { - return typeof(InternalAsyncHelper) - .GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static) - .MakeGenericMethod(taskReturnType) - .Invoke(null, new object[] {actualReturnValue, action, finalAction}); - } - } + public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, + object actualReturnValue, + Func action, Action finalAction) + { + return typeof(InternalAsyncHelper) + .GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static) + .MakeGenericMethod(taskReturnType) + .Invoke(null, new object[] { actualReturnValue, action, finalAction }); + } + } } \ No newline at end of file diff --git a/Blog.Core.Extensions/Authorizations/Policys/JwtToken.cs b/Blog.Core.Extensions/Authorizations/Policys/JwtToken.cs index 9b8a7389..2b9435a5 100644 --- a/Blog.Core.Extensions/Authorizations/Policys/JwtToken.cs +++ b/Blog.Core.Extensions/Authorizations/Policys/JwtToken.cs @@ -13,7 +13,7 @@ public class JwtToken /// /// 获取基于JWT的Token /// - /// 需要在登陆的时候配置 + /// 需要在登录的时候配置 /// 在startup中定义的参数 /// public static TokenInfoViewModel BuildJwtToken(Claim[] claims, PermissionRequirement permissionRequirement) diff --git a/Blog.Core.Extensions/Authorizations/Policys/PermissionHandler.cs b/Blog.Core.Extensions/Authorizations/Policys/PermissionHandler.cs index c1cf9069..8d09be4f 100644 --- a/Blog.Core.Extensions/Authorizations/Policys/PermissionHandler.cs +++ b/Blog.Core.Extensions/Authorizations/Policys/PermissionHandler.cs @@ -149,14 +149,14 @@ orderby item.Id if (user.IsDeleted) { - _user.MessageModel = new ApiResponse(StatusCode.CODE401, "用户已被删除,禁止登陆!").MessageModel; + _user.MessageModel = new ApiResponse(StatusCode.CODE401, "用户已被删除,禁止登录!").MessageModel; context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg)); return; } if (!user.Enable) { - _user.MessageModel = new ApiResponse(StatusCode.CODE401, "用户已被禁用!禁止登陆!").MessageModel; + _user.MessageModel = new ApiResponse(StatusCode.CODE401, "用户已被禁用!禁止登录!").MessageModel; context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg)); return; } diff --git a/Blog.Core.Extensions/Blog.Core.Extensions.csproj b/Blog.Core.Extensions/Blog.Core.Extensions.csproj index 96183e46..3ea16203 100644 --- a/Blog.Core.Extensions/Blog.Core.Extensions.csproj +++ b/Blog.Core.Extensions/Blog.Core.Extensions.csproj @@ -1,6 +1,5 @@  - @@ -9,16 +8,18 @@ - - + + + - - - - - - + + + + + + + diff --git a/Blog.Core.Extensions/Middlewares/IpLogMiddleware.cs b/Blog.Core.Extensions/Middlewares/IpLogMiddleware.cs index 61e9861c..ee74d6fc 100644 --- a/Blog.Core.Extensions/Middlewares/IpLogMiddleware.cs +++ b/Blog.Core.Extensions/Middlewares/IpLogMiddleware.cs @@ -5,137 +5,133 @@ using Newtonsoft.Json; using System; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; namespace Blog.Core.Extensions.Middlewares { - /// - /// 中间件 - /// 记录IP请求数据 - /// - public class IpLogMiddleware - { - /// - /// - /// - private readonly RequestDelegate _next; + /// + /// 中间件 + /// 记录IP请求数据 + /// + public class IpLogMiddleware + { + /// + /// + /// + private readonly RequestDelegate _next; - private readonly IWebHostEnvironment _environment; + private readonly IWebHostEnvironment _environment; + private readonly ILogger _logger; - /// - /// - /// - /// - public IpLogMiddleware(RequestDelegate next, IWebHostEnvironment environment) - { - _next = next; - _environment = environment; - } + public IpLogMiddleware(RequestDelegate next, IWebHostEnvironment environment, ILogger logger) + { + _next = next; + _environment = environment; + _logger = logger; + } - public async Task InvokeAsync(HttpContext context) - { - if (AppSettings.app("Middleware", "IPLog", "Enabled").ObjToBool()) - { - // 过滤,只有接口 - if (context.Request.Path.Value.Contains("api")) - { - context.Request.EnableBuffering(); + public async Task InvokeAsync(HttpContext context) + { + if (AppSettings.app("Middleware", "IPLog", "Enabled").ObjToBool()) + { + // 过滤,只有接口 + if (context.Request.Path.Value.Contains("api")) + { + context.Request.EnableBuffering(); - // 存储请求数据 - var request = context.Request; + // 存储请求数据 + var request = context.Request; - var requestInfo = JsonConvert.SerializeObject(new RequestInfo() - { - Ip = GetClientIP(context), - Url = request.Path.ObjToString().TrimEnd('/').ToLower(), - Datetime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), - Date = DateTime.Now.ToString("yyyy-MM-dd"), - Week = GetWeek(), - }); + var requestInfo = JsonConvert.SerializeObject(new RequestInfo() + { + Ip = GetClientIP(context), + Url = request.Path.ObjToString().TrimEnd('/').ToLower(), + Datetime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), + Date = DateTime.Now.ToString("yyyy-MM-dd"), + Week = GetWeek(), + }); - if (!string.IsNullOrEmpty(requestInfo)) - { - // 自定义log输出 - Parallel.For(0, 1, e => - { - //LogLock.OutSql2Log("RequestIpInfoLog", new string[] { requestInfo + "," }, false); - LogLock.OutLogAOP("RequestIpInfoLog", context.TraceIdentifier, - new string[] {requestInfo.GetType().ToString(), requestInfo}, false); - }); + if (!string.IsNullOrEmpty(requestInfo)) + { + // 自定义log输出 + _logger.LogInformation("RequestIpInfoLog:{TraceIdentifier}: {RequestInfo}", + context.TraceIdentifier, + requestInfo); + + //try + //{ + // var testLogMatchRequestInfo = JsonConvert.DeserializeObject(requestInfo); + // if (testLogMatchRequestInfo != null) + // { + // var logFileName = FileHelper.GetAvailableFileNameWithPrefixOrderSize(_environment.ContentRootPath, "RequestIpInfoLog"); + // SerilogServer.WriteLog(logFileName, new string[] { requestInfo + "," }, false, "", true); + // } + //} + //catch (Exception e) + //{ + // log.Error(requestInfo + "\r\n" + e.GetBaseException().ToString()); + //} - //try - //{ - // var testLogMatchRequestInfo = JsonConvert.DeserializeObject(requestInfo); - // if (testLogMatchRequestInfo != null) - // { - // var logFileName = FileHelper.GetAvailableFileNameWithPrefixOrderSize(_environment.ContentRootPath, "RequestIpInfoLog"); - // SerilogServer.WriteLog(logFileName, new string[] { requestInfo + "," }, false, "", true); - // } - //} - //catch (Exception e) - //{ - // log.Error(requestInfo + "\r\n" + e.GetBaseException().ToString()); - //} - - request.Body.Position = 0; - } + request.Body.Position = 0; + } - await _next(context); - } - else - { - await _next(context); - } - } - else - { - await _next(context); - } - } + await _next(context); + } + else + { + await _next(context); + } + } + else + { + await _next(context); + } + } - private string GetWeek() - { - string week = string.Empty; - switch (DateTime.Now.DayOfWeek) - { - case DayOfWeek.Monday: - week = "周一"; - break; - case DayOfWeek.Tuesday: - week = "周二"; - break; - case DayOfWeek.Wednesday: - week = "周三"; - break; - case DayOfWeek.Thursday: - week = "周四"; - break; - case DayOfWeek.Friday: - week = "周五"; - break; - case DayOfWeek.Saturday: - week = "周六"; - break; - case DayOfWeek.Sunday: - week = "周日"; - break; - default: - week = "N/A"; - break; - } + private string GetWeek() + { + string week = string.Empty; + switch (DateTime.Now.DayOfWeek) + { + case DayOfWeek.Monday: + week = "周一"; + break; + case DayOfWeek.Tuesday: + week = "周二"; + break; + case DayOfWeek.Wednesday: + week = "周三"; + break; + case DayOfWeek.Thursday: + week = "周四"; + break; + case DayOfWeek.Friday: + week = "周五"; + break; + case DayOfWeek.Saturday: + week = "周六"; + break; + case DayOfWeek.Sunday: + week = "周日"; + break; + default: + week = "N/A"; + break; + } - return week; - } + return week; + } - public static string GetClientIP(HttpContext context) - { - var ip = context.Request.Headers["X-Forwarded-For"].ObjToString(); - if (string.IsNullOrEmpty(ip)) - { - ip = context.Connection.RemoteIpAddress.ObjToString(); - } + public static string GetClientIP(HttpContext context) + { + var ip = context.Request.Headers["X-Forwarded-For"].ObjToString(); + if (string.IsNullOrEmpty(ip)) + { + ip = context.Connection.RemoteIpAddress.ObjToString(); + } - return ip; - } - } + return ip; + } + } } \ No newline at end of file diff --git a/Blog.Core.Extensions/Middlewares/RecordAccessLogsMiddleware.cs b/Blog.Core.Extensions/Middlewares/RecordAccessLogsMiddleware.cs index 5c203fb3..cee13581 100644 --- a/Blog.Core.Extensions/Middlewares/RecordAccessLogsMiddleware.cs +++ b/Blog.Core.Extensions/Middlewares/RecordAccessLogsMiddleware.cs @@ -16,121 +16,116 @@ namespace Blog.Core.Extensions.Middlewares { - /// - /// 中间件 - /// 记录用户方访问数据 - /// - public class RecordAccessLogsMiddleware - { - /// - /// - /// - private readonly RequestDelegate _next; - - private readonly IUser _user; - private readonly ILogger _logger; - private readonly IWebHostEnvironment _environment; - private Stopwatch _stopwatch; - - /// - /// - /// - /// - public RecordAccessLogsMiddleware(RequestDelegate next, IUser user, ILogger logger, - IWebHostEnvironment environment) - { - _next = next; - _user = user; - _logger = logger; - _environment = environment; - _stopwatch = new Stopwatch(); - } - - public async Task InvokeAsync(HttpContext context) - { - if (AppSettings.app("Middleware", "RecordAccessLogs", "Enabled").ObjToBool()) - { - var api = context.Request.Path.ObjToString().TrimEnd('/').ToLower(); - var ignoreApis = AppSettings.app("Middleware", "RecordAccessLogs", "IgnoreApis"); - - // 过滤,只有接口 - if (api.Contains("api") && !ignoreApis.Contains(api)) - { - _stopwatch.Restart(); - var userAccessModel = new UserAccessModel(); - - HttpRequest request = context.Request; - - userAccessModel.API = api; - userAccessModel.User = _user.Name; - userAccessModel.IP = IpLogMiddleware.GetClientIP(context); - userAccessModel.BeginTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); - userAccessModel.RequestMethod = request.Method; - userAccessModel.Agent = request.Headers["User-Agent"].ObjToString(); - - - // 获取请求body内容 - if (request.Method.ToLower().Equals("post") || request.Method.ToLower().Equals("put")) - { - // 启用倒带功能,就可以让 Request.Body 可以再次读取 - request.EnableBuffering(); - - Stream stream = request.Body; - byte[] buffer = new byte[request.ContentLength.Value]; - stream.Read(buffer, 0, buffer.Length); - userAccessModel.RequestData = Encoding.UTF8.GetString(buffer); - - request.Body.Position = 0; - } - else if (request.Method.ToLower().Equals("get") || request.Method.ToLower().Equals("delete")) - { - userAccessModel.RequestData = HttpUtility.UrlDecode(request.QueryString.ObjToString(), Encoding.UTF8); - } - - await _next(context); - - // 响应完成记录时间和存入日志 - context.Response.OnCompleted(() => - { - _stopwatch.Stop(); - - userAccessModel.OPTime = _stopwatch.ElapsedMilliseconds + "ms"; - - // 自定义log输出 - var requestInfo = JsonConvert.SerializeObject(userAccessModel); - Parallel.For(0, 1, e => - { - //LogLock.OutSql2Log("RecordAccessLogs", new string[] { requestInfo + "," }, false); - LogLock.OutLogAOP("RecordAccessLogs", context.TraceIdentifier, - new string[] {userAccessModel.GetType().ToString(), requestInfo}, false); - }); - //var logFileName = FileHelper.GetAvailableFileNameWithPrefixOrderSize(_environment.ContentRootPath, "RecordAccessLogs"); - //SerilogServer.WriteLog(logFileName, new string[] { requestInfo + "," }, false); - return Task.CompletedTask; - }); - } - else - { - await _next(context); - } - } - else - { - await _next(context); - } - } - - } - - public class UserAccessModel - { - public string User { get; set; } - public string IP { get; set; } - public string API { get; set; } - public string BeginTime { get; set; } - public string OPTime { get; set; } - public string RequestMethod { get; set; } - public string RequestData { get; set; } - public string Agent { get; set; } - } + /// + /// 中间件 + /// 记录用户方访问数据 + /// + public class RecordAccessLogsMiddleware + { + /// + /// + /// + private readonly RequestDelegate _next; + + private readonly IUser _user; + private readonly ILogger _logger; + private readonly IWebHostEnvironment _environment; + private Stopwatch _stopwatch; + + /// + /// + /// + /// + public RecordAccessLogsMiddleware(RequestDelegate next, IUser user, ILogger logger, + IWebHostEnvironment environment) + { + _next = next; + _user = user; + _logger = logger; + _environment = environment; + _stopwatch = new Stopwatch(); + } + + public async Task InvokeAsync(HttpContext context) + { + if (AppSettings.app("Middleware", "RecordAccessLogs", "Enabled").ObjToBool()) + { + var api = context.Request.Path.ObjToString().TrimEnd('/').ToLower(); + var ignoreApis = AppSettings.app("Middleware", "RecordAccessLogs", "IgnoreApis"); + + // 过滤,只有接口 + if (api.Contains("api") && !ignoreApis.Contains(api)) + { + _stopwatch.Restart(); + var userAccessModel = new UserAccessModel(); + + HttpRequest request = context.Request; + + userAccessModel.API = api; + userAccessModel.User = _user.Name; + userAccessModel.IP = IpLogMiddleware.GetClientIP(context); + userAccessModel.BeginTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + userAccessModel.RequestMethod = request.Method; + userAccessModel.Agent = request.Headers["User-Agent"].ObjToString(); + + + // 获取请求body内容 + if (request.Method.ToLower().Equals("post") || request.Method.ToLower().Equals("put")) + { + // 启用倒带功能,就可以让 Request.Body 可以再次读取 + request.EnableBuffering(); + + Stream stream = request.Body; + byte[] buffer = new byte[request.ContentLength.Value]; + stream.Read(buffer, 0, buffer.Length); + userAccessModel.RequestData = Encoding.UTF8.GetString(buffer); + + request.Body.Position = 0; + } + else if (request.Method.ToLower().Equals("get") || request.Method.ToLower().Equals("delete")) + { + userAccessModel.RequestData = + HttpUtility.UrlDecode(request.QueryString.ObjToString(), Encoding.UTF8); + } + + await _next(context); + + // 响应完成记录时间和存入日志 + context.Response.OnCompleted(() => + { + _stopwatch.Stop(); + + userAccessModel.OPTime = _stopwatch.ElapsedMilliseconds + "ms"; + + // 自定义log输出 + var requestInfo = JsonConvert.SerializeObject(userAccessModel); + _logger.LogInformation("RecordAccessLogs:{TraceIdentifier}: {RequestInfo}", + context.TraceIdentifier, + requestInfo); + return Task.CompletedTask; + }); + } + else + { + await _next(context); + } + } + else + { + await _next(context); + } + } + } + + public class UserAccessModel + { + public string User { get; set; } + public string IP { get; set; } + public string API { get; set; } + public string BeginTime { get; set; } + public string OPTime { get; set; } + public string RequestMethod { get; set; } + public string RequestData { get; set; } + public string Agent { get; set; } + } } \ No newline at end of file diff --git a/Blog.Core.Extensions/Middlewares/RequRespLogMiddleware.cs b/Blog.Core.Extensions/Middlewares/RequRespLogMiddleware.cs index 510b87ef..9917d83d 100644 --- a/Blog.Core.Extensions/Middlewares/RequRespLogMiddleware.cs +++ b/Blog.Core.Extensions/Middlewares/RequRespLogMiddleware.cs @@ -11,124 +11,111 @@ namespace Blog.Core.Extensions.Middlewares { - /// - /// 中间件 - /// 记录请求和响应数据 - /// - public class RequRespLogMiddleware - { - /// - /// - /// - private readonly RequestDelegate _next; - - private readonly ILogger _logger; - - /// - /// - /// - /// - public RequRespLogMiddleware(RequestDelegate next, ILogger logger) - { - _next = next; - _logger = logger; - } - - - public async Task InvokeAsync(HttpContext context) - { - if (AppSettings.app("Middleware", "RequestResponseLog", "Enabled").ObjToBool()) - { - // 过滤,只有接口 - if (context.Request.Path.Value.Contains("api")) - { - context.Request.EnableBuffering(); - - // 存储请求数据 - await RequestDataLog(context); - - await _next(context); - - // 存储响应数据 - ResponseDataLog(context.Response); - } - else - { - await _next(context); - } - } - else - { - await _next(context); - } - } - - private async Task RequestDataLog(HttpContext context) - { - var request = context.Request; - var sr = new StreamReader(request.Body); - RequestLogInfo requestResponse = new RequestLogInfo() - { - Path = request.Path, - QueryString = request.QueryString.ToString(), - BodyData = await sr.ReadToEndAsync() - }; - var content = JsonConvert.SerializeObject(requestResponse); - //var content = $" QueryData:{request.Path + request.QueryString}\r\n BodyData:{await sr.ReadToEndAsync()}"; - - if (!string.IsNullOrEmpty(content)) - { - Parallel.For(0, 1, e => - { - //LogLock.OutSql2Log("RequestResponseLog", new string[] { "Request Data:", content }); - LogLock.OutLogAOP("RequestResponseLog", context.TraceIdentifier, - new string[] {"Request Data - RequestJsonDataType:" + requestResponse.GetType().ToString(), content}); - }); - //SerilogServer.WriteLog("RequestResponseLog", new string[] { "Request Data:", content }); - - request.Body.Position = 0; - } - } - - private void ResponseDataLog(HttpResponse response) - { - var responseBody = response.GetResponseBody(); - - // 去除 Html - var reg = "<[^>]+>"; - - if (!string.IsNullOrEmpty(responseBody)) - { - var isHtml = Regex.IsMatch(responseBody, reg); - Parallel.For(0, 1, e => - { - //LogLock.OutSql2Log("RequestResponseLog", new string[] { "Response Data:", ResponseBody }); - LogLock.OutLogAOP("RequestResponseLog", response.HttpContext.TraceIdentifier, - new string[] {"Response Data - ResponseJsonDataType:" + responseBody.GetType().ToString(), responseBody}); - }); - //SerilogServer.WriteLog("RequestResponseLog", new string[] { "Response Data:", responseBody }); - } - } - - private void ResponseDataLog(HttpResponse response, MemoryStream ms) - { - ms.Position = 0; - var responseBody = new StreamReader(ms).ReadToEnd(); - - // 去除 Html - var reg = "<[^>]+>"; - var isHtml = Regex.IsMatch(responseBody, reg); - - if (!string.IsNullOrEmpty(responseBody)) - { - Parallel.For(0, 1, e => - { - //LogLock.OutSql2Log("RequestResponseLog", new string[] { "Response Data:", ResponseBody }); - LogLock.OutLogAOP("RequestResponseLog", response.HttpContext.TraceIdentifier, - new string[] {"Response Data - ResponseJsonDataType:" + responseBody.GetType().ToString(), responseBody}); - }); - //SerilogServer.WriteLog("RequestResponseLog", new string[] { "Response Data:", responseBody }); - } - } - } + /// + /// 中间件 + /// 记录请求和响应数据 + /// + public class RequRespLogMiddleware + { + /// + /// + /// + private readonly RequestDelegate _next; + + private readonly ILogger _logger; + + /// + /// + /// + /// + public RequRespLogMiddleware(RequestDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + } + + + public async Task InvokeAsync(HttpContext context) + { + if (AppSettings.app("Middleware", "RequestResponseLog", "Enabled").ObjToBool()) + { + // 过滤,只有接口 + if (context.Request.Path.Value.Contains("api")) + { + context.Request.EnableBuffering(); + + // 存储请求数据 + await RequestDataLog(context); + + await _next(context); + + // 存储响应数据 + ResponseDataLog(context.Response); + } + else + { + await _next(context); + } + } + else + { + await _next(context); + } + } + + private async Task RequestDataLog(HttpContext context) + { + var request = context.Request; + var sr = new StreamReader(request.Body); + RequestLogInfo requestResponse = new RequestLogInfo() + { + Path = request.Path, + QueryString = request.QueryString.ToString(), + BodyData = await sr.ReadToEndAsync() + }; + var content = JsonConvert.SerializeObject(requestResponse); + //var content = $" QueryData:{request.Path + request.QueryString}\r\n BodyData:{await sr.ReadToEndAsync()}"; + + if (!string.IsNullOrEmpty(content)) + { + _logger.LogInformation("RequestResponseLog:{TraceIdentifier}: {RequestData}", + context.TraceIdentifier, + content); + request.Body.Position = 0; + } + } + + private void ResponseDataLog(HttpResponse response) + { + var responseBody = response.GetResponseBody(); + + // 去除 Html + var reg = "<[^>]+>"; + + if (!string.IsNullOrEmpty(responseBody)) + { + var isHtml = Regex.IsMatch(responseBody, reg); + _logger.LogInformation("RequestResponseLog:{TraceIdentifier}: {ResponseData}", + response.HttpContext.TraceIdentifier, + responseBody); + } + } + + private void ResponseDataLog(HttpResponse response, MemoryStream ms) + { + ms.Position = 0; + var responseBody = new StreamReader(ms).ReadToEnd(); + + // 去除 Html + var reg = "<[^>]+>"; + var isHtml = Regex.IsMatch(responseBody, reg); + + if (!string.IsNullOrEmpty(responseBody)) + { + _logger.LogInformation("RequestResponseLog:{TraceIdentifier}: {ResponseData}", + response.HttpContext.TraceIdentifier, + responseBody); + } + } + } } \ No newline at end of file diff --git a/Blog.Core.Extensions/Middlewares/SignalRSendMiddleware.cs b/Blog.Core.Extensions/Middlewares/SignalRSendMiddleware.cs index f15df7b4..f1e4ee51 100644 --- a/Blog.Core.Extensions/Middlewares/SignalRSendMiddleware.cs +++ b/Blog.Core.Extensions/Middlewares/SignalRSendMiddleware.cs @@ -17,6 +17,7 @@ public class SignalRSendMiddleware /// /// private readonly RequestDelegate _next; + private readonly IHubContext _hubContext; /// @@ -26,22 +27,20 @@ public class SignalRSendMiddleware /// public SignalRSendMiddleware(RequestDelegate next, IHubContext hubContext) { - _next = next; + _next = next; _hubContext = hubContext; } - public async Task InvokeAsync(HttpContext context) { if (AppSettings.app("Middleware", "SignalR", "Enabled").ObjToBool()) { //TODO 主动发送错误消息 - await _hubContext.Clients.All.SendAsync("ReceiveUpdate", LogLock.GetLogData()); + await _hubContext.Clients.All.SendAsync("ReceiveUpdate", "这是一个Log"); } + await _next(context); } - } -} - +} \ No newline at end of file diff --git a/Blog.Core.Extensions/ServiceExtensions/CacheSetup.cs b/Blog.Core.Extensions/ServiceExtensions/CacheSetup.cs index eb810f52..b1c30a35 100644 --- a/Blog.Core.Extensions/ServiceExtensions/CacheSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/CacheSetup.cs @@ -1,48 +1,58 @@ using System.Threading.Tasks; using Blog.Core.Common; using Blog.Core.Common.Caches; +using Blog.Core.Common.Caches.Distributed; +using Blog.Core.Common.Caches.Interface; using Blog.Core.Common.Option; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Serilog; using StackExchange.Redis; namespace Blog.Core.Extensions.ServiceExtensions; public static class CacheSetup { - /// - /// 统一注册缓存 - /// - /// - public static void AddCacheSetup(this IServiceCollection services) - { - var cacheOptions = App.GetOptions(); - if (cacheOptions.Enable) - { - // 配置启动Redis服务,虽然可能影响项目启动速度,但是不能在运行的时候报错,所以是合理的 - services.AddSingleton(sp => - { - //获取连接字符串 - var configuration = ConfigurationOptions.Parse(cacheOptions.ConnectionString, true); - configuration.ResolveDns = true; - return ConnectionMultiplexer.Connect(configuration); - }); - services.AddSingleton(p => p.GetService() as ConnectionMultiplexer); - //使用Redis - services.AddStackExchangeRedisCache(options => - { - options.ConnectionMultiplexerFactory = () => Task.FromResult(App.GetService(false)); - if (!cacheOptions.InstanceName.IsNullOrEmpty()) options.InstanceName = cacheOptions.InstanceName; - }); + /// + /// 统一注册缓存 + /// + /// + public static void AddCacheSetup(this IServiceCollection services) + { + var cacheOptions = App.GetOptions(); + if (cacheOptions.Enable) + { + // 配置启动Redis服务,虽然可能影响项目启动速度,但是不能在运行的时候报错,所以是合理的 + services.AddSingleton(sp => + { + //获取连接字符串 + var configuration = ConfigurationOptions.Parse(cacheOptions.ConnectionString, true); + configuration.ResolveDns = true; + return ConnectionMultiplexer.Connect(configuration); + }); + services.AddSingleton(p => p.GetService() as ConnectionMultiplexer); + //使用Redis + services.AddStackExchangeRedisCache(options => + { + options.ConnectionMultiplexerFactory = + () => Task.FromResult(App.GetService(false)); + if (!cacheOptions.InstanceName.IsNullOrEmpty()) options.InstanceName = cacheOptions.InstanceName; + }); - services.AddTransient(); - } - else - { - //使用内存 - services.AddMemoryCache(); - services.AddDistributedMemoryCache(); - } + services.AddTransient(); + } + else + { + //使用内存 + services.Remove(services.FirstOrDefault(x => x.ServiceType == typeof(IMemoryCache))); + services.AddSingleton(); + services.AddSingleton(provider => provider.GetService()); + services.AddOptions(); + services.AddSingleton(); + } - services.AddSingleton(); - } + services.AddSingleton(); + } } \ No newline at end of file diff --git a/Blog.Core.Extensions/ServiceExtensions/DataProtectionSetup.cs b/Blog.Core.Extensions/ServiceExtensions/DataProtectionSetup.cs new file mode 100644 index 00000000..5c94074c --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/DataProtectionSetup.cs @@ -0,0 +1,25 @@ +using Blog.Core.Common; +using Blog.Core.Common.Option; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.Extensions.DependencyInjection; +using StackExchange.Redis; + +namespace Blog.Core.Extensions.ServiceExtensions; + +public static class DataProtectionSetup +{ + public static void AddDataProtectionSetup(this IServiceCollection services) + { + var builder = services.AddDataProtection(); + + var redisOption = App.GetOptions(); + if (redisOption.Enable) + { + builder.PersistKeysToStackExchangeRedis(App.GetService()); + return; + } + + //默认写到 webroot/temp/ + builder.PersistKeysToFileSystem(new DirectoryInfo(App.WebHostEnvironment.WebRootPath + "/Temp/Sessions/")); + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/ServiceExtensions/HttpRuntimeCache.cs b/Blog.Core.Extensions/ServiceExtensions/HttpRuntimeCache.cs deleted file mode 100644 index ac7713f6..00000000 --- a/Blog.Core.Extensions/ServiceExtensions/HttpRuntimeCache.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Microsoft.Extensions.Caching.Memory; -using SqlSugar; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Reflection; - -namespace Blog.Core.Extensions -{ - /// - /// 实现SqlSugar的ICacheService接口 - /// - public class SqlSugarMemoryCacheService : ICacheService - { - protected IMemoryCache _memoryCache; - public SqlSugarMemoryCacheService(IMemoryCache memoryCache) - { - _memoryCache = memoryCache; - } - public void Add(string key, V value) - { - _memoryCache.Set(key, value); - } - public void Add(string key, V value, int cacheDurationInSeconds) - { - _memoryCache.Set(key, value, DateTimeOffset.Now.AddSeconds(cacheDurationInSeconds)); - } - public bool ContainsKey(string key) - { - return _memoryCache.TryGetValue(key, out _); - } - - public V Get(string key) - { - return _memoryCache.Get(key); - } - - public IEnumerable GetAllKey() - { - const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; - var coherentState = _memoryCache.GetType().GetField("_coherentState", flags).GetValue(_memoryCache); - var entries = coherentState.GetType().GetField("_entries", flags).GetValue(coherentState); - var cacheItems = entries as IDictionary; - var keys = new List(); - if (cacheItems == null) return keys; - foreach (DictionaryEntry cacheItem in cacheItems) - { - keys.Add(cacheItem.Key.ToString()); - } - return keys; - } - - public V GetOrCreate(string cacheKey, Func create, int cacheDurationInSeconds = int.MaxValue) - { - if (!_memoryCache.TryGetValue(cacheKey, out V value)) - { - value = create(); - _memoryCache.Set(cacheKey, value, DateTime.Now.AddSeconds(cacheDurationInSeconds)); - } - return value; - } - - public void Remove(string key) - { - _memoryCache.Remove(key); - } - } -} diff --git a/Blog.Core.Extensions/ServiceExtensions/IpPolicyRateLimitSetup.cs b/Blog.Core.Extensions/ServiceExtensions/IpPolicyRateLimitSetup.cs index e75e587d..c92c077e 100644 --- a/Blog.Core.Extensions/ServiceExtensions/IpPolicyRateLimitSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/IpPolicyRateLimitSetup.cs @@ -1,5 +1,6 @@ using AspNetCoreRateLimit; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -13,18 +14,15 @@ public static class IpPolicyRateLimitSetup public static void AddIpPolicyRateLimitSetup(this IServiceCollection services, IConfiguration Configuration) { if (services == null) throw new ArgumentNullException(nameof(services)); - - // needed to store rate limit counters and ip rules - services.AddMemoryCache(); + //load general configuration from appsettings.json services.Configure(Configuration.GetSection("IpRateLimiting")); - // inject counter and rules stores - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + // inject counter and rules distributed cache stores - //services.AddSingleton(); - //services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); // the clientId/clientIp resolvers use it. services.AddSingleton(); diff --git a/Blog.Core.Extensions/ServiceExtensions/MiniProfilerSetup.cs b/Blog.Core.Extensions/ServiceExtensions/MiniProfilerSetup.cs index a40d684d..1bd601f4 100644 --- a/Blog.Core.Extensions/ServiceExtensions/MiniProfilerSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/MiniProfilerSetup.cs @@ -1,6 +1,15 @@ using Blog.Core.Common; using Microsoft.Extensions.DependencyInjection; using System; +using Blog.Core.Common.Https; +using Blog.Core.Common.Option; +using Blog.Core.Common.Swagger; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Caching.Memory; +using StackExchange.Profiling; +using StackExchange.Profiling.SqlFormatters; +using StackExchange.Profiling.Storage; +using StackExchange.Redis; namespace Blog.Core.Extensions { @@ -12,23 +21,68 @@ public static class MiniProfilerSetup public static void AddMiniProfilerSetup(this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof(services)); - if(AppSettings.app(new string[] { "Startup", "MiniProfiler", "Enabled" }).ObjToBool()) - { - services.AddMiniProfiler(); - } - // 3.x使用MiniProfiler,必须要注册MemoryCache服务 - // services.AddMiniProfiler(options => - // { - // options.RouteBasePath = "/profiler"; - // //(options.Storage as MemoryCacheStorage).CacheDuration = TimeSpan.FromMinutes(10); - // options.PopupRenderPosition = StackExchange.Profiling.RenderPosition.Left; - // options.PopupShowTimeWithChildren = true; - - // // 可以增加权限 - // //options.ResultsAuthorize = request => request.HttpContext.User.IsInRole("Admin"); - // //options.UserIdProvider = request => request.HttpContext.User.Identity.Name; - // } - //); + if (!AppSettings.app("Startup", "MiniProfiler", "Enabled").ObjToBool()) return; + + //使用MiniProfiler + services.AddMiniProfiler(options => + { + //访问地址路由根目录;默认为:/mini-profiler-resources + //options.RouteBasePath = "/profiler"; + //数据缓存时间 + //获取redis配置 + var redisOptions = App.GetOptions(); + if (redisOptions.Enable) + options.Storage = + new RedisStorage((ConnectionMultiplexer)App.GetService()); + else + options.Storage = new MemoryCacheStorage(App.GetService(), TimeSpan.FromMinutes(60)); + + //sql格式化设置 + options.SqlFormatter = new InlineFormatter(); + //跟踪连接打开关闭 + options.TrackConnectionOpenClose = true; + //界面主题颜色方案;默认浅色 + options.ColorScheme = ColorScheme.Dark; + //.net core 3.0以上:对MVC过滤器进行分析 + options.EnableMvcFilterProfiling = true; + //对视图进行分析 + options.EnableMvcViewProfiling = true; + + //控制访问页面授权,默认所有人都能访问 + //options.ResultsAuthorize; + //要控制分析哪些请求,默认说有请求都分析 + //options.ShouldProfile + + //内部异常处理 + //options.OnInternalError = e => MyExceptionLogger(e); + //options.RouteBasePath = "/profiler"; + + //(options.Storage as MemoryCacheStorage).CacheDuration = TimeSpan.FromMinutes(10); + options.PopupRenderPosition = RenderPosition.Left; + options.PopupShowTimeWithChildren = true; + + //只监控api 接口 + options.ShouldProfile = ShouldProfile; + + // 可以增加权限 + // options.ResultsAuthorize = request => + // { + // if (request.IsLocal()) return true; + // + // var path = request.HttpContext.Request.Path.Value; + // if (path == null || !path.StartsWith(options.RouteBasePath)) return true; + // + // var flag = request.HttpContext.IsSuccessSwagger(); + // if (!flag) request.HttpContext.RedirectSwaggerLogin(); + // return flag; + // }; + } + ); + } + + private static bool ShouldProfile(HttpRequest request) + { + return request.Path.StartsWithSegments("/api"); } } -} +} \ No newline at end of file diff --git a/Blog.Core.Extensions/ServiceExtensions/SqlsugarSetup.cs b/Blog.Core.Extensions/ServiceExtensions/SqlsugarSetup.cs index 0700a74c..71e1f7fd 100644 --- a/Blog.Core.Extensions/ServiceExtensions/SqlsugarSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/SqlsugarSetup.cs @@ -7,6 +7,8 @@ using SqlSugar; using Blog.Core.Common.Caches; using System.Text.RegularExpressions; +using Blog.Core.Common.Option; +using Blog.Core.Common.Utility; namespace Blog.Core.Extensions { @@ -21,6 +23,8 @@ public static void AddSqlsugarSetup(this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof(services)); + StaticConfig.CustomSnowFlakeFunc = IdGeneratorUtility.NextId; + // 默认添加主数据库连接 if (!AppSettings.app("MainDB").IsNullOrEmpty()) { @@ -33,7 +37,7 @@ public static void AddSqlsugarSetup(this IServiceCollection services) { ConfigId = m.ConnId.ObjToString().ToLower(), ConnectionString = m.Connection, - DbType = (DbType) m.DbType, + DbType = (DbType)m.DbType, IsAutoCloseConnection = true, // Check out more information: https://github.com/anjoy8/Blog.Core/issues/122 //IsShardSameThread = false, @@ -52,6 +56,8 @@ public static void AddSqlsugarSetup(this IServiceCollection services) // 自定义特性 ConfigureExternalServices = new ConfigureExternalServices() { + //不建议使用,性能有很大问题,会导致redis堆积 + //核心问题在于SqlSugar,每次query都会查缓存, insert\update\delete,又会频繁GetAllKey,导致性能特别低 DataInfoCacheService = new SqlSugarCacheService(), EntityService = (property, column) => { @@ -69,8 +75,8 @@ public static void AddSqlsugarSetup(this IServiceCollection services) } else { - if (string.Equals(config.ConfigId, MainDb.CurrentDbConnId, - StringComparison.CurrentCultureIgnoreCase)) + if (string.Equals(config.ConfigId.ToString(), MainDb.CurrentDbConnId, + StringComparison.CurrentCultureIgnoreCase)) { BaseDBConfig.MainConfig = config; } @@ -100,7 +106,7 @@ public static void AddSqlsugarSetup(this IServiceCollection services) { BaseDBConfig.ValidConfig.ForEach(config => { - var dbProvider = db.GetConnectionScope((string) config.ConfigId); + var dbProvider = db.GetConnectionScope((string)config.ConfigId); // 打印SQL语句 dbProvider.Aop.OnLogExecuting = (s, parameters) => @@ -122,6 +128,7 @@ public static void AddSqlsugarSetup(this IServiceCollection services) SqlSugarReuse.AutoChangeAvailableConnect(db); }); }); + services.AddTransient(s => s.GetService() as SqlSugarScope); } private static string GetWholeSql(SugarParameter[] paramArr, string sql) diff --git a/Blog.Core.Extensions/ServiceExtensions/SwaggerSetup.cs b/Blog.Core.Extensions/ServiceExtensions/SwaggerSetup.cs index 85dff620..ade9f993 100644 --- a/Blog.Core.Extensions/ServiceExtensions/SwaggerSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/SwaggerSetup.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using Blog.Core.Common.Swagger.Filter; using static Blog.Core.Extensions.CustomApiVersion; namespace Blog.Core.Extensions @@ -65,7 +66,10 @@ public static void AddSwaggerSetup(this IServiceCollection services) // 在header中添加token,传递到后台 c.OperationFilter(); - + //自定义过滤器 + c.SchemaFilter(); + c.DocumentFilter(); + // ids4和jwt切换 if (Permissions.IsUseIds4) { diff --git a/Blog.Core.FrameWork/Blog.Core.FrameWork.csproj b/Blog.Core.FrameWork/Blog.Core.FrameWork.csproj index 51022b3c..76d6f7e6 100644 --- a/Blog.Core.FrameWork/Blog.Core.FrameWork.csproj +++ b/Blog.Core.FrameWork/Blog.Core.FrameWork.csproj @@ -1,6 +1,5 @@ - diff --git a/Blog.Core.Gateway/Blog.Core.Gateway.csproj b/Blog.Core.Gateway/Blog.Core.Gateway.csproj index a21feb4b..d45e9ba5 100644 --- a/Blog.Core.Gateway/Blog.Core.Gateway.csproj +++ b/Blog.Core.Gateway/Blog.Core.Gateway.csproj @@ -1,6 +1,5 @@  - ..\Blog.Core.Gateway\Blog.Core.Gateway.xml @@ -21,9 +20,9 @@ - - - + + + diff --git a/Blog.Core.Gateway/Helper/CustomJwtTokenAuthMiddleware.cs b/Blog.Core.Gateway/Helper/CustomJwtTokenAuthMiddleware.cs index f9120ec0..9441e00c 100644 --- a/Blog.Core.Gateway/Helper/CustomJwtTokenAuthMiddleware.cs +++ b/Blog.Core.Gateway/Helper/CustomJwtTokenAuthMiddleware.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authentication; using Blog.Core.Common; using Blog.Core.Common.Caches; +using Blog.Core.Common.Caches.Interface; using Blog.Core.Common.Helper; namespace Blog.Core.AuthHelper diff --git a/Blog.Core.Gateway/Startup.cs b/Blog.Core.Gateway/Startup.cs index 1a37cc9f..d8f81253 100644 --- a/Blog.Core.Gateway/Startup.cs +++ b/Blog.Core.Gateway/Startup.cs @@ -5,6 +5,7 @@ using Blog.Core.Gateway.Extensions; using Microsoft.AspNetCore.Authentication; using System.Reflection; +using Blog.Core.Common.Caches.Interface; namespace Blog.Core.AdminMvc { diff --git a/Blog.Core.IServices/Blog.Core.IServices.csproj b/Blog.Core.IServices/Blog.Core.IServices.csproj index 20e8e658..b5cc2f2c 100644 --- a/Blog.Core.IServices/Blog.Core.IServices.csproj +++ b/Blog.Core.IServices/Blog.Core.IServices.csproj @@ -1,6 +1,5 @@  - diff --git a/Blog.Core.IServices/IGuestbookServices.cs b/Blog.Core.IServices/IGuestbookServices.cs index 7fc72f62..cd8b2287 100644 --- a/Blog.Core.IServices/IGuestbookServices.cs +++ b/Blog.Core.IServices/IGuestbookServices.cs @@ -15,5 +15,7 @@ public partial interface IGuestbookServices : IBaseServices Task TestTranPropagationNoTran(); Task TestTranPropagationTran(); + Task TestTranPropagationTran2(); + Task TestTranPropagationTran3(); } } \ No newline at end of file diff --git a/Blog.Core.IServices/IPasswordLibServices.cs b/Blog.Core.IServices/IPasswordLibServices.cs index d7a0b310..67532fd6 100644 --- a/Blog.Core.IServices/IPasswordLibServices.cs +++ b/Blog.Core.IServices/IPasswordLibServices.cs @@ -9,5 +9,6 @@ public partial interface IPasswordLibServices :IBaseServices Task TestTranPropagation2(); Task TestTranPropagationNoTranError(); Task TestTranPropagationTran2(); + Task TestTranPropagationTran3(); } } diff --git a/Blog.Core.Model/Blog.Core.Model.csproj b/Blog.Core.Model/Blog.Core.Model.csproj index 841a4665..fb04c45c 100644 --- a/Blog.Core.Model/Blog.Core.Model.csproj +++ b/Blog.Core.Model/Blog.Core.Model.csproj @@ -1,6 +1,5 @@  - ..\Blog.Core.Api\Blog.Core.Model.xml @@ -14,7 +13,7 @@ - + diff --git a/Blog.Core.Model/Models/Permission.cs b/Blog.Core.Model/Models/Permission.cs index 9dd6238d..95a46b85 100644 --- a/Blog.Core.Model/Models/Permission.cs +++ b/Blog.Core.Model/Models/Permission.cs @@ -57,6 +57,11 @@ public Permission() [SugarColumn(Length = 100, IsNullable = true)] public string Icon { get; set; } /// + /// 菜单图标新 + /// + [SugarColumn(Length = 100, IsNullable = true)] + public string IconNew { get; set; } + /// /// 菜单描述 /// [SugarColumn(Length = 100, IsNullable = true)] diff --git a/Blog.Core.Repository/Blog.Core.Repository.csproj b/Blog.Core.Repository/Blog.Core.Repository.csproj index 84b5dfad..fff34572 100644 --- a/Blog.Core.Repository/Blog.Core.Repository.csproj +++ b/Blog.Core.Repository/Blog.Core.Repository.csproj @@ -1,6 +1,5 @@  - diff --git a/Blog.Core.Repository/UnitOfWorks/UnitOfWorkManage.cs b/Blog.Core.Repository/UnitOfWorks/UnitOfWorkManage.cs index fe190b3b..cfaffd9b 100644 --- a/Blog.Core.Repository/UnitOfWorks/UnitOfWorkManage.cs +++ b/Blog.Core.Repository/UnitOfWorks/UnitOfWorkManage.cs @@ -51,21 +51,25 @@ public UnitOfWork CreateUnitOfWork() public void BeginTran() { + Console.WriteLine("Begin Transaction Before:" + GetDbClient().ContextID); lock (this) { _tranCount++; GetDbClient().BeginTran(); } + Console.WriteLine("Begin Transaction After:" + GetDbClient().ContextID); } public void BeginTran(MethodInfo method) { + Console.WriteLine("Begin Transaction Before:" + GetDbClient().ContextID); lock (this) { GetDbClient().BeginTran(); TranStack.Push(method.GetFullName()); _tranCount = TranStack.Count; } + Console.WriteLine("Begin Transaction After:" + GetDbClient().ContextID); } public void CommitTran() diff --git a/Blog.Core.Serilog.Es/Blog.Core.Serilog.Es.csproj b/Blog.Core.Serilog.Es/Blog.Core.Serilog.Es.csproj index a53e98f5..2024571e 100644 --- a/Blog.Core.Serilog.Es/Blog.Core.Serilog.Es.csproj +++ b/Blog.Core.Serilog.Es/Blog.Core.Serilog.Es.csproj @@ -1,13 +1,12 @@ - - - - + + + diff --git a/Blog.Core.Serilog/Blog.Core.Serilog.csproj b/Blog.Core.Serilog/Blog.Core.Serilog.csproj index 9994dce2..c1c161b1 100644 --- a/Blog.Core.Serilog/Blog.Core.Serilog.csproj +++ b/Blog.Core.Serilog/Blog.Core.Serilog.csproj @@ -1,6 +1,5 @@  - diff --git a/Blog.Core.Services/Blog.Core.Services.csproj b/Blog.Core.Services/Blog.Core.Services.csproj index 04437f0c..215dde29 100644 --- a/Blog.Core.Services/Blog.Core.Services.csproj +++ b/Blog.Core.Services/Blog.Core.Services.csproj @@ -1,6 +1,5 @@  - ..\Blog.Core.Api\bin\Debug\ diff --git a/Blog.Core.Services/GuestbookServices.cs b/Blog.Core.Services/GuestbookServices.cs index daff1cee..33364a51 100644 --- a/Blog.Core.Services/GuestbookServices.cs +++ b/Blog.Core.Services/GuestbookServices.cs @@ -7,7 +7,9 @@ using System; using System.Threading.Tasks; using Blog.Core.Common.DB; +using Blog.Core.Common.Utility; using Blog.Core.Repository.UnitOfWorks; +using SqlSugar; namespace Blog.Core.Services { @@ -15,14 +17,17 @@ public class GuestbookServices : BaseServices, IGuestbookServices { private readonly IUnitOfWorkManage _unitOfWorkManage; private readonly IBaseRepository _passwordLibRepository; - private readonly IPasswordLibServices _passwordLibServices; + private readonly ISqlSugarClient _db; + private SqlSugarScope db => _db as SqlSugarScope; - public GuestbookServices(IUnitOfWorkManage unitOfWorkManage, IBaseRepository dal, IBaseRepository passwordLibRepository, IPasswordLibServices passwordLibServices) + public GuestbookServices(IUnitOfWorkManage unitOfWorkManage, IBaseRepository dal, + IBaseRepository passwordLibRepository, IPasswordLibServices passwordLibServices, ISqlSugarClient db) { _unitOfWorkManage = unitOfWorkManage; _passwordLibRepository = passwordLibRepository; _passwordLibServices = passwordLibServices; + _db = db; } public async Task> TestTranInRepository() @@ -193,8 +198,9 @@ public async Task TestTranPropagationNoTran() public async Task TestTranPropagationTran() { var guestbooks = await base.Query(); + guestbooks = await base.Query(); Console.WriteLine($"first time : the count of guestbooks is :{guestbooks.Count}"); - + Console.WriteLine(base.Db.ContextID); var insertGuestbook = await base.Add(new Guestbook() { username = "bbb", @@ -207,5 +213,68 @@ public async Task TestTranPropagationTran() return true; } + + [UseTran(Propagation = Propagation.Required)] + public async Task TestTranPropagationTran2() + { + await Db.Insertable(new Guestbook() + { + username = "bbb", + blogId = 1, + createdate = DateTime.Now, + isshow = true + }).ExecuteReturnSnowflakeIdAsync(); + + + await Db.Insertable(new PasswordLib() + { + PLID = IdGeneratorUtility.NextId(), + IsDeleted = false, + plAccountName = "aaa", + plCreateTime = DateTime.Now + }).ExecuteReturnSnowflakeIdAsync(); + + await _passwordLibServices.TestTranPropagationTran2(); + + Console.WriteLine("完成"); + } + + public async Task TestTranPropagationTran3() + { + try + { + Console.WriteLine("Begin Transaction Before:" + db.ContextID); + db.BeginTran(); + Console.WriteLine("Begin Transaction After:" + db.ContextID); + + await db.Insertable(new Guestbook() + { + username = "bbb", + blogId = 1, + createdate = DateTime.Now, + isshow = true + }).ExecuteReturnSnowflakeIdAsync(); + + + await db.Insertable(new PasswordLib() + { + PLID = IdGeneratorUtility.NextId(), + IsDeleted = false, + plAccountName = "aaa", + plCreateTime = DateTime.Now + }).ExecuteReturnSnowflakeIdAsync(); + + await _passwordLibServices.TestTranPropagationTran3(); + + db.CommitTran(); + Console.WriteLine("完成"); + } + catch (Exception e) + { + db.RollbackTran(); + throw; + } + + } } } \ No newline at end of file diff --git a/Blog.Core.Services/PasswordLibServices.cs b/Blog.Core.Services/PasswordLibServices.cs index de58a6b6..c2f39391 100644 --- a/Blog.Core.Services/PasswordLibServices.cs +++ b/Blog.Core.Services/PasswordLibServices.cs @@ -2,20 +2,28 @@ using System.Threading.Tasks; using Blog.Core.Common; using Blog.Core.Common.DB; +using Blog.Core.Common.Utility; using Blog.Core.IRepository.Base; using Blog.Core.IServices; using Blog.Core.Model.Models; +using Blog.Core.Repository.UnitOfWorks; using Blog.Core.Services.BASE; +using SqlSugar; namespace Blog.Core.Services { public partial class PasswordLibServices : BaseServices, IPasswordLibServices { IBaseRepository _dal; + private readonly IUnitOfWorkManage _unitOfWorkManage; + private readonly ISqlSugarClient _db; + private SqlSugarScope db => _db as SqlSugarScope; - public PasswordLibServices(IBaseRepository dal) + public PasswordLibServices(IBaseRepository dal, IUnitOfWorkManage unitOfWorkManage, ISqlSugarClient db) { this._dal = dal; + _unitOfWorkManage = unitOfWorkManage; + _db = db; base.BaseDal = dal; } @@ -24,6 +32,7 @@ public async Task TestTranPropagation2() { await _dal.Add(new PasswordLib() { + PLID = IdGeneratorUtility.NextId(), IsDeleted = false, plAccountName = "aaa", plCreateTime = DateTime.Now @@ -48,13 +57,33 @@ await _dal.Add(new PasswordLib() [UseTran(Propagation = Propagation.Nested)] public async Task TestTranPropagationTran2() { - await _dal.Add(new PasswordLib() + await db.Insertable(new PasswordLib() { + PLID = IdGeneratorUtility.NextId(), IsDeleted = false, plAccountName = "aaa", plCreateTime = DateTime.Now - }); + }).ExecuteReturnSnowflakeIdAsync(); + + //throw new Exception("测试嵌套事务异常回滚"); + return true; + } + + public async Task TestTranPropagationTran3() + { + Console.WriteLine("Begin Transaction Before:" + db.ContextID); + db.BeginTran(); + Console.WriteLine("Begin Transaction After:" + db.ContextID); + Console.WriteLine(""); + await db.Insertable(new PasswordLib() + { + PLID = IdGeneratorUtility.NextId(), + IsDeleted = false, + plAccountName = "aaa", + plCreateTime = DateTime.Now + }).ExecuteReturnSnowflakeIdAsync(); + //throw new Exception("测试嵌套事务异常回滚"); return true; } } diff --git a/Blog.Core.Tasks/Blog.Core.Tasks.csproj b/Blog.Core.Tasks/Blog.Core.Tasks.csproj index abc0d331..d93eb8bd 100644 --- a/Blog.Core.Tasks/Blog.Core.Tasks.csproj +++ b/Blog.Core.Tasks/Blog.Core.Tasks.csproj @@ -1,6 +1,5 @@  - diff --git a/Blog.Core.Tasks/QuartzNet/Jobs/JobBase.cs b/Blog.Core.Tasks/QuartzNet/Jobs/JobBase.cs index b4486dfc..8da86165 100644 --- a/Blog.Core.Tasks/QuartzNet/Jobs/JobBase.cs +++ b/Blog.Core.Tasks/QuartzNet/Jobs/JobBase.cs @@ -27,7 +27,7 @@ public async Task ExecuteJob(IJobExecutionContext context, Func fu //记录Job TasksLog tasksLog = new TasksLog(); //JOBID - int jobid = context.JobDetail.Key.Name.ObjToInt(); + long jobid = context.JobDetail.Key.Name.ObjToLong(); //JOB组名 string groupName = context.JobDetail.Key.Group; //日志 @@ -77,7 +77,7 @@ public async Task ExecuteJob(IJobExecutionContext context, Func fu } } - Console.Out.WriteLine(jobHistory); + //Console.Out.WriteLine(jobHistory); return jobHistory; } } diff --git a/Blog.Core.Tasks/QuartzNet/Jobs/Job_AccessTrendLog_Quartz.cs b/Blog.Core.Tasks/QuartzNet/Jobs/Job_AccessTrendLog_Quartz.cs index 68a9aa53..88b79e8b 100644 --- a/Blog.Core.Tasks/QuartzNet/Jobs/Job_AccessTrendLog_Quartz.cs +++ b/Blog.Core.Tasks/QuartzNet/Jobs/Job_AccessTrendLog_Quartz.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; /// /// 这里要注意下,命名空间和程序集是一样的,不然反射不到 @@ -20,64 +21,39 @@ public class Job_AccessTrendLog_Quartz : JobBase, IJob { private readonly IAccessTrendLogServices _accessTrendLogServices; private readonly IWebHostEnvironment _environment; + private readonly ILogger _logger; - public Job_AccessTrendLog_Quartz(IAccessTrendLogServices accessTrendLogServices, IWebHostEnvironment environment, ITasksQzServices tasksQzServices, ITasksLogServices tasksLogServices) + public Job_AccessTrendLog_Quartz(IAccessTrendLogServices accessTrendLogServices, + IWebHostEnvironment environment, ITasksQzServices tasksQzServices, ITasksLogServices tasksLogServices, + ILogger logger) : base(tasksQzServices, tasksLogServices) { _accessTrendLogServices = accessTrendLogServices; - _environment = environment; - _tasksQzServices = tasksQzServices; + _environment = environment; + _logger = logger; + _tasksQzServices = tasksQzServices; } + public async Task Execute(IJobExecutionContext context) { var executeLog = await ExecuteJob(context, async () => await Run(context)); } + public async Task Run(IJobExecutionContext context) { - // 可以直接获取 JobDetail 的值 var jobKey = context.JobDetail.Key; - var jobId = jobKey.Name; + var jobId = jobKey.Name; // 也可以通过数据库配置,获取传递过来的参数 JobDataMap data = context.JobDetail.JobDataMap; - var lastestLogDatetime = (await _accessTrendLogServices.Query(null, d => d.UpdateTime, false)).FirstOrDefault()?.UpdateTime; + var lastestLogDatetime = (await _accessTrendLogServices.Query(null, d => d.UpdateTime, false)) + .FirstOrDefault()?.UpdateTime; if (lastestLogDatetime == null) { lastestLogDatetime = Convert.ToDateTime("2021-09-01"); } - var accLogs = GetAccessLogs().Where(d => d.User != "" && d.BeginTime.ObjToDate() >= lastestLogDatetime).ToList(); - var logUpdate = DateTime.Now; - - var activeUsers = (from n in accLogs - group n by new { n.User } into g - select new ActiveUserVM - { - user = g.Key.User, - count = g.Count(), - }).ToList(); - - foreach (var item in activeUsers) - { - var user = (await _accessTrendLogServices.Query(d => d.UserInfo != "" && d.UserInfo == item.user)).FirstOrDefault(); - if (user != null) - { - user.Count += item.count; - user.UpdateTime = logUpdate; - await _accessTrendLogServices.Update(user); - } - else - { - await _accessTrendLogServices.Add(new AccessTrendLog() - { - Count = item.count, - UpdateTime = logUpdate, - UserInfo = item.user - }); - } - } - // 重新拉取 var actUsers = await _accessTrendLogServices.Query(d => d.UserInfo != "", d => d.Count, false); actUsers = actUsers.Take(15).ToList(); @@ -87,61 +63,13 @@ await _accessTrendLogServices.Add(new AccessTrendLog() { activeUserVMs.Add(new ActiveUserVM() { - user = item.UserInfo, + user = item.UserInfo, count = item.Count }); } - Parallel.For(0, 1, e => - { - LogLock.OutLogAOP("ACCESSTRENDLOG", "", new string[] { activeUserVMs.GetType().ToString(), JsonConvert.SerializeObject(activeUserVMs) }, false); - }); + _logger.LogInformation("Job_AccessTrendLog_Quartz: {ActiveUserVMs}", + JsonConvert.SerializeObject(activeUserVMs)); } - - private List GetAccessLogs() - { - List userAccessModels = new(); - var accessLogs = LogLock.ReadLog( - Path.Combine(_environment.ContentRootPath, "Log"), "RecordAccessLogs_", Encoding.UTF8, ReadType.Prefix, 2 - ).ObjToString().TrimEnd(','); - - try - { - return JsonConvert.DeserializeObject>("[" + accessLogs + "]"); - } - catch (Exception) - { - var accLogArr = accessLogs.Split("\n"); - foreach (var item in accLogArr) - { - if (item.ObjToString() != "") - { - try - { - var accItem = JsonConvert.DeserializeObject(item.TrimEnd(',')); - userAccessModels.Add(accItem); - } - catch (Exception) - { - } - } - } - - } - - return userAccessModels; - } - } - public class UserAccessFromFIles - { - public string User { get; set; } - public string IP { get; set; } - public string API { get; set; } - public string BeginTime { get; set; } - public string OPTime { get; set; } - public string RequestMethod { get; set; } = ""; - public string Agent { get; set; } - } - -} +} \ No newline at end of file diff --git a/Blog.Core.Tasks/QuartzNet/Jobs/Job_Blogs_Quartz.cs b/Blog.Core.Tasks/QuartzNet/Jobs/Job_Blogs_Quartz.cs index f116fe05..6d1276c8 100644 --- a/Blog.Core.Tasks/QuartzNet/Jobs/Job_Blogs_Quartz.cs +++ b/Blog.Core.Tasks/QuartzNet/Jobs/Job_Blogs_Quartz.cs @@ -2,25 +2,91 @@ using Quartz; using System; using System.Threading.Tasks; +using Blog.Core.Common.Utility; +using Blog.Core.Model.Models; +using Blog.Core.Repository.UnitOfWorks; +using SqlSugar; /// /// 这里要注意下,命名空间和程序集是一样的,不然反射不到 /// namespace Blog.Core.Tasks { + [DisallowConcurrentExecution] public class Job_Blogs_Quartz : JobBase, IJob { private readonly IBlogArticleServices _blogArticleServices; + private readonly IGuestbookServices _guestbookServices; + private readonly IUnitOfWorkManage _uowm; + private readonly ISqlSugarClient _db; + private SqlSugarScope db => _db as SqlSugarScope; - public Job_Blogs_Quartz(IBlogArticleServices blogArticleServices, ITasksQzServices tasksQzServices, ITasksLogServices tasksLogServices) + public Job_Blogs_Quartz(IBlogArticleServices blogArticleServices, ITasksQzServices tasksQzServices, ITasksLogServices tasksLogServices, + IGuestbookServices guestbookServices, IUnitOfWorkManage uowm, ISqlSugarClient db) : base(tasksQzServices, tasksLogServices) { _blogArticleServices = blogArticleServices; + _guestbookServices = guestbookServices; + _uowm = uowm; + this._db = db; } + + /// + /// 直接写就没有锁库 上下文ContextID一样 + /// + /// + public async Task Execute2(IJobExecutionContext context) + { + try + { + db.BeginTran(); + Console.WriteLine(_uowm.GetDbClient().ContextID); + await db.Insertable(new Guestbook() + { + username = "bbb", + blogId = 1, + createdate = DateTime.Now, + isshow = true + }).ExecuteReturnSnowflakeIdAsync(); + await db.Insertable(new PasswordLib() + { + PLID = IdGeneratorUtility.NextId(), + IsDeleted = false, + plAccountName = "aaa", + plCreateTime = DateTime.Now + }).ExecuteReturnSnowflakeIdAsync(); + + db.BeginTran(); + Console.WriteLine(db.ContextID); + await db.Insertable(new PasswordLib() + { + PLID = IdGeneratorUtility.NextId(), + IsDeleted = false, + plAccountName = "aaa", + plCreateTime = DateTime.Now + }).ExecuteReturnSnowflakeIdAsync(); + + db.CommitTran(); + + Console.WriteLine(db.ContextID); + db.CommitTran(); + Console.WriteLine("完成"); + } + catch (Exception e) + { + db.RollbackTran(); + } + } + + /// + /// 但是调用其他类方法 上下文ContextID就不一样 + /// + /// public async Task Execute(IJobExecutionContext context) { var executeLog = await ExecuteJob(context, async () => await Run(context)); } + public async Task Run(IJobExecutionContext context) { System.Console.WriteLine($"Job_Blogs_Quartz 执行 {DateTime.Now.ToShortTimeString()}"); @@ -28,6 +94,8 @@ public async Task Run(IJobExecutionContext context) // 也可以通过数据库配置,获取传递过来的参数 JobDataMap data = context.JobDetail.JobDataMap; //int jobId = data.GetInt("JobParam"); + + await _guestbookServices.TestTranPropagationTran2(); } } -} +} \ No newline at end of file diff --git a/Blog.Core.Tasks/QuartzNet/Jobs/Job_OperateLog_Quartz.cs b/Blog.Core.Tasks/QuartzNet/Jobs/Job_OperateLog_Quartz.cs index abcd81ea..49e95847 100644 --- a/Blog.Core.Tasks/QuartzNet/Jobs/Job_OperateLog_Quartz.cs +++ b/Blog.Core.Tasks/QuartzNet/Jobs/Job_OperateLog_Quartz.cs @@ -20,11 +20,12 @@ public class Job_OperateLog_Quartz : JobBase, IJob private readonly IOperateLogServices _operateLogServices; private readonly IWebHostEnvironment _environment; - public Job_OperateLog_Quartz(IOperateLogServices operateLogServices, IWebHostEnvironment environment, ITasksQzServices tasksQzServices, ITasksLogServices tasksLogServices) + public Job_OperateLog_Quartz(IOperateLogServices operateLogServices, IWebHostEnvironment environment, + ITasksQzServices tasksQzServices, ITasksLogServices tasksLogServices) : base(tasksQzServices, tasksLogServices) { _operateLogServices = operateLogServices; - _environment = environment; + _environment = environment; } public async Task Execute(IJobExecutionContext context) @@ -34,50 +35,51 @@ public async Task Execute(IJobExecutionContext context) public async Task Run(IJobExecutionContext context) { - // 可以直接获取 JobDetail 的值 var jobKey = context.JobDetail.Key; - var jobId = jobKey.Name; + var jobId = jobKey.Name; // 也可以通过数据库配置,获取传递过来的参数 JobDataMap data = context.JobDetail.JobDataMap; - List excLogs = new List(); - var exclogContent = LogLock.ReadLog(Path.Combine(_environment.ContentRootPath, "Log"), $"GlobalExceptionLogs_{DateTime.Now.ToString("yyyMMdd")}.log", Encoding.UTF8); - - if (!string.IsNullOrEmpty(exclogContent)) - { - excLogs = exclogContent.Split("--------------------------------") - .Where(d => !string.IsNullOrEmpty(d) && d != "\n" && d != "\r\n") - .Select(d => new LogInfo - { - Datetime = (d.Split("|")[0]).Split(',')[0].ObjToDate(), - Content = d.Split("|")[1]?.Replace("\r\n", "
    "), - LogColor = "EXC", - Import = 9, - }).ToList(); - } - - var filterDatetime = DateTime.Now.AddHours(-1); - excLogs = excLogs.Where(d => d.Datetime >= filterDatetime).ToList(); - - var operateLogs = new List() { }; - excLogs.ForEach(m => - { - operateLogs.Add(new OperateLog() - { - LogTime = m.Datetime, - Description = m.Content, - IPAddress = m.IP, - UserId = 0, - IsDeleted = false, - }); - }); - - - if (operateLogs.Count > 0) - { - var logsIds = await _operateLogServices.Add(operateLogs); - } + //[INFO] 注释无用代码,后续如果需要再调整 + // List excLogs = new List(); + // var exclogContent = LogLock.ReadLog(Path.Combine(_environment.ContentRootPath, "Log"), + // $"GlobalExceptionLogs_{DateTime.Now.ToString("yyyMMdd")}.log", Encoding.UTF8); + // + // if (!string.IsNullOrEmpty(exclogContent)) + // { + // excLogs = exclogContent.Split("--------------------------------") + // .Where(d => !string.IsNullOrEmpty(d) && d != "\n" && d != "\r\n") + // .Select(d => new LogInfo + // { + // Datetime = (d.Split("|")[0]).Split(',')[0].ObjToDate(), + // Content = d.Split("|")[1]?.Replace("\r\n", "
    "), + // LogColor = "EXC", + // Import = 9, + // }).ToList(); + // } + // + // var filterDatetime = DateTime.Now.AddHours(-1); + // excLogs = excLogs.Where(d => d.Datetime >= filterDatetime).ToList(); + // + // var operateLogs = new List() { }; + // excLogs.ForEach(m => + // { + // operateLogs.Add(new OperateLog() + // { + // LogTime = m.Datetime, + // Description = m.Content, + // IPAddress = m.IP, + // UserId = 0, + // IsDeleted = false, + // }); + // }); + // + // + // if (operateLogs.Count > 0) + // { + // var logsIds = await _operateLogServices.Add(operateLogs); + // } } } } \ No newline at end of file diff --git a/Blog.Core.Tests/Blog.Core.Tests.csproj b/Blog.Core.Tests/Blog.Core.Tests.csproj index 22c227e0..50a63431 100644 --- a/Blog.Core.Tests/Blog.Core.Tests.csproj +++ b/Blog.Core.Tests/Blog.Core.Tests.csproj @@ -1,6 +1,5 @@  - false false @@ -18,7 +17,8 @@ - + + @@ -29,6 +29,7 @@ + diff --git a/Blog.Core.Tests/Common_Test/CacheTest.cs b/Blog.Core.Tests/Common_Test/CacheTest.cs new file mode 100644 index 00000000..f3a78396 --- /dev/null +++ b/Blog.Core.Tests/Common_Test/CacheTest.cs @@ -0,0 +1,37 @@ +using Autofac; +using Blog.Core.Common; +using Blog.Core.Common.Caches.Interface; +using Xunit; +using Xunit.Abstractions; + +namespace Blog.Core.Tests.Common_Test; + +public class CacheTest +{ + private readonly ITestOutputHelper _testOutputHelper; + DI_Test dI_Test = new DI_Test(); + private readonly ICaching _cache; + + public CacheTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + dI_Test.Build(); + _cache = App.GetService(); + } + + [Fact] + public void TestCaching() + { + _cache.Set("test", "test", new TimeSpan(0, 10, 0)); + + var result = _cache.Get("test"); + Assert.Equal("test", result); + + var caches = _cache.GetAllCacheKeys(); + _testOutputHelper.WriteLine(caches.ToJson()); + Assert.NotNull(caches); + + var count = _cache.GetAllCacheKeys().Count; + Assert.Equal(1, count); + } +} \ No newline at end of file diff --git a/Blog.Core.Tests/Common_Test/HttpHelper_Should.cs b/Blog.Core.Tests/Common_Test/HttpHelper_Should.cs index b7bfa83d..16053f43 100644 --- a/Blog.Core.Tests/Common_Test/HttpHelper_Should.cs +++ b/Blog.Core.Tests/Common_Test/HttpHelper_Should.cs @@ -17,9 +17,12 @@ public void Get_Async_Test() [Fact] public void Post_Async_Test() { - var responseString = HttpHelper.PostAsync("http://apk.neters.club/api/Login/swgLogin", "{\"name\":\"admin\",\"pwd\":\"admin\"}").Result; - - Assert.NotNull(responseString); + var handler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator + }; + + using var client = new HttpClient(handler); } } diff --git a/Blog.Core.Tests/DependencyInjection/DI_Test.cs b/Blog.Core.Tests/DependencyInjection/DI_Test.cs index 1ac4d5a7..bb037efd 100644 --- a/Blog.Core.Tests/DependencyInjection/DI_Test.cs +++ b/Blog.Core.Tests/DependencyInjection/DI_Test.cs @@ -18,6 +18,8 @@ using System.Reflection; using System.Security.Claims; using System.Text; +using Blog.Core.Common.Core; +using Blog.Core.Extensions.ServiceExtensions; using Microsoft.Extensions.Logging; namespace Blog.Core.Tests @@ -51,7 +53,11 @@ public IContainer DICollections() var basePath = AppContext.BaseDirectory; IServiceCollection services = new ServiceCollection(); + services.ConfigureApplication(); + + services.AddLogging(); services.AddAutoMapperSetup(); + services.AddCacheSetup(); services.AddSingleton(new AppSettings(basePath)); services.AddScoped(); @@ -99,11 +105,11 @@ public IContainer DICollections() var builder = new ContainerBuilder(); //builder.RegisterType().As(); builder.RegisterInstance(new LoggerFactory()) - .As(); + .As(); builder.RegisterGeneric(typeof(Logger<>)) - .As(typeof(ILogger<>)) - .SingleInstance(); + .As(typeof(ILogger<>)) + .SingleInstance(); //指定已扫描程序集中的类型注册为提供所有其实现的接口。 builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>)).InstancePerDependency(); //注册仓储 @@ -118,15 +124,15 @@ public IContainer DICollections() var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll"); var assemblysServices = Assembly.LoadFrom(servicesDllFile); builder.RegisterAssemblyTypes(assemblysServices) - .AsImplementedInterfaces() - .InstancePerLifetimeScope() - .PropertiesAutowired() - .EnableInterfaceInterceptors(); + .AsImplementedInterfaces() + .InstancePerLifetimeScope() + .PropertiesAutowired() + .EnableInterfaceInterceptors(); var repositoryDllFile = Path.Combine(basePath, "Blog.Core.Repository.dll"); var assemblysRepository = Assembly.LoadFrom(repositoryDllFile); builder.RegisterAssemblyTypes(assemblysRepository) - .PropertiesAutowired().AsImplementedInterfaces(); + .PropertiesAutowired().AsImplementedInterfaces(); services.Replace(ServiceDescriptor.Transient()); @@ -136,9 +142,18 @@ public IContainer DICollections() builder.Populate(services); //使用已进行的组件登记创建新容器 - var ApplicationContainer = builder.Build(); + var applicationContainer = builder.Build(); + return applicationContainer; + } - return ApplicationContainer; + public IServiceProvider Build() + { + var container = DICollections(); + var serviceProvider = new AutofacServiceProvider(container); + serviceProvider.ConfigureApplication(); + App.IsBuild = true; + App.IsRun = true; + return serviceProvider; } } } \ No newline at end of file diff --git a/Blog.Core.Tests/Service_Test/DbTest.cs b/Blog.Core.Tests/Service_Test/DbTest.cs new file mode 100644 index 00000000..377859fe --- /dev/null +++ b/Blog.Core.Tests/Service_Test/DbTest.cs @@ -0,0 +1,39 @@ +using Microsoft.Data.SqlClient; +using Xunit; +using Xunit.Abstractions; + +namespace Blog.Core.Tests; + +public class DbTest(ITestOutputHelper testOutputHelper) +{ + [Fact] + public void Test_CreateDataBase() + { + string connectionString = "Database=master;TrustServerCertificate=true;Persist Security Info=False;Trusted_Connection=True;server=(local)"; + string connectionString2 = "Database=Blog.Core;TrustServerCertificate=true;Persist Security Info=False;Trusted_Connection=True;server=(local)"; + + // 创建数据库 + using (SqlConnection masterConnection = new SqlConnection(connectionString)) + { + masterConnection.Open(); + string createDbQuery = $"CREATE DATABASE [Blog.Core];"; + using (SqlCommand command = new SqlCommand(createDbQuery, masterConnection)) + { + command.ExecuteNonQuery(); + testOutputHelper.WriteLine("Database created successfully."); + } + } + + // 连接到新创建的数据库 + using (SqlConnection newDbConnection = new SqlConnection(connectionString2)) + { + newDbConnection.Open(); + string testQuery = "SELECT 1;"; + using (SqlCommand command = new SqlCommand(testQuery, newDbConnection)) + { + int result = (int)command.ExecuteScalar()!; + testOutputHelper.WriteLine("Connection to new database successful, test query result: " + result); + } + } + } +} \ No newline at end of file diff --git a/Blog.Core.Tests/Utility/SqlSugarSnowflakeHelperTest.cs b/Blog.Core.Tests/Utility/SqlSugarSnowflakeHelperTest.cs new file mode 100644 index 00000000..0ce29b44 --- /dev/null +++ b/Blog.Core.Tests/Utility/SqlSugarSnowflakeHelperTest.cs @@ -0,0 +1,29 @@ +using System.Globalization; +using Blog.Core.Common.Utility; +using JetBrains.Annotations; +using SqlSugar; +using SqlSugar.DistributedSystem.Snowflake; +using Xunit; +using Xunit.Abstractions; + +namespace Blog.Core.Tests.Utility; + +[TestSubject(typeof(SqlSugarSnowflakeHelper))] +public class SqlSugarSnowflakeHelperTest(ITestOutputHelper testOutputHelper) +{ + [Fact] + public void Test_Id_To_Datetime() + { + var id = SnowFlakeSingle.Instance.NextId(); + + testOutputHelper.WriteLine(SqlSugarSnowflakeHelper.GetDateTime(id).ToString(CultureInfo.InvariantCulture)); + } + + [Fact] + public void Test_Id() + { + var id = SnowFlakeSingle.Instance.NextId(); + + testOutputHelper.WriteLine(SqlSugarSnowflakeHelper.Decode(id).ToJson()); + } +} \ No newline at end of file diff --git a/Blog.Core.Tests/Utility/YitterSnowflakeHelperTest.cs b/Blog.Core.Tests/Utility/YitterSnowflakeHelperTest.cs new file mode 100644 index 00000000..f55ef405 --- /dev/null +++ b/Blog.Core.Tests/Utility/YitterSnowflakeHelperTest.cs @@ -0,0 +1,33 @@ +using System.Globalization; +using Blog.Core.Common.Utility; +using JetBrains.Annotations; +using Xunit; +using Xunit.Abstractions; +using Yitter.IdGenerator; + +namespace Blog.Core.Tests.Utility; + +[TestSubject(typeof(YitterSnowflakeHelper))] +public class YitterSnowflakeHelperTest(ITestOutputHelper testOutputHelper) +{ + private static readonly IdGeneratorOptions _options = new IdGeneratorOptions { WorkerId = 1 }; + private readonly IIdGenerator _idGenInstance = new DefaultIdGenerator(_options); + + [Fact] + public void Test_Id_To_Datetime() + { + var id = _idGenInstance.NewLong(); + + var dateTime = YitterSnowflakeHelper.GetDateTime(_options, id); + testOutputHelper.WriteLine(dateTime.ToString(CultureInfo.InvariantCulture)); + } + + [Fact] + public void Test_Id() + { + var id = _idGenInstance.NewLong(); + + var decoded = YitterSnowflakeHelper.Decode(_options, id); + testOutputHelper.WriteLine(decoded.ToJson()); + } +} \ No newline at end of file diff --git a/Blog.Core.sln b/Blog.Core.sln index 1cdf0453..c04e775b 100644 --- a/Blog.Core.sln +++ b/Blog.Core.sln @@ -30,12 +30,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionIt Blog.Core.Publish.Docker.sh = Blog.Core.Publish.Docker.sh Blog.Core.Publish.Linux.sh = Blog.Core.Publish.Linux.sh codecov.yml = codecov.yml - build\common.targets = build\common.targets CreateYourProject.bat = CreateYourProject.bat DockerBuild.bat = DockerBuild.bat Dockerfile = Dockerfile nuget.config = nuget.config README.md = README.md + Directory.Build.props = Directory.Build.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{EDA8901E-541E-4ADC-B71E-59697D5F9549}" @@ -60,6 +60,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Nacos", "Oc EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blog.Core.Serilog", "Blog.Core.Serilog\Blog.Core.Serilog.csproj", "{7F9057F0-ED8D-4694-B590-7D75C012DF00}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{D6240AF3-765A-41BE-8EAD-5D13F9D3DC3C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Service", "Service", "{4ED6E3A6-13BE-4E9B-A01F-5CABE01A1B5F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Expand", "Expand", "{9C90FB30-ADB8-4C74-9A2F-43109B58C87E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -136,6 +142,15 @@ Global {17C9E9DC-E926-4C90-9025-3DAC55D7EDA3} = {A592C96A-4E44-4F2A-AC21-30683AF6C493} {A11C0DF2-1E13-4EED-BA49-44A57136B189} = {E2BD7D4D-9ED5-41CD-8401-C3FB26F203BB} {6463FB13-5F01-4A1D-8B62-A454FB3812EB} = {E2BD7D4D-9ED5-41CD-8401-C3FB26F203BB} + {97D32A49-994C-44C5-A167-51E71D173B6F} = {D6240AF3-765A-41BE-8EAD-5D13F9D3DC3C} + {558F1B39-07E4-4FAB-BE7E-5B6104607064} = {D6240AF3-765A-41BE-8EAD-5D13F9D3DC3C} + {52AFAB53-D1CA-4014-8B63-3550FDCDA6E1} = {9C90FB30-ADB8-4C74-9A2F-43109B58C87E} + {7F9057F0-ED8D-4694-B590-7D75C012DF00} = {9C90FB30-ADB8-4C74-9A2F-43109B58C87E} + {F8E9FA1F-4079-4F62-B717-E389BC0014E8} = {D6240AF3-765A-41BE-8EAD-5D13F9D3DC3C} + {A2EFEFFC-39AD-48D2-8337-E6840B26023B} = {4ED6E3A6-13BE-4E9B-A01F-5CABE01A1B5F} + {8D651E7F-49D3-4D27-8486-ADCF000BB24D} = {4ED6E3A6-13BE-4E9B-A01F-5CABE01A1B5F} + {E725F0A1-0B03-406F-B84B-0F486C6137FC} = {4ED6E3A6-13BE-4E9B-A01F-5CABE01A1B5F} + {37BB8600-94DA-4A2C-9230-DE93EA1EB0BD} = {4ED6E3A6-13BE-4E9B-A01F-5CABE01A1B5F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AB40D0C5-E3EA-4A9B-86C2-38F0BB33FC04} diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..2934ef7e --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,6 @@ + + + net8.0 + enable + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 15ba36e5..575d3583 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,11 @@ EXPOSE 80 FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src + +# 复制 Directory.Build.props 文件 +COPY ["Directory.Build.props", "."] + +# 复制所有项目文件 COPY ["Blog.Core.Api/Blog.Core.Api.csproj", "Blog.Core.Api/"] COPY ["Blog.Core.Extensions/Blog.Core.Extensions.csproj", "Blog.Core.Extensions/"] COPY ["Blog.Core.EventBus/Blog.Core.EventBus.csproj", "Blog.Core.EventBus/"] @@ -25,8 +30,14 @@ COPY ["Blog.Core.IServices/Blog.Core.IServices.csproj", "Blog.Core.IServices/"] COPY ["Blog.Core.Repository/Blog.Core.Repository.csproj", "Blog.Core.Repository/"] COPY ["Blog.Core.Tasks/Blog.Core.Tasks.csproj", "Blog.Core.Tasks/"] COPY ["build", "build/"] + +# 恢复依赖项 RUN dotnet restore "Blog.Core.Api/Blog.Core.Api.csproj" + +# 复制其余的源代码 COPY . . + +# 构建项目 WORKDIR "/src/Blog.Core.Api" RUN dotnet build "Blog.Core.Api.csproj" -c Release -o /app/build diff --git a/Ocelot.Provider.Nacos/Ocelot.Provider.Nacos.csproj b/Ocelot.Provider.Nacos/Ocelot.Provider.Nacos.csproj index 541ee1e9..a4c4225f 100644 --- a/Ocelot.Provider.Nacos/Ocelot.Provider.Nacos.csproj +++ b/Ocelot.Provider.Nacos/Ocelot.Provider.Nacos.csproj @@ -1,6 +1,5 @@  - softlgl softlgl @@ -13,8 +12,8 @@ - + - + diff --git a/README.md b/README.md index df9f989d..20b0dabf 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ [![sdk](https://img.shields.io/badge/sdk-6.0.1-d.svg)](#) [![Build status](https://github.com/anjoy8/blog.core/workflows/.NET%20Core/badge.svg)](https://github.com/anjoy8/Blog.Core/actions) [![Build Status](https://dev.azure.com/laozhangisphi/anjoy8/_apis/build/status/anjoy8.Blog.Core?branchName=master)](https://dev.azure.com/laozhangisphi/anjoy8/_build?definitionId=1) [![codecov](https://codecov.io/gh/anjoy8/Blog.Core/branch/master/graph/badge.svg)](https://codecov.io/gh/anjoy8/Blog.Core) [![License MIT](https://img.shields.io/badge/license-Apache-blue.svg?style=flat-square)](https://github.com/anjoy8/Blog.Core/blob/master/LICENSE) [![star this repo](http://githubbadges.com/star.svg?user=anjoy8&repo=blog.core&style=flat)](https://github.com/boennemann/badges) [![fork this repo](http://githubbadges.com/fork.svg?user=anjoy8&repo=blog.core&style=flat)](https://github.com/boennemann/badges/fork) [![博客园](https://img.shields.io/badge/博客园-老张的哲学-brightgreen.svg)](https://www.cnblogs.com/laozhang-is-phi/) +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=anjoy8/blog.core&type=Date)](https://star-history.com/#anjoy8/blog.core&Date) +     @@ -31,6 +35,10 @@ Blog.Core 开箱即用的企业级前后端分离【 .NET Core6.0 Api + Vue 2.x 同时如果企业有付费咨询,欢迎联系老张(QQ:3143422472)。 +### 核心项目组成员(排名不分先后) + +[hudingwen](https://github.com/hudingwen)、[LemonNoCry](https://github.com/LemonNoCry)、、[Jamnine何拾玖](https://github.com/Jamnine)、 + #### ❤ 真实用户反馈 ❤ ``` @@ -118,7 +126,8 @@ Blog.Core 开箱即用的企业级前后端分离【 .NET Core6.0 Api + Vue 2.x - [x] 新增 微信公众号管理,并集成到Blog.Admin后台 ✨; - [x] 新增 - 数据部门权限; - [x] 新增 - Serilog 集成日志数据持久化到数据库; -- [x] 新增 - 多租户模式(单表,多表,多库三种模式); +- [x] 新增 - 多租户模式(单表,多表,多库三种模式); +- [x] 新增 - 使用 [SnowflakeId.AutoRegister](https://github.com/LemonNoCry/SnowflakeId.AutoRegister) 自动注册雪花Id WorkerId,支持分布式部署 ✨; 微服务模块: diff --git a/build/README.md b/build/README.md new file mode 100644 index 00000000..e69de29b diff --git a/build/common.targets b/build/common.targets deleted file mode 100644 index eeb37544..00000000 --- a/build/common.targets +++ /dev/null @@ -1,6 +0,0 @@ - - - net8.0 - enable - - \ No newline at end of file