蓦然回首,木已成林,.NET 框架 Furion 斩获近 1.2万⭐,v4.9.3

来源: 投稿
作者: 百小僧
2024-05-10 16:26:00

自上次发布以来的短短 20 天里,Furion 框架再次爆发了增长势头。在此期间,Furion 文档的注册用户激增,已突破 6 万人大关,其中近 1.9 万用户选择升级为文档会员,更有近 620 位用户开通了 VIP 会员。

同时,在 Gitee 平台上,我们收获了近 12K Stars 评价和超过 6.2K Watching,这一成绩充分证明了 Furion 框架的受欢迎程度和专业实力。

此外,我们的 NuGet 总下载量也迈过了 1600 万的里程碑,这一数字不仅是对我们努力的肯定,更是对未来发展的坚实支撑。这一系列的成绩值得我们骄傲和记录,同时也激励着我们不断前行,为 Furion 框架的未来发展继续贡献力量。

商业化半年回顾

六个月的时光匆匆而过,Furion 框架文档收费的实施也走过了其初始阶段。站在这个节点上,我想与大家分享一些感悟和体会。

首先,开源确实很棒,它打破了技术的壁垒,让知识得以自由传播。然而,开源并不意味着免费。开源项目的持续运营和维护需要巨大的投入成本,这些成本可能包括服务器费用、人力成本、时间投入等。Furion 框架亦是如此,我们一直致力于为开发者提供高质量的工具和文档,但这背后是我们团队无数日夜的辛勤付出

文档收费的决策,我们在充分考量现有情况后,为确保 Furion 框架能够持续、健康地发展而做出的选择。我们希望通过这一方式,为团队提供稳定的资金来源,以便我们能够更加专注于框架的研发、优化以及文档的更新和维护。

这六个月里,我们听到了许多声音,有支持、有理解,也有质疑和担忧。但无论如何,我们始终坚信,只有确保了项目的持续运营和健康发展,才能更好地为开发者服务。

通过文档收费,我们得以更加专注于提升文档质量、优化用户体验。我们倾听每一位开发者的反馈和建议,努力让 Furion 框架的文档更加完善、易用。同时,我们也积极投入资源,加强技术支持和研发力度,为开发者提供更加高效、稳定的开发工具。

回顾这六个月,我们深刻体会到开源项目的不易。但我们也看到了社区的力量和潜力。感谢每一位支持 Furion 的开发者,是你们的信任让我们有动力前行。我们将继续努力,为开源社区贡献更多的力量。

蓦然回首,木已成林

基于 Furion 开源项目已在各大平台崭露头角,成为行业的翘楚。据统计,已有超过 4200 家企业选择成为 Furion 会员,并在招聘平台上发布相关职位,这些企业均采用了 Furion 进行项目开发

借助大数据工具的分析,我们进一步发现,利用 Furion 构建的网站系统数量已超过 20 万,充分证明了其在业界的广泛认可与实际应用价值。

招聘信息:https://www.oschina.net/news/288793/furion-4-9-2-25-released

项目信息

本期亮点

随着 Furion 近期用户数量的显著增长,其需求也呈现出井喷态势,因此本次更新汇聚了众多引人注目的亮点。

1. 新增 远程请求 HttpResponseModel<T> 对象,支持携带更多返回数据

问题分析

早期的 Furion 版本在处理远程请求时,提供了多样化的返回类型支持,涵盖了从原始的 HttpResponseMessageStream 到具体类型 T、字符串和字节数组等。如:

  • 字符串拓展方式
// HttpResponseMessage
+ using var response = await "https://furion.net/".GetAsync();
// Stream
+ var (stream, encoding, response) = await "https://furion.net/".GetAsStreamAsync();
// T
+ var user = await "https://furion.net/".GetAsAsync<User>();
// String
+ var str = await "https://www.baidu.com".GetAsStringAsync();
// Byte[]
+ var bytes = await "https://www.baidu.com".GetAsByteArrayAsync();
  • 代理方式
public interface IHttp : IHttpDispatchProxy
{
    // HttpResponseMessage
    [Post("https://furion.net/post")]
+   Task<HttpResponseMessage> PostXXXAsync();
    // Stream
    [Post("https://furion.net/post")]
+   Task<Stream> PostXXXAsync();
    // T
    [Post("https://furion.net/post")]
+   Task<User> PostXXXAsync();
    // String
    [Post("https://furion.net/post")]
+   Task<string> PostXXXAsync();
    // Byte[]
    [Post("https://furion.net/post")]
+   Task<byte[]> PostXXXAsync();
}

然而,这些方法的局限性在于每次调用仅能获取单一结果,导致若需同时访问响应信息及内容时,不得不先获取 HttpResponseMessage 后再做额外转换,影响了开发效率和代码简洁度。

解决方案

本次 Furion 框架的更新,引入了 HttpResponseModel<T> 新类型,旨在一举解决上述难题。

