17. 【.NET 8 实战--孢子记账--从单体到微服务】--记账模块--主币种设置

server/2024/11/28 10:01:34/

记账模块是我们项目的核心模块,也是用户使用最多的模块,因此这个模块的东西比较多,我们要分为多个部分编写代码。

一、需求

币种设置的需求涉及到了我们前面编写的代码,我们来具体看一下需求。

编号需求说明
1主币种设置1. 用户可修改主币种;2. 在注册新用户成功后,为用户设置主币种为人民币

二、功能编写

在主币种设置这个需求中我们需要增加一个配置表Config ,这个表用来存储用户的配置,就目前来说我们用这个表存储用户设置的主币种。同时我们也需要增加用来操作配置表的Controller ConfigController,这个Controller 中目前只需要两个Action QueryUpdate。最后我们要在SysUserController中新增一个根据用户Id获取用户信息的Action QueryUserInfo,这个Action 返回的不仅包括SysUser表中的信息,还会返回用户的配置信息(到目前为止)。这些类和方法在这里就不列出了,大家按前面所说的自己动手编写代码实现业务功能,然后对比一下我写的代码。
这里我们讲解一下在注册新用户成功后,为用户设置主币种为人民币这个需求怎么实现。看到这个需求估计大部分人会觉得直接在Register Action 中增加设置主币种的代码就可以了,如果你也是这么想的那就错了。用户注册功能和设置主币种的关系不是很大,我们可以在注册时让系统设置主币种,也可以让用户自己手动设置,但是根据需求来看我们只能采用第一种方法,因此我们要需解决这么一个问题:在保持Register Action 单一职责的情况下,实现在注册时自动设置主币种为人民币。我相信已经有一部分读者想到可以使用通知来实现。是的没错,要实现这个功能我们可以使用通知的方式,也就是说当用户注册成功后Register Action会发送一条通知,告诉Config增加一条用户主币种。实现通知的功能我们可以选择的方法很多:基于事件机制共享对象使用消息队列等,在这里我们为了项目后期的扩展性就选用消息队列 来实现通知的功能,并且选目前在开发领域用的最多的MQ软件**RabbitMQ **。下面我们就一起来看一下如何实现需求吧。

2.1 安装 RabbitMQ

由于现在还处于开发阶段,因此可以把RabbitMQ安装到本地。接下来我们一起将RabbitMQ安装到Docker中吧。打开命令行工具,输入如下命令就可以吧RabbitMQ安装到我们本地了:

docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq

Tip:对于Docker不熟悉的同学,请先去Docker官网或者前往我的专栏Docker极简教程学习

2.2 实现需求

