接上一篇 Newbe.Claptrap 框架入门,第二步 —— 简单业务,清空购物车 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务。通过本篇阅读,您便可以开始学会添加一个全新的 Claptrap。

Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架。如果您是首次阅读本系列文章。建议可以先从本文末尾的入门文章开始了解。

开篇摘要

本篇,我通过实现 “管理库存” 的需求来了解一下如何在已有的项目样例中定义一个 Claptrap。

结合前一篇的基本步骤,定义 Claptrap 只要而外增加一些步骤就可以了。完整的步骤如下所示,其中标记为 “新内容” 的部分属于本篇的区别于前篇的新内容:

  1. 定义 ClaptrapTypeCode (新内容)
  2. 定义 State (新内容)
  3. 定义 Grain 接口 (新内容)
  4. 实现 Grain (新内容)
  5. 注册 Grain (新内容)
  6. 定义 EventCode
  7. 定义 Event
  8. 实现 EventHandler
  9. 注册 EventHandler
  10. 实现 IInitialStateDataFactory (新内容)
  11. 修改 Controller

这是一个从下向上的过程,实际的编码过程中开发也可以有所调整。

本篇实现的业务用例:

  1. 实现表示库存数据的 SKU(Stock keeping Unit) 对象。
  2. 能够对 SKU 进行更新和读取。

定义 ClaptrapTypeCode

ClaptrapTypeCode 是一个 Claptrap 的唯一编码。其在 State 的识别,序列化等方面起到了重要的作用。

打开 HelloClaptrap.Models 项目中的 ClaptrapCodes 类。

添加 SKU 的 ClaptrapTypeCode。

 
  namespace HelloClaptrap.Models
{
public static class ClaptrapCodes
{
public const string CartGrain = "cart_claptrap_newbe";
private const string CartEventSuffix = "_e_" + CartGrain;
public const string AddItemToCart = "addItem" + CartEventSuffix;
public const string RemoveItemFromCart = "removeItem" + CartEventSuffix; #region Sku + public const string SkuGrain = "sku_claptrap_newbe";
+ private const string SkuEventSuffix = "_e_" + SkuGrain; #endregion
}
}

定义 State

State 在 Actor 模式中代表了 Actor 对象当前的数据表现。

由于 Claptrap 是基于事件溯源模式的 Actor。因此定义恰好的 State 非常重要。

在该示例当中,我们只需要记录当前 SKU 的库存即可,因此,State 的设计非常的简单。

在 HelloClaptrap.Models 项目添加 Sku 文件夹,并在该文件夹下创建 SkuState 类。

添加如下代码:

 
+ using Newbe.Claptrap;
+
+ namespace HelloClaptrap.Models.Sku
+ {
+ public class SkuState : IStateData
+ {
+ public int Inventory { get; set; }
+ }
+ }

Inventory 表示当前 SKU 的库存。

IStateData 接口是框架中表示 State 的空接口,用于在泛型推断时使用。

定义 Grain 接口

定义 Grain 接口的定义,才能够提供外部与 Claptrap 的互操作性。

在 HelloClaptrap.IActors 项目中添加 ISkuGrain 接口。

添加接口以及 Attribute。

 
+ using System.Threading.Tasks;
+ using HelloClaptrap.Models;
+ using HelloClaptrap.Models.Sku;
+ using Newbe.Claptrap;
+ using Newbe.Claptrap.Orleans;
+
+ namespace HelloClaptrap.IActor
+ {
+ [ClaptrapState(typeof(SkuState), ClaptrapCodes.SkuGrain)]
+ public interface ISkuGrain : IClaptrapGrain
+ {
+ /// <summary>
+ /// Get latest inventory of this sku
+ /// </summary>
+ /// <returns></returns>
+ Task<int> GetInventoryAsync();
+
+ /// <summary>
+ /// Update inventory by add diff, diff could be negative number
+ /// </summary>
+ /// <param name="diff"></param>
+ /// <returns>Inventory after updating</returns>
+ Task<int> UpdateInventoryAsync(int diff);
+ }
+ }

