live-forum/server/webapi/LiveForum/LiveForum.Service/Others/WechatApiClientManager.cs
2026-03-24 11:27:37 +08:00

250 lines
7.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using LiveForum.IService.Others;
using LiveForum.Model.Dto.Others;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SKIT.FlurlHttpClient.Wechat.Api;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace LiveForum.Service.Others
{
/// <summary>
/// 微信 API 客户端管理器实现(支持配置热更新)
/// </summary>
public class WechatApiClientManager : IWechatApiClientManager
{
private readonly IOptionsMonitor<WechatConfig> _configMonitor;
private readonly ILogger<WechatApiClientManager> _logger;
private readonly object _lockObject = new object();
private WechatApiClient _client;
private string _currentAppId;
public WechatApiClientManager(
IOptionsMonitor<WechatConfig> configMonitor,
ILogger<WechatApiClientManager> logger)
{
_configMonitor = configMonitor;
_logger = logger;
// 监听配置变更
_configMonitor.OnChange(OnConfigChanged);
// 初始化客户端
InitializeClient();
}
/// <summary>
/// 初始化客户端
/// </summary>
private void InitializeClient()
{
try
{
var config = _configMonitor.CurrentValue;
// 验证配置
if (!ValidateConfig(config))
{
_logger.LogWarning("微信配置验证失败,无法创建客户端");
_client = null;
_currentAppId = null;
return;
}
var options = new WechatApiClientOptions()
{
AppId = config.AppId,
AppSecret = config.AppSecret
};
lock (_lockObject)
{
_client = new WechatApiClient(options);
_currentAppId = config.AppId;
}
var appIdDisplay = GetMaskedAppId(config.AppId);
_logger.LogInformation("微信 API 客户端已初始化 - AppId: {AppId}", appIdDisplay);
}
catch (Exception ex)
{
_logger.LogError(ex, "初始化微信 API 客户端失败");
_client = null;
_currentAppId = null;
}
}
/// <summary>
/// 验证配置
/// </summary>
private bool ValidateConfig(WechatConfig config)
{
if (config == null)
{
_logger.LogError("微信配置为空");
return false;
}
if (string.IsNullOrWhiteSpace(config.AppId))
{
_logger.LogError("微信 AppId 为空");
return false;
}
if (string.IsNullOrWhiteSpace(config.AppSecret))
{
_logger.LogError("微信 AppSecret 为空");
return false;
}
return true;
}
/// <summary>
/// 获取微信 API 客户端实例
/// </summary>
public WechatApiClient GetClient()
{
lock (_lockObject)
{
if (_client == null)
{
_logger.LogWarning("微信 API 客户端不可用,尝试重新初始化");
InitializeClient();
}
return _client;
}
}
/// <summary>
/// 重新加载客户端(配置变更时调用)
/// </summary>
public bool Reload()
{
try
{
_logger.LogInformation("开始重新加载微信 API 客户端...");
var newConfig = _configMonitor.CurrentValue;
// 验证新配置
if (!ValidateConfig(newConfig))
{
_logger.LogWarning("新配置验证失败,无法重新加载客户端");
return false;
}
WechatApiClient oldClient = null;
lock (_lockObject)
{
// 保存旧客户端引用(用于延迟 Dispose
oldClient = _client;
// 创建新客户端
var options = new WechatApiClientOptions()
{
AppId = newConfig.AppId,
AppSecret = newConfig.AppSecret
};
_client = new WechatApiClient(options);
_currentAppId = newConfig.AppId;
var appIdDisplay = GetMaskedAppId(newConfig.AppId);
_logger.LogInformation("微信 API 客户端已重新加载 - 新 AppId: {AppId}", appIdDisplay);
}
// 延迟释放旧客户端(确保正在进行的请求完成)
//ScheduleDispose(oldClient);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "重新加载微信 API 客户端失败");
return false;
}
}
/// <summary>
/// 检查客户端是否可用
/// </summary>
public bool IsAvailable()
{
lock (_lockObject)
{
return _client != null;
}
}
/// <summary>
/// 获取当前配置的 AppId
/// </summary>
public string GetCurrentAppId()
{
lock (_lockObject)
{
return _currentAppId;
}
}
/// <summary>
/// 配置变更回调
/// </summary>
private void OnConfigChanged(WechatConfig newConfig)
{
var newAppIdDisplay = GetMaskedAppId(newConfig?.AppId);
_logger.LogInformation("检测到微信配置变更 - 新 AppId: {AppId}", newAppIdDisplay);
// 自动重新加载(可选,也可以手动调用)
// Reload();
}
/// <summary>
/// 延迟释放旧客户端
/// </summary>
private void ScheduleDispose(WechatApiClient oldClient)
{
if (oldClient == null) return;
// 延迟30秒后释放确保所有正在进行的请求完成
Task.Run(async () =>
{
try
{
await Task.Delay(TimeSpan.FromSeconds(30));
// 检查是否实现了 IDisposable
if (oldClient is IDisposable disposable)
{
disposable.Dispose();
_logger.LogInformation("旧的微信 API 客户端已释放");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "释放旧的微信 API 客户端时发生错误");
}
});
}
/// <summary>
/// 获取脱敏的 AppId用于日志
/// </summary>
private string GetMaskedAppId(string appId)
{
if (string.IsNullOrEmpty(appId))
return "未配置";
if (appId.Length <= 8)
return appId;
return $"{appId.Substring(0, 8)}***";
}
}
}