🦉 谋士以身入局,.NET 框架 Furion v4.9.2 发布
谋士以身入局,破局以力行实践。
谋士以身入局,破局以力行实践。世间之事,如同棋局,纷繁复杂,难以一眼洞穿。唯有亲身涉入其中,方能体悟其微妙变化,洞悉其深藏之规律。
人生亦是如此,奇妙无穷,福祸相依。有时,我们会在逆境中遇到转机,峰回路转;有时,又会在顺境中遭遇波折,跌宕起伏。但正是这些起起伏伏,构成了我们丰富多彩的人生画卷。
2024.03.31 百小僧
时间总是在不经意间从每日充实且忙碌的生活中悄悄溜走。距离上一次发版已经过去了整整十天。
随着 Furion 的声誉日益提升,其用户数量也呈现出迅猛的增长态势。作为 .NET 领域的热门流行框架,Furion 在 Gitee 平台上收获了超过 11K 的星星,NuGet 下载量更是突破了 1500 万大关。同时,我们的文档注册用户数量已经达到了 4.8万+,每日浏览量高达 60万+,独立访客数量也超过了 4 万。更令人振奋的是,已经有超过 4100 家企业选择使用 Furion,这充分证明了其在业界的广泛认可与卓越价值。
开源需躬身入局
只要你的开源项目足够风靡,且你对其倾注的心血如同养育子女般深厚,那么在寻求商业化的道路上,难免会遇到一些毫无底线、唯利是图的企业和个人开发者。正因如此,像Elasticsearch、Kibana、MongoDB和Redis这样的知名开源项目,都不得不选择修改其开源协议,以应对这种局面。传统的OSI定义显然已经无法准确概括现代开源运动的复杂性和多样性,因此,越来越多的项目开始倾向于采用更加灵活和适应性强的SSPL协议。
然而,反观国内的开源圈,不少所谓的导师、布道师,甚至是开源平台的创始人,他们中的大多数人都没有亲自经历过从零到一打造一款成功的开源项目,更谈不上实现商业化的蜕变。但即便如此,他们却热衷于以导师的身份来指导我们如何进行开源工作。这不禁让人想起那句评论“文章写尽太平事,不肯俯首见苍生。”,他们或许擅长纸上谈兵,但对于真正的开源世界和其中的艰辛与挑战,却缺乏深入的了解和体验。
项目信息
- Gitee:https://gitee.com/dotnetchina/Furion
- Github:https://github.com/MonkSoul/Furion
- 文档:https://furion.net
本期亮点
1. 控制器/动态 WebAPI 方法添加 [DisplayName] 特性生成 Swagger 文档注释
问题分析
在默认情况下,当控制器中的 Action 方法需要在 Swagger 中附带注释时,开发者通常会使用 /// 注释语法。例如:
+/// <summary>
+/// 我是一段注释
+/// </summary>
public void SomeAction()
{
}
在实际的项目开发中,为了提高代码的可读性和便于后续的审计日志记录,我们更倾向于使用 [DisplayName] 特性来为每一个 Action 方法命名。这会导致代码中存在冗余的注释信息,如下所示:
/// <summary>
+/// 新增用户
/// </summary>
+[DisplayName("新增用户")]
public void SomeAction()
{
}
解决方案
为了简化这一流程,减少不必要的重复工作,我们提供了一个功能:使 [DisplayName] 特性也能自动生成注释。这样,开发者就无需再额外添加 /// 注释语法,也能在 Swagger 中看到相应的注释信息。
具体实现如下:
-/// <summary>
-/// 新增用户
-/// </summary>
+[DisplayName("新增用户")]
public void SomeAction()
{
}
通过上述改进,开发者可以更加高效地为 Action 方法添加描述信息,同时保持代码的整洁和可读性。
最终效果
优先级
如果同时提供了 /// 注释和 [DisplayName] 特性,那么后者将覆盖前者。
2. 定时任务持久化 IJobPersistence 接口方法为异步方法
为了避免定时任务持久化存在死锁风险,故将所有方法调整为异步方法(破坏性):
/// <summary>
/// 作业调度持久化器
/// </summary>
public interface IJobPersistence
{
/// <summary>
/// 作业调度器预加载服务
/// </summary>
/// <param name="stoppingToken">取消任务 Token</param>
/// <returns><see cref="Task"/></returns>
- IEnumerable<SchedulerBuilder> Preload();
+ Task<IEnumerable<SchedulerBuilder>> PreloadAsync(CancellationToken stoppingToken);
/// <summary>
/// 作业计划初始化通知
/// </summary>
/// <param name="builder">作业计划构建器</param>
/// <param name="stoppingToken">取消任务 Token</param>
/// <returns><see cref="Task"/></returns>
- SchedulerBuilder OnLoading(SchedulerBuilder builder);
+ Task<SchedulerBuilder> OnLoadingAsync(SchedulerBuilder builder, CancellationToken stoppingToken);
/// <summary>
/// 作业信息更改通知
/// </summary>
/// <param name="context">作业信息持久化上下文</param>
/// <returns><see cref="Task"/></returns>
- void OnChanged(PersistenceContext context);
+ Task OnChangedAsync(PersistenceContext context);
/// <summary>
/// 作业触发器更改通知
/// </summary>
/// <param name="context">作业触发器持久化上下文</param>
/// <returns><see cref="Task"/></returns>
- void OnTriggerChanged(PersistenceTriggerContext context);
+ Task OnTriggerChangedAsync(PersistenceTriggerContext context);
/// <summary>
/// 作业触发记录通知
/// </summary>
/// <param name="timeline">作业触发器运行记录</param>
/// <returns><see cref="Task"/></returns>
- void OnExecutionRecord(TriggerTimeline timeline);
+ Task OnExecutionRecordAsync(TriggerTimeline timeline);
}
最新 IJobPersistence 接口声明:
namespace Furion.Schedule;
/// <summary>
/// 作业调度持久化器
/// </summary>
public interface IJobPersistence
{
/// <summary>
/// 作业调度器预加载服务
/// </summary>
/// <param name="stoppingToken">取消任务 Token</param>
/// <returns><see cref="Task"/></returns>
Task<IEnumerable<SchedulerBuilder>> PreloadAsync(CancellationToken stoppingToken);
/// <summary>
/// 作业计划初始化通知
/// </summary>
/// <param name="builder">作业计划构建器</param>
/// <param name="stoppingToken">取消任务 Token</param>
/// <returns><see cref="Task"/></returns>
Task<SchedulerBuilder> OnLoadingAsync(SchedulerBuilder builder, CancellationToken stoppingToken);
/// <summary>
/// 作业信息更改通知
/// </summary>
/// <param name="context">作业信息持久化上下文</param>
/// <returns><see cref="Task"/></returns>
Task OnChangedAsync(PersistenceContext context);
/// <summary>
/// 作业触发器更改通知
/// </summary>
/// <param name="context">作业触发器持久化上下文</param>
/// <returns><see cref="Task"/></returns>
Task OnTriggerChangedAsync(PersistenceTriggerContext context);
/// <summary>
/// 作业触发记录通知
/// </summary>
/// <param name="timeline">作业触发器运行记录</param>
/// <returns><see cref="Task"/></returns>
Task OnExecutionRecordAsync(TriggerTimeline timeline);
}
3. 动态 WebAPI 自定义 [Route] 模板中包含路由约束并且含有大小写字母导致生成错误路由问题
问题分析
在特定情况下,当控制器及其内部的 Action 方法均采用自定义的 [Route] 特性,并且 Action 层面的 [Route] 定义中包含了含有大小写字母的路由参数约束时,系统默认的行为会导致此类参数中的大小写字母被统一转换为小写,从而引发路由匹配失效的问题。比如:
+[Route("api/[controller]/[action]")]
public class ShareController : IDynamicApiController
{
+ [Route("/api/share/position/position/{positionID:int}")]
public void Position(int positionID)
{
// ...
}
}
上述代码片段中,Position 方法的路由约束 {positionID:int} 显然包含一个大小写混合的参数名,其中 ID 部分采用了大写字母。然而,在实际运行时,该参数会被错误地格式化成全小写形式:
/api/share/position/position/{positionid}
这种格式化行为导致原本期望匹配的 positionID 变为了 positionid,进而使得按照原始定义的大小写格式发送请求时,路由无法正确匹配。
修正效果
针对这一问题,在当前的版本更新中已实施了相应的修复措施,具体做法是:保持路由约束内 {} 包含的参数名称的原始大小写不变。因此,在修复后的场景下,正确的路由格式应该是:
/api/share/position/position/{positionID}
这样一来,路由参数的大小写得以保留,确保了路由匹配逻辑的正确性和一致性。
4. 数据库日志写入接口 IDatabaseLoggingWriter 方法为异步 WriteAsync
问题背景
解决方案
IDatabaseLoggingWriter 接口方法调整(破坏性更改):
- public void Write(LogMessage logMsg, bool flush)
+ public Task WriteAsync(LogMessage logMsg, bool flush) // 改为异步方法,提升日志写入吞吐量
调整为异步方法之后,可以大大提升数据库日志写入的吞吐量,同时还能实现速率的控制,如:
public async Task WriteAsync(LogMessage logMsg, bool flush)
{
// 数据库写入~
+ await Task.Delay(50); // 延迟 0.05 秒写入数据库,有效减少高频写入数据库导致死锁问题
}
在增强日志提供器的队列容量后,成功缓解了高频日志引发的长时间队列积压和阻塞问题,并显著提升了文件写入的吞吐量。这一改进遵循了“以空间换取时间”的设计理念,即通过适度牺牲部分即时内存来换取更为出色的系统处理效率,从而实现了整体性能的优化。
测试实图
内存情况
文件大小
20万行写入
本期更新
-
新特性
-
突破性变化
-
问题修复
- [修复] 在
.NET8之后修改System.Text.Json默认序列化选项引发This JsonSerializerOptions instance is read-only or has already been used in serialization or deserialization.异常问题 4.9.2.2 ⏱️2024.03.29 9f44653 - [修复] 远程请求
IHttpDispatchProxy模式配置重试策略无效 4.9.2.1 ⏱️2024.03.29 #I9CK7X - [修复] 动态
WebAPI自定义[Route]模板中包含路由约束并且含有大小写字母导致生成错误路由问题 4.9.1.61 ⏱️2024.03.27 cc1a7ec - [修复] 定时任务持久化单个作业触发器订阅执行器出现异常导致持久化服务宕机问题 4.9.1.60 ⏱️2024.03.26 a1014db
- [修复]
EntityFramework Core反向工程脚本cli.ps1正则表达式匹配错误 4.9.1.59 ⏱️2024.03.26 !872 @cnbdas
- [修复] 在
-
其他更改
-
文档
- [更新] 事件总线文档、定时任务文档、规范化接口文档、远程请求文档








