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

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

一、需求

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

编号需求说明
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相关的操作请大家自己动手来实现一下,完成后和我的代码对比一下看看有什么不同。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/921955.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HDR视频技术之四:HDR 主要标准

HDR 是 UHD 技术中最重要维度之一&#xff0c;带来新的视觉呈现体验。 HDR 技术涉及到采集、加工、传输、呈现等视频流程上的多个环节&#xff0c;需要定义出互联互通的产业标准&#xff0c;以支持规模化应用和部署。本文整理当前 HDR 应用中的一些代表性的国际标准。 1 HDR 发…

Ubuntu中使用多版本的GCC

我的系统中已经安装了GCC11.4&#xff0c;在安装cuda时出现以下错误提示&#xff1a; 意思是当前的GCC版本过高&#xff0c;要在保留GCC11.4的同时安装GCC9并可以切换&#xff0c;可以通过以下步骤实现&#xff1a; 步骤 1: 安装 GCC 9 sudo apt-get update sudo apt-get ins…

dubbo-go框架介绍

框架介绍 什么是 dubbo-go Dubbo-go 是 Apache Dubbo 的 go 语言实现&#xff0c;它完全遵循 Apache Dubbo 设计原则与目标&#xff0c;是 go 语言领域的一款优秀微服务开发框架。dubbo-go 提供&#xff1a; API 与 RPC 协议&#xff1a;帮助解决组件之间的 RPC 通信问题&am…

DataGear 5.2.0 发布,数据可视化分析平台

DataGear 企业版 1.3.0 已发布&#xff0c;欢迎体验&#xff01; http://datagear.tech/pro/ DataGear 5.2.0 发布&#xff0c;图表插件支持定义依赖库、严重 BUG 修复、功能改进、安全增强&#xff0c;具体更新内容如下&#xff1a; 重构&#xff1a;各模块管理功能访问路径…

2023年3月GESPC++一级真题解析

一、单选题&#xff08;每题2分&#xff0c;共30分&#xff09; 题目123456789101112131415答案BAACBDDAADBCDBC 1.以下不属于计算机输入设备的有&#xff08; &#xff09;。 A &#xff0e;键盘 B &#xff0e;音箱 C &#xff0e;鼠标 D &#xff0e;传感器 【答案】 …

RabbitMQ2:介绍、安装、快速入门、数据隔离

欢迎来到“雪碧聊技术”CSDN博客&#xff01; 在这里&#xff0c;您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者&#xff0c;还是具有一定经验的开发者&#xff0c;相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导&#xff0c;我将…

vue2.0 luoyi框架 代码漏洞检查问题

