using System.Text.Json; using Senparc.Weixin.MP.AdvancedAPIs; using Senparc.Weixin.WxOpen.AdvancedAPIs.Sns; using Tiku.Application.Auth; namespace Tiku.Infrastructure.Auth; public sealed class WechatOAuthClient : IWechatOAuthClient { public async Task ExchangeWebCodeAsync( WechatProviderOptions options, string code, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); try { var token = await OAuthApi.GetAccessTokenAsync( options.AppId, options.AppSecret, code, "authorization_code"); cancellationToken.ThrowIfCancellationRequested(); EnsureSuccess(token.ErrorCodeValue, token.errmsg); if (string.IsNullOrWhiteSpace(token.access_token)) { throw new InvalidCredentialsException("wechat_access_token_missing"); } if (string.IsNullOrWhiteSpace(token.openid)) { throw new InvalidCredentialsException("wechat_openid_missing"); } var user = await OAuthApi.GetUserInfoAsync( token.access_token, token.openid, Senparc.Weixin.Language.zh_CN); cancellationToken.ThrowIfCancellationRequested(); return new WechatIdentity( token.openid.Trim(), FirstNonBlank(user.unionid, token.unionid), NullIfBlank(user.nickname), NullIfBlank(user.headimgurl), null, SerializeRaw(new { accessToken = token, user })); } catch (InvalidCredentialsException) { throw; } catch (Exception exception) { throw new InvalidCredentialsException(MapWechatException(exception)); } } public async Task ExchangeMiniAppCodeAsync( WechatProviderOptions options, string code, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); try { var result = await SnsApi.JsCode2JsonAsync( options.AppId, options.AppSecret, code, "authorization_code"); cancellationToken.ThrowIfCancellationRequested(); EnsureSuccess(result.ErrorCodeValue, result.errmsg); if (string.IsNullOrWhiteSpace(result.openid)) { throw new InvalidCredentialsException("wechat_openid_missing"); } if (string.IsNullOrWhiteSpace(result.session_key)) { throw new InvalidCredentialsException("wechat_session_key_missing"); } return new WechatIdentity( result.openid.Trim(), NullIfBlank(result.unionid), null, null, result.session_key.Trim(), SerializeRaw(result)); } catch (InvalidCredentialsException) { throw; } catch (Exception exception) { throw new InvalidCredentialsException(MapWechatException(exception)); } } private static void EnsureSuccess(int errorCode, string? errorMessage) { if (errorCode == 0) { return; } throw new InvalidCredentialsException( string.IsNullOrWhiteSpace(errorMessage) ? "wechat_code_exchange_failed" : $"wechat_code_exchange_failed:{errorCode}"); } private static string SerializeRaw(T value) { return JsonSerializer.Serialize(value); } private static string? FirstNonBlank(params string?[] values) { return values.Select(NullIfBlank).FirstOrDefault(value => value is not null); } private static string? NullIfBlank(string? value) { return string.IsNullOrWhiteSpace(value) ? null : value.Trim(); } private static string MapWechatException(Exception exception) { return exception is HttpRequestException ? "wechat_http_error" : "wechat_code_exchange_failed"; } }