RabbitMQ 安装完了,我们现在就来开发需求。

  1. 在项目中安装RabbitMQ官方的.NET包RabbitMQ.Client

    dotnet add package RabbitMQ.Client
    
  2. 在项目中新建一个文件夹MQ,在这个文件夹下创建RabbitMQConnection类,这个类是用来初始化连接,以及释放链接资源,代码如下:

    using RabbitMQ.Client;
    using SporeAccounting.MQ.Model;namespace SporeAccounting.MQ;/// <summary>
    /// RabbitMQ连接类
    /// </summary>
    public class RabbitMQConnection : IDisposable
    {/// <summary>/// 连接/// </summary>private readonly IConnection _connection;/// <summary>/// 构造函数/// </summary>/// <param name="options"></param>public RabbitMQConnection(RabbitMQOptions options){var factory = new ConnectionFactory{HostName = options.HostName,Port = options.Port,VirtualHost = options.VirtualHost,UserName = options.UserName,Password = options.Password};_connection = factory.CreateConnectionAsync().Result;}/// <summary>/// 创建通道/// </summary>/// <returns></returns>public async Task<IChannel> CreateChannel(){return await _connection.CreateChannelAsync();}/// <summary>/// 释放资源/// </summary>public void Dispose() => _connection.Dispose();
    }
    

    这段代码实现了一个 RabbitMQConnection 类,用于管理与 RabbitMQ 的连接和通道创建。它接收一个包含 RabbitMQ 配置的 RabbitMQOptions 对象作为参数,通过 ConnectionFactory 初始化连接。配置包括主机名、端口、虚拟主机、用户名和密码等信息。在构造函数中,利用 CreateConnectionAsync().Result 创建同步连接实例 _connection。此外,该类提供了 CreateChannel 方法,通过调用 _connection.CreateChannelAsync() 异步生成消息通道,方便与 RabbitMQ 进行通信。为了防止资源泄漏,RabbitMQConnection 实现了 IDisposable 接口,在调用 Dispose 方法时释放 _connection 占用的资源。这个类封装了连接和通道管理的逻辑,适合在需要频繁访问 RabbitMQ 的场景中使用,提高代码的可维护性和复用性。

  3. 然后我们新建发布类RabbitMQPublisher代码如下:

    using RabbitMQ.Client;namespace SporeAccounting.MQ;/// <summary>
    /// RabbitMQ发布者类
    /// </summary>
    public class RabbitMQPublisher
    {/// <summary>/// RabbitMQ连接/// </summary>private readonly RabbitMQConnection _connection;/// <summary>/// 构造函数/// </summary>/// <param name="connection"></param>public RabbitMQPublisher(RabbitMQConnection connection){_connection = connection;}/// <summary>/// 发布消息/// </summary>/// <param name="queue"></param>/// <param name="routingKey"></param>/// <param name="message"></param>public async System.Threading.Tasks.Task Publish(string queue, string routingKey, string message){await using var channel = await _connection.CreateChannel();await channel.QueueDeclareAsync(queue, durable: true);var body = System.Text.Encoding.UTF8.GetBytes(message);await channel.BasicPublishAsync(exchange: string.Empty, routingKey: routingKey, body: body);}
    }
    

    这段代码实现了一个 RabbitMQPublisher 类,用于向 RabbitMQ 消息队列发布消息。类中通过依赖注入的方式,接受一个 RabbitMQConnection 对象用于管理与 RabbitMQ 的连接。其核心功能集中在 Publish 方法中,该方法负责将消息发送到指定的队列。调用时,方法首先通过 _connection.CreateChannel 异步创建一个消息通道,并确保通道在使用完毕后释放资源。接着调用 QueueDeclareAsync 方法声明队列,确保目标队列存在,并将队列设置为持久化。然后,将传入的消息字符串转换为 UTF-8 字节数组以符合 RabbitMQ 的消息格式要求。最终,通过 BasicPublishAsync 方法发送消息到指定的路由键和队列。此类有效地封装了消息发布的逻辑,提供了简洁的接口来进行队列操作和消息发送,适合构建发布订阅模式的生产者端代码。通过异步编程模式,它可以在处理大量并发消息时保持高性能和资源利用率。

  4. 接着我们编写订阅类RabbitMQSubscriber,代码如下:

    using System.Text;
    using RabbitMQ.Client;
    using RabbitMQ.Client.Events;namespace SporeAccounting.MQ;/// <summary>
    /// RabbitMQ订阅者
    /// </summary>
    public class RabbitMQSubscriber
    {/// <summary>/// RabbitMQ连接/// </summary>private readonly RabbitMQConnection _connection;/// <summary>/// 构造函数/// </summary>/// <param name="connection"></param>public RabbitMQSubscriber(RabbitMQConnection connection){_connection = connection;}/// <summary>/// 订阅/// </summary>/// <param name="queue"></param>/// <param name="routingKey"></param>/// <param name="onMessage"></param>/// <returns></returns>public async System.Threading.Tasks.Task Subscribe(string queue, string routingKey,Action<string> onMessage){await using var channel = await _connection.CreateChannel();await channel.QueueDeclareAsync(queue, durable: false, exclusive: false, autoDelete: false,arguments: null);var consumer = new AsyncEventingBasicConsumer(channel);consumer.ReceivedAsync += (sender, @event) =>{var body = @event.Body.ToArray();var message = Encoding.UTF8.GetString(body);onMessage(message);return System.Threading.Tasks.Task.CompletedTask;};await channel.BasicConsumeAsync(queue, autoAck: true, consumer: consumer);}
    }
    

    这段代码定义了一个名为 RabbitMQSubscriber 的类,用于实现 RabbitMQ 的订阅功能。类中包含一个 _connection 字段,表示与 RabbitMQ 的连接,通过依赖注入的方式传入 RabbitMQConnection 对象并在构造函数中初始化。核心方法是 Subscribe,用于订阅指定队列的消息。调用时需要传入队列名称、路由键以及一个处理消息的回调函数 onMessage。在方法内部,先通过 _connection 创建一个通道,然后声明队列以确保其存在。随后,实例化 AsyncEventingBasicConsumer 作为消息消费者,并在其 ReceivedAsync 事件中注册逻辑:每当接收到消息时,将消息体从字节数组解码为字符串,并通过 onMessage 回调执行自定义处理逻辑。最后,通过 BasicConsumeAsync 启动对队列的监听,启用消息自动确认模式,完成订阅过程。这种设计使得消息处理逻辑可动态配置,适用于异步场景。

  5. 我们将前面编写的三个类注入到项目中,代码如下:

    builder.Services.AddSingleton(new RabbitMQOptions
    {HostName = configurationManager["RabbitMQ:Host"],Port = int.Parse(configurationManager["RabbitMQ:Port"]),UserName = configurationManager["RabbitMQ:UserName"],Password = configurationManager["RabbitMQ:Password"],VirtualHost = configurationManager["RabbitMQ:VirtualHost"],
    });
    builder.Services.AddSingleton<RabbitMQConnection>();
    builder.Services.AddSingleton<RabbitMQPublisher>();
    builder.Services.AddSingleton<RabbitMQSubscriber>();
    

    这段代码通过依赖注入将 RabbitMQOptions 配置、RabbitMQConnection、发布器和订阅器类注册为单例服务,供应用全局使用,简化了 RabbitMQ 的连接与消息处理管理。

  6. 接下来我们在Register Action 中添加发布设置主币种消息的代码,更新后的代码如下:

    /// <summary>
    /// 注册
    /// </summary>
    /// <param name="sysUserViewModel"></param>
    /// <returns></returns>
    [HttpPost]
    [Route("Register")]
    public ActionResult<ResponseData<bool>> Register(SysUserViewModel sysUserViewModel)
    {try{var role = _sysRoleServer.QueryByName("Consumer");SysUser sysUser = _mapper.Map<SysUser>(sysUserViewModel);sysUser.Salt = Guid.NewGuid().ToString("N");sysUser.Password = HashPasswordWithSalt(sysUser.Password, sysUser.Salt);sysUser.CreateUserId = sysUser.Id;sysUser.CreateDateTime = DateTime.Now;sysUser.RoleId = role.Id;_sysUserServer.Add(sysUser);//发布设置主币种消息_ = _rabbitMqPublisher.Publish("SetMainCurrency", "SetMainCurrency", sysUser.Id);return Ok(new ResponseData<bool>(HttpStatusCode.OK, "", false));}catch (Exception ex){return Ok(new ResponseData<bool>(HttpStatusCode.InternalServerError, "服务端异常", false));}
    }
    
  7. 然后,我们还要在MQ文件夹下新建Message文件夹并编写接收主币种消息的类SetMainCurrency,代码如下:

    using SporeAccounting.Models;
    using SporeAccounting.Server.Interface;namespace SporeAccounting.MQ.Message;/// <summary>
    /// 设置主货币
    /// </summary>
    public static class SetMainCurrency
    {/// <summary>/// 开始监听/// </summary>public static void Start(IServiceProvider serviceProvider){var subscriber = serviceProvider.GetRequiredService<RabbitMQSubscriber>();_ = subscriber.Subscribe("SetMainCurrency", "SetMainCurrency", async (userId) =>{var accountBookServer = serviceProvider.GetRequiredService<IConfigServer>();accountBookServer.Add(new Config(){Id = Guid.NewGuid().ToString(),UserId = userId,Value = "CNY",ConfigTypeEnum = ConfigTypeEnum.Currency,CreateDateTime = DateTime.Now,CreateUserId = userId});});}
    }
    

    这段代码定义了一个 SetMainCurrency 静态类,负责监听 RabbitMQ 消息并处理设置主货币的操作。在 Start 方法中,它通过依赖注入获取了一个 RabbitMQSubscriber 实例,并调用 Subscribe 方法监听 SetMainCurrency 队列。监听到消息后,异步回调函数会执行,该回调通过 serviceProvider 获取 IConfigServer 服务。随后,它创建一个新的 Config 对象,设置其为主货币配置(“CNY”),并将其存储在 accountBookServer 中。该配置对象包含了用户 ID、配置类型、创建时间等信息。此设计实现了一个简单的消息处理机制,能够在接收到特定消息时更新系统配置。

  8. 最后我们在Programe类中设置在项目启动时开启监听

    //开启监听主币种
    SetMainCurrency.Start(app.Services);
    

三、总结

这篇文章我们实现了在注册时设置主币种的功能。我们之所以使用消息队列实现是因为注册功能和设置主币种属于两个业务,因此我们需要将它们分离出来,同时即使设置主币种操作失败了也不影响注册功能。
这里只列出了核心的类和方法,其他的方法以及和Config相关的操作请大家自己动手来实现一下,完成后和我的代码对比一下看看有什么不同。


http://www.ppmy.cn/server/145594.html

相关文章

git源码安装

源码安装。执行以下步骤&#xff1a; 使用"wget"命令下载Git源码包&#xff0c;例如&#xff1a;"wget https://www.kernel.org/pub/software/scm/git/git-2.0.5.tar.gz"。 使用"tar"命令解压缩下载的源码包&#xff0c;例如&#xff1a;"…

C语言编码规范

以下是一套较为完整的C语言编码规范&#xff1a; 一、代码布局 缩进 使用空格进行缩进&#xff0c;每级缩进为4个空格。这样可以保证代码在不同的编辑器和环境中都有一致的缩进效果。例如&#xff1a; if (condition) {// 这里缩进了4个空格statement1;statement2; }大括号 函…

快速理解微服务中Fegin的概念

一.由来 1.在传统的架构里面&#xff0c;我们是通过使用RestTemplate来访问其他的服务&#xff0c;但是这种方式就存在了一个很大的缺陷&#xff0c;也就是被调用方如果发生了服务的迁移(IP和端口发生了变化)&#xff0c;那么调用方也需要同步的在代码里面进行修改&#xff0c;…

大数据新视界 -- Hive 与其他大数据工具的集成:协同作战的优势(上)(13/ 30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

PHP实现插入排序

插入排序&#xff08;Insertion Sort&#xff09;是一种简单直观的排序算法&#xff0c;适用于少量数据的排序。它的工作原理是通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插入。以下是一个用PHP实现插入排序…

根据后台数据结构,构建搜索目录树

效果图&#xff1a; 数据源 const data [{"categoryidf": "761525000288210944","categoryids": "766314364226637824","menunamef": "经济运行","menunames": "经济运行总览","tempn…

[java] 什么是 Apache Felix

概述 Apache Felix是一个开源的、符合OSGi&#xff08;Open Service Gateway Initiative&#xff09;R4规范的实现框架。OSGi是一个用于Java动态模块系统的一系列规范&#xff0c;而Apache Felix则是对这些规范的具体实现&#xff0c;它提供了一个轻量级的、高效的平台&#xf…

Linux操作系统2-进程控制3(进程替换,exec相关函数和系统调用)

上篇文章&#xff1a;Linux操作系统2-进程控制2(进程等待&#xff0c;waitpid系统调用&#xff0c;阻塞与非阻塞等待)-CSDN博客 本篇代码Gitee仓库&#xff1a;Linux操作系统-进程的程序替换学习 d0f7bb4 橘子真甜/linux学习 - Gitee.com 本篇重点&#xff1a;进程替换 目录 …