29.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--用户配置服务

用户配置服务是孢子记账中最简单的部分。简单说,用户配置服务就是用户自定义的配置项存储服务,用于我们的APP根据用户的配置实现指定的功能。它提供了一个简单的接口,允许用户存储和检索他们的配置数据。就目前来说,用户配置只有一个配置项:默认币种设置。在后续的版本中,我们会根据用户的反馈和需求,添加更多的配置项。

Tip:应大多数读者要求,在这篇文章开始,我们将只讲解每个服务的核心内容。完整代码请访问课程GitHub仓库代码。

一、用户配置服务实现

用户配置服务我们只需要对外开放两个Web Api :获取所有配置、更新配置,我们一起来实现这两个接口。

1.1 获取所有配置

获取所有配置,用于获取当前用户的所有配置项。我们可以通过一个简单的GET请求来实现这个功能。首先,我们需要在IConfigServer接口中定义一个方法来获取所有配置项。然后,我们需要在ConfigController中实现这个方法.

IConfigServer接口中添加方法定义:

/// <summary>
/// 查询用户配置
/// </summary>
/// <returns>用户配置</returns>
List<ConfigResponse> GetConfig();

接口方法很简单,没必要多讲,我们重点看一下ConfigController的实现。实现代码如下:

/// <summary>
/// 查询用户配置
/// </summary>
/// <returns>用户配置</returns>
public List<ConfigResponse> GetConfig()
{long userId = _contextSession.UserId;// 查询用户配置List<Config> configs = _context.Configs.Where(c => c.UserId == userId).ToList();// 将配置实体转换为响应模型List<ConfigResponse> configResponses = _autoMapper.Map<List<ConfigResponse>>(configs);return configResponses;
}

在这个方法中,我们首先获取当前用户的ID,然后查询数据库中与该用户相关的所有配置项。最后,我们将查询结果转换为响应模型并返回。
在实际应用中,我们可能需要对配置项进行分页查询,以提高性能和用户体验。这里我们暂时不做分页处理,是因为当前用户配置项不多,待配置型变多后我们会在后续版本会添加。

Tip:代码中的_contextSession是当前用户的会话上下文,里面存储了当前用户的ID和用户名,它方便我们在服务中获取当前用户的信息。具体实现我们将在后续的用户会话服务中讲解。在这里我们只需要知道它可以帮助我们获取当前用户的ID即可。

在Controller中我们实现调用GetConfig方法的逻辑:

/// <summary>
/// 获取所有配置
/// </summary>
/// <returns>用户配置列表</returns>
[HttpGet]
public ActionResult<List<ConfigResponse>> GetConfigs()
{List<ConfigResponse> configs = _configServer.GetConfig();return Ok(configs);
}

在这个方法中,我们调用了_configServer.GetConfig()来获取所有配置项,并将结果返回给客户端。

1.2 更新配置

更新配置,用于更新当前用户的配置项。我们可以通过一个简单的POST请求来实现这个功能。首先,在IConfigServer接口中定义一个方法来更新配置项。

/// <summary>
/// 更新用户配置
/// </summary>
/// <param name="config">配置更新请求</param>
void UpdateConfig(ConfigResponse config);

接着,在ConfigController中实现这个方法。实现代码如下:

/// <summary>
/// 更新用户配置
/// </summary>
/// <param name="config">配置更新请求</param>
public void UpdateConfig(ConfigResponse config)
{long userId = _contextSession.UserId;// 查询用户配置Config? existingConfig = _context.Configs.FirstOrDefault(c => c.Id == config.Id);if (existingConfig == null){throw new NotFoundException("配置项不存在");}existingConfig.Value = config.Value;SettingCommProperty.Edit(existingConfig);_context.Configs.Update(existingConfig);// 保存更改到数据库_context.SaveChanges();
}

在这个方法中,我们首先获取当前用户的ID,然后查询数据库中与该配置项相关的配置项。如果配置项不存在,则抛出一个NotFoundException异常。接着,我们更新配置项的值,并保存更改到数据库。
在Controller中我们实现调用UpdateConfig方法的逻辑:

