feat(wechat): Add access token refresh control and improve template message error handling
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
- Add forceRefresh parameter to GetServiceAccountAccessTokenAsync for cache bypass capability - Implement automatic token refresh retry logic for HTTP 412 and token expiration error codes (40001, 42001) - Extract SendServiceAccountTemplateMessageAsync into internal method to support retry mechanism - Enhance logging with contextual parameters (AppId, ToUser, TemplateId, AccessToken prefix) for better diagnostics - Add conditional logging for failed requests with full diagnostic information - Improve error handling to distinguish between token expiration scenarios and other failures - Add informational logging when requesting new access tokens
This commit is contained in:
parent
e34d90886d
commit
9be72eb106
|
|
@ -67,8 +67,9 @@ public interface IWeChatService
|
|||
/// <summary>
|
||||
/// 获取服务号AccessToken
|
||||
/// </summary>
|
||||
/// <param name="forceRefresh">是否强制刷新(忽略缓存)</param>
|
||||
/// <returns>AccessToken</returns>
|
||||
Task<string?> GetServiceAccountAccessTokenAsync();
|
||||
Task<string?> GetServiceAccountAccessTokenAsync(bool forceRefresh = false);
|
||||
|
||||
/// <summary>
|
||||
/// 获取服务号关注用户信息(包含UnionId)
|
||||
|
|
|
|||
|
|
@ -371,7 +371,7 @@ public class WeChatService : IWeChatService
|
|||
|
||||
private const string ServiceAccountAccessTokenCacheKey = "wechat:service_account:access_token";
|
||||
|
||||
public async Task<string?> GetServiceAccountAccessTokenAsync()
|
||||
public async Task<string?> GetServiceAccountAccessTokenAsync(bool forceRefresh = false)
|
||||
{
|
||||
// 检查服务号配置
|
||||
if (string.IsNullOrEmpty(_options.ServiceAccount?.AppId) ||
|
||||
|
|
@ -381,10 +381,13 @@ public class WeChatService : IWeChatService
|
|||
return null;
|
||||
}
|
||||
|
||||
// 先从缓存获取
|
||||
var cached = await _cache.GetStringAsync(ServiceAccountAccessTokenCacheKey);
|
||||
if (!string.IsNullOrEmpty(cached))
|
||||
return cached;
|
||||
// 先从缓存获取(非强制刷新时)
|
||||
if (!forceRefresh)
|
||||
{
|
||||
var cached = await _cache.GetStringAsync(ServiceAccountAccessTokenCacheKey);
|
||||
if (!string.IsNullOrEmpty(cached))
|
||||
return cached;
|
||||
}
|
||||
|
||||
// 请求新的AccessToken
|
||||
var url = $"https://api.weixin.qq.com/cgi-bin/token" +
|
||||
|
|
@ -392,6 +395,8 @@ public class WeChatService : IWeChatService
|
|||
$"&appid={_options.ServiceAccount.AppId}" +
|
||||
$"&secret={_options.ServiceAccount.AppSecret}";
|
||||
|
||||
_logger.LogInformation("请求服务号AccessToken: AppId={AppId}", _options.ServiceAccount.AppId);
|
||||
|
||||
var httpResponse = await _httpClient.GetAsync(url);
|
||||
var responseContent = await httpResponse.Content.ReadAsStringAsync();
|
||||
var response = JsonSerializer.Deserialize<AccessTokenResponse>(responseContent);
|
||||
|
|
@ -402,6 +407,8 @@ public class WeChatService : IWeChatService
|
|||
return null;
|
||||
}
|
||||
|
||||
_logger.LogInformation("获取服务号AccessToken成功");
|
||||
|
||||
// 缓存AccessToken,提前5分钟过期
|
||||
var expireSeconds = response.ExpiresIn - 300;
|
||||
await _cache.SetStringAsync(
|
||||
|
|
@ -416,13 +423,20 @@ public class WeChatService : IWeChatService
|
|||
}
|
||||
|
||||
public async Task<bool> SendServiceAccountTemplateMessageAsync(ServiceAccountTemplateMessageRequest request)
|
||||
{
|
||||
return await SendServiceAccountTemplateMessageInternalAsync(request, isRetry: false);
|
||||
}
|
||||
|
||||
private async Task<bool> SendServiceAccountTemplateMessageInternalAsync(
|
||||
ServiceAccountTemplateMessageRequest request, bool isRetry)
|
||||
{
|
||||
try
|
||||
{
|
||||
var accessToken = await GetServiceAccountAccessTokenAsync();
|
||||
if (string.IsNullOrEmpty(accessToken))
|
||||
{
|
||||
_logger.LogError("获取服务号AccessToken失败");
|
||||
_logger.LogError("获取服务号AccessToken失败, AppId={AppId}, ToUser={ToUser}, TemplateId={TemplateId}",
|
||||
_options.ServiceAccount?.AppId, request.ToUser, request.TemplateId);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -454,33 +468,51 @@ public class WeChatService : IWeChatService
|
|||
var response = await _httpClient.PostAsJsonAsync(url, requestBody);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
_logger.LogInformation("发送服务号模板消息: StatusCode={StatusCode}, Response={Response}",
|
||||
(int)response.StatusCode, responseContent);
|
||||
// 非200时打印完整诊断信息
|
||||
if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(responseContent))
|
||||
{
|
||||
_logger.LogWarning("发送服务号模板消息失败: StatusCode={StatusCode}, AppId={AppId}, ToUser={ToUser}, TemplateId={TemplateId}, AccessToken={AccessToken}, IsRetry={IsRetry}, Response={Response}",
|
||||
(int)response.StatusCode, _options.ServiceAccount?.AppId, request.ToUser, request.TemplateId,
|
||||
accessToken?[..Math.Min(accessToken.Length, 20)] + "...", isRetry, responseContent);
|
||||
}
|
||||
|
||||
// 打印请求体方便排查
|
||||
var requestJson = System.Text.Json.JsonSerializer.Serialize(requestBody);
|
||||
_logger.LogInformation("发送服务号模板消息请求体: {RequestBody}", requestJson);
|
||||
// HTTP 412 表示 access_token 失效,强制刷新后重试一次
|
||||
if ((int)response.StatusCode == 412 && !isRetry)
|
||||
{
|
||||
_logger.LogWarning("服务号AccessToken可能已失效(412),强制刷新后重试");
|
||||
await _cache.RemoveAsync(ServiceAccountAccessTokenCacheKey);
|
||||
return await SendServiceAccountTemplateMessageInternalAsync(request, isRetry: true);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(responseContent))
|
||||
{
|
||||
_logger.LogWarning("发送服务号模板消息失败: 微信返回空响应");
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize<WeChatApiResponse>(responseContent);
|
||||
|
||||
// errcode 40001/42001 也是 token 失效,重试
|
||||
if (result != null && (result.ErrCode == 40001 || result.ErrCode == 42001) && !isRetry)
|
||||
{
|
||||
_logger.LogWarning("服务号AccessToken失效({ErrCode}),强制刷新后重试, AppId={AppId}", result.ErrCode, _options.ServiceAccount?.AppId);
|
||||
await _cache.RemoveAsync(ServiceAccountAccessTokenCacheKey);
|
||||
return await SendServiceAccountTemplateMessageInternalAsync(request, isRetry: true);
|
||||
}
|
||||
|
||||
if (result?.ErrCode != 0)
|
||||
{
|
||||
_logger.LogWarning("发送服务号模板消息失败: {ErrCode} - {ErrMsg}", result?.ErrCode, result?.ErrMsg);
|
||||
_logger.LogWarning("发送服务号模板消息失败: ErrCode={ErrCode}, ErrMsg={ErrMsg}, AppId={AppId}, ToUser={ToUser}, TemplateId={TemplateId}",
|
||||
result?.ErrCode, result?.ErrMsg, _options.ServiceAccount?.AppId, request.ToUser, request.TemplateId);
|
||||
return false;
|
||||
}
|
||||
|
||||
_logger.LogInformation("发送服务号模板消息成功: {ToUser}", request.ToUser);
|
||||
_logger.LogInformation("发送服务号模板消息成功: ToUser={ToUser}, TemplateId={TemplateId}", request.ToUser, request.TemplateId);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "发送服务号模板消息异常");
|
||||
_logger.LogError(ex, "发送服务号模板消息异常: AppId={AppId}, ToUser={ToUser}, TemplateId={TemplateId}",
|
||||
_options.ServiceAccount?.AppId, request.ToUser, request.TemplateId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user