28.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--币种服务(二)
仅有币种服务还不够,记账应用还需支持不同币种间的转换。要实现这一功能,首先需要获取币种之间的汇率。因此,本文将介绍如何实现汇率的同步。
一、汇率数据从何而来?
汇率数据无时无刻都在变动,因此需要一个可靠的来源来获取最新的汇率信息。通常可以通过以下几种方式获取:
- 爬取数据:一些专业的金融数据服务商提供汇率数据,可以通过订阅获取。
- 手动输入:对于小型应用,可以手动输入汇率数据,但这不适合大规模或实时更新的应用。
- 第三方API:许多金融服务提供商提供免费的或付费的API接口,可以获取实时汇率数据。
下面,我们对这三种方式做一个简单的对比,选择出最适合的获取汇率的方式。首先,爬取数据,需要编写爬虫程序,而且还要无时无刻的关注数据源的变动,比如:页面的变化,数据格式的变化等,这样的工作量是非常大的。而且,编写爬虫获取数据具有一定的法律风险,除非在得到数据源许可的情况,方可爬取。其次,手动输入汇率数据虽然简单,但不适合需要频繁更新的应用,应用管理员每天都要录入汇率数据,这样的工作量也是非常大的。最后,第三方API,通过API获取汇率数据是最简单、最可靠的方式。只需要调用API接口,就可以获取最新的汇率数据,而且大多数API提供商会定期更新数据,确保数据的准确性和实时性。因此,我们的项目将采用第三方API的方式来获取汇率数据。
在选择第三方API时,要考虑一下几点:
- 数据的准确性:确保API提供的数据是可靠的,最好是由知名金融机构或服务商提供。
- 更新频率:汇率数据需要实时更新,选择一个更新频率高的API。
- 使用成本:有些API是免费的,但有些可能需要付费,需要根据项目的预算来选择。
- 易用性:API的文档和使用方式是否清晰,是否容易集成到现有项目中。
在本文中,我们将使用 exchangerate api 提供的汇率API来获取汇率数据。这个API提供了多种币种之间的汇率转换功能,它有免费版、专业版和商业版三个版本,免费版汇率更新频率是一天更新一次,每月1500次的调用请求,专业版是每小时更新一次,每月3000次的调用请求,商业版是每五分钟更新一次,每月12500次的调用请求。对于我们的项目来说,免费版已经满足我们使用了,因此我们将使用免费版的API。
Tip:具体注册和使用方式这里就不讲解了,可以参考 exchangerate api 的官方文档。
二、汇率数据获取
在币种服务SP.CurrencyService中,新建Task文件夹,这个文件夹将作为币种服务的定时任务类的文件夹,然后在这个文件夹下新建ExchangeRate文件夹作为存储汇率数据获取的定时任务类的文件夹。接着在ExchangeRate文件夹下新建ExchangeRateApiData.cs类文件,代码如下:
using System.Text.Json.Serialization;namespace SP.CurrencyService.Task.ExchangeRate;// <summary>
// 存储从汇率API获取的数据
// </summary>
public class ExchangeRateApiData
{/// <summary>/// 数据状态/// </summary>[JsonPropertyName("result")]public string Result { get; set; }/// <summary>/// 数据文档/// </summary>[JsonPropertyName("documentation")]public string Documentation { get; set; }/// <summary>/// 数据条款/// </summary>[JsonPropertyName("terms_of_use")]public string TermsOfUse { get; set; }/// <summary>/// 上次更新数据时间戳/// </summary>[JsonPropertyName("time_last_update_unix")]public long TimeLastUpdateUnix { get; set; }/// <summary>/// 上次更新数据时间/// </summary>[JsonPropertyName("time_last_update_utc")]public string TimeLastUpdateUtc { get; set; }/// <summary>/// 下次更新数据时间戳/// </summary>[JsonPropertyName("time_next_update_unix")]public long TimeNextUpdateUnix { get; set; }/// <summary>/// 下次更新数据时间/// </summary>[JsonPropertyName("time_next_update_utc")]public string TimeNextUpdateUtc { get; set; }/// <summary>/// 基础货币代码/// </summary>[JsonPropertyName("base_code")]public string BaseCode { get; set; }/// <summary>/// 汇率集合/// </summary>[JsonPropertyName("conversion_rates")]public Dictionary<string, decimal> ConversionRates { get; set; }
}
该类中包含了从exchangerate api获取的汇率数据的各个字段,使用了JsonPropertyName
特性来指定JSON序列化时的字段名称。
接着,新建ExchangeRateTimer.cs类文件,这个类将作为获取汇率数据的定时任务类,代码如下:
using System.Text.Json;
using Quartz;
using SP.CurrencyService.Models.Entity;
using SP.CurrencyService.Models.Response;
using SP.CurrencyService.Service;namespace SP.CurrencyService.Task.ExchangeRate;/// <summary>
/// 获取汇率定时器
/// </summary>
public class ExchangeRateTimer : IJob
{private readonly IHttpClientFactory _httpClientFactory;private readonly IConfiguration _configuration;private readonly IServiceScopeFactory _serviceScopeFactory;private readonly ICurrencyServer _currencyServer;/// <summary>/// 构造函数/// </summary>/// <param name="httpClientFactory"></param>/// <param name="configuration"></param>/// <param name="serviceScopeFactory"></param>/// <param name="currencyServer"></param>public ExchangeRateTimer(IHttpClientFactory httpClientFactory,IConfiguration configuration, IServiceScopeFactory serviceScopeFactory,ICurrencyServer currencyServer){_httpClientFactory = httpClientFactory;_configuration = configuration;_serviceScopeFactory = serviceScopeFactory;_currencyServer = currencyServer;}/// <summary>/// 执行/// </summary>/// <param name="context"></param>/// <returns></returns>public System.Threading.Tasks.Task Execute(IJobExecutionContext context){string exchangeRateUrl = _configuration["ExchangeRate"];//获取全部币种List<CurrencyResponse> currencies = _currencyServer.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<IExchangeRateRecordServer>();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{ExchangeRate = rate.Value,//汇率记录的币种代码是基础币种代码和目标币种代码的组合ConvertCurrency = $"{resultModel.BaseCode}_{rate.Key}",SourceCurrencyId = currency.Id,TargetCurrencyId = currencies.First(c => c.Abbreviation == rate.Key).Id,Date = DateTime.Now,CreateDateTime = DateTime.Now,CreateUserId = 7333155174099406848,IsDeleted = false});}//存入数据库exchangeRateRecordService.Add(exchangeRateRecords);}}});}return System.Threading.Tasks.Task.CompletedTask;}
}
该类实现了IJob
接口,表示这是一个Quartz定时任务。它的构造函数接受了IHttpClientFactory
、IConfiguration
和IServiceScopeFactory
三个参数,分别用于创建HTTP客户端、获取配置和创建服务作用域。Execute
方法是定时任务的执行入口,首先从配置中获取汇率API的URL,然后查询所有币种信息。接着,对于每个币种,通过HTTP客户端调用汇率API获取该币种的汇率数据,并将结果存储到数据库中。
Tip:这里使用了异步的方式来处理HTTP请求,以避免阻塞定时任务的执行。每次获取汇率数据后,都会创建一个新的服务作用域,以确保在多线程环境下能够正确地访问数据库。
最后,我们需要将这个定时任务注册到Quartz中。在Program.cs文件中,添加以下代码:
// 添加定时任务
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;
});
这段代码将ExchangeRateTimer
定时任务注册到Quartz中,并设置为每天凌晨1点执行一次。AddQuartzHostedService
方法用于启用Quartz的托管服务,确保在应用程序停止时等待任务完成后再关闭。
三、总结
通过以上步骤,我们成功实现了从第三方API获取汇率数据的功能,并将其存储到数据库中。这个功能为币种服务提供了强大的支持,使得记账应用能够处理不同币种之间的转换和汇率计算。