Bladeren bron

!1756 参考:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK…
Merge pull request !1756 from Zou-Jia/main

zuohuaijun 11 maanden geleden
bovenliggende
commit
5d2d729fd2

+ 203 - 210
Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatService.cs

@@ -12,214 +12,207 @@ namespace Admin.NET.Core.Service;
 [ApiDescriptionSettings(Order = 230)]
 public class SysWechatService : IDynamicApiController, ITransient
 {
-    private readonly SqlSugarRepository<SysWechatUser> _sysWechatUserRep;
-    private readonly SysConfigService _sysConfigService;
-    private readonly WechatApiClientFactory _wechatApiClientFactory;
-    private readonly WechatApiClient _wechatApiClient;
-    private readonly SysCacheService _sysCacheService;
-
-    public SysWechatService(SqlSugarRepository<SysWechatUser> sysWechatUserRep,
-        SysConfigService sysConfigService,
-        WechatApiClientFactory wechatApiClientFactory,
-        SysCacheService sysCacheService)
-    {
-        _sysWechatUserRep = sysWechatUserRep;
-        _sysConfigService = sysConfigService;
-        _wechatApiClientFactory = wechatApiClientFactory;
-        _wechatApiClient = wechatApiClientFactory.CreateWechatClient();
-        _sysCacheService = sysCacheService;
-    }
-
-    /// <summary>
-    /// 生成网页授权Url 🔖
-    /// </summary>
-    /// <param name="input"></param>
-    /// <returns></returns>
-    [AllowAnonymous]
-    [DisplayName("生成网页授权Url")]
-    public string GenAuthUrl(GenAuthUrlInput input)
-    {
-        return _wechatApiClient.GenerateParameterizedUrlForConnectOAuth2Authorize(input.RedirectUrl, input.Scope, input.State);
-    }
-
-    /// <summary>
-    /// 获取微信用户OpenId 🔖
-    /// </summary>
-    /// <param name="input"></param>
-    [AllowAnonymous]
-    [DisplayName("获取微信用户OpenId")]
-    public async Task<string> SnsOAuth2([FromQuery] WechatOAuth2Input input)
-    {
-        var reqOAuth2 = new SnsOAuth2AccessTokenRequest()
-        {
-            Code = input.Code,
-        };
-        var resOAuth2 = await _wechatApiClient.ExecuteSnsOAuth2AccessTokenAsync(reqOAuth2);
-        if (resOAuth2.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
-            throw Oops.Oh(resOAuth2.ErrorMessage + " " + resOAuth2.ErrorCode);
-
-        var wxUser = await _sysWechatUserRep.GetFirstAsync(p => p.OpenId == resOAuth2.OpenId);
-        if (wxUser == null)
-        {
-            var reqUserInfo = new SnsUserInfoRequest()
-            {
-                OpenId = resOAuth2.OpenId,
-                AccessToken = resOAuth2.AccessToken,
-            };
-            var resUserInfo = await _wechatApiClient.ExecuteSnsUserInfoAsync(reqUserInfo);
-            wxUser = resUserInfo.Adapt<SysWechatUser>();
-            wxUser.Avatar = resUserInfo.HeadImageUrl;
-            wxUser.NickName = resUserInfo.Nickname;
-            wxUser.OpenId = resOAuth2.OpenId;
-            wxUser.UnionId = resOAuth2.UnionId;
-            wxUser.AccessToken = resOAuth2.AccessToken;
-            wxUser.RefreshToken = resOAuth2.RefreshToken;
-            wxUser = await _sysWechatUserRep.AsInsertable(wxUser).ExecuteReturnEntityAsync();
-        }
-        else
-        {
-            wxUser.AccessToken = resOAuth2.AccessToken;
-            wxUser.RefreshToken = resOAuth2.RefreshToken;
-            await _sysWechatUserRep.AsUpdateable(wxUser).IgnoreColumns(true).ExecuteCommandAsync();
-        }
-
-        return resOAuth2.OpenId;
-    }
-
-    /// <summary>
-    /// 微信用户登录OpenId 🔖
-    /// </summary>
-    /// <param name="input"></param>
-    /// <returns></returns>
-    [AllowAnonymous]
-    [DisplayName("微信用户登录OpenId")]
-    public async Task<dynamic> OpenIdLogin(WechatUserLogin input)
-    {
-        var wxUser = await _sysWechatUserRep.GetFirstAsync(p => p.OpenId == input.OpenId);
-        if (wxUser == null)
-            throw Oops.Oh("微信用户登录OpenId错误");
-
-        var tokenExpire = await _sysConfigService.GetTokenExpire();
-        return new
-        {
-            wxUser.Avatar,
-            accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
-            {
-                { ClaimConst.UserId, wxUser.Id },
-                { ClaimConst.NickName, wxUser.NickName },
-                { ClaimConst.LoginMode, LoginModeEnum.APP },
-            }, tokenExpire)
-        };
-    }
-
-    /// <summary>
-    /// 获取配置签名参数(wx.config) 🔖
-    /// </summary>
-    /// <returns></returns>
-    [DisplayName("获取配置签名参数(wx.config)")]
-    public async Task<dynamic> GenConfigPara(SignatureInput input)
-    {
-        var resCgibinToken = await _wechatApiClient.ExecuteCgibinTokenAsync(new CgibinTokenRequest());
-        var request = new CgibinTicketGetTicketRequest()
-        {
-            AccessToken = resCgibinToken.AccessToken
-        };
-        var response = await _wechatApiClient.ExecuteCgibinTicketGetTicketAsync(request);
-        if (!response.IsSuccessful())
-            throw Oops.Oh(response.ErrorMessage);
-        return _wechatApiClient.GenerateParametersForJSSDKConfig(response.Ticket, input.Url);
-    }
-
-    /// <summary>
-    /// 获取模板列表 🔖
-    /// </summary>
-    [DisplayName("获取模板列表")]
-    public async Task<dynamic> GetMessageTemplateList()
-    {
-        var accessToken = await GetCgibinToken();
-        var reqTemplate = new CgibinTemplateGetAllPrivateTemplateRequest()
-        {
-            AccessToken = accessToken
-        };
-        var resTemplate = await _wechatApiClient.ExecuteCgibinTemplateGetAllPrivateTemplateAsync(reqTemplate);
-        if (resTemplate.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
-            throw Oops.Oh(resTemplate.ErrorMessage + " " + resTemplate.ErrorCode);
-
-        return resTemplate.TemplateList;
-    }
-
-    /// <summary>
-    /// 发送模板消息 🔖
-    /// </summary>
-    /// <param name="input"></param>
-    /// <returns></returns>
-    [DisplayName("发送模板消息")]
-    public async Task<dynamic> SendTemplateMessage(MessageTemplateSendInput input)
-    {
-        var dataInfo = input.Data.ToDictionary(k => k.Key, k => k.Value);
-        var messageData = new Dictionary<string, CgibinMessageTemplateSendRequest.Types.DataItem>();
-        foreach (var item in dataInfo)
-        {
-            messageData.Add(item.Key, new CgibinMessageTemplateSendRequest.Types.DataItem() { Value = "" + item.Value.Value.ToString() + "" });
-        }
-
-        var accessToken = await GetCgibinToken();
-        var reqMessage = new CgibinMessageTemplateSendRequest()
-        {
-            AccessToken = accessToken,
-            TemplateId = input.TemplateId,
-            ToUserOpenId = input.ToUserOpenId,
-            Url = input.Url,
-            MiniProgram = new CgibinMessageTemplateSendRequest.Types.MiniProgram
-            {
-                AppId = _wechatApiClientFactory._wechatOptions.WxOpenAppId,
-                PagePath = input.MiniProgramPagePath,
-            },
-            Data = messageData
-        };
-        var resMessage = await _wechatApiClient.ExecuteCgibinMessageTemplateSendAsync(reqMessage);
-        return resMessage;
-    }
-
-    /// <summary>
-    /// 删除模板 🔖
-    /// </summary>
-    /// <param name="input"></param>
-    /// <returns></returns>
-    [ApiDescriptionSettings(Name = "DeleteMessageTemplate"), HttpPost]
-    [DisplayName("删除模板")]
-    public async Task<dynamic> DeleteMessageTemplate(DeleteMessageTemplateInput input)
-    {
-        var accessToken = await GetCgibinToken();
-        var reqMessage = new CgibinTemplateDeletePrivateTemplateRequest()
-        {
-            AccessToken = accessToken,
-            TemplateId = input.TemplateId
-        };
-        var resTemplate = await _wechatApiClient.ExecuteCgibinTemplateDeletePrivateTemplateAsync(reqMessage);
-        return resTemplate;
-    }
-
-    /// <summary>
-    /// 获取Access_token
-    /// </summary>
-    [NonAction]
-    public async Task<string> GetCgibinToken()
-    {
-        // 先从缓存中取 AccessToken
-        var wx_accessToken = _sysCacheService.Get<string>("sys_wx_accessToken");
-        if (!string.IsNullOrWhiteSpace(wx_accessToken))
-        {
-            return wx_accessToken;
-        }
-
-        // 若缓存没有则从微信公众号重新获取 AccessToken
-        var reqCgibinToken = new CgibinTokenRequest();
-        var resCgibinToken = await _wechatApiClient.ExecuteCgibinTokenAsync(reqCgibinToken);
-        if (resCgibinToken.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
-            throw Oops.Oh(resCgibinToken.ErrorMessage + " " + resCgibinToken.ErrorCode);
-
-        _sysCacheService.Set("sys_wx_accessToken", resCgibinToken.AccessToken, TimeSpan.FromSeconds(resCgibinToken.ExpiresIn)); // 缓存 AccessToken
-        return resCgibinToken.AccessToken;
-    }
+	private readonly SqlSugarRepository<SysWechatUser> _sysWechatUserRep;
+	private readonly SysConfigService _sysConfigService;
+	private readonly WechatApiClientFactory _wechatApiClientFactory;
+	private readonly WechatApiClient _wechatApiClient;
+	private readonly SysCacheService _sysCacheService;
+
+	public SysWechatService(SqlSugarRepository<SysWechatUser> sysWechatUserRep,
+		SysConfigService sysConfigService,
+		WechatApiClientFactory wechatApiClientFactory,
+		SysCacheService sysCacheService)
+	{
+		_sysWechatUserRep = sysWechatUserRep;
+		_sysConfigService = sysConfigService;
+		_wechatApiClientFactory = wechatApiClientFactory;
+		_wechatApiClient = wechatApiClientFactory.CreateWechatClient();
+		_sysCacheService = sysCacheService;
+	}
+
+	/// <summary>
+	/// 生成网页授权Url 🔖
+	/// </summary>
+	/// <param name="input"></param>
+	/// <returns></returns>
+	[AllowAnonymous]
+	[DisplayName("生成网页授权Url")]
+	public string GenAuthUrl(GenAuthUrlInput input)
+	{
+		return _wechatApiClient.GenerateParameterizedUrlForConnectOAuth2Authorize(input.RedirectUrl, input.Scope, input.State);
+	}
+
+	/// <summary>
+	/// 获取微信用户OpenId 🔖
+	/// </summary>
+	/// <param name="input"></param>
+	[AllowAnonymous]
+	[DisplayName("获取微信用户OpenId")]
+	public async Task<string> SnsOAuth2([FromQuery] WechatOAuth2Input input)
+	{
+		var reqOAuth2 = new SnsOAuth2AccessTokenRequest()
+		{
+			Code = input.Code,
+		};
+		var resOAuth2 = await _wechatApiClient.ExecuteSnsOAuth2AccessTokenAsync(reqOAuth2);
+		if (resOAuth2.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
+			throw Oops.Oh(resOAuth2.ErrorMessage + " " + resOAuth2.ErrorCode);
+
+		var wxUser = await _sysWechatUserRep.GetFirstAsync(p => p.OpenId == resOAuth2.OpenId);
+		if (wxUser == null)
+		{
+			var reqUserInfo = new SnsUserInfoRequest()
+			{
+				OpenId = resOAuth2.OpenId,
+				AccessToken = resOAuth2.AccessToken,
+			};
+			var resUserInfo = await _wechatApiClient.ExecuteSnsUserInfoAsync(reqUserInfo);
+			wxUser = resUserInfo.Adapt<SysWechatUser>();
+			wxUser.Avatar = resUserInfo.HeadImageUrl;
+			wxUser.NickName = resUserInfo.Nickname;
+			wxUser.OpenId = resOAuth2.OpenId;
+			wxUser.UnionId = resOAuth2.UnionId;
+			wxUser.AccessToken = resOAuth2.AccessToken;
+			wxUser.RefreshToken = resOAuth2.RefreshToken;
+			wxUser = await _sysWechatUserRep.AsInsertable(wxUser).ExecuteReturnEntityAsync();
+		}
+		else
+		{
+			wxUser.AccessToken = resOAuth2.AccessToken;
+			wxUser.RefreshToken = resOAuth2.RefreshToken;
+			await _sysWechatUserRep.AsUpdateable(wxUser).IgnoreColumns(true).ExecuteCommandAsync();
+		}
+
+		return resOAuth2.OpenId;
+	}
+
+	/// <summary>
+	/// 微信用户登录OpenId 🔖
+	/// </summary>
+	/// <param name="input"></param>
+	/// <returns></returns>
+	[AllowAnonymous]
+	[DisplayName("微信用户登录OpenId")]
+	public async Task<dynamic> OpenIdLogin(WechatUserLogin input)
+	{
+		var wxUser = await _sysWechatUserRep.GetFirstAsync(p => p.OpenId == input.OpenId);
+		if (wxUser == null)
+			throw Oops.Oh("微信用户登录OpenId错误");
+
+		var tokenExpire = await _sysConfigService.GetTokenExpire();
+		return new
+		{
+			wxUser.Avatar,
+			accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
+			{
+				{ ClaimConst.UserId, wxUser.Id },
+				{ ClaimConst.NickName, wxUser.NickName },
+				{ ClaimConst.LoginMode, LoginModeEnum.APP },
+			}, tokenExpire)
+		};
+	}
+
+	/// <summary>
+	/// 获取配置签名参数(wx.config) 🔖
+	/// </summary>
+	/// <returns></returns>
+	[DisplayName("获取配置签名参数(wx.config)")]
+	public async Task<dynamic> GenConfigPara(SignatureInput input)
+	{
+		string ticket = await _wechatApiClientFactory.TryGetWechatJsApiTicketAsync();
+		return _wechatApiClient.GenerateParametersForJSSDKConfig(ticket, input.Url);
+	}
+
+	/// <summary>
+	/// 获取模板列表 🔖
+	/// </summary>
+	[DisplayName("获取模板列表")]
+	public async Task<dynamic> GetMessageTemplateList()
+	{
+		var accessToken = await GetCgibinToken();
+		var reqTemplate = new CgibinTemplateGetAllPrivateTemplateRequest()
+		{
+			AccessToken = accessToken
+		};
+		var resTemplate = await _wechatApiClient.ExecuteCgibinTemplateGetAllPrivateTemplateAsync(reqTemplate);
+		if (resTemplate.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
+			throw Oops.Oh(resTemplate.ErrorMessage + " " + resTemplate.ErrorCode);
+
+		return resTemplate.TemplateList;
+	}
+
+	/// <summary>
+	/// 发送模板消息 🔖
+	/// </summary>
+	/// <param name="input"></param>
+	/// <returns></returns>
+	[DisplayName("发送模板消息")]
+	public async Task<dynamic> SendTemplateMessage(MessageTemplateSendInput input)
+	{
+		var dataInfo = input.Data.ToDictionary(k => k.Key, k => k.Value);
+		var messageData = new Dictionary<string, CgibinMessageTemplateSendRequest.Types.DataItem>();
+		foreach (var item in dataInfo)
+		{
+			messageData.Add(item.Key, new CgibinMessageTemplateSendRequest.Types.DataItem() { Value = "" + item.Value.Value.ToString() + "" });
+		}
+
+		var accessToken = await GetCgibinToken();
+		var reqMessage = new CgibinMessageTemplateSendRequest()
+		{
+			AccessToken = accessToken,
+			TemplateId = input.TemplateId,
+			ToUserOpenId = input.ToUserOpenId,
+			Url = input.Url,
+			MiniProgram = new CgibinMessageTemplateSendRequest.Types.MiniProgram
+			{
+				AppId = _wechatApiClientFactory._wechatOptions.WxOpenAppId,
+				PagePath = input.MiniProgramPagePath,
+			},
+			Data = messageData
+		};
+		var resMessage = await _wechatApiClient.ExecuteCgibinMessageTemplateSendAsync(reqMessage);
+		return resMessage;
+	}
+
+	/// <summary>
+	/// 删除模板 🔖
+	/// </summary>
+	/// <param name="input"></param>
+	/// <returns></returns>
+	[ApiDescriptionSettings(Name = "DeleteMessageTemplate"), HttpPost]
+	[DisplayName("删除模板")]
+	public async Task<dynamic> DeleteMessageTemplate(DeleteMessageTemplateInput input)
+	{
+		var accessToken = await GetCgibinToken();
+		var reqMessage = new CgibinTemplateDeletePrivateTemplateRequest()
+		{
+			AccessToken = accessToken,
+			TemplateId = input.TemplateId
+		};
+		var resTemplate = await _wechatApiClient.ExecuteCgibinTemplateDeletePrivateTemplateAsync(reqMessage);
+		return resTemplate;
+	}
+
+	/// <summary>
+	/// 获取Access_token
+	/// </summary>
+	[NonAction]
+	public async Task<string> GetCgibinToken()
+	{
+		// 先从缓存中取 AccessToken
+		var wx_accessToken = _sysCacheService.Get<string>("sys_wx_accessToken");
+		if (!string.IsNullOrWhiteSpace(wx_accessToken))
+		{
+			return wx_accessToken;
+		}
+
+		// 若缓存没有则从微信公众号重新获取 AccessToken
+		var reqCgibinToken = new CgibinTokenRequest();
+		var resCgibinToken = await _wechatApiClient.ExecuteCgibinTokenAsync(reqCgibinToken);
+		if (resCgibinToken.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
+			throw Oops.Oh(resCgibinToken.ErrorMessage + " " + resCgibinToken.ErrorCode);
+
+		_sysCacheService.Set("sys_wx_accessToken", resCgibinToken.AccessToken, TimeSpan.FromSeconds(resCgibinToken.ExpiresIn)); // 缓存 AccessToken
+		return resCgibinToken.AccessToken;
+	}
 }

+ 192 - 170
Admin.NET/Admin.NET.Core/Service/Wechat/WechatApiHttpClient.cs

@@ -13,174 +13,196 @@ namespace Admin.NET.Core.Service;
 /// </summary>
 public partial class WechatApiClientFactory : ISingleton
 {
-    private readonly IHttpClientFactory _httpClientFactory;
-    public readonly WechatOptions _wechatOptions;
-    private readonly SysCacheService _sysCacheService;
-
-    public WechatApiClientFactory(IHttpClientFactory httpClientFactory, IOptions<WechatOptions> wechatOptions, SysCacheService sysCacheService)
-    {
-        _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
-        _wechatOptions = wechatOptions.Value ?? throw new ArgumentNullException(nameof(wechatOptions));
-        _sysCacheService = sysCacheService;
-    }
-
-    /// <summary>
-    /// 微信公众号
-    /// </summary>
-    /// <returns></returns>
-    public WechatApiClient CreateWechatClient()
-    {
-        if (string.IsNullOrEmpty(_wechatOptions.WechatAppId) || string.IsNullOrEmpty(_wechatOptions.WechatAppSecret))
-            throw Oops.Oh("微信公众号配置错误");
-
-        var client = WechatApiClientBuilder.Create(new WechatApiClientOptions()
-        {
-            AppId = _wechatOptions.WechatAppId,
-            AppSecret = _wechatOptions.WechatAppSecret,
-            PushToken = _wechatOptions.WechatToken,
-            PushEncodingAESKey = _wechatOptions.WechatEncodingAESKey,
-        })
-        .UseHttpClient(_httpClientFactory.CreateClient(), disposeClient: false) // 设置 HttpClient 不随客户端一同销毁
-        .Build();
-
-        client.Configure(config =>
-        {
-            JsonSerializerSettings jsonSerializerSettings = NewtonsoftJsonSerializer.GetDefaultSerializerSettings();
-            jsonSerializerSettings.Formatting = Formatting.Indented;
-            config.JsonSerializer = new NewtonsoftJsonSerializer(jsonSerializerSettings); // 指定 System.Text.Json JSON序列化
-            // config.JsonSerializer = new SystemTextJsonSerializer(jsonSerializerOptions); // 指定 Newtonsoft.Json  JSON序列化
-        });
-
-        return client;
-    }
-
-    /// <summary>
-    /// 微信小程序
-    /// </summary>
-    /// <returns></returns>
-    public WechatApiClient CreateWxOpenClient()
-    {
-        if (string.IsNullOrEmpty(_wechatOptions.WxOpenAppId) || string.IsNullOrEmpty(_wechatOptions.WxOpenAppSecret))
-            throw Oops.Oh("微信小程序配置错误");
-
-        var client = WechatApiClientBuilder.Create(new WechatApiClientOptions()
-        {
-            AppId = _wechatOptions.WxOpenAppId,
-            AppSecret = _wechatOptions.WxOpenAppSecret,
-            PushToken = _wechatOptions.WxToken,
-            PushEncodingAESKey = _wechatOptions.WxEncodingAESKey,
-        })
-        .UseHttpClient(_httpClientFactory.CreateClient(), disposeClient: false) // 设置 HttpClient 不随客户端一同销毁
-        .Build();
-
-        client.Configure(config =>
-        {
-            JsonSerializerSettings jsonSerializerSettings = NewtonsoftJsonSerializer.GetDefaultSerializerSettings();
-            jsonSerializerSettings.Formatting = Formatting.Indented;
-            config.JsonSerializer = new NewtonsoftJsonSerializer(jsonSerializerSettings); // 指定 System.Text.Json JSON序列化
-            // config.JsonSerializer = new SystemTextJsonSerializer(jsonSerializerOptions); // 指定 Newtonsoft.Json  JSON序列化
-        });
-
-        return client;
-    }
-
-    /// <summary>
-    /// 获取微信公众号AccessToken
-    /// </summary>
-    /// <returns></returns>
-    public async Task<string> TryGetWechatAccessTokenAsync()
-    {
-        if (!_sysCacheService.ExistKey($"WxAccessToken_{_wechatOptions.WechatAppId}") || string.IsNullOrEmpty(_sysCacheService.Get<string>($"WxAccessToken_{_wechatOptions.WechatAppId}")))
-        {
-            var client = CreateWechatClient();
-            var reqCgibinToken = new CgibinTokenRequest();
-            var resCgibinToken = await client.ExecuteCgibinTokenAsync(reqCgibinToken);
-            if (resCgibinToken.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
-                throw Oops.Oh(resCgibinToken.ErrorMessage + " " + resCgibinToken.ErrorCode);
-            _sysCacheService.Set($"WxAccessToken_{_wechatOptions.WechatAppId}", resCgibinToken.AccessToken, TimeSpan.FromSeconds(resCgibinToken.ExpiresIn - 60));
-        }
-
-        return _sysCacheService.Get<string>($"WxAccessToken_{_wechatOptions.WechatAppId}");
-    }
-
-    /// <summary>
-    /// 获取微信小程序AccessToken
-    /// </summary>
-    /// <returns></returns>
-    public async Task<string> TryGetWxOpenAccessTokenAsync()
-    {
-        if (!_sysCacheService.ExistKey($"WxAccessToken_{_wechatOptions.WxOpenAppId}") || string.IsNullOrEmpty(_sysCacheService.Get<string>($"WxAccessToken_{_wechatOptions.WxOpenAppId}")))
-        {
-            var client = CreateWxOpenClient();
-            var reqCgibinToken = new CgibinTokenRequest();
-            var resCgibinToken = await client.ExecuteCgibinTokenAsync(reqCgibinToken);
-            if (resCgibinToken.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
-                throw Oops.Oh(resCgibinToken.ErrorMessage + " " + resCgibinToken.ErrorCode);
-            _sysCacheService.Set($"WxAccessToken_{_wechatOptions.WxOpenAppId}", resCgibinToken.AccessToken, TimeSpan.FromSeconds(resCgibinToken.ExpiresIn - 60));
-        }
-
-        return _sysCacheService.Get<string>($"WxAccessToken_{_wechatOptions.WxOpenAppId}");
-    }
-
-    /// <summary>
-    /// 检查微信公众号AccessToken
-    /// </summary>
-    /// <returns></returns>
-    public async Task CheckWechatAccessTokenAsync()
-    {
-        if (string.IsNullOrEmpty(_wechatOptions.WechatAppId) || string.IsNullOrEmpty(_wechatOptions.WechatAppSecret)) return;
-
-        var req = new CgibinOpenApiQuotaGetRequest
-        {
-            AccessToken = await TryGetWxOpenAccessTokenAsync(),
-            CgiPath = "/cgi-bin/token"
-        };
-        var client = CreateWechatClient();
-        var res = await client.ExecuteCgibinOpenApiQuotaGetAsync(req);
-
-        var originColor = Console.ForegroundColor;
-        if (res.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
-        {
-            _sysCacheService.Remove($"WxAccessToken_{_wechatOptions.WechatAppId}");
-            Console.ForegroundColor = ConsoleColor.Red;
-            Console.WriteLine("【" + DateTime.Now + "】" + _wechatOptions.WxOpenAppId + " 微信公众号令牌 无效");
-        }
-        else
-        {
-            Console.ForegroundColor = ConsoleColor.Magenta;
-            Console.WriteLine("【" + DateTime.Now + "】" + _wechatOptions.WxOpenAppId + " 微信公众号令牌 有效");
-        }
-        Console.ForegroundColor = originColor;
-    }
-
-    /// <summary>
-    /// 检查微信小程序AccessToken
-    /// </summary>
-    /// <returns></returns>
-    public async Task CheckWxOpenAccessTokenAsync()
-    {
-        if (string.IsNullOrEmpty(_wechatOptions.WxOpenAppId) || string.IsNullOrEmpty(_wechatOptions.WxOpenAppSecret)) return;
-
-        var req = new CgibinOpenApiQuotaGetRequest
-        {
-            AccessToken = await TryGetWxOpenAccessTokenAsync(),
-            CgiPath = "/cgi-bin/token"
-        };
-        var client = CreateWxOpenClient();
-        var res = await client.ExecuteCgibinOpenApiQuotaGetAsync(req);
-
-        var originColor = Console.ForegroundColor;
-        if (res.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
-        {
-            _sysCacheService.Remove($"WxAccessToken_{_wechatOptions.WxOpenAppId}");
-            Console.ForegroundColor = ConsoleColor.Red;
-            Console.WriteLine("【" + DateTime.Now + "】" + _wechatOptions.WxOpenAppId + " 微信小程序令牌 无效");
-        }
-        else
-        {
-            Console.ForegroundColor = ConsoleColor.Magenta;
-            Console.WriteLine("【" + DateTime.Now + "】" + _wechatOptions.WxOpenAppId + " 微信小程序令牌 有效");
-        }
-        Console.ForegroundColor = originColor;
-    }
+	private readonly IHttpClientFactory _httpClientFactory;
+	public readonly WechatOptions _wechatOptions;
+	private readonly SysCacheService _sysCacheService;
+
+	public WechatApiClientFactory(IHttpClientFactory httpClientFactory, IOptions<WechatOptions> wechatOptions, SysCacheService sysCacheService)
+	{
+		_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
+		_wechatOptions = wechatOptions.Value ?? throw new ArgumentNullException(nameof(wechatOptions));
+		_sysCacheService = sysCacheService;
+	}
+
+	/// <summary>
+	/// 微信公众号
+	/// </summary>
+	/// <returns></returns>
+	public WechatApiClient CreateWechatClient()
+	{
+		if (string.IsNullOrEmpty(_wechatOptions.WechatAppId) || string.IsNullOrEmpty(_wechatOptions.WechatAppSecret))
+			throw Oops.Oh("微信公众号配置错误");
+
+		var client = WechatApiClientBuilder.Create(new WechatApiClientOptions()
+		{
+			AppId = _wechatOptions.WechatAppId,
+			AppSecret = _wechatOptions.WechatAppSecret,
+			PushToken = _wechatOptions.WechatToken,
+			PushEncodingAESKey = _wechatOptions.WechatEncodingAESKey,
+		})
+		.UseHttpClient(_httpClientFactory.CreateClient(), disposeClient: false) // 设置 HttpClient 不随客户端一同销毁
+		.Build();
+
+		client.Configure(config =>
+		{
+			JsonSerializerSettings jsonSerializerSettings = NewtonsoftJsonSerializer.GetDefaultSerializerSettings();
+			jsonSerializerSettings.Formatting = Formatting.Indented;
+			config.JsonSerializer = new NewtonsoftJsonSerializer(jsonSerializerSettings); // 指定 System.Text.Json JSON序列化
+																						  // config.JsonSerializer = new SystemTextJsonSerializer(jsonSerializerOptions); // 指定 Newtonsoft.Json  JSON序列化
+		});
+
+		return client;
+	}
+
+	/// <summary>
+	/// 微信小程序
+	/// </summary>
+	/// <returns></returns>
+	public WechatApiClient CreateWxOpenClient()
+	{
+		if (string.IsNullOrEmpty(_wechatOptions.WxOpenAppId) || string.IsNullOrEmpty(_wechatOptions.WxOpenAppSecret))
+			throw Oops.Oh("微信小程序配置错误");
+
+		var client = WechatApiClientBuilder.Create(new WechatApiClientOptions()
+		{
+			AppId = _wechatOptions.WxOpenAppId,
+			AppSecret = _wechatOptions.WxOpenAppSecret,
+			PushToken = _wechatOptions.WxToken,
+			PushEncodingAESKey = _wechatOptions.WxEncodingAESKey,
+		})
+		.UseHttpClient(_httpClientFactory.CreateClient(), disposeClient: false) // 设置 HttpClient 不随客户端一同销毁
+		.Build();
+
+		client.Configure(config =>
+		{
+			JsonSerializerSettings jsonSerializerSettings = NewtonsoftJsonSerializer.GetDefaultSerializerSettings();
+			jsonSerializerSettings.Formatting = Formatting.Indented;
+			config.JsonSerializer = new NewtonsoftJsonSerializer(jsonSerializerSettings); // 指定 System.Text.Json JSON序列化
+																						  // config.JsonSerializer = new SystemTextJsonSerializer(jsonSerializerOptions); // 指定 Newtonsoft.Json  JSON序列化
+		});
+
+		return client;
+	}
+
+	/// <summary>
+	/// 获取微信公众号AccessToken
+	/// </summary>
+	/// <returns></returns>
+	public async Task<string> TryGetWechatAccessTokenAsync()
+	{
+		if (!_sysCacheService.ExistKey($"WxAccessToken_{_wechatOptions.WechatAppId}") || string.IsNullOrEmpty(_sysCacheService.Get<string>($"WxAccessToken_{_wechatOptions.WechatAppId}")))
+		{
+			var client = CreateWechatClient();
+			var reqCgibinToken = new CgibinTokenRequest();
+			var resCgibinToken = await client.ExecuteCgibinTokenAsync(reqCgibinToken);
+			if (resCgibinToken.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
+				throw Oops.Oh(resCgibinToken.ErrorMessage + " " + resCgibinToken.ErrorCode);
+			_sysCacheService.Set($"WxAccessToken_{_wechatOptions.WechatAppId}", resCgibinToken.AccessToken, TimeSpan.FromSeconds(resCgibinToken.ExpiresIn - 60));
+		}
+
+		return _sysCacheService.Get<string>($"WxAccessToken_{_wechatOptions.WechatAppId}");
+	}
+
+	/// <summary>
+	/// 获取微信小程序AccessToken
+	/// </summary>
+	/// <returns></returns>
+	public async Task<string> TryGetWxOpenAccessTokenAsync()
+	{
+		if (!_sysCacheService.ExistKey($"WxAccessToken_{_wechatOptions.WxOpenAppId}") || string.IsNullOrEmpty(_sysCacheService.Get<string>($"WxAccessToken_{_wechatOptions.WxOpenAppId}")))
+		{
+			var client = CreateWxOpenClient();
+			var reqCgibinToken = new CgibinTokenRequest();
+			var resCgibinToken = await client.ExecuteCgibinTokenAsync(reqCgibinToken);
+			if (resCgibinToken.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
+				throw Oops.Oh(resCgibinToken.ErrorMessage + " " + resCgibinToken.ErrorCode);
+			_sysCacheService.Set($"WxAccessToken_{_wechatOptions.WxOpenAppId}", resCgibinToken.AccessToken, TimeSpan.FromSeconds(resCgibinToken.ExpiresIn - 60));
+		}
+
+		return _sysCacheService.Get<string>($"WxAccessToken_{_wechatOptions.WxOpenAppId}");
+	}
+
+	/// <summary>
+	/// 检查微信公众号AccessToken
+	/// </summary>
+	/// <returns></returns>
+	public async Task CheckWechatAccessTokenAsync()
+	{
+		if (string.IsNullOrEmpty(_wechatOptions.WechatAppId) || string.IsNullOrEmpty(_wechatOptions.WechatAppSecret)) return;
+
+		var req = new CgibinOpenApiQuotaGetRequest
+		{
+			AccessToken = await TryGetWxOpenAccessTokenAsync(),
+			CgiPath = "/cgi-bin/token"
+		};
+		var client = CreateWechatClient();
+		var res = await client.ExecuteCgibinOpenApiQuotaGetAsync(req);
+
+		var originColor = Console.ForegroundColor;
+		if (res.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
+		{
+			_sysCacheService.Remove($"WxAccessToken_{_wechatOptions.WechatAppId}");
+			Console.ForegroundColor = ConsoleColor.Red;
+			Console.WriteLine("【" + DateTime.Now + "】" + _wechatOptions.WxOpenAppId + " 微信公众号令牌 无效");
+		}
+		else
+		{
+			Console.ForegroundColor = ConsoleColor.Magenta;
+			Console.WriteLine("【" + DateTime.Now + "】" + _wechatOptions.WxOpenAppId + " 微信公众号令牌 有效");
+		}
+		Console.ForegroundColor = originColor;
+	}
+
+	/// <summary>
+	/// 检查微信小程序AccessToken
+	/// </summary>
+	/// <returns></returns>
+	public async Task CheckWxOpenAccessTokenAsync()
+	{
+		if (string.IsNullOrEmpty(_wechatOptions.WxOpenAppId) || string.IsNullOrEmpty(_wechatOptions.WxOpenAppSecret)) return;
+
+		var req = new CgibinOpenApiQuotaGetRequest
+		{
+			AccessToken = await TryGetWxOpenAccessTokenAsync(),
+			CgiPath = "/cgi-bin/token"
+		};
+		var client = CreateWxOpenClient();
+		var res = await client.ExecuteCgibinOpenApiQuotaGetAsync(req);
+
+		var originColor = Console.ForegroundColor;
+		if (res.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
+		{
+			_sysCacheService.Remove($"WxAccessToken_{_wechatOptions.WxOpenAppId}");
+			Console.ForegroundColor = ConsoleColor.Red;
+			Console.WriteLine("【" + DateTime.Now + "】" + _wechatOptions.WxOpenAppId + " 微信小程序令牌 无效");
+		}
+		else
+		{
+			Console.ForegroundColor = ConsoleColor.Magenta;
+			Console.WriteLine("【" + DateTime.Now + "】" + _wechatOptions.WxOpenAppId + " 微信小程序令牌 有效");
+		}
+		Console.ForegroundColor = originColor;
+	}
+
+	/// <summary>
+	/// 获取微信JS接口临时票据jsapi_ticket
+	/// </summary>
+	/// <returns></returns>
+	public async Task<string> TryGetWechatJsApiTicketAsync()
+	{
+		if (!_sysCacheService.ExistKey($"WxJsApiTicket_{_wechatOptions.WechatAppId}") || string.IsNullOrEmpty(_sysCacheService.Get<string>($"WxJsApiTicket_{_wechatOptions.WechatAppId}")))
+		{
+			var accessToken = await TryGetWechatAccessTokenAsync();
+			var client = CreateWechatClient();
+			var request = new CgibinTicketGetTicketRequest()
+			{
+				AccessToken = accessToken
+			};
+			var response = await client.ExecuteCgibinTicketGetTicketAsync(request);
+			if (!response.IsSuccessful())
+				throw Oops.Oh(response.ErrorMessage + " " + response.ErrorCode);
+			_sysCacheService.Set($"WxJsApiTicket_{_wechatOptions.WechatAppId}", response.Ticket, TimeSpan.FromSeconds(response.ExpiresIn - 60));
+		}
+		return _sysCacheService.Get<string>($"WxJsApiTicket_{_wechatOptions.WechatAppId}");
+	}
 }