用戶配置服務是孢子記賬中最簡單的部分。簡單說,用戶配置服務就是用戶自定義的配置項存儲服務,用于我們的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。然后,我們將該配置項添加到數據庫中,并保存更改。
三、總結
在本節中,我們實現了用戶配置服務的核心功能,包括獲取所有配置和更新配置。我們還通過事件機制實現了用戶注冊后自動設置默認幣種的功能。這些功能為孢子記賬應用提供了基礎的用戶配置管理能力,使得用戶可以根據自己的需求進行個性化設置。