/// <summary>
/// 更新配置
/// </summary>
/// <param name="config">配置更新请求</param>
/// <returns>更新结果</returns>
[HttpPut]
public ActionResult<bool> UpdateConfig([FromBody] ConfigResponse config)
{_configServer.UpdateConfig(config);return Ok(true);
}

在这个方法中,我们调用了_configServer.UpdateConfig(config)来更新配置项,并将结果返回给客户端。

二、接收用户注册后的配置设置

在这一小节,我们将实现一个功能:当用户注册成功后,自动为用户创建默认的配置项。这样,用户在第一次使用应用时就可以直接使用默认配置,而不需要手动设置。
要实现这个功能有两种方式:一种是用户注册服务中调用配置服务,另一种是直接在通过事件机制。我们先来对比一下这两种方式。

  • 用户注册服务调用配置服务:在用户注册服务中,我们可以在用户注册成功后,直接调用配置服务来创建默认配置项。这样做的好处是简单直接,不需要额外的事件机制支持。但是,这种方式会导致用户注册服务和配置服务之间的耦合度较高,增加了系统的复杂性。
  • 通过事件机制:我们可以在用户注册成功后,发布一个事件,然后在配置服务中订阅这个事件。当事件被触发时,配置服务会自动创建默认配置项。这种方式的好处是解耦了用户注册服务和配置服务,使得系统更加灵活和可扩展,如果配置服务需要修改或替换,只需要修改配置服务的实现,而不需要修改用户注册服务的代码。

我们在这里选择第二种方式,通过事件机制来实现用户注册后的配置设置,这样可以使得系统更加灵活和可扩展,实现事件机制的方法我们选择使用RabbitMQ消息队列来实现。
首先,我们需要在身份认证服务SP.IdentityService中的AuthorizationServiceImpl实现类中的AddUserAsync方法中发布一个用户注册成功的事件。我们可以在用户注册成功后,使用RabbitMQ的生产者发送一个消息到指定的队列。补充代码如下:

// more codepublic async Task<long> AddUserAsync(UserAddRequest user)
{// more code// 发送mq,设配默认币种MqPublisher publisher = new MqPublisher(newUser.Id.ToString(),MqExchange.UserConfigExchange,MqRoutingKey.UserConfigDefaultCurrencyRoutingKey,MqQueue.UserConfigQueue,MessageType.UserConfigDefaultCurrency,ExchangeType.Direct);await _rabbitMqMessage.SendAsync(publisher);// more code
}

在这段代码中,我们创建了一个MqPublisher对象,并设置了相关的交换机、路由键和队列。然后,我们使用_rabbitMqMessage.SendAsync(publisher)方法将消息发送到RabbitMQ。

Tip:MqPublisher的实现代码在SP.Common项目中,它是一个简单的消息发布者,用于发送消息到RabbitMQ。它包含了交换机、路由键、队列和消息类型等信息。

接下来,我们需要在配置服务SP.ConfigService中订阅这个事件,并在收到事件时创建默认配置项。我们可以在配置服务的启动类中添加一个RabbitMQ的消费者来处理这个事件。实现代码如下:

using SP.Common.ExceptionHandling.Exceptions;
using SP.Common.Message.Model;
using SP.Common.Message.Mq;
using SP.Common.Message.Mq.Model;
using SP.ConfigService.Service;namespace SP.ConfigService.Mq;/// <summary>
/// 用户配置默认币种消息消费者服务
/// </summary>
public class UserConfigDefaultCurrencyConsumerService : BackgroundService
{/// <summary>/// RabbitMQ消息处理/// </summary>private readonly RabbitMqMessage _rabbitMqMessage;/// <summary>/// 用户配置服务/// </summary>private readonly IConfigServer _configServer;/// <summary>/// 日志记录器/// </summary>private readonly ILogger<UserConfigDefaultCurrencyConsumerService> _logger;/// <summary>/// 配置/// </summary>private readonly IConfiguration _configuration;/// <summary>/// 用户配置默认币种消息消费者服务构造函数/// </summary>/// <param name="rabbitMqMessage"></param>/// <param name="logger"></param>/// <param name="configuration"></param>public UserConfigDefaultCurrencyConsumerService(RabbitMqMessage rabbitMqMessage, ILogger<UserConfigDefaultCurrencyConsumerService> logger,IConfiguration configuration){_rabbitMqMessage = rabbitMqMessage;_logger = logger;_configuration = configuration;}/// <summary>/// 消费者服务/// </summary>protected override async Task ExecuteAsync(CancellationToken stoppingToken){MqSubscriber subscriber = new MqSubscriber(MqExchange.UserConfigExchange,MqRoutingKey.UserConfigDefaultCurrencyRoutingKey, MqQueue.UserConfigQueue);await _rabbitMqMessage.ReceiveAsync(subscriber, async message =>{MqMessage mqMessage = message as MqMessage;if (mqMessage == null){_logger.LogError("消息转换失败");throw new ArgumentNullException(nameof(mqMessage));}string userId = mqMessage.Body;if (string.IsNullOrEmpty(userId)){_logger.LogError("用户ID不能为空");throw new BusinessException(nameof(userId));}_logger.LogInformation($"接收到用户配置默认币种消息,用户ID: {userId}");if (!long.TryParse(userId, out long parsedUserId)){_logger.LogError("用户ID格式错误");throw new BusinessException("用户ID格式错误");}// 设置用户默认币种,默认币种id从配置文件中获取string defaultCurrencyId = _configuration["DefaultCurrencyId"];_logger.LogInformation($"nacos中配置的默认币种ID: {defaultCurrencyId}");// 调用币种服务设置用户默认币种await _configServer.SetUserDefaultCurrencyAsync(parsedUserId,defaultCurrencyId);});}
}

在这个消费者服务中,我们首先创建了一个MqSubscriber对象,并设置了相关的交换机、路由键和队列。然后,我们使用_rabbitMqMessage.ReceiveAsync(subscriber, async message => { ... })方法来接收消息。当收到消息时,我们将消息体转换为MqMessage对象,并从中获取用户ID。接着,我们调用配置服务的SetUserDefaultCurrencyAsync方法来设置用户的默认币种。
SetUserDefaultCurrencyAsync方法中,我们可以实现设置用户默认币种的逻辑。
IConfigServer接口中添加方法定义:

/// <summary>
/// 设置用户默认货币
/// </summary>
/// <param name="userId"></param>
/// <param name="defaultCurrencyId"></param>
/// <returns></returns>
Task SetUserDefaultCurrencyAsync(long userId, string defaultCurrencyId);

ConfigServerImpl实现类中实现这个方法:

/// <summary>
/// 设置用户默认货币
/// </summary>
/// <param name="userId"></param>
/// <param name="defaultCurrencyId"></param>
/// <returns></returns>
public Task SetUserDefaultCurrencyAsync(long userId, string defaultCurrencyId)
{Config userConfig = new Config();// 更新默认货币IDuserConfig.Value = defaultCurrencyId;userConfig.UserId = userId;userConfig.ConfigType = ConfigTypeEnum.Currency;userConfig.Id = Snow.GetId();userConfig.CreateDateTime = DateTime.Now;userConfig.CreateUserId = userId;_context.Configs.Add(userConfig);// 保存到数据库_context.SaveChanges();return Task.CompletedTask;
}

在这个方法中,我们创建了一个新的Config对象,并设置其值为默认币种ID。然后,我们将该配置项添加到数据库中,并保存更改。

三、总结

在本节中,我们实现了用户配置服务的核心功能,包括获取所有配置和更新配置。我们还通过事件机制实现了用户注册后自动设置默认币种的功能。这些功能为孢子记账应用提供了基础的用户配置管理能力,使得用户可以根据自己的需求进行个性化设置。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python实现PDF按页分割:灵活拆分文档的技术指南

