29.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--用户配置服务
用户配置服务是孢子记账中最简单的部分。简单说,用户配置服务就是用户自定义的配置项存储服务,用于我们的APP根据用户的配置实现指定的功能。它提供了一个简单的接口,允许用户存储和检索他们的配置数据。就目前来说,用户配置只有一个配置项:默认币种设置。在后续的版本中,我们会根据用户的反馈和需求,添加更多的配置项。
Tip:应大多数读者要求,在这篇文章开始,我们将只讲解每个服务的核心内容。完整代码请访问课程GitHub仓库代码。
一、用户配置服务实现
用户配置服务我们只需要对外开放两个Web Api :获取所有配置、更新配置,我们一起来实现这两个接口。
1.1 获取所有配置
获取所有配置,用于获取当前用户的所有配置项。我们可以通过一个简单的GET请求来实现这个功能。首先,我们需要在IConfigServer
接口中定义一个方法来获取所有配置项。然后,我们需要在ConfigController
中实现这个方法.
在IConfigServer
接口中添加方法定义:
/// /// 查询用户配置/// /// 用户配置List<ConfigResponse> GetConfig();
接口方法很简单,没必要多讲,我们重点看一下ConfigController
的实现。实现代码如下:
/// /// 查询用户配置/// /// 用户配置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
方法的逻辑:
/// /// 获取所有配置/// /// 用户配置列表[HttpGet]public ActionResult<List<ConfigResponse>> GetConfigs(){ List<ConfigResponse> configs = _configServer.GetConfig(); return Ok(configs);}
在这个方法中,我们调用了_configServer.GetConfig()
来获取所有配置项,并将结果返回给客户端。
1.2 更新配置
更新配置,用于更新当前用户的配置项。我们可以通过一个简单的POST请求来实现这个功能。首先,在IConfigServer
接口中定义一个方法来更新配置项。
/// /// 更新用户配置/// /// 配置更新请求void UpdateConfig(ConfigResponse config);
接着,在ConfigController
中实现这个方法。实现代码如下:
/// /// 更新用户配置/// /// 配置更新请求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
方法的逻辑:
/// /// 更新配置/// /// 配置更新请求/// 更新结果[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;/// /// 用户配置默认币种消息消费者服务/// public class UserConfigDefaultCurrencyConsumerService : BackgroundService{ /// /// RabbitMQ消息处理 /// private readonly RabbitMqMessage _rabbitMqMessage; /// /// 用户配置服务 /// private readonly IConfigServer _configServer; /// /// 日志记录器 /// private readonly ILogger<UserConfigDefaultCurrencyConsumerService> _logger; /// /// 配置 /// private readonly IConfiguration _configuration; /// /// 用户配置默认币种消息消费者服务构造函数 /// /// /// /// public UserConfigDefaultCurrencyConsumerService(RabbitMqMessage rabbitMqMessage, ILogger<UserConfigDefaultCurrencyConsumerService> logger, IConfiguration configuration) { _rabbitMqMessage = rabbitMqMessage; _logger = logger; _configuration = configuration; } /// /// 消费者服务 /// 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
接口中添加方法定义:
/// /// 设置用户默认货币/// /// /// /// Task SetUserDefaultCurrencyAsync(long userId, string defaultCurrencyId);
在ConfigServerImpl
实现类中实现这个方法:
/// /// 设置用户默认货币/// /// /// /// public Task SetUserDefaultCurrencyAsync(long userId, string defaultCurrencyId){ Config userConfig = new Config(); // 更新默认货币ID userConfig.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。然后,我们将该配置项添加到数据库中,并保存更改。
三、总结
在本节中,我们实现了用户配置服务的核心功能,包括获取所有配置和更新配置。我们还通过事件机制实现了用户注册后自动设置默认币种的功能。这些功能为孢子记账应用提供了基础的用户配置管理能力,使得用户可以根据自己的需求进行个性化设置。