其中增加了以下内容:

  1. 标记了 ClaptrapState,使得 State 与 Grain 进行关联。
  2. 接口继承了 IClaptrapGrain,这是框架定义的 Grain 接口,这是依托于 Orleans 运行必须继承的接口。
  3. 增加了 GetInventoryAsync 方法,表示 “获取当前库存”。
  4. 增加了 UpdateInventoryAsync 方法,表示 “增量更新当前库存”。diff > 0 表示增加库存,diff < 0 表示减少库存。
  5. 需要注意的是 Grain 的方法定义有一定限制。详细可以参见《Developing a Grain》

实现 Grain

定义好 ISkuGrain 之后,便可以添加代码进行实现。

在 HelloClaptrap.Actors 项目新建 Sku 文件夹,并在该文件夹中添加 SkuGrain 类。

 
+ using System;
+ using System.Threading.Tasks;
+ using HelloClaptrap.IActor;
+ using HelloClaptrap.Models;
+ using HelloClaptrap.Models.Sku;
+ using Newbe.Claptrap;
+ using Newbe.Claptrap.Orleans;
+
+ namespace HelloClaptrap.Actors.Sku
+ {
+ public class SkuGrain : ClaptrapBoxGrain<SkuState>, ISkuGrain
+ {
+ public SkuGrain(IClaptrapGrainCommonService claptrapGrainCommonService)
+ : base(claptrapGrainCommonService)
+ {
+ }
+
+ public Task<int> GetInventoryAsync()
+ {
+ return Task.FromResult(StateData.Inventory);
+ }
+
+ public async Task<int> UpdateInventoryAsync(int diff)
+ {
+ if (diff == )
+ {
+ throw new BizException("diff can`t be 0");
+ }
+
+ var old = StateData.Inventory;
+ var newInventory = old + diff;
+ if (newInventory < )
+ {
+ throw new BizException(
+ $"failed to update inventory. It will be less than 0 if add diff amount. current : {old} , diff : {diff}");
+ }
+
+ throw new NotImplementedException();
+ }
+ }
+ }

其中增加了以下内容:

  1. 继承 ClaptrapBoxGrain<SkuState> 并实现 ISkuGrainClaptrapBoxGrain 是框架定义的 Grain 基类,其中的泛型参数表示对应的 State 类型。
  2. 实现 GetInventoryAsync 方法,从 StateData 中读取当前的库存。
  3. 实现 UpdateInventoryAsync 方法,添加业务判断代码,若不满足业务操作的条件则抛出异常。
  4. UpdateInventoryAsync 的最后我们现在抛出 NotImplementedException ,因为当前事件还没有定义,需要等待后续的代码实现。
  5. BizException 是一个自定义异常,可以自行添加。实际开发中也可以不使用抛出异常的方式表示业务中断,改用状态码或者其他返回值也是可以的。

注册 Grain

Claptrap 对应的 Grain 需要在应用程序启动时进行注册,这样框架才能扫描发现。

由于示例代码采用的是程序集范围内扫描,因此实际上不需要进行修改。

这里指出发生注册的位置:

打开 HelloClaptrap.BackendServer 项目的 Program 类。

 
  using System;
using Autofac;
using HelloClaptrap.Actors.Cart;
using HelloClaptrap.IActor;
using HelloClaptrap.Repository;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newbe.Claptrap;
using Newbe.Claptrap.Bootstrapper;
using NLog.Web;
using Orleans; namespace HelloClaptrap.BackendServer
{
public class Program
{ public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); })
.UseClaptrap(
builder =>
{
+ builder
+ .ScanClaptrapDesigns(new[]
+ {
+ typeof(ICartGrain).Assembly,
+ typeof(CartGrain).Assembly,
+ });
},
builder => { builder.RegisterModule<RepositoryModule>(); })
.UseOrleansClaptrap()
.UseOrleans(builder => builder.UseDashboard(options => options.Port = ))
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.Trace);
})
.UseNLog();
}
}

因为 ISkuGrain 和 SkuGrain 分别于 ICartGrain 和 CartGrain 属于同一程序集,因而此处不需要修改。

定义 EventCode

前面我们已经实现了 Claptrap 的主要部分,但唯独没有完成更新库存的操作。这是因为更新库存是需要对 State 进行更新的。而我们都知道 Claptrap 是基于事件溯源的 Actor 模式,对 State 的更新需要通过事件才能完成。故而由这里开始,我们来通过事件更新库存。

EventCode 是 Claptrap 系统每个事件的唯一编码。其在事件的识别,序列化等方面起到了重要的作用。

打开 HelloClaptrap.Models 项目中的 ClaptrapCodes 类。

添加 “更新库存” 的 EventCode。

 
  namespace HelloClaptrap.Models
{
public static class ClaptrapCodes
{
#region Cart public const string CartGrain = "cart_claptrap_newbe";
private const string CartEventSuffix = "_e_" + CartGrain;
public const string AddItemToCart = "addItem" + CartEventSuffix;
public const string RemoveItemFromCart = "removeItem" + CartEventSuffix;
public const string RemoveAllItemsFromCart = "remoeAllItems" + CartEventSuffix; #endregion #region Sku public const string SkuGrain = "sku_claptrap_newbe";
private const string SkuEventSuffix = "_e_" + SkuGrain;
+ public const string SkuInventoryUpdate = "inventoryUpdate" + SkuEventSuffix; #endregion
}
}

定义 Event

Event 是事件溯源的关键。用于改变 Claptrap 中的 State。并且 Event 会被持久化在持久层。

在 HelloClaptrap.Models 项目的 Sku/Events 文件夹下创建 InventoryUpdateEvent 类。

添加如下代码:

+ using Newbe.Claptrap;
+
+ namespace HelloClaptrap.Models.Sku.Events
+ {
+ public class InventoryUpdateEvent : IEventData
+ {
+ public int Diff { get; set; }
+ public int NewInventory { get; set; }
+ }
+ }
 
  1. Diff 表示此次更新库存的数额,diff > 0 表示增加库存,diff < 0 表示减少库存。
  2. NewInventory 表示更新之后的库存。此处,提前给出一个建议,但由于篇幅问题,不展开讨论:建议在事件中包含 State 的更新后数据。

实现 EventHandler

EventHandler 用于将事件更新到 Claptrap 的 State 上。

在 HelloClaptrap.Actors 项目的 Sku/Events 文件夹下创建 InventoryUpdateEventHandler 类。

添加如下代码:

+ using System.Threading.Tasks;
+ using HelloClaptrap.Models.Sku;
+ using HelloClaptrap.Models.Sku.Events;
+ using Newbe.Claptrap;
+
+ namespace HelloClaptrap.Actors.Sku.Events
+ {
+ public class InventoryUpdateEventHandler
+ : NormalEventHandler<SkuState, InventoryUpdateEvent>
+ {
+ public override ValueTask HandleEvent(SkuState stateData,
+ InventoryUpdateEvent eventData,
+ IEventContext eventContext)
+ {
+ stateData.Inventory = eventData.NewInventory;
+ return new ValueTask();
+ }
+ }
+ }
 
  1. 因为事件中已经包含了更新后的库存,故而直接对 StateData 进行赋值即可。

注册 EventHandler

实现并测试完 EventHandler 之后,便可以将 EventHandler 进行注册,以便与 EventCode 以及 Claptrap 进行关联。

打开 HelloClaptrap.Actors 项目的 SkuGrain 类。

使用 Attribute 进行标记,并修改 UpdateInventoryAsync 执行事件。

 
  using System.Threading.Tasks;
+ using HelloClaptrap.Actors.Sku.Events;
using HelloClaptrap.IActor;
using HelloClaptrap.Models;
using HelloClaptrap.Models.Sku;
+ using HelloClaptrap.Models.Sku.Events;
using Newbe.Claptrap;
using Newbe.Claptrap.Orleans; namespace HelloClaptrap.Actors.Sku
{
+ [ClaptrapEventHandler(typeof(InventoryUpdateEventHandler), ClaptrapCodes.SkuInventoryUpdate)]
public class SkuGrain : ClaptrapBoxGrain<SkuState>, ISkuGrain
{
public SkuGrain(IClaptrapGrainCommonService claptrapGrainCommonService)
: base(claptrapGrainCommonService)
{
} public Task<int> GetInventoryAsync()
{
return Task.FromResult(StateData.Inventory);
} public async Task<int> UpdateInventoryAsync(int diff)
{
if (diff == )
{
throw new BizException("diff can`t be 0");
} var old = StateData.Inventory;
var newInventory = old + diff;
if (newInventory < )
{
throw new BizException(
$"failed to update inventory. It will be less than 0 if add diff amount. current : {old} , diff : {diff}");
} - throw new NotImplementedException();
+ var evt = this.CreateEvent(new InventoryUpdateEvent
+ {
+ Diff = diff,
+ NewInventory = newInventory
+ });
+ await Claptrap.HandleEventAsync(evt);
+ return StateData.Inventory;
}
}
}