Python实现PDF按页分割&#xff1a;灵活拆分文档的技术指南 PDF文件处理是日常工作中的常见需求&#xff0c;特别是当我们需要将大型PDF文档拆分为多个部分时。本文将介绍如何使用Python创建一个灵活的PDF分割工具&#xff0c;能够根据用户指定的页数范围任意分割文档。 需求分…

「iOS」——GCD其他方法详解

GCD学习GCD其他方法dispatch_semaphore &#xff08;信号量&#xff09;**什么是信号量**dispatch_semaphore主要作用dispatch_semaphore主要作用异步转同步设置一个最大开辟的线程数加锁机制dispatch_time_t 两种形式GCD一次性代码(只执行一次)dispatch_barrier_async/sync栅栏…

【图像处理基石】如何实现一个车辆检测算法?

基于AI的车牌检测和识别算法 问题描述、应用场景与难点 问题描述 车牌检测和识别是计算机视觉领域的一个特定任务&#xff0c;主要包含两个核心步骤&#xff1a; 车牌检测&#xff1a;从图像中准确定位车牌的位置和区域车牌识别&#xff1a;对检测到的车牌区域进行字符识别&…

计算机学报 2025年 区块链论文 录用汇总 附pdf下载

计算机学报 Year&#xff1a;2025 2024请看 1 Title: 基于区块链的动态多云多副本数据完整性审计方法研究 Authors: Key words: 区块链&#xff1b;云存储&#xff1b;多云多副本存储&#xff1b;数据完整性审计 Abstract: 随着云计算技术的快速发展和云存储服务的日益…

计算机网络-UDP协议

UDP&#xff08;用户数据报协议&#xff09;是传输层的一种无连接、不可靠、轻量级的协议&#xff0c;适用于对实时性要求高、能容忍少量数据丢失的场景&#xff08;如视频流、DNS查询等&#xff09;。以下是UDP的详细解析&#xff1a;1. UDP的核心特点特性说明无连接通信前无需…

子域名收集和c段查询

子域名收集方法一、sitesite&#xff1a; 要查询的域名可以查到相关网站二、oneforall &#xff08;子域名查找工具&#xff09;下载后解压的文件夹在当前文件夹打开终端然后运行命令 python oneforall.py --target xxxxxxxx&#xff08;这里放你要查的网址&#xff09; run最…

计网-TCP拥塞控制

TCP的拥塞控制&#xff08;Congestion Control&#xff09;是核心机制之一&#xff0c;用于动态调整发送方的数据传输速率&#xff0c;避免网络因过载而出现性能急剧下降&#xff08;如丢包、延迟激增&#xff09;。其核心思想是探测网络可用带宽&#xff0c;并在拥塞发生时主动…

依赖倒置原则 Dependency Inversion Principle - DIP

基本知识 1.依赖倒置原则&#xff08;DIP&#xff09;是面向对象设计&#xff08;OOD&#xff09;中的五个基本原则之一&#xff0c;通常被称为 SOLID 原则中的 D 2.核心思想&#xff1a; 高层模块不应该依赖低层模块&#xff0c;两者都应该依赖抽象。 (High-level modules sho…

原生input添加删除图标类似vue里面移入显示删除[jquery]

<input type"text" id"servicer-search" class"form-control" autocomplete"off" />上面是刚开始的input <div class"servicer-search-box"><input type"text" id"servicer-search" cla…

整理分享 | Photoshop 2025 (v26.5) 安装记录

导语&#xff1a; 最近整理资源时&#xff0c;发现有朋友在找新版 Photoshop。正好手边有 Photoshop 2025年7月的版本&#xff08;v26.5&#xff09;&#xff0c;就记录下来分享给大家&#xff0c;供有需要的朋友参考。关于这个版本&#xff1a;这个 Photoshop v26.5 安装包&am…

【Redis】Redis 数据存储原理和结构

