From 2688957cf396c3fb53691c58c12676a9d2c3512e Mon Sep 17 00:00:00 2001 From: real-zony Date: Wed, 10 Jan 2024 12:43:47 +0800 Subject: [PATCH 1/3] refactor: Removed the unused HttpClient factory class. --- .../AbpWeChatPayModule.cs | 5 + .../AbpWeChatPayHttpClientFactory.cs | 108 ------------------ .../DefaultWeChatPayApiRequester.cs | 6 +- .../HttpMessageHandlerCacheModel.cs | 14 --- .../IAbpWeChatPayHttpClientFactory.cs | 10 -- 5 files changed, 8 insertions(+), 135 deletions(-) delete mode 100644 src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/AbpWeChatPayHttpClientFactory.cs delete mode 100644 src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/HttpMessageHandlerCacheModel.cs delete mode 100644 src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/IAbpWeChatPayHttpClientFactory.cs diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/AbpWeChatPayModule.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/AbpWeChatPayModule.cs index 8ff11bc..1fb00c3 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/AbpWeChatPayModule.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/AbpWeChatPayModule.cs @@ -1,4 +1,5 @@ using EasyAbp.Abp.WeChat.Common; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.BlobStoring; using Volo.Abp.Json.Newtonsoft; using Volo.Abp.Modularity; @@ -13,5 +14,9 @@ namespace EasyAbp.Abp.WeChat.Pay )] public class AbpWeChatPayModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddHttpClient(); + } } } \ No newline at end of file diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/AbpWeChatPayHttpClientFactory.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/AbpWeChatPayHttpClientFactory.cs deleted file mode 100644 index b568a7a..0000000 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/AbpWeChatPayHttpClientFactory.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Net.Http; -using System.Security.Authentication; -using System.Threading.Tasks; -using EasyAbp.Abp.WeChat.Common.Extensions; -using EasyAbp.Abp.WeChat.Pay.Options; -using EasyAbp.Abp.WeChat.Pay.Security; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Timing; - -namespace EasyAbp.Abp.WeChat.Pay.ApiRequests; - -public class AbpWeChatPayHttpClientFactory : IAbpWeChatPayHttpClientFactory, ITransientDependency -{ - // TODO: Confirm checking every 10 second? @gdlc88 - public static TimeSpan SkipCertificateBytesCheckUntilDuration = TimeSpan.FromSeconds(10); - - private const string DefaultHandlerKey = "__Default__"; - - protected static ConcurrentDictionary>> CachedHandlers { get; } = - new(); - - protected IClock Clock { get; } - protected IAbpLazyServiceProvider AbpLazyServiceProvider { get; } - protected IAbpWeChatPayOptionsProvider AbpWeChatPayOptionsProvider { get; } - protected ICertificatesManager CertificatesManager { get; } - - public AbpWeChatPayHttpClientFactory( - IClock clock, - IAbpLazyServiceProvider abpLazyServiceProvider, - IAbpWeChatPayOptionsProvider abpWeChatPayOptionsProvider, - ICertificatesManager certificatesManager) - { - Clock = clock; - AbpLazyServiceProvider = abpLazyServiceProvider; - AbpWeChatPayOptionsProvider = abpWeChatPayOptionsProvider; - CertificatesManager = certificatesManager; - } - - public virtual async Task CreateAsync(string mchId) - { - var options = await AbpWeChatPayOptionsProvider.GetAsync(mchId); - var handler = await GetOrCreateHttpClientHandlerAsync(options); - - return new HttpClient(handler, disposeHandler: false); - } - - protected virtual async Task GetOrCreateHttpClientHandlerAsync(AbpWeChatPayOptions options) - { - if (!CachedHandlers.TryGetValue(options.MchId, out var item)) - { - return (await CachedHandlers.GetOrAdd(options.MchId ?? DefaultHandlerKey, - _ => new Lazy>(() => - CreateHttpClientHandlerCacheModelAsync(options))).Value).Handler; - } - - var handlerCacheModel = await item.Value; - if (handlerCacheModel.SkipCertificateBytesCheckUntil > Clock.Now) - { - return handlerCacheModel.Handler; - } - - var certificate = await CertificatesManager.GetCertificateAsync(options.MchId); - if (handlerCacheModel.WeChatPayCertificate.CertificateHashCode.VerifySha256(certificate.CertificateHashCode)) - { - return handlerCacheModel.Handler; - } - - // If the certificate has expired, need to pull the latest one from BLOB again. - CachedHandlers.TryUpdate( - options.MchId ?? DefaultHandlerKey, - new Lazy>(() => - CreateHttpClientHandlerCacheModelAsync(certificate)), - item); - - return (await CachedHandlers.GetOrDefault(options.MchId).Value).Handler; - } - - protected virtual async Task CreateHttpClientHandlerCacheModelAsync(AbpWeChatPayOptions options) - { - var certificate = await CertificatesManager.GetCertificateAsync(options.MchId); - return await CreateHttpClientHandlerCacheModelAsync(certificate); - } - - protected virtual Task CreateHttpClientHandlerCacheModelAsync(WeChatPayCertificate weChatPayCertificate) - { - var handler = new HttpClientHandler - { - ClientCertificateOptions = ClientCertificateOption.Manual, - SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls - }; - - if (weChatPayCertificate.X509Certificate != null) - { - handler.ClientCertificates.Add(weChatPayCertificate.X509Certificate); - handler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true; - } - - return Task.FromResult(new HttpMessageHandlerCacheModel - { - Handler = handler, - WeChatPayCertificate = weChatPayCertificate, - SkipCertificateBytesCheckUntil = Clock.Now.Add(SkipCertificateBytesCheckUntilDuration) - }); - } -} \ No newline at end of file diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/DefaultWeChatPayApiRequester.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/DefaultWeChatPayApiRequester.cs index b8c5ecd..4f9fb26 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/DefaultWeChatPayApiRequester.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/DefaultWeChatPayApiRequester.cs @@ -23,11 +23,11 @@ public class DefaultWeChatPayApiRequester : IWeChatPayApiRequester, ITransientDe NullValueHandling = NullValueHandling.Ignore }; - private readonly IAbpWeChatPayHttpClientFactory _httpClientFactory; + private readonly IHttpClientFactory _httpClientFactory; private readonly IWeChatPayAuthorizationGenerator _authorizationGenerator; private readonly IAbpWeChatPayOptionsProvider _optionsProvider; - public DefaultWeChatPayApiRequester(IAbpWeChatPayHttpClientFactory httpClientFactory, + public DefaultWeChatPayApiRequester(IHttpClientFactory httpClientFactory, IAbpWeChatPayOptionsProvider optionsProvider, IWeChatPayAuthorizationGenerator authorizationGenerator) { @@ -66,7 +66,7 @@ public async Task RequestRawAsync(HttpMethod method, string await _authorizationGenerator.GenerateAuthorizationAsync(method, url, body, mchId)); // Sending the request. - var client = await _httpClientFactory.CreateAsync(mchId); + var client = _httpClientFactory.CreateClient(); return await client.SendAsync(request); } diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/HttpMessageHandlerCacheModel.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/HttpMessageHandlerCacheModel.cs deleted file mode 100644 index dfdb7e4..0000000 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/HttpMessageHandlerCacheModel.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Net.Http; -using EasyAbp.Abp.WeChat.Pay.Security; - -namespace EasyAbp.Abp.WeChat.Pay.ApiRequests; - -public class HttpMessageHandlerCacheModel -{ - public HttpMessageHandler Handler { get; set; } - - public WeChatPayCertificate WeChatPayCertificate { get; set; } - - public DateTime SkipCertificateBytesCheckUntil { get; set; } -} \ No newline at end of file diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/IAbpWeChatPayHttpClientFactory.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/IAbpWeChatPayHttpClientFactory.cs deleted file mode 100644 index fd62e8b..0000000 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/ApiRequests/IAbpWeChatPayHttpClientFactory.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Net.Http; -using System.Threading.Tasks; -using JetBrains.Annotations; - -namespace EasyAbp.Abp.WeChat.Pay.ApiRequests; - -public interface IAbpWeChatPayHttpClientFactory -{ - Task CreateAsync([CanBeNull] string mchId); -} \ No newline at end of file From cf4d59a431c2f4e9566371c4fead13c39139aaac Mon Sep 17 00:00:00 2001 From: real-zony Date: Wed, 10 Jan 2024 12:44:12 +0800 Subject: [PATCH 2/3] fix: Adjusted the lifecycle of the certificate manager to singleton. --- src/Pay/EasyAbp.Abp.WeChat.Pay/Security/CertificatesManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/CertificatesManager.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/CertificatesManager.cs index 45495bc..9170bd7 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/CertificatesManager.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/CertificatesManager.cs @@ -13,7 +13,7 @@ namespace EasyAbp.Abp.WeChat.Pay.Security; /// /// 的默认实现。 /// -public class CertificatesManager : ICertificatesManager, ITransientDependency +public class CertificatesManager : ICertificatesManager, ISingletonDependency { protected IAbpLazyServiceProvider AbpLazyServiceProvider { get; } protected IAbpWeChatPayOptionsProvider AbpWeChatPayOptionsProvider { get; } From a9e2958935aa8c4bb11d83abc771087d0a587989 Mon Sep 17 00:00:00 2001 From: real-zony Date: Wed, 10 Jan 2024 12:59:24 +0800 Subject: [PATCH 3/3] perf: Removed distributed cache and switched to using a dictionary for storing certificates. --- .../EasyAbp.Abp.WeChat.Pay.csproj | 1 - .../PlatformCertificateEntity.cs | 16 ------- .../PlatformCertificateManager.cs | 46 ++++--------------- .../PlatformCertificatesCacheItem.cs | 10 ---- .../PlatformCertificateManagerTests.cs | 4 -- 5 files changed, 10 insertions(+), 67 deletions(-) delete mode 100644 src/Pay/EasyAbp.Abp.WeChat.Pay/Security/PlatformCertificate/PlatformCertificatesCacheItem.cs diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/EasyAbp.Abp.WeChat.Pay.csproj b/src/Pay/EasyAbp.Abp.WeChat.Pay/EasyAbp.Abp.WeChat.Pay.csproj index 057b575..a83daa7 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/EasyAbp.Abp.WeChat.Pay.csproj +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/EasyAbp.Abp.WeChat.Pay.csproj @@ -17,7 +17,6 @@ - diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/PlatformCertificate/PlatformCertificateEntity.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/PlatformCertificate/PlatformCertificateEntity.cs index 3803518..94f641d 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/PlatformCertificate/PlatformCertificateEntity.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/PlatformCertificate/PlatformCertificateEntity.cs @@ -6,21 +6,6 @@ namespace EasyAbp.Abp.WeChat.Pay.Security.PlatformCertificate; -// TODO: There might be performance issues here because serialization and deserialization operations are involved every time the cache is accessed. The best practice would be to cache the certificates directly in memory. -public class X509Certificate2JsonConverter : JsonConverter -{ - public override void WriteJson(JsonWriter writer, X509Certificate2 value, JsonSerializer serializer) - { - serializer.Serialize(writer, value.Export(X509ContentType.Pfx)); - } - - public override X509Certificate2 ReadJson(JsonReader reader, Type objectType, X509Certificate2 existingValue, bool hasExistingValue, JsonSerializer serializer) - { - var bytes = serializer.Deserialize(reader); - return new X509Certificate2(bytes); - } -} - [Serializable] public class PlatformCertificateEntity { @@ -32,7 +17,6 @@ public class PlatformCertificateEntity public DateTime ExpireTime { get; set; } - [JsonConverter(typeof(X509Certificate2JsonConverter))] public X509Certificate2 Certificate { get; set; } public PlatformCertificateEntity() diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/PlatformCertificate/PlatformCertificateManager.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/PlatformCertificate/PlatformCertificateManager.cs index fd95cb2..243d279 100644 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/PlatformCertificate/PlatformCertificateManager.cs +++ b/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/PlatformCertificate/PlatformCertificateManager.cs @@ -1,13 +1,12 @@ using System; +using System.Collections.Concurrent; using System.Threading.Tasks; using EasyAbp.Abp.WeChat.Pay.Options; using EasyAbp.Abp.WeChat.Pay.Security.Extensions; using EasyAbp.Abp.WeChat.Pay.Services; using EasyAbp.Abp.WeChat.Pay.Services.OtherServices; -using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Logging; using Volo.Abp; -using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; namespace EasyAbp.Abp.WeChat.Pay.Security.PlatformCertificate; @@ -19,18 +18,16 @@ public class PlatformCertificateManager : IPlatformCertificateManager, ISingleto private readonly ILogger _logger; private readonly IAbpWeChatPayServiceFactory _abpWeChatPayServiceFactory; private readonly IAbpWeChatPayOptionsProvider _abpWeChatPayOptionsProvider; - private readonly IDistributedCache _distributedCache; + private readonly ConcurrentDictionary> _certificatesCache = new(); public PlatformCertificateManager( ILogger logger, IAbpWeChatPayServiceFactory abpWeChatPayServiceFactory, - IAbpWeChatPayOptionsProvider abpWeChatPayOptionsProvider, - IDistributedCache distributedCache) + IAbpWeChatPayOptionsProvider abpWeChatPayOptionsProvider) { _logger = logger; _abpWeChatPayServiceFactory = abpWeChatPayServiceFactory; _abpWeChatPayOptionsProvider = abpWeChatPayOptionsProvider; - _distributedCache = distributedCache; } public virtual async Task GetPlatformCertificateAsync(string mchId, string serialNo) @@ -38,19 +35,14 @@ public virtual async Task GetPlatformCertificateAsync Check.NotNullOrWhiteSpace(mchId, nameof(mchId)); Check.NotNullOrWhiteSpace(serialNo, nameof(serialNo)); - var cacheItem = await _distributedCache.GetAsync(PlatformCertificatesCacheItemKey); - if (cacheItem != null) - { - return GetSpecifiedCachedCertificate(cacheItem, serialNo); - } + var cacheItem = _certificatesCache.TryGetValue(serialNo, out var lazyCertificate) + ? lazyCertificate.Value + : null; + if (cacheItem != null) return cacheItem; var options = await _abpWeChatPayOptionsProvider.GetAsync(mchId); - var certificateService = await _abpWeChatPayServiceFactory.CreateAsync(mchId); - cacheItem = new PlatformCertificatesCacheItem(); - var cacheExpiration = TimeSpan.FromDays(31); - try { var certificates = await certificateService.GetPlatformCertificatesAsync(); @@ -62,35 +54,17 @@ public virtual async Task GetPlatformCertificateAsync certificate.EncryptCertificateData.Nonce, certificate.EncryptCertificateData.Ciphertext); - cacheItem.Certificates.Add(certificate.SerialNo, + _certificatesCache.TryAdd(certificate.SerialNo,new Lazy(() => new PlatformCertificateEntity(certificate.SerialNo, certificateString, - certificate.EffectiveTime, certificate.ExpireTime)); + certificate.EffectiveTime, certificate.ExpireTime))); } } catch (Exception e) { _logger.LogWarning("Fail to get and cache the platform certificates"); _logger.LogException(e); - cacheExpiration = TimeSpan.FromSeconds(10); // lock for 10s on failure. - } - - await _distributedCache.SetAsync(PlatformCertificatesCacheItemKey, cacheItem, - new DistributedCacheEntryOptions - { - AbsoluteExpirationRelativeToNow = cacheExpiration - }); - - return GetSpecifiedCachedCertificate(cacheItem, serialNo); - } - - protected virtual PlatformCertificateEntity GetSpecifiedCachedCertificate( - PlatformCertificatesCacheItem cacheItem, string serialNo) - { - if (!cacheItem.Certificates.TryGetValue(serialNo, out var secretModel)) - { - throw new InvalidOperationException("Platform certificate not found."); } - return secretModel; + return _certificatesCache[serialNo].Value; } } \ No newline at end of file diff --git a/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/PlatformCertificate/PlatformCertificatesCacheItem.cs b/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/PlatformCertificate/PlatformCertificatesCacheItem.cs deleted file mode 100644 index 7741ef9..0000000 --- a/src/Pay/EasyAbp.Abp.WeChat.Pay/Security/PlatformCertificate/PlatformCertificatesCacheItem.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace EasyAbp.Abp.WeChat.Pay.Security.PlatformCertificate; - -[Serializable] -public class PlatformCertificatesCacheItem -{ - public Dictionary Certificates { get; set; } = new(); -} \ No newline at end of file diff --git a/tests/EasyAbp.Abp.WeChat.Pay.Tests/Security/PlatformCertificateManagerTests.cs b/tests/EasyAbp.Abp.WeChat.Pay.Tests/Security/PlatformCertificateManagerTests.cs index 40ab95b..61a74c5 100644 --- a/tests/EasyAbp.Abp.WeChat.Pay.Tests/Security/PlatformCertificateManagerTests.cs +++ b/tests/EasyAbp.Abp.WeChat.Pay.Tests/Security/PlatformCertificateManagerTests.cs @@ -1,7 +1,3 @@ -using System; -using System.IO; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; using EasyAbp.Abp.WeChat.Pay.Security.PlatformCertificate;