实现 IInitialStateDataFactory

前面我们已经完成了库存的查询和更新。不过通常来说库存有一个初始数额,我们本节在补充这部分逻辑。

在 HelloClaptrap.Actors 项目的 Sku 文件夹下创建 SkuStateInitHandler 类。

+ using System.Threading.Tasks;
+ using HelloClaptrap.Models.Sku;
+ using HelloClaptrap.Repository;
+ using Newbe.Claptrap;
+
+ namespace HelloClaptrap.Actors.Sku
+ {
+ public class SkuStateInitHandler : IInitialStateDataFactory
+ {
+ private readonly ISkuRepository _skuRepository;
+
+ public SkuStateInitHandler(
+ ISkuRepository skuRepository)
+ {
+ _skuRepository = skuRepository;
+ }
+
+ public async Task<IStateData> Create(IClaptrapIdentity identity)
+ {
+ var skuId = identity.Id;
+ var inventory = await _skuRepository.GetInitInventoryAsync(skuId);
+ var re = new SkuState
+ {
+ Inventory = inventory
+ };
+ return re;
+ }
+ }
+ }
 
  1. IInitialStateDataFactory 会在 Claptrap 初次激活时被调用,用来创建 State 的初始值。
  2. 注入 ISkuRepository 从数据库中读取 Sku 对应的库存初始数额,具体的代码此处不进行罗列,读者可以查看样例仓库中的实现。

除了实现代码之外,还需要进行注册才会被调用。

打开 HelloClaptrap.Actors 项目的 SkuGrain 类。

 
  using System.Threading.Tasks;
using HelloClaptrap.Actors.Sku.Events;
using HelloClaptrap.IActor;
using HelloClaptrap.Models;
using HelloClaptrap.Models.Sku;
using HelloClaptrap.Models.Sku.Events;
using Newbe.Claptrap;
using Newbe.Claptrap.Orleans; namespace HelloClaptrap.Actors.Sku
{
+ [ClaptrapStateInitialFactoryHandler(typeof(SkuStateInitHandler))]
[ClaptrapEventHandler(typeof(InventoryUpdateEventHandler), ClaptrapCodes.SkuInventoryUpdate)]
public class SkuGrain : ClaptrapBoxGrain<SkuState>, ISkuGrain
{
public SkuGrain(IClaptrapGrainCommonService claptrapGrainCommonService)
: base(claptrapGrainCommonService)
{
} public Task<int> GetInventoryAsync()
{
return Task.FromResult(StateData.Inventory);
} public async Task<int> UpdateInventoryAsync(int diff)
{
if (diff == )
{
throw new BizException("diff can`t be 0");
} var old = StateData.Inventory;
var newInventory = old + diff;
if (newInventory < )
{
throw new BizException(
$"failed to update inventory. It will be less than 0 if add diff amount. current : {old} , diff : {diff}");
} var evt = this.CreateEvent(new InventoryUpdateEvent
{
Diff = diff,
NewInventory = newInventory
});
await Claptrap.HandleEventAsync(evt);
return StateData.Inventory;
}
}
}

修改 Controller

前面的所有步骤完成之后,就已经完成了 Claptrap 的所有部分。但由于 Claptrap 无法直接提供与外部程序的互操作性。因此,还需要在在 Controller 层增加一个 API 以便外部进行 “读取库存” 的操作。

在 HelloClaptrap.Web 项目的 Controllers 文件夹下新建 SkuController 类。

+ using System.Threading.Tasks;
+ using HelloClaptrap.IActor;
+ using Microsoft.AspNetCore.Mvc;
+ using Orleans;
+
+ namespace HelloClaptrap.Web.Controllers
+ {
+ [Route("api/[controller]")]
+ public class SkuController : Controller
+ {
+ private readonly IGrainFactory _grainFactory;
+
+ public SkuController(
+ IGrainFactory grainFactory)
+ {
+ _grainFactory = grainFactory;
+ }
+
+ [HttpGet("{id}")]
+ public async Task<IActionResult> GetItemsAsync(string id)
+ {
+ var skuGrain = _grainFactory.GetGrain<ISkuGrain>(id);
+ var inventory = await skuGrain.GetInventoryAsync();
+ return Json(new
+ {
+ skuId = id,
+ inventory = inventory,
+ });
+ }
+ }
+ }
 
  1. 新增 API 读取特定 SkuId 的库存。按照样例代码的实现,可以传入 yueluo-123 得到库存数额为 666。不存在的 SkuId 将会抛出异常。
  2. 此处没有创建更新库存的对外 API,因为本示例将在下篇进行下单购物时进行库存操作,此处暂不需要 API。