检查出在element ui存在漏洞 经过在elemen-ui.common.js文件中查找没发现eval函数 后发现是打包之后生成的产物 解决方法 在vue.config.js文件中进行打包配置 configureWebpack: {devtool: source-map, // 禁用 eval&#xff0c;使用 source-map 进行源码映射},

管家婆财贸ERP BR035.回款利润明细表

最低适用版本: 财贸系列 23.5 插件简要功能说明: 报表统计销售单/销售退货单/销售发票回款情况更多细节描述见下方详细文档插件操作视频: 进销存类定制插件--回款利润明细表 插件详细功能文档: 1. 应用中心增加报表【回款利润明细表】 a. b. 查询条件: ⅰ. 日期区间:…

学习QT第二天

QT6示例运行 运行一个Widgets程序运行一个QT Quick示例 工作太忙了&#xff0c;难得抽空学点东西。-_-||| 博客中有错误的地方&#xff0c;请各位道友及时指正&#xff0c;感谢&#xff01; 运行一个Widgets程序 在QT Creator的欢迎界面中&#xff0c;点击左侧的示例&#xf…

【图像检测】深度学习与传统算法的区别(识别逻辑、学习能力、泛化能力)

识别逻辑 深度学习 使用了端到端的学习策略&#xff0c;直接学习从图像到检测结果的映射关系&#xff0c;自动提取特征&#xff0c;并且根据特征与特征之间的关系&#xff0c;计算出检测结果。 传统算法 则是人工提取特征&#xff0c;比如边缘特征&#xff0c;直线特征&#x…

2024数学建模亚太赛【C题】赛题详细解析

目录 &#x1f4d1;一、竞赛时间 &#x1f5dd;️二、奖项设置 ✏️三、选题思路 &#x1f50d;阶段一&#xff1a;【数据预处理与探索性分析】 1.【数据清洗与预处理】 2.【探索性数据分析&#xff08;EDA&#xff09;】 &#x1f50d;阶段二&#xff1a;【时间序列建模…

移远通信推出全新5G RedCap模组RG255AA系列,以更高性价比加速5G轻量化大规模商用

11月20&#xff0c;全球领先的物联网整体解决方案供应商移远通信宣布&#xff0c;正式推出其全新5G RedCap模组RG255AA系列。该系列模组支持5G NR独立组网&#xff08;SA&#xff09;和LTE Cat 4双模通信&#xff0c;具有高性能高集成度、低功耗、小尺寸、高性价比等优势&#…

任务中断的两套API函数(改进FormISR的实时性)资源管理_互斥操作的本质(解决DH11经常出错的问题)

任务中断的两套API函数 为什么需要两套 API 在任务函数中&#xff0c;我们可以调用各类 API 函数&#xff0c;比如队列操作函数&#xff1a;xQueueSendToBack。 但是在 ISR 中使用这个函数会导致问题&#xff0c;应该使用另一个函数&#xff1a;xQueueSendToBackFromISR&…

基于SpringBoot+Vue的高校社团管理系统

摘要 随着高校社团活动日益丰富多样&#xff0c;传统人工管理模式弊端凸显&#xff0c;迫切需要信息化的社团管理系统。本文介绍了基于 SpringBoot Vue 开发的高校社团管理系统。在技术选型方面&#xff0c;SpringBoot 作为后端框架&#xff0c;凭借其强大的自动配置功能&…

vscode插件Todo tree

# 需求 &#xff1a; 将注释形成可视化列表 快速找到相关代码位置 免搜索 # 使用步骤 1. 安装todo tree 插件 2. 使用 todo tree 按快捷键 ctrlshiftp 输入 todo tree:add tag 添加你打注释的开头关键字 比如 // 这是一条注释 示例可以添加搜索tag为 //空格 3. t…

Windows Server 2022 Web2

载入靶机&#xff0c;看到相关描述&#xff1a; 进入虚拟机发现桌面有phpstudy和解题两个软件&#xff1a; 先点击“解题.exe”&#xff1a; 1.攻击者的IP地址&#xff08;两个&#xff09;&#xff1f; 2.攻击者的webshell文件名&#xff1f; 3.攻击者的webshell密码&#x…

学习Prompt Turning

传统的微调因为代价很高&#xff0c;而且一旦权重很大&#xff0c;这种fine 微微的意思是调不动模型的&#xff0c;所以需要这种提示词调 mindnlp直接有 peft config peft_config PromptTuningConfig(task_type“SEQ_CLS”, num_virtual_tokens10) 方便我们进行prompt tunin…

分类算法——基于heart数据集实现

1 heart数据集——描述性统计分析 import matplotlib.pyplot as plt import pandas as pd# Load the dataset heart pd.read_csv(r"heart.csv", sep,)# Check the columns in the DataFrame print(heart.columns)aheart.loc[:, y].value_counts() print(a) heart.l…

POA-CNN-SVM鹈鹕算法优化卷积神经网络结合支持向量机多特征分类预测

分类预测 | Matlab实现POA-CNN-SVM鹈鹕算法优化卷积神经网络结合支持向量机多特征分类预测 目录 分类预测 | Matlab实现POA-CNN-SVM鹈鹕算法优化卷积神经网络结合支持向量机多特征分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现POA-CNN-SVM鹈鹕算法…

【SVN和GIT】版本控制系统详细下载使用教程

文章目录 ** 参考文章一、什么是SVN和GIT二、软件使用介绍1 SVN安装1.1 服务端SVN下载地址1.2 客户端SVN下载地址2 SVN使用2.1 服务端SVN基础使用2.1.1 创建存储库和用户成员2.1.2 为存储库添加访问人员2.2 客户端SVN基础使用2.2.1 在本地下载库中的内容2.2.2 版本文件操作--更…