一、Redis 存储结构 1.1 KV结构 Redis 本质上是一个 Key-Value&#xff08;键值对&#xff0c;KV&#xff09;数据库&#xff0c;在它丰富多样的数据结构底层&#xff0c;都基于一种统一的键值对存储结构来进行数据的管理和操作 Redis 使用一个全局的哈希表来管理所有的键值对…

【RAG优化】深度剖析OCR错误,从根源修复RAG应用的识别问题

1. 引言:OCR——RAG系统中的关键问题 当我们将一个包含扫描页面的PDF或一张报告截图扔给RAG系统时,我们期望它能“读懂”里面的内容。这个“读懂”的第一步,就是OCR。然而,OCR过程并非100%准确,它受到图像质量、文字布局、字体、语言等多种因素的影响。 一个看似微不足道…

【第六节】方法与事件处理器

方法与事件处理器 方法处理器 可以用 v-on 指令监听 DOM 事件: <div id="example"> <button v-on:click="greet">Greet</button></div>绑定一个单击事件处理器到一个方法 greet 。下面在 Vue 实例中定义这个方法 var vm=new V…

大语言模型Claude 4简介

Anthropic公司成立于2021年&#xff0c;由一群OpenAI前员工组成。他们最新发布的大语言模型(Large Language Model, LLM) Claude 4系列包括两个版本&#xff1a;Claude Opus 4和Claude Sonnet 4&#xff1a;(1).Claude Sonnet 4&#xff1a;是Claude Sonnet 3.7的升级&#xff…

国产化PDF处理控件Spire.PDF教程:Python 将 PDF 转换为 Markdown (含批量转换示例)

PDF 是数字文档管理的普遍格式&#xff0c;但其固定布局特性限制了在需要灵活编辑、更新或现代工作流集成场景下的应用。相比之下&#xff0c;Markdown&#xff08;.md&#xff09;语法轻量、易读&#xff0c;非常适合网页发布、文档编写和版本控制。 E-iceblue旗下Spire系列产…

PDF转Markdown - Python 实现方案与代码

PDF作为广泛使用的文档格式&#xff0c;转换为轻量级标记语言Markdown后&#xff0c;可无缝集成到技术文档、博客平台和版本控制系统中&#xff0c;提高内容的可编辑性和可访问性。本文将详细介绍如何使用国产Spire.PDF for Python 库将 PDF 文档转换为 Markdown 格式。 技术优…

深度解析 inaSpeechSegmenter:高效音频语音分割与检测开源工具

项目简介 inaSpeechSegmenter 是法国国家视听研究院(INA)开源的音频分割与检测工具,专为广播、播客、采访、影视等多媒体内容的自动化处理设计。它能够高效地将长音频自动分割为语音、音乐、噪声、静音等片段,并支持性别检测(男声/女声),为后续的语音识别、内容检索、转…

VirtualBox安装Ubuntu 22.04后终端无法打开的解决方案

问题现象在VirtualBox中使用"快速安装"模式安装Ubuntu 22.04后图形终端&#xff08;gnome-terminal&#xff09;无法通过图标或快捷键(CtrlAltT)启动系统其他功能正常根本原因语言环境(Locale)配置异常导致&#xff1a;快速安装模式可能跳过Locale生成步骤gnome-term…

java磁盘操作与IO流(序列化、Properties类)

目录 一、磁盘操作 1、File类&#xff1a; &#xff08;1&#xff09;创建File对象&#xff1a; &#xff08;2&#xff09;获取文件信息&#xff1a; &#xff08;3&#xff09;判断文件 &#xff08;4&#xff09;删除文件 &#xff08;5&#xff09;创建文件&#xff…

【WPF】WPF Prism 开发经验总结:菜单命令删除项时报 InvalidCastException 的问题分析与解决

WPF Prism 开发经验总结&#xff1a;菜单命令删除项时报 InvalidCastException 的问题分析与解决 在 WPF Prism 项目中使用 ContextMenu 执行删除操作时&#xff0c;遇到一个令人疑惑的问题&#xff1a;命令绑定本身没有问题&#xff0c;但点击“删除”菜单后&#xff0c;程序抛…