小结

至此,我们就完成了 “管理商品库存” 这个简单需求的所有内容。

您可以从以下地址来获取本文章对应的源代码:

最后但是最重要!

最近作者正在构建以反应式Actor模式事件溯源为理论基础的一套服务端开发框架。希望为开发者提供能够便于开发出 “分布式”、“可水平扩展”、“可测试性高” 的应用系统 ——Newbe.Claptrap

本篇文章是该框架的一篇技术选文,属于技术构成的一部分。如果读者对该内容感兴趣,欢迎转发、评论、收藏文章以及项目。您的支持是促进项目成功的关键。

联系方式:

您还可以查阅本系列的其他选文:

理论入门篇

  1. Newbe.Claptrap - 一套以 “事件溯源” 和 “Actor 模式” 作为基本理论的服务端开发框架

术语介绍篇

  1. Actor 模式
  2. 事件溯源(Event Sourcing)
  3. Claptrap
  4. Minion
  5. 事件 (Event)
  6. 状态 (State)
  7. 状态快照 (State Snapshot)
  8. Claptrap 设计图 (Claptrap Design)
  9. Claptrap 工厂 (Claptrap Factory)
  10. Claptrap Identity
  11. Claptrap Box
  12. Claptrap 生命周期(Claptrap Lifetime Scope)
  13. 序列化(Serialization)

实现入门篇

  1. Newbe.Claptrap 框架入门,第一步 —— 创建项目,实现简易购物车
  2. Newbe.Claptrap 框架入门,第二步 —— 简单业务,清空购物车
  3. Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存

样例实践篇

  1. 构建一个简易的火车票售票系统,Newbe.Claptrap 框架用例,第一步 —— 业务分析
  2. 在线体验火车票售票系统

其他番外篇

  1. 谈反应式编程在服务端中的应用,数据库操作优化,从 20 秒到 0.5 秒
  2. 谈反应式编程在服务端中的应用,数据库操作优化,提速 Upsert
  3. 十万同时在线用户,需要多少内存?——Newbe.Claptrap 框架水平扩展实验
  4. docker-mcr 助您全速下载 dotnet 镜像
  5. 十多位全球技术专家,为你献上近十个小时的.Net 微服务介绍
  6. 年轻的樵夫哟,你掉的是这个免费 8 核 4G 公网服务器,还是这个随时可用的 Docker 实验平台?

GitHub 项目地址:https://github.com/newbe36524/Newbe.Claptrap

Gitee 项目地址:https://gitee.com/yks/Newbe.Claptrap