这一新型响应模型不仅包含了目标数据类型 T 的结果,还封装了原始的 HttpResponseMessage 对象以及内容编码信息,为开发者提供了全方位的响应处理能力。

  • 字符串拓展方式
// 支持多种类型
var httpResponseModel = await "https://furion.net/".GetAsync<HttpResponseModel<string>>();
var httpResponseModel = await "https://furion.net/".GetAsync<HttpResponseModel<Stream>>();
var httpResponseModel = await "https://furion.net/".GetAsync<HttpResponseModel<byte[]>>();
var httpResponseModel = await "https://furion.net/".GetAsync<HttpResponseModel<User>>();
  • 代理方式
public interface IHttp : IHttpDispatchProxy
{
    // 支持多种类型
    [Post("https://furion.net/post")]
    Task<HttpResponseModel<string>> PostXXXAsync();
    Task<HttpResponseModel<Stream>> PostXXXAsync();
    Task<HttpResponseModel<byte[]>> PostXXXAsync();
    Task<HttpResponseModel<User>> PostXXXAsync();
}

无论是通过字符串拓展方法还是代理接口调用,用户现在只需一个调用即可同时获得响应体数据、响应消息对象及编码信息。通过 httpResponseModel.Response.Result  .Encoding,分别快速访问 HttpResponseMessage、数据结果及内容编码。

支持 Stream、String、Byte[] 和自定义类型作为 T,确保了广泛适用性,同时明确了不支持 HttpResponseMessage 自身作为 T 的类型限制,防止潜在的类型循环引用问题。


2. 新增 EntityFramework Core 支持批量更新设置包含或排除的属性列表

问题分析

在过去版本的程序中,当进行批量更新时,系统会自动更新对象的所有字段。若实际需求仅需要更新对象的部分字段或者需要排除某些字段不进行更新,开发者则需要使用 for/foreach 循环遍历每个实体,并分别调用 UpdateInclude  UpdateExclude 方法来指定或排除需要更新的字段。

这种做法不仅代码繁琐,而且效率不高。例如:

var entities = new List<Person> { ... };  
  
- foreach(var entity in entities)  
- {  
-     // 更新指定列  
-     repository.UpdateInclude(entity, new string[] { nameof(entity.Name), nameof(entity.Age) });  
-       
-     // 或者排除指定列  
-     // repository.UpdateExclude(entity, new string[] { nameof(entity.Address) });  
- }

解决方案

在新版本中,我们针对这一痛点进行了优化。现在,开发者无需再为每个实体单独调用更新方法,而是可以直接在批量更新操作中通过传递参数来指定需要更新或排除的字段列表。这种方式既简洁又高效。代码示例如下:

var entities = new List<Person> { ... };  
  
// 更新指定列  
+ repository.Update(entities, includePropertyNames: new string[] { nameof(Person.Name), nameof(Person.Age) });  
  
// 或者排除指定列  
+ repository.Update(entities, excludePropertyNames: new string[] { nameof(Person.Address) });

在上面的代码中,我们使用了 _repository.Update 方法,并通过 includePropertyNames  excludePropertyNames 参数来指定需要更新或排除的字段列表。

这种方式不仅简化了代码,提高了可读性,还提高了批量更新的效率。开发者只需要一次性设置参数,即可对整个实体列表进行批量更新操作。

其他说明

所有批量更新方法都添加了以上两个参数,包含 UpdateNow/UpdateAsync/UpdateNowAsync


3. 新增定时任务可配置 RunOnStart 的逻辑

问题分析

定时任务作业触发器包含一个 RunOnStart 属性配置,允许用户设置是否在程序启动时触发执行。

原先的逻辑是这样的:

RunOnStart 属性用于控制是否在启动时执行一次任务。如果同时设置了 StartTime 属性,并且 StartTime  null 或小于当前时间,那么任务会在程序启动时立即执行。否则,任务会在 StartTime 指定的时间执行。

😳😳😳 虽然这个逻辑在表面上看似合理,但问题在于它限制了用户的自由度和灵活性。

然而,我们遇到了一位有特殊需求的 VIP 用户,他们希望更加灵活地控制任务的启动执行行为。

用户截图

解决方案

为了满足用户的个性化需求,我们开放了 RunOnStart 的处理逻辑,引入了一个名为 RunOnStartProvider 的提供器配置。通过此配置,用户可以自定义 RunOnStart 的行为逻辑。例如:

services.AddSchedule(options => 
{
+    options.RunOnStartProvider = (trigger, checkTime) =>
+    {
         // 如果用户未设置 StartTime,则返回一个比当前时间早一秒的时间
         // 如果设置了 StartTime,则返回 StartTime 的下一个发生时间(😊😊😊这是用户需要的)
+        return (trigger.StartTime == null)
+            ? checkTime.AddSeconds(-1)
+            : trigger.GetNextOccurrence(checkTime);
+    };
});

此外,框架底层也提供了一个默认的 RunOnStartProvider 实现,用于处理通用的启动执行逻辑:

