diff --git a/Blog.Core.Api/Blog.Core.xml b/Blog.Core.Api/Blog.Core.xml index 5a36963c..0ed61c91 100644 --- a/Blog.Core.Api/Blog.Core.xml +++ b/Blog.Core.Api/Blog.Core.xml @@ -891,6 +891,13 @@ + + + 雪花Id To DateTime + + + + WeChatCompanyController @@ -1318,6 +1325,22 @@ + + + SqlSugar 相关测试 + + + + + SqlSugar 相关测试 + + + + + 测试建表后,SqlSugar缓存 + + + 缓存管理 @@ -1522,6 +1545,55 @@ + + + 枚举测试 + + + + + 获取学生信息 + + 学生类型 + + + 学生信息 + + + + 学生类型 + + + + + 小学生 + + + + + 中学生 + + + + + 大学生 + + + + + 学生姓名 + + + + + 学生年龄 + + + + + 学生类型 + + Summary:全局路由权限公约 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/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/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/ValuesController.cs b/Blog.Core.Api/Controllers/ValuesController.cs index 46797dca..22d663e9 100644 --- a/Blog.Core.Api/Controllers/ValuesController.cs +++ b/Blog.Core.Api/Controllers/ValuesController.cs @@ -48,14 +48,17 @@ public class ValuesController : BaseApiController private readonly SeqOptions _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) + 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; @@ -66,10 +69,10 @@ public ValuesController(IBlogArticleServices blogArticleServices, IMapper mapper // 测试redis消息队列 _blogArticleServices = blogArticleServices; // httpPolly - _httpPollyHelper = httpPollyHelper; + _httpPollyHelper = httpPollyHelper; _persistentConnection = persistentConnection; - _cache = caching; - _seqOptions = seqOptions.Value; + _cache = caching; + _seqOptions = seqOptions.Value; } /// @@ -84,7 +87,8 @@ public IActionResult TestRabbitMqPublish() _persistentConnection.TryConnect(); } - _persistentConnection.PublishMessage("Hello, RabbitMQ!", exchangeName: "blogcore", routingKey: "myRoutingKey"); + _persistentConnection.PublishMessage("Hello, RabbitMQ!", exchangeName: "blogcore", + routingKey: "myRoutingKey"); return Ok(); } @@ -104,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)); @@ -120,7 +125,7 @@ public MessageModel> MyClaims() response = (_user.GetClaimsIdentity().ToList()).Select(d => new ClaimDto { - Type = d.Type, + Type = d.Type, Value = d.Value } ).ToList() @@ -194,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层返回异常 @@ -290,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) }; } @@ -353,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 @@ -494,6 +499,18 @@ public async Task> TestCacheAsync() 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/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/appsettings.json b/Blog.Core.Api/appsettings.json index d70f9f57..d62aae9e 100644 --- a/Blog.Core.Api/appsettings.json +++ b/Blog.Core.Api/appsettings.json @@ -78,7 +78,8 @@ "SeedDBDataEnabled": true, //生成表,并初始化数据 "Author": "Blog.Core", "SvcName": "", // /svc/blog - "UseLoadTest": false + "UseLoadTest": false, + "CacheDbEnabled": false }, //优化DB配置、不会再区分单库多库 diff --git a/Blog.Core.Common/Blog.Core.Common.csproj b/Blog.Core.Common/Blog.Core.Common.csproj index a72ca817..6e1624a3 100644 --- a/Blog.Core.Common/Blog.Core.Common.csproj +++ b/Blog.Core.Common/Blog.Core.Common.csproj @@ -30,10 +30,11 @@ - - - - + + + + + diff --git a/Blog.Core.Common/Caches/SqlSugarCacheService.cs b/Blog.Core.Common/Caches/SqlSugarCacheService.cs index c2c0cdb5..d94eec54 100644 --- a/Blog.Core.Common/Caches/SqlSugarCacheService.cs +++ b/Blog.Core.Common/Caches/SqlSugarCacheService.cs @@ -1,63 +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/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/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/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 index 9098eece..dcc308b1 100644 --- a/Blog.Core.Common/Utility/IdGeneratorUtility.cs +++ b/Blog.Core.Common/Utility/IdGeneratorUtility.cs @@ -11,6 +11,8 @@ namespace Blog.Core.Common.Utility; public class IdGeneratorUtility { + private static IdGeneratorOptions _options; + private static readonly Lazy AutoRegister = new(() => { var builder = new AutoRegisterBuilder() @@ -44,16 +46,23 @@ public class IdGeneratorUtility var config = AutoRegister.Value.Register(); //WorkerId DataCenterId 取值 1-31 - var options = new IdGeneratorOptions - { - WorkerId = (ushort)config.WorkerId, - }; + 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(); 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.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/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/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/SqlsugarSetup.cs b/Blog.Core.Extensions/ServiceExtensions/SqlsugarSetup.cs index 54c53014..71e1f7fd 100644 --- a/Blog.Core.Extensions/ServiceExtensions/SqlsugarSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/SqlsugarSetup.cs @@ -7,6 +7,7 @@ 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 @@ -36,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, @@ -55,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) => { @@ -73,7 +76,7 @@ public static void AddSqlsugarSetup(this IServiceCollection services) else { if (string.Equals(config.ConfigId.ToString(), MainDb.CurrentDbConnId, - StringComparison.CurrentCultureIgnoreCase)) + StringComparison.CurrentCultureIgnoreCase)) { BaseDBConfig.MainConfig = config; } @@ -103,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) => 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.Tasks/QuartzNet/Jobs/JobBase.cs b/Blog.Core.Tasks/QuartzNet/Jobs/JobBase.cs index 8e1cbc0a..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; //日志 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_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 c4f11eed..50a63431 100644 --- a/Blog.Core.Tests/Blog.Core.Tests.csproj +++ b/Blog.Core.Tests/Blog.Core.Tests.csproj @@ -17,6 +17,7 @@ + @@ -28,6 +29,7 @@ + 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/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 8814184f..c04e775b 100644 --- a/Blog.Core.sln +++ b/Blog.Core.sln @@ -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/README.md b/README.md index 25eef268..20b0dabf 100644 --- a/README.md +++ b/README.md @@ -126,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,支持分布式部署 ✨; 微服务模块: