16. 【.NET 8 实战--孢子记账--从单体到微服务】--汇率获取定时器

news/2024/11/27 4:55:50/

这篇文章我们将一起编写这个系列专栏中第一个和外部系统交互的功能:获取每日汇率。下面我们一起来编写代码吧。

一、需求

根据文章标题可知,在这片文章中我们只进行汇率的获取和写入数据库。

编号需求说明
1获取每日汇率1. 从第三方汇率API中获取汇率信息并存入数据库中;2. 每天凌晨1点获取汇率;3. 目前仅支持人民币、日元、欧元、韩元、美元、港币、澳门元、英镑、新台币之间的汇率

二、功能编写

网上有很多获取汇率的API,有收费的也有免费的,由于我们开发的项目只是用于实战练习,而不会真正的发布出去让别人使用,因此我们选择免费的API。但是免费的API有好有坏,有的API只提供有限的免费请求额度,有的API只能免费使用很短的几天。所以即使是使用免费的API我们也要谨慎选择,以免耽误我们的练习。
但是,大家不用担心,我已经为大家选好了一个不错的获取汇率的API:Exchange Rate API 。它对免费用户提供每月1500次的接口调用,并且可以一直使用免费的接口,对于我们的项目来说已经足够了。

2.1 编写数据库映射类

我们的需求是获取每天的汇率,因此我们的数据库映射类应该包含汇率日期字段Data。并且我们还需要保存币种之间的汇率,因此还需要币种转换关系字段ConvertCurrency,以及币种汇率字段ExchangeRate
以下的代码就是根据前面分析所编写的数据库映射类ExchangeRateRecord,再次强调的是ExchangeRateRecord 编写完后别忘了迁移数据库:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using SporeAccounting.BaseModels;namespace SporeAccounting.Models;/// <summary>
/// 汇率记录表
/// </summary>
[Table(name: "ExchangeRate")]
public class ExchangeRateRecord : BaseModel
{/// <summary>/// 汇率/// </summary>[Column(TypeName = "decimal(10,2)")][Required]public decimal ExchangeRate { get; set; }/// <summary>/// 币种转换/// </summary>[Column(TypeName = "nvarchar(20)")][Required]public string ConvertCurrency { get; set; }/// <summary>/// 汇率日期/// </summary>[Column(TypeName = "date")][Required]public DateTime Date { get; set; }
}
2.2 编写定时器

