仅有币种服务还不够,记账应用还需支持不同币种间的转换。要实现这一功能,首先需要获取币种之间的汇率。因此,本文将介绍如何实现汇率的同步。
一、汇率数据从何而来?
汇率数据无时无刻都在变动,因此需要一个可靠的来源来获取最新的汇率信息。通常可以通过以下几种方式获取:
- 爬取数据:一些专业的金融数据服务商提供汇率数据,可以通过订阅获取。
- 手动输入:对于小型应用,可以手动输入汇率数据,但这不适合大规模或实时更新的应用。
- 第三方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类文件,代码如下:
csharp
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类文件,这个类将作为获取汇率数据的定时任务类,代码如下:
csharp
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文件中,添加以下代码:
csharp
// 添加定时任务
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获取汇率数据的功能,并将其存储到数据库中。这个功能为币种服务提供了强大的支持,使得记账应用能够处理不同币种之间的转换和汇率计算。