您当前查看的是先行发布于 www.newbe.pro 上的博客文章,实际开发文档随版本而迭代。若要查看最新的开发文档,需要移步 claptrap.newbe.pro

Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存的更多相关文章

  1. Newbe.Claptrap 框架入门,第一步 —— 开发环境准备

    Newbe.Claptrap 框架依托于一些关键性的基础组件和一些可选的辅助组件.本篇我们来介绍一下如何准备一个开发环境. Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架.如 ...

  2. Newbe.Claptrap 框架入门,第一步 —— 创建项目,实现简易购物车

    让我们来实现一个简单的 “电商购物车” 需求来了解一下如何使用 Newbe.Claptrap 进行开发. 业务需求 实现一个简单的 “电商购物车” 需求,这里实现几个简单的业务: 获取当前购物车中的商 ...

  3. 轻松应对并发,Newbe.Claptrap 框架入门,第四步 —— 利用 Minion,商品下单

    接上一篇 Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务.通过本篇阅读,您便可以开 ...

  4. Newbe.Claptrap 框架入门,第二步 —— 简单业务,清空购物车

    接上一篇 Newbe.Claptrap 框架入门,第一步 —— 创建项目,实现简易购物车 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务.通过本篇阅读,您便可以开始尝试使用 ...

  5. Newbe.Claptrap 框架入门,第二步 —— 创建项目

    接上一篇 Newbe.Claptrap 框架入门,第一步 -- 开发环境准备 ,我们继续了解如何创建一个 Newbe.Claptrap 项目. Newbe.Claptrap 是一个用于轻松应对并发问题 ...

  6. C#使用Thrift作为RPC框架入门(三)之三层架构

    前言 这是我们讲解Thrift框架的第三篇文章,前两篇我们讲了Thrift作为RPC框架的基本用法以及架构的设计.为了我们更好的使用和理解Thrift框架,接下来,我们将来学习一下Thrift框架提供 ...

  7. Ubuntu14.04 CUDA8.0 CUDN4.0 NVIDIA1080 多种深度框架(懒人三步装) - 从入门到放弃

    这是一个懒人快速安装教程,1080卡有点麻烦,因为cuda需要8.0.为了安装方便直接把命令写成三个shell脚本. 代码基本是http://blog.csdn.net/langb2014/artic ...

  8. Gemini.Workflow 双子工作流入门教程三:定义流程:流程节点、迁移条件参数配置

    简介: Gemini.Workflow 双子工作流,是一套功能强大,使用简单的工作流,简称双子流,目前配套集成在Aries框架中. 下面介绍本篇教程:定义流程:流程节点.迁移条件参数配置. 一.普通节 ...

  9. springboot2.0入门(三)----定义编程风格+jackjson使用+postMan测试

    一.RESTFul风格API 1.优点: )看Url就知道要什么资源 )看http method就知道针对资源干什么 )看http status code就知道结果如何 HTTP方法体现对资源的操作: ...

随机推荐

  1. 油田问题 bfs

    #include<iostream> #include<stdio.h> #include<stdlib.h> #include<time.h> #in ...

  2. mac下高效安装 homebrew 及完美避坑姿势 (亲测有效)

    世上无难事,只要找到 Homebrew 的正确安装方式. Homebrew 是什么 Homebrew是 mac的包管理器,仅需执行相应的命令,就能下载安装需要的软件包,可以省掉自己去下载.解压.拖拽( ...

  3. PHP show_source() 函数

    实例 对测试文件("test.php")进行 PHP 语法高亮显示: <html><body><?phpshow_source("test. ...

  4. 一本通 1783 矩阵填数 状压dp 容斥 计数

    LINK:矩阵填数 刚看到题目的时候感觉是无从下手的. 可以看到有n<=2的点 两个矩形. 如果只有一个矩形 矩形外的方案数容易计算考虑 矩形内的 必须要存在x这个最大值 且所有值<=x. ...

  5. node日志管理 / pm2-logrotate-ext日志管理

    本篇文章说的是,如何使用pm2管理node项目的日志输出(切割和备份),文章步骤是基于已经安装了pm2的前提下,没有的,请自行百度. 第一步:需要登录公司服务器,查看一下目前服务器保存的所有日志(用于 ...

  6. 通过源码分析Java开源任务调度框架Quartz的主要流程

    通过源码分析Java开源任务调度框架Quartz的主要流程 从使用效果.调用链路跟踪.E-R图.循环调度逻辑几个方面分析Quartz. github项目地址: https://github.com/t ...

  7. .net core下获取自身服务器地址

    网上的例子千篇一律都是Request.HttpContext.Connect.Connection.XX这种 或者依赖于IHttpContextAccessor的 而我的场景是在非控制器流程获取自身服 ...

  8. 【BZOJ4173】数学 题解(数论)

    前言:体验到了推式子的快感orz 题目大意:求$\varphi(n)*\varphi(m)*\sum_{n\ mod\ k+m\ mod\ k\geq k} \varphi(k)\ mod\ 9982 ...

  9. 为写程序而生的连字字体 Fira Code

    Fira Code,等宽的编程连字字体 **等宽 ** 是指所有字符的宽度相同,如: W 和 i 用一样的宽度去显示 连字(ligatures)为文字排印的一个特性,比如「f」和「 i」放在一起的时候 ...

  10. linux下的node版本管理利器:nvm

    nvm是一款node版本管理工具,简单来说,如果你想在一个环境下安装多个node版本,并向自由地切换相关版本,那你就需要使用nvm进行版本管理,有点类似pyenv,也是一款python版本管理工具. ...