services.AddSchedule(options => 
{
+    options.RunOnStartProvider = (trigger, checkTime) =>
+    {
         // 如果 StartTime 为 null 或小于等于当前时间 checkTime,则返回比当前时间早一秒的时间
         // 否则,返回 StartTime 作为启动执行的时间
+        return (trigger.StartTime == null || trigger.StartTime.Value <= checkTime)
+            ? checkTime.AddSeconds(-1)
+            : trigger.StartTime;
+    };
});

通过这一改进,我们既保留了原有逻辑的通用性,又为用户提供了足够的灵活性和扩展性,以满足各种个性化需求。

4. 新增日志模块 MessageProcess 配置,可对日志消息进行额外处理,如敏感内容脱敏

在某些场景中,我们可能无意中会将敏感信息,如用户密码或手机号等,输出到日志中。为了增强数据安全性,我们可以采取以下措施进行额外的拦截处理:

  • 控制台日志配置
services.AddConsoleFormatter(options =>
{
+    options.MessageProcess = (originalMessage) => 
+    {
+        // 对原始消息进行脱敏处理,以确保敏感信息不被泄露
+        var newMessage = 脱敏处理(originalMessage); // 这里的“脱敏处理”只是一个例子。
+        return newMessage;
+    };
});
  • 文件日志配置
services.AddFileLogging(options =>
{
+    options.MessageProcess = (originalMessage) => 
+    {
+        // 对原始消息进行脱敏处理,以确保敏感信息不被泄露
+        var newMessage = 脱敏处理(originalMessage); // 这里的“脱敏处理”只是一个例子。
+        return newMessage;
+    };
});
  • 存储介质日志配置
services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{
+    options.MessageProcess = (originalMessage) => 
+    {
+        // 对原始消息进行脱敏处理,以确保敏感信息不被泄露
+        var newMessage = 脱敏处理(originalMessage); // 这里的“脱敏处理”只是一个例子。
+        return newMessage;
+    };
});

在上述代码中,脱敏处理 函数用于执行实际的脱敏操作,即替换或移除原始消息中的敏感信息。这样,即使日志被记录或查看,敏感信息也不会被泄露。通过这种方法,我们可以有效地保护用户数据的安全,避免潜在的风险。


5. 新增 动态 WebAPI 支持配置默认的 BindingInfo

问题分析

输入图片说明

 Furion 框架中,动态 WebAPI 针对基元类型和字符串类型的参数,默认情况下会自动应用 [FromRoute] 绑定元数据。这导致了与原生 ASP.NET Core 在路由生成上的显著差异。具体示例如下:

+ public class TestController : IDynamicApiController // 动态 WebAPI
 {
+    [HttpGet("Method1")]
+    public void method1(int id) // Furion 框架生成路由为:/test/method1/{id}
     {
     }

+    [HttpGet("Method2/{id:int}")]
+    public void method2(int id) // 生成路由与原生 ASP.NET Core 一致:/test/method2/{id:int}
     {
     }

+    [HttpGet("Method3")]
+    public void method3(int id, string name) // Furion 框架生成路由为:/test/method3/{id}/{name}
     {
     }
 }

然而,原生 ASP.NET Core 生成的路由则是:

+ public class TestController : ControllerBase // 控制器基类
 {
+    [HttpGet("Method1")]
+    public void method1(int id) // 原生 ASP.NET Core 生成路由为:/test/method1,id 参数通过 ?id= 传入
     {
     }

+    [HttpGet("Method2/{id:int}")]
+    public void method2(int id) // 生成路由与 Furion 框架一致:/test/method2/{id:int}
     {
     }

+    [HttpGet("Method3")]
+    public void method3(int id, string name) // 原生 ASP.NET Core 生成路由为:/test/method3,id 和 name 参数通过 ?id=&name= 传入
     {
     }
}

这种差异导致在将旧项目迁移到 Furion 框架动态 WebAPI 时,可能会因为路由不匹配而引发错误。

解决方案

为了解决这一问题,我们为动态 WebAPI 引入了 DefaultBindingInfo 配置参数。该参数默认值为 route,表示基元类型和字符串类型参数将使用 [FromRoute] 绑定元数据。同时,我们也提供了一个 query 选项,允许开发者选择使用 [FromQuery] 绑定元数据。

具体配置如下:

"DynamicApiControllerSettings": {
+  "DefaultBindingInfo": "query" // 默认值原为 route,现在设置为 query
}

通过上述配置,动态 WebAPI 生成的路由将与原生 ASP.NET Core 保持一致,从而确保项目迁移过程中的路由兼容性。例如:

+ public class TestController : IDynamicApiController // 动态 WebAPI
 {
+    [HttpGet("Method1")]
+    public void method1(int id) // 生成路由为:/test/method1,id 参数通过 ?id= 传入
     {
     }

+    [HttpGet("Method2/{id:int}")]
+    public void method2(int id) // 生成路由与原生 ASP.NET Core 一致:/test/method2/{id:int}
     {
     }

+    [HttpGet("Method3")]
+    public void method3(int id, string name) // 生成路由为:/test/method3,id 和 name 参数通过 ?id=&name= 传入
     {
     }
 }

通过以上优化,我们提供了更加灵活和兼容的路由配置选项,从而满足了不同项目迁移和集成的需求。


6. 改进定时任务看板,新增更多可配置特性

定时任务看板有一年没有更新了,今天花了点时间做了不少改进。

改进一:作业 Id 列固定

输入图片说明

改进二:点击作业信息列表【行】可【展开/收缩】作业触发器列表

输入图片说明

改进三:没有作业触发器的作业部分操作按钮显示【禁用】状态

输入图片说明

改进四:作业触发器看板文字超出自动换行

输入图片说明

改进五:可配置作业列表【是否显示空触发器的作业】

app.UseScheduleUI(options => 
{
+    // 是否显示空触发器的作业信息
+    options.DisplayEmptyTriggerJobs = false;
});

输入图片说明

改进六:可配置是否隐藏【页头】,方便嵌入到各种系统中

app.UseScheduleUI(options => 
{
+    // 是否显示页头
+    options.DisplayHead = false;
});

输入图片说明

改进七:自从配置作业触发器释放默认展开

默认情况下,定时任务看板都是【收缩】状态,点击再【展开】,现在可以支持配置默认【展开】啦。

app.UseScheduleUI(options => 
{
+    // 是否默认展开所有作业
+    options.DefaultExpandAllJobs = true;
});

输入图片说明

其他改进

输入图片说明


7. 新增定时任务支持取消指定触发器正在执行的作业程序

问题分析

Furion 框架的定时任务功能虽然支持取消正在执行的作业处理程序,但这一操作目前仅能通过作业的 Id 来执行。当作业包含多个触发器且它们同时运行时,使用现有的取消机制会导致所有触发器下的作业处理程序都被强制终止。

今天收到了一个来自 VIP 用户的定时任务需求:

用户需求截图1

用户需求截图2

从上述需求中可以看出,用户期望能够对定时任务的取消操作进行更精细化的控制,而不仅仅是基于作业 Id。显然,现有的 Furion 框架定时任务功能无法满足这一需求。

解决方案

为了满足这一需求,在最新的版本更新中(Furion 4.9.2.38+)引入了新的取消机制,允许用户通过指定作业触发器的 Id 来取消对应的作业处理程序。具体实现如下:

  • 使用 IScheduler 接口的方式:
// 取消作业下所有触发器正在执行的程序
scheduler.Cancel();

// 新增功能:取消作业下特定触发器正在执行的程序
+ scheduler.Cancel("triggerId"); // Furion 4.9.2.38+ 支持
  • 使用 ISchedulerFactory 接口的方式:
// 取消作业下所有触发器正在执行的程序
var scheduleResult = _schedulerFactory.TryCancelJob("job1", out var scheduler);
var scheduleResult = _schedulerFactory.TryCancelJob(scheduler);

// 新增功能:取消作业下特定触发器正在执行的程序
+ var scheduleResult = _schedulerFactory.TryCancelJob("job1", out var scheduler, "triggerId"); // Furion 4.9.2.38+ 支持
+ var scheduleResult = _schedulerFactory.TryCancelJob(scheduler, "triggerId"); // Furion 4.9.2.38+ 支持

通过这一更新,增强了 Furion 框架定时任务的灵活性和控制能力,使得用户能够更精准地管理定时任务的执行和取消。

破坏性更改

JobExecutionContext.RunId 属性类型从 Guid 类型调整为 String 类型。


8. 新增定时任务支持立即执行特定的作业触发器

问题分析

 Furion 框架的早期版本中,存在一个功能限制:仅支持立即执行指定的作业,而无法直接触发特定作业下的特定触发器。

解决方案

为了解决这一限制,我们在本次更新中实现了对特定作业触发器的直接执行功能,并同步更新了定时任务的管理界面

更新后的定时任务看板

如何使用新特性:

  • 使用 ISchedulerFactory 接口
// 之前的版本只能立即执行整个作业
var scheduleResult = _schedulerFactory.TryRunJob("job1", out var scheduler);
var scheduleResult = _schedulerFactory.TryRunJob(scheduler);

// Furion 4.9.2.44+ 新增支持,可以指定触发器 Id 来立即执行特定触发器
+ var scheduleResult = _schedulerFactory.TryRunJob("job1", out var scheduler, "triggerId");
+ var scheduleResult = _schedulerFactory.TryRunJob(scheduler, "triggerId");
  • 使用 IScheduler 接口
// 之前的版本只能执行整个作业的所有触发器
scheduler.Run();

// Furion 4.9.2.44+ 新增支持,可以直接指定触发器 Id 来执行特定触发器
+ scheduler.Run("triggerId");

这样,开发者就能够更灵活地管理和执行定时任务了。