需求上说需要在每天凌晨1点获取当天的汇率,因此需要编写一个定时器来定时执行获取汇率的工作。我们可以选择的定时器很多,有.NET 原生的也有第三方的。在这里我们选择 Quartz.NET,Quartz.NET 是 Quartz 的.NET版,它在.NET领域中使用的最多。下面我们使用Quartz.NET一起来编写定时器。

  1. 安装 Quartz.NET
    我们不直接使用Quartz.NET,而是使用Quartz.NET的ASP.NET Core集成版。在Nuget中搜索Quartz.AspNetCore,选择支持.NET8的最新版,点击安装即可。
  2. 编写Job
    在Quartz中,一个定时器就是一个Job。新建Job类ExchangeRateTimer,它继承Quartz的IJob接口,并实现Execute方法。在Execute方法中我们要实现向Exchange Rate API获取汇率的接口发送请求并获取汇率信息的功能,以及将汇率信息存入数据库的功能。这里不多说直接上代码:
    using System.Text.Json;
    using Quartz;
    using SporeAccounting.Models;
    using SporeAccounting.Server.Interface;
    using SporeAccounting.Task.Timer.Model;namespace SporeAccounting.Task.Timer;/// <summary>
    /// 获取汇率定时器
    /// </summary>
    public class ExchangeRateTimer : IJob
    {private readonly IHttpClientFactory _httpClientFactory;private readonly IConfiguration _configuration;private readonly IServiceScopeFactory _serviceScopeFactory;private readonly ICurrencyService _currencyService;public ExchangeRateTimer(IHttpClientFactory httpClientFactory,IConfiguration configuration, IServiceScopeFactory serviceScopeFactory,ICurrencyService currencyService){_httpClientFactory = httpClientFactory;_configuration = configuration;_serviceScopeFactory = serviceScopeFactory;_currencyService = currencyService;}public System.Threading.Tasks.Task Execute(IJobExecutionContext context){string exchangeRateUrl = _configuration["ExchangeRate"];//获取全部币种List<Currency> currencies = _currencyService.Query().ToList();//获取对每种币种的汇率foreach (var currency in currencies){_httpClientFactory.CreateClient().GetAsync($"{exchangeRateUrl}{currency.Abbreviation}").ContinueWith(response =>{using var scope = _serviceScopeFactory.CreateScope();var exchangeRateRecordService =scope.ServiceProvider.GetRequiredService<IExchangeRateRecordService>();List<ExchangeRateRecord> exchangeRateRecords = new();if (response.Result.IsSuccessStatusCode){var result = response.Result.Content.ReadAsStringAsync().Result;var resultModel = JsonSerializer.Deserialize<ExchangeRateApiData>(result);if (resultModel?.Result == "success"){foreach (var rate in resultModel.ConversionRates){//只获取人民币、日元、欧元、韩元、美元、港币、澳门元、英镑、新台币之间的汇率//其他币种的汇率直接跳过if (currencies.All(c => c.Abbreviation != rate.Key)){continue;}exchangeRateRecords.Add(new ExchangeRateRecord{Id = Guid.NewGuid().ToString(),ExchangeRate = rate.Value,//汇率记录的币种代码是基础币种代码和目标币种代码的组合ConvertCurrency = $"{resultModel.BaseCode}_{rate.Key}",Date = DateTime.Now,CreateDateTime = DateTime.Now,CreateUserId = "System",IsDeleted = false});}//存入数据库exchangeRateRecordService.Add(exchangeRateRecords);}}});}return System.Threading.Tasks.Task.CompletedTask;}
    }
    
    这段代码看着很复杂,其实是完全按照前面的需求实现的功能,因此这里就不多讲解的。唯一需要注意的是的我们在ContinueWith方法的回调函数中使用了如下代码来获取IExchangeRateRecordService的实例 :
    using var scope = _serviceScopeFactory.CreateScope();
    var exchangeRateRecordService = scope.ServiceProvider.GetRequiredService<IExchangeRateRecordService>	
    
    为什么要这么做呢,为什么不在构造函数中通过注入的方式获取呢?这是因为我们使用异步的方式来获取汇率数据的,因此ContinueWith内的方法是在另一个线程上运行的,如果通过注入的方式在构造函数中获取IExchangeRateRecordService实例的话,在Execute方法执行完毕后实例就被释放回收了,因此这时如果在ContinueWith内的方法中使用IExchangeRateRecordService的实例就会出发链接已被关闭的异常。
2.3 配置定时器

Job的逻辑已经编写完了,那么最后要做的就是配置定时器,让它在凌晨一点是定时获取汇率信息。在Program类中加入如下代码:

 // 添加定时任务
builder.Services.AddQuartz(q =>
{var exchangeRateTimerJobKey=new JobKey("ExchangeRateTimer");q.AddJob<ExchangeRateTimer>(opts=>opts.WithIdentity(exchangeRateTimerJobKey));q.AddTrigger(opts=>opts.ForJob(exchangeRateTimerJobKey).WithIdentity("ExchangeRateTimerTrigger").StartNow().WithCronSchedule("0 0 1 * * ?"));
});
builder.Services.AddQuartzHostedService(options =>
{//启用 Quartz 的托管服务,`WaitForJobsToComplete = true` 表示在应用程序停止时等待任务完成后再关闭。options.WaitForJobsToComplete = true;
});

这段代码通过 AddQuartz 方法注册了 Quartz 服务容器,在配置定时Job和触发器时,我们先创建了一个唯一标识Job的Job Key exchangeRateTimerJobKey,用于区分不同Job,并通过AddJob方法注册了刚才我们编写的Job类 ExchangeRateTimer,并将其与 exchangeRateTimerJobKey 绑定。接着通过AddTrigger方法创建一个触发器,并将触发器绑定到指定Job上,然后使用StartNow方法将Job注册为立即开始,最后使用WithCronSchedule方法通过Cron 表达式设置每天凌晨 1 点触发Job。

Tip:这篇文章我们只展示出了核心的类、方法以及配置,还有一个方法、接口以及接口的实现类没有展示。一方面因为这些代码和前面文章中的代码类似,另一方面就是我一直再强调的我希望大家能自己动手写写代码。

三、总结

我们一起编写了获取每日汇率的定时器,掌握了 Quartz.NET 的使用,我们的项目也距离完成越来越近了。


http://www.ppmy.cn/news/1550246.html

相关文章

小程序-基于java+SpringBoot+Vue的美食推荐系统设计与实现

项目运行 1.运行环境&#xff1a;最好是java jdk 1.8&#xff0c;我们在这个平台上运行的。其他版本理论上也可以。 2.IDE环境&#xff1a;IDEA&#xff0c;Eclipse,Myeclipse都可以。推荐IDEA; 3.tomcat环境&#xff1a;Tomcat 7.x,8.x,9.x版本均可 4.硬件环境&#xff1a…

PyQt5安装使用教程

目录 一、工具二、安装PyQt5三、pycharm配置3.1 配置QtDesigner3.2 PyUIC配置3.3 Pyrcc配置3.4 配置成功效果图 四、使用方法4.2 QTDesigner使用方法4.2 PyUIC使用方法 调用自定义布局py文件 一、工具 1、python 2、pycharm 3、PyQt5模块 二、安装PyQt5 1、安装pyqt5 pip i…

pytorch3d linux安装

目录 测试成功2024.11.21&#xff1a; 测试成功2024.11.21&#xff1a; python3.10 GitHub - facebookresearch/pytorch3d: PyTorch3D is FAIRs library of reusable components for deep learning with 3D data 安装脚本&#xff1a; cd pytorch3d && pip install…

论文阅读:A Software Platform for Manipulating theCamera Imaging Pipeline

论文代码开源链接&#xff1a; A Software Platform for Manipulating the Camera Imaging Pipelinehttps://karaimer.github.io/camera-pipeline/摘要&#xff1a;论文提出了一个Pipline软件平台&#xff0c;可以方便地访问相机成像Pipline的每个阶段。该软件允许修改单个模块…

C# .net core web 程序远程调试

如果有两个同一个web的程序池&#xff0c;附加进程的时候就有两个选择。

vue3 reactive响应式实现源码

Vue 3 的 reactive 是基于 JavaScript 的 Proxy 实现的&#xff0c;因此它通过代理机制来拦截对象的操作&#xff0c;从而实现响应式数据的追踪。下面是 Vue 3 的 reactive 源码简化版。 Vue 3 reactive 源码简化版 首先&#xff0c;我们需要了解 reactive 是如何工作的&…

面试干货:软件测试常见面试题(附答案)

1、文档测试主要包含什么内容? 参考答案&#xff1a; 在国内软件开发管理中&#xff0c;文档管理几乎是最弱的一项&#xff0c;因而在测试工作中特别容易忽略文档测试也就不足为奇了。要想给用户提供完整的产品&#xff0c;文档测试是必不可少的。文档测试一般注重下面几个方…

css:感觉稍微高级一点的布局

精灵图 有时候我们下载网页里的小元素图片的时候&#xff0c;就会一下子下载一大张&#xff0c;这就是精灵图&#xff0c;也叫雪碧图&#xff08;sprites&#xff09; 一个网页由很多图像作为修饰&#xff0c;当网页中图像过多时&#xff0c;服务器会频繁地解释和发送氢气图片…