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类文件,代码如下:

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定时任务。它的构造函数接受了IHttpClientFactoryIConfigurationIServiceScopeFactory三个参数,分别用于创建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获取汇率数据的功能,并将其存储到数据库中。这个功能为币种服务提供了强大的支持,使得记账应用能够处理不同币种之间的转换和汇率计算。

相关推荐
ZC1111K5 小时前
maven(配置)
java·maven
慕y2746 小时前
Java学习第五十八部分——设计模式
java·学习·设计模式
躲在云朵里`6 小时前
SpringBoot的介绍和项目搭建
java·spring boot·后端
喵个咪6 小时前
Golang微服框架Kratos与它的小伙伴系列 - 分布式事务框架 - DTM
后端·微服务·go
菜还不练就废了6 小时前
7.19-7.20 Java基础 | File类 I/O流学习笔记
java·笔记·学习
Yweir6 小时前
Elastic Search 8.x 分片和常见性能优化
java·python·elasticsearch
设计师小聂!6 小时前
尚庭公寓--------登陆流程介绍以及功能代码
java·spring boot·maven·mybatis·idea
心平愈三千疾7 小时前
学习秒杀系统-页面优化技术
java·学习·面试
程序员JerrySUN8 小时前
Valgrind Memcheck 全解析教程:6个程序说明基础内存错误
android·java·linux·运维·开发语言·学习
一只IT攻城狮8 小时前
构建一个简单的Java框架来测量并发执行任务的时间
java·算法·多线程·并发编程