9. 新增日志模块设置上下文数据支持无限嵌套

在一些特别的业务场景中,我们可能需要多重设置日志上下文,如:

  • ScopeContext 方式
+ using (logger.ScopeContext(ctx => ctx.Set("one", "one")))
 {
+     using (logger.ScopeContext(ctx => ctx.Set("two", "two")))
      {
         logger.LogInformation("第two层日志输出");

+        using _ = logger.ScopeContext(ctx => ctx.Set("three", "three"));

         logger.LogInformation("第three层日志输出");
      }

     logger.LogInformation("第one层日志输出");
}

+ using (logger.ScopeContext(ctx => ctx.Set("一", "一")))
  {
     logger.LogInformation("第一层日志输出");
  }
  • BeginScope 方式
+ using (logger.BeginScope(new List<KeyValuePair<string, object>>
  {
     new("one", "one")
  }))
  {
+      using (logger.BeginScope(new List<KeyValuePair<string, object>>
       {
          new("two", "two")
       }))
       {
          logger.LogInformation("第two层日志输出");

+         using _ = logger.BeginScope(new List<KeyValuePair<string, object>>
          {
             new("three", "three")
          });

          logger.LogInformation("第three层日志输出");
       }

      logger.LogInformation("第one层日志输出");
  }

+ using (logger.BeginScope(new List<KeyValuePair<string, object>>
  {
     new("一", "一")
  }))
  {
     logger.LogInformation("第一层日志输出");
  }

现在支持这样的方式设置了。


10. 新增定时任务支持检查作业信息额外数据的键是否定义

输入图片说明


11. 修复/改进任务队列执行方式,将队列消费速度提升百倍以上

问题分析

昨天一位 VIP 用户反馈了一个关于任务队列在动态编译 JavaScript 引擎时的性能问题。用户表示,发送了超过 500 条消息,但任务队列在出队时仅处理了 80 多次,且处理速度异常缓慢。

任务队列性能问题截图

初步分析后,发现用户使用了 Jint 库,然而该库不支持异步和多线程操作,这导致了任务队列服务在出队消费时遭遇同步阻塞。

Jint库不支持异步多线程

Jint库限制

解决方案

实际上,Furion 框架在处理定时任务和事件总线时已经解决了类似的问题,但这一解决方案尚未应用于任务队列😳😒。现在,我们将通过 TaskFactory  Parallel 的组合来轻松解决此问题。

+ // 创建一个任务工厂并保证执行任务都使用当前的计划程序
+ var taskFactory = new TaskFactory(TaskScheduler.Current);

+ // 使用 Parallel.For 启动一个并行操作,仅执行一次迭代即可(解决 `Thread` 线程阻塞问题)
+ Parallel.For(0, 1, _ =>
+ {
+     // 创建新的线程执行
+     taskFactory.StartNew(async () =>
+     {
          // 在这里可以调用任何异步或同步(包括阻塞线程)的代码 🎉🎉🎉😊😊😊
+     }, stoppingToken);
+ });

上述代码在定时任务和事件总线中已得到验证,并成功应用于此次任务队列问题。用户提供的测试案例已通过验证,并显示出显著的性能提升。

问题解决后截图

结语

现在,任务队列的性能已至少提升了一百倍,且不再出现阻塞现象。


12. 修复 远程请求 HttpResponseModel<T> 不支持重复读 Stream 问题

问题分析

在上一版本中引入的 HttpResponseModel<T> 类型, T 类型为自定义类时,存在一个潜在问题:一旦从 Response.Content 流中读取数据并将其转换为 T 类型后,原始流会被释放,从而阻止后续尝试再次读取该流。这会导致在尝试重复读取流时遇到“流已关闭”或“流不可读”的错误。

var httpResponseModel1 = await "https://furion.net/getuser/1".PostAsync<HttpResponseModel<User>>();

- // 下列的代码会出错 ❌❌❌
- var stream = await httpResponseModel1.Response.Content.ReadAsStreamAsync();
- using var streamReader = new StreamReader(stream, httpResponseModel1.Encoding);

解决方案

为了解决此问题,框架底层在读取原始响应流时采用了以下策略:

  1. 使用 ReadAsStreamAsync 方法从 response.Content 中读取流。
  2. 创建一个流的副本,保留原始数据的完整副本。
  3. 将该副本重新设置回 response.Content,以便后续可以重复读取。

此过程确保了即使在将流转换为 T 类型后,原始数据仍然可以通过 response.Content 访问,而不会遇到流已关闭的错误。

输入图片说明

此外,为了更好地管理资源释放,HttpResponseModel<T> 类型现在实现了 IDisposable 接口。这意味着当不再需要 HttpResponseModel<T> 实例时,可以确保与其关联的流被正确释放。

输入图片说明

使用注意事项

由于现在 HttpResponseModel<T> 实现了 IDisposable 接口,因此在创建其实例时,建议使用 using 语句来确保资源得到正确管理。

+ // 使用 using 语句确保 HttpResponseModel<T> 资源得到释放
+ using var httpResponseModel1 = await "https://furion.net/getuser/1".PostAsync<HttpResponseModel<User>>();

+ // 下列的代码正常啦 ✅✅✅
+ var stream = await httpResponseModel1.Response.Content.ReadAsStreamAsync();
+ using var streamReader = new StreamReader(stream, httpResponseModel1.Encoding);

请注意,在大多数情况下,直接从 HttpResponseModel<T>.Data 访问 T 类型的数据就足够了,无需再次读取 Response.Content。如果确实需要访问原始流,则应在 using 语句块内部执行此操作,以确保在流被释放之前完成所有必要的读取操作。


13. 修复 模板引擎不支持将粘土对象或 DynamicObject 派生类类型设置为模板数据

问题分析

过往版本的模板引擎在处理模板数据时,存在限制,仅兼容 匿名类型强类型集合类型。这导致了当尝试使用 Furion 框架的粘土对象 Clay 作为模板数据源时,系统抛出异常,无法正常执行。例如:

var sql = @"
@foreach(var item in Model)
{
    @:insert into table(member_id, site_id) values(@item.member_id, @item.site_id);

    @foreach(var subItem in item.goods_list)
    {
        @:insert into table(order_id, goods_id) values(@subItem.order_id, @subItem.goods_id);
    }
}";

+ object clay = Clay.Parse("""
+            [{
+                "member_id": 69697,
+                "site_id": 1,
+                "remark": "",
+                "order_id": 344,
+                "order_no": "1202405051550696970001",
+                "order_status": 3,
+                "name": "百签科技(广东)有限公司",
+                "mobile": "13800138000",
+                "telephone": "",
+                "address": "广东省中山市",
+                "full_address": "广东省中山市西区",
+                "create_time": 1714895456,
+                "pay_money": "148.20",
+                "buyer_message": "",
+                "drug_code": null,
+                "goods_list": [
+                    {
+                        "order_id": 344,
+                        "goods_id": 816503,
+                        "num": 60,
+                        "price": "2.60",
+                        "real_goods_money": "148.20",
+                        "refund_real_money": "0.00",
+                        "country_code": "ZHONGSHAN",
+                        "goods_code": "YPJN0000776",
+                        "third_id": "SPH00008614"
+                    }
+                ]
+            }]
+            """);

// 下面两种方式将出现异常
- var result = await viewEngine.RunCompileAsync(sql, clay);    // IViewEngine 方式
- var result = await sql.RunCompileAsync(clay);    // 字符串拓展方式

解决方案

最新提交的更新已成功解决该问题,现全面支持将粘土对象 Clay 及任何派生于 DynamicObject 的类型直接作为模板数据使用。

// 可正常使用
+ var result = await viewEngine.RunCompileAsync(sql, clay);    // IViewEngine 方式
+ var result = await sql.RunCompileAsync(clay);    // 字符串拓展方式

14. 修复 定时任务毫秒级间隔触发器存在严重的误差问题

问题剖析

在之前的两次提交(734a8c3  1756ab4)对间隔触发器的计算逻辑进行了修正,有效地解决了之前存在的误差问题。

然而,开发过程中出现了疏漏:原有的误差处理代码未能同步移除,导致系统在新逻辑执行后仍额外进行了一次误差校正操作。所以出现了间隔触发器出现预料以外的触发时间。

用户反馈

输入图片说明

输入图片说明


15. 修复文件日志在一些特定情况下出现 `The stream writer is currently in use by a previous write operation.` 异常

在之前的版本中,文件日志的写入操作采用同步与异步方法混合的方式,这在某些情况下引发了 The stream writer is currently in use by a previous write operation. 的异常问题。为了解决这个问题,本版本进行了优化,现在完全采用异步方法进行文件日志的写入操作,以确保更高效、稳定的数据处理。

输入图片说明


16. 修复定时任务看板触发器文字过多出现超出布局情况

旧版本

输入图片说明

新版本

输入图片说明

本期更新

更新日志:https://furion.net/docs/category/upgrade/

  • 新特性

    • [新增] 规范化结果支持拦截 JWT 授权出现代码异常 4.9.3 ⏱️2024.05.10 52d3c2c edc51f4
    • [新增] 定时任务支持立即执行触发特定作业下的特定触发器 4.9.3 ⏱️2024.05.10 3d83342
    • [新增] 日志模块设置上下文支持无限极嵌套 4.9.3 ⏱️2024.05.10 0e313d2
    • [新增] SHA1 加密和比较功能的静态类和字符串拓展支持 4.9.2.41 ⏱️2024.05.08 @superbisu !879 f592757
    • [新增] 定时任务看板可配置是否默认展开所有作业触发器 DefaultExpandAllJobs 4.9.2.40 ⏱️2024.05.07 77c1e6f
    • [新增] 定时任务看板支持是否显示空触发器作业 DisplayEmptyTriggerJobs 和是否显示页头 DisplayHead 4.9.2.39 ⏱️2024.05.07 f64d45f
    • [新增] 定时任务支持取消指定触发器正在执行的作业程序 4.9.2.38 ⏱️2024.05.07 5aa20b5
    • [新增] 粘土对象 Clay 转换为可枚举对象 AsEnumerable() 方法 4.9.2.37 ⏱️2024.05.06 b1c8fa4
    • [新增] 远程请求支持返回 HttpResponseModel<T> 类型,包含 HttpResponseMessage、返回值等属性 4.9.2.34 ⏱️2024.04.30 42ccdaa
    • [新增] 定时任务作业计划支持根据触发器 Id 集合做批量删除操作 4.9.2.33 ⏱️2024.04.30 d01a6e7
    • [新增] 动态 WebAPI 支持配置基元类型和字符串类型默认绑定信息 4.9.2.32 ⏱️2024.04.28 d7e7a02
    • [新增] 支持检查作业信息额外数据的键是否定义 ContainsProperty(key) 方法 4.9.2.32 ⏱️2024.04.28 71f97f0
    • [新增] 日志模块 MessageProcess 配置,可对日志消息进行额外处理,如敏感内容脱敏 4.9.2.32 ⏱️2024.04.28 0d9ff5e
    • [新增] 定时任务支持配置作业触发器 RunOnStart 的处理逻辑 options.RunOnStartProvider 4.9.2.29 ⏱️2024.04.23 c9e0e3e
    • [新增] EFCore 批量更新支持设置 includePropertyNames  excludePropertyNames 参数 4.9.2.28 ⏱️2024.04.23 c9926cc
    • [新增] 远程请求代理模式支持 [BaseAddress] 特性快速设置 HttpClient 客户端 BaseAddress 4.9.2.25 ⏱️2024.04.19 ea88c95
    • [新增] 粘土对象进行固化类型时支持 JsonSerializerOptions 序列化配置 4.9.2.24 ⏱️2024.04.17 cc6dd13
    • [新增] 动态 WebAPI 支持贴 [Route] 特性动态生成控制器 4.9.2.19 ⏱️2024.04.16 #I9H1QH
    • [新增] 粘土对象支持无限极组合嵌套功能 4.9.2.19 ⏱️2024.04.16 b02916e
    • [新增] AES 加解密支持向量 IV、模式 Mode 和填充 Padding 配置 4.9.2.18 ⏱️2024.04.15 d549bba
    • [新增] 定时任务作业计划工厂 ISchedulerFactory 启停作业 StartJob  PauseJob 方法 4.9.2.16 ⏱️2024.04.11 89061ef
    • [新增] AppSettings 拓展程序集 ExternalAssemblies 配置支持目录扫描 4.9.2.14 ⏱️2024.04.10 e68f0a6
    • [新增] 定时任务批量设置作业组名称 .GroupSet 方法 4.9.2.9 ⏱️2024.04.09 9e08278
    • [新增] 控制器/动态 WebAPI 方法添加 [DisplayName] 特性生成 Swagger 文档注释 4.9.2.3 ⏱️2024.03.30 0f24c66
    • [新增] 远程请求且出现异常时输出重试日志 4.9.2.1 ⏱️2024.03.29 e4549eb
    • [新增] 定时任务启动时检查不合法的作业触发器配置并打印警告日志 4.9.2 ⏱️2024.03.28 3190f4c
  • 突破性变化

    • [调整] 规范化结果 IUnifyResultProvider 接口,新增 OnAuthorizeException 方法 4.9.3 ⏱️2024.05.10 52d3c2c edc51f4
    • [调整] 授权处理程序 AppAuthorizeHandler 接口的 HandleAsync 方法签名,新增 DefaultHttpContext  参数 4.9.3 ⏱️2024.05.10 52d3c2c edc51f4
    • [调整] DES 加解密相关类和方法命名: DESCEncryption->DESEncryptionToDESCEncrypt->ToDESEncryptToDESCDecrypt->ToDESDecrypt 4.9.2.41 ⏱️2024.05.08 a46f129
    • [调整] 定时任务作业执行上下文 RunId 类型,由 Guid 改为 string 类型 4.9.2.38 ⏱️2024.05.07 5aa20b5
    • [调整] Swagger 文档注释逻辑,将 /// 注释方式优先级调整至最高,可覆盖 [DisplayName] 特性方式 4.9.2.17 ⏱️2024.04.14 ba5249c
    • [调整] 定时任务作业计划工厂 TryRunJob 方法签名,追加 out IScheduler scheduler 参数 4.9.2.16 ⏱️2024.04.11 89061ef
  • 问题修复

    • [修复] 任务队列在个别情况下出现出队同步阻塞问题 4.9.2.43 ⏱️2024.05.08 f595b47
    • [修复] 定时任务看板点击作业信息列表的操作按钮也会触发展开/收缩作业触发器 bug 4.9.2.40 ⏱️2024.05.07 77c1e6f
    • [修复] 模板引擎不支持将粘土对象或 DynamicObject 派生类类型设置为模板数据 4.9.2.36 ⏱️2024.05.05 07ee172
    • [修复] 启动时输出控制台日志配置 options.MessageProcess 无效问题 4.9.2.36 ⏱️2024.05.05 b5cb0fe
    • [修复] 远程请求 HttpResponseModel<T> 不支持重复读 Response.Content 流问题 4.9.2.35 ⏱️2024.04.30 7ca0650
    • [修复] 审计日志 Monitor 捕获异常时因其 StackTrace 堆栈信息可能为 null 引发的空异常问题 4.9.2.31 ⏱️2024.04.25 @xjj_0906 !875 7621e75
    • [修复] 定时任务间隔触发器获取下一周期时间缺少了 RunOnStart  StartTime 考虑场景 4.9.2.30 ⏱️2024.04.23 2595379 7ac6a54
    • [修复] 定时任务看板作业触发器类型文字过多出现超出布局情况 4.9.2.29 ⏱️2024.04.23 f9dd33b
    • [修复] 文件日志在一些特定情况下出现 The stream writer is currently in use by a previous write operation. 异常 4.9.2.27 ⏱️2024.04.22 3ca012b
    • [修复] 定时任务毫秒级间隔触发器存在严重的误差问题 4.9.2.26 ⏱️2024.04.22 9c8210c
    • [修复] 定时任务创建作业处理程序存在内存溢出风险 4.9.2.25 ⏱️2024.04.19 #I9D0RH
    • [修复] 动态 WebAPI 不支持 [BindNever] 特性忽略路由和 Action 参数设置 4.9.2.25 ⏱️2024.04.19 21599e6
    • [修复] 审计日志 Monitor 不支持粘土对象 Clay/dynamic 类型格式化输出 4.9.2.24 ⏱️2024.04.17 d578cfb
    • [修复] 粘土对象无限嵌套粘土对象且 XElement 属性包含 type="null" 节点出现异常问题 4.9.2.21 ⏱️2024.04.16 9d5870f
    • [修复] 粘土对象嵌套粘土对象只输出第一个属性问题 4.9.2.20 ⏱️2024.04.16 1a75778
    • [修复] 动态 WebAPI 错误将 CancellationToken 类型当作路由参数 4.9.2.19 ⏱️2024.04.16 #I9H14X
    • [修复] 定时任务因新增 GroupSet 功能影响到了原有的 SetGroupName 逻辑 4.9.2.15 ⏱️2024.04.11 #I9FOU0 9e08278
    • [修复] 定时任务生成 PostgreSQL 数据库 SQL 语句的字段名缺少 " 双引号 4.9.2.13 ⏱️2024.04.10 #I9FD9Y
    • [修复] 定时任务使用 JobBuilder 构建委托作业永远无法执行问题 4.9.2.10 ⏱️2024.04.10 Sundial#I7KU7K
    • [修复] 规范化结果在未启用 401/403 等状态码中间件时进行了错误拦截 4.9.2.8 ⏱️2024.04.08 b135e8c
    • [修复] 客户端设置 JWT Token 时如果 Bearer 后面跟多个空格导致验证失败问题 4.9.2.8 ⏱️2024.04.08 @xuejf168 !874
    • [修复] SQL 查询结果转模型不支持 DateOnly  TimeOnly 属性类型 4.9.2.7 ⏱️2024.04.04 31f9d23
    • [修复] 粘土对象调整原先类型并设置混合类型异常问题 4.9.2.6 ⏱️2024.04.03 83b216f
    • [修复] 粘土对象将 Object 类型设置给 Array 类型出现递归死循环问题 4.9.2.5 ⏱️2024.04.03 1126c74
    • [修复] 粘土对象不支持嵌套粘土对象问题 4.9.2.4 ⏱️2024.04.02 fcb1223
    • [修复] 粘土对象序列化后出现二次序列化成字符串问题 4.9.2.4 ⏱️2024.04.02 fcb1223
    • [修复] 在 .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
  • 其他更改

    • [调整] IPC 管道消息消费方式由无序改为有序 4.9.2.33 ⏱️2024.04.30 ce59c3a
    • [调整] 默认 System.Text.Json 序列化提供器选项为不区分大小写匹配 4.9.2.1 ⏱️2024.03.29 b58e7be
  • 文档

    • [更新] 事件总线文档、定时任务文档、规范化接口文档、远程请求文档、粘土对象文档、FS 静态类文档、序列化文档、模块化文档、数据加解密文档、动态 WebAPI 文档、IPC 通信模块文档、日志文档、安全授权文档
  • 贡献者

展开阅读全文
点击加入讨论🔥(7) 发布并加入讨论🔥
本篇精彩评论
入选开源中国耻辱柱
2024-05-11 10:09
21
举报
恬不知耻
2024-05-11 09:12
17
举报
7 评论
3 收藏
分享
返回顶部
顶部