mirror of
https://gitee.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat.git
synced 2025-07-15 23:13:32 +08:00
refactor(tenpayv3): 优化反射性能
This commit is contained in:
parent
1eef179f0a
commit
6630c357ca
@ -6,150 +6,10 @@ using System.Reflection;
|
|||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||||
{
|
{
|
||||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
|
||||||
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities;
|
||||||
|
|
||||||
public static class WechatTenpayClientRequestEncryptionExtensions
|
public static class WechatTenpayClientRequestEncryptionExtensions
|
||||||
{
|
{
|
||||||
private static TRequest InnerEncryptRequestSensitivePropertyByRSA<TRequest>(WechatTenpayClient client, TRequest request)
|
|
||||||
where TRequest : WechatTenpayRequest
|
|
||||||
{
|
|
||||||
Utilities.ReflectionHelper.ReplacePropertyStringValue(ref request, (target, currentProp, oldValue) =>
|
|
||||||
{
|
|
||||||
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
|
|
||||||
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
|
|
||||||
.FirstOrDefault(attr => Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(attr.Scheme));
|
|
||||||
if (attribute is null)
|
|
||||||
return (false, oldValue);
|
|
||||||
|
|
||||||
if (client.PlatformCertificateManager is null)
|
|
||||||
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is not initialized.");
|
|
||||||
|
|
||||||
string certificate;
|
|
||||||
if (string.IsNullOrEmpty(request.WechatpayCertificateSerialNumber))
|
|
||||||
{
|
|
||||||
// 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的
|
|
||||||
IEnumerable<CertificateEntry> entries = client.PlatformCertificateManager.AllEntries()
|
|
||||||
.Where(e => CertificateEntry.ALGORITHM_TYPE_RSA.Equals(e.AlgorithmType))
|
|
||||||
.OrderByDescending(e => e.ExpireTime);
|
|
||||||
if (!entries.Any())
|
|
||||||
{
|
|
||||||
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is empty. Please make sure you have downloaded platform certificates first.");
|
|
||||||
}
|
|
||||||
|
|
||||||
CertificateEntry entry = entries.First();
|
|
||||||
certificate = entry.Certificate;
|
|
||||||
request.WechatpayCertificateSerialNumber = entry.SerialNumber;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值
|
|
||||||
CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(request.WechatpayCertificateSerialNumber!);
|
|
||||||
if (!entry.HasValue)
|
|
||||||
{
|
|
||||||
throw new WechatTenpayException($"Failed to encrypt request, because the platform certificate manager does not contain a certificate matched the serial number \"{request.WechatpayCertificateSerialNumber}\". Please make sure you have downloaded platform certificates first.");
|
|
||||||
}
|
|
||||||
|
|
||||||
certificate = entry.Value.Certificate;
|
|
||||||
}
|
|
||||||
|
|
||||||
string newValue;
|
|
||||||
switch (attribute.Algorithm)
|
|
||||||
{
|
|
||||||
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
|
|
||||||
{
|
|
||||||
newValue = Utilities.RSAUtility.EncryptWithECBByCertificate(
|
|
||||||
certificate: certificate,
|
|
||||||
plainText: oldValue
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
|
|
||||||
{
|
|
||||||
newValue = Utilities.RSAUtility.EncryptWithECBByCertificate(
|
|
||||||
certificate: certificate,
|
|
||||||
plainText: oldValue,
|
|
||||||
paddingMode: "PKCS1PADDING"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
throw new WechatTenpayException($"Failed to encrypt request. Unsupported encryption algorithm: \"{attribute.Algorithm}\".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (true, newValue);
|
|
||||||
});
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TRequest InnerEncryptRequestSensitivePropertyBySM<TRequest>(WechatTenpayClient client, TRequest request)
|
|
||||||
where TRequest : WechatTenpayRequest
|
|
||||||
{
|
|
||||||
Utilities.ReflectionHelper.ReplacePropertyStringValue(ref request, (target, currentProp, oldValue) =>
|
|
||||||
{
|
|
||||||
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
|
|
||||||
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
|
|
||||||
.FirstOrDefault(attr => Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(attr.Scheme));
|
|
||||||
if (attribute is null)
|
|
||||||
return (false, oldValue);
|
|
||||||
|
|
||||||
if (client.PlatformCertificateManager is null)
|
|
||||||
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is not initialized.");
|
|
||||||
|
|
||||||
string certificate;
|
|
||||||
if (string.IsNullOrEmpty(request.WechatpayCertificateSerialNumber))
|
|
||||||
{
|
|
||||||
// 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的
|
|
||||||
IEnumerable<CertificateEntry> entries = client.PlatformCertificateManager.AllEntries()
|
|
||||||
.Where(e => CertificateEntry.ALGORITHM_TYPE_SM2.Equals(e.AlgorithmType))
|
|
||||||
.OrderByDescending(e => e.ExpireTime);
|
|
||||||
if (!entries.Any())
|
|
||||||
{
|
|
||||||
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is empty. Please make sure you have downloaded platform certificates first.");
|
|
||||||
}
|
|
||||||
|
|
||||||
CertificateEntry entry = entries.First();
|
|
||||||
certificate = entry.Certificate;
|
|
||||||
request.WechatpayCertificateSerialNumber = entry.SerialNumber;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值
|
|
||||||
CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(request.WechatpayCertificateSerialNumber!);
|
|
||||||
if (!entry.HasValue)
|
|
||||||
{
|
|
||||||
throw new WechatTenpayException($"Failed to encrypt request, because the platform certificate manager does not contain a certificate matched the serial number \"{request.WechatpayCertificateSerialNumber}\". Please make sure you have downloaded platform certificates first.");
|
|
||||||
}
|
|
||||||
|
|
||||||
certificate = entry.Value.Certificate;
|
|
||||||
}
|
|
||||||
|
|
||||||
string newValue;
|
|
||||||
switch (attribute.Algorithm)
|
|
||||||
{
|
|
||||||
case Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1:
|
|
||||||
{
|
|
||||||
newValue = Utilities.SM2Utility.EncryptByCertificate(
|
|
||||||
certificate: certificate,
|
|
||||||
plainText: oldValue,
|
|
||||||
asn1Encoding: true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
throw new WechatTenpayException($"Failed to encrypt request. Unsupported encryption algorithm: \"{attribute.Algorithm}\".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (true, newValue);
|
|
||||||
});
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <para>加密请求中传入的敏感数据。该方法会改变传入的请求模型对象。</para>
|
/// <para>加密请求中传入的敏感数据。该方法会改变传入的请求模型对象。</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -164,19 +24,98 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 遍历并加密被标记为敏感数据的字段
|
bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute));
|
||||||
bool requireEncrypt = Attribute.IsDefined(request.GetType(), typeof(WechatTenpaySensitiveAttribute));
|
if (!requireEncrypt)
|
||||||
if (requireEncrypt)
|
return request;
|
||||||
{
|
|
||||||
switch (client.Credentials.SignScheme)
|
|
||||||
{
|
|
||||||
case Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256:
|
|
||||||
return InnerEncryptRequestSensitivePropertyByRSA(client, request);
|
|
||||||
|
|
||||||
case Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3:
|
string signScheme = client.Credentials.SignScheme;
|
||||||
return InnerEncryptRequestSensitivePropertyBySM(client, request);
|
string algorithmType = // 签名方式与加密算法保持一致:RSA_SHA256 签名需 RSA 加密,SM3 签名需 SM2 加密
|
||||||
|
Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_RSA :
|
||||||
|
Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_SM2 :
|
||||||
|
throw new WechatTenpayException($"Failed to encrypt request. Unsupported signing scheme: \"{signScheme}\".");
|
||||||
|
|
||||||
|
ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) =>
|
||||||
|
{
|
||||||
|
if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute)))
|
||||||
|
return (false, oldValue);
|
||||||
|
|
||||||
|
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
|
||||||
|
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
|
||||||
|
.FirstOrDefault(attr => attr.Scheme == signScheme);
|
||||||
|
if (attribute is null)
|
||||||
|
return (false, oldValue);
|
||||||
|
|
||||||
|
string certificate;
|
||||||
|
if (string.IsNullOrEmpty(request.WechatpayCertificateSerialNumber))
|
||||||
|
{
|
||||||
|
if (client.PlatformCertificateManager is null)
|
||||||
|
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is not initialized.");
|
||||||
|
|
||||||
|
// 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的
|
||||||
|
IEnumerable<CertificateEntry> entries = client.PlatformCertificateManager.AllEntries()
|
||||||
|
.Where(e => e.AlgorithmType == algorithmType)
|
||||||
|
.OrderByDescending(e => e.ExpireTime);
|
||||||
|
if (!entries.Any())
|
||||||
|
{
|
||||||
|
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is empty. Please make sure you have downloaded platform (NOT merchant) certificates first.");
|
||||||
|
}
|
||||||
|
|
||||||
|
CertificateEntry entry = entries.First();
|
||||||
|
certificate = entry.Certificate;
|
||||||
|
request.WechatpayCertificateSerialNumber = entry.SerialNumber;
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
// 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值
|
||||||
|
CertificateEntry? entry = client.PlatformCertificateManager?.GetEntry(request.WechatpayCertificateSerialNumber!);
|
||||||
|
if (!entry.HasValue)
|
||||||
|
{
|
||||||
|
throw new WechatTenpayException($"Failed to encrypt request, because the platform certificate manager does not contain a certificate matched the serial number \"{request.WechatpayCertificateSerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first.");
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate = entry.Value.Certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
string newValue;
|
||||||
|
switch (attribute.Algorithm)
|
||||||
|
{
|
||||||
|
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
|
||||||
|
{
|
||||||
|
newValue = RSAUtility.EncryptWithECBByCertificate(
|
||||||
|
certificate: certificate,
|
||||||
|
plainText: oldValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
|
||||||
|
{
|
||||||
|
newValue = RSAUtility.EncryptWithECBByCertificate(
|
||||||
|
certificate: certificate,
|
||||||
|
plainText: oldValue,
|
||||||
|
paddingMode: "PKCS1PADDING"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1:
|
||||||
|
{
|
||||||
|
newValue = SM2Utility.EncryptByCertificate(
|
||||||
|
certificate: certificate,
|
||||||
|
plainText: oldValue,
|
||||||
|
asn1Encoding: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new WechatTenpayException($"Failed to encrypt request. Unsupported encryption algorithm: \"{attribute.Algorithm}\".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (true, newValue);
|
||||||
|
});
|
||||||
|
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,22 @@ using System.Text;
|
|||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||||
{
|
{
|
||||||
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Models;
|
||||||
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities;
|
||||||
|
|
||||||
public static class WechatTenpayClientResponseDecryptionExtensions
|
public static class WechatTenpayClientResponseDecryptionExtensions
|
||||||
{
|
{
|
||||||
private static TResponse InnerDecryptResponseSensitiveProperty<TResponse>(WechatTenpayClient client, TResponse response)
|
/// <summary>
|
||||||
where TResponse : Models.QueryCertificatesResponse
|
/// <para>解密响应中返回的敏感数据。该方法会改变传入的响应模型对象。</para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static QueryCertificatesResponse DecryptResponseSensitiveProperty(this WechatTenpayClient client, QueryCertificatesResponse response)
|
||||||
{
|
{
|
||||||
|
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||||
|
if (response is null) throw new ArgumentNullException(nameof(response));
|
||||||
|
|
||||||
if (response.CertificateList is null)
|
if (response.CertificateList is null)
|
||||||
return response;
|
return response;
|
||||||
|
|
||||||
@ -25,7 +36,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
if (string.IsNullOrEmpty(client.Credentials.MerchantCertificatePrivateKey))
|
if (string.IsNullOrEmpty(client.Credentials.MerchantCertificatePrivateKey))
|
||||||
throw new WechatTenpayException("Failed to decrypt response, because the merchant private key is not set.");
|
throw new WechatTenpayException("Failed to decrypt response, because the merchant private key is not set.");
|
||||||
|
|
||||||
certificate.EncryptCertificate.CipherText = Utilities.AESUtility.DecryptWithGCM(
|
certificate.EncryptCertificate.CipherText = AESUtility.DecryptWithGCM(
|
||||||
key: client.Credentials.MerchantV3Secret,
|
key: client.Credentials.MerchantV3Secret,
|
||||||
nonce: certificate.EncryptCertificate.Nonce,
|
nonce: certificate.EncryptCertificate.Nonce,
|
||||||
aad: certificate.EncryptCertificate.AssociatedData,
|
aad: certificate.EncryptCertificate.AssociatedData,
|
||||||
@ -41,11 +52,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
|
|
||||||
// REF: https://pay.weixin.qq.com/docs/merchant/development/shangmi/guide.html
|
// REF: https://pay.weixin.qq.com/docs/merchant/development/shangmi/guide.html
|
||||||
// 由于 SM4 密钥长度的限制,密钥由 APIv3 密钥通过国密 SM3 Hash 计算生成。SM4 密钥取其摘要(256bit)的前 128bit。
|
// 由于 SM4 密钥长度的限制,密钥由 APIv3 密钥通过国密 SM3 Hash 计算生成。SM4 密钥取其摘要(256bit)的前 128bit。
|
||||||
byte[] secretBytes = Utilities.SM3Utility.Hash(Encoding.UTF8.GetBytes(client.Credentials.MerchantV3Secret));
|
byte[] secretBytes = SM3Utility.Hash(Encoding.UTF8.GetBytes(client.Credentials.MerchantV3Secret));
|
||||||
byte[] keyBytes = new byte[16];
|
byte[] keyBytes = new byte[16];
|
||||||
Array.Copy(secretBytes, keyBytes, keyBytes.Length);
|
Array.Copy(secretBytes, keyBytes, keyBytes.Length);
|
||||||
|
|
||||||
byte[] plainBytes = Utilities.SM4Utility.DecryptWithGCM(
|
byte[] plainBytes = SM4Utility.DecryptWithGCM(
|
||||||
keyBytes: keyBytes,
|
keyBytes: keyBytes,
|
||||||
nonceBytes: Encoding.UTF8.GetBytes(certificate.EncryptCertificate.Nonce),
|
nonceBytes: Encoding.UTF8.GetBytes(certificate.EncryptCertificate.Nonce),
|
||||||
aadBytes: Encoding.UTF8.GetBytes(certificate.EncryptCertificate.AssociatedData ?? string.Empty),
|
aadBytes: Encoding.UTF8.GetBytes(certificate.EncryptCertificate.AssociatedData ?? string.Empty),
|
||||||
@ -65,85 +76,6 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TResponse InnerDecryptResponseSensitivePropertyByRSA<TResponse>(WechatTenpayClient client, TResponse response)
|
|
||||||
where TResponse : WechatTenpayResponse
|
|
||||||
{
|
|
||||||
Utilities.ReflectionHelper.ReplacePropertyStringValue(ref response, (target, currentProp, oldValue) =>
|
|
||||||
{
|
|
||||||
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
|
|
||||||
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
|
|
||||||
.FirstOrDefault(attr => Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(attr.Scheme));
|
|
||||||
if (attribute is null)
|
|
||||||
return (false, oldValue);
|
|
||||||
|
|
||||||
string newValue;
|
|
||||||
switch (attribute.Algorithm)
|
|
||||||
{
|
|
||||||
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
|
|
||||||
{
|
|
||||||
newValue = Utilities.RSAUtility.DecryptWithECB(
|
|
||||||
privateKey: client.Credentials.MerchantCertificatePrivateKey,
|
|
||||||
cipherText: oldValue
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
|
|
||||||
{
|
|
||||||
newValue = Utilities.RSAUtility.DecryptWithECB(
|
|
||||||
privateKey: client.Credentials.MerchantCertificatePrivateKey,
|
|
||||||
cipherText: oldValue,
|
|
||||||
paddingMode: "PKCS1PADDING"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
throw new WechatTenpayException($"Failed to decrypt response. Unsupported encryption algorithm: \"{attribute.Algorithm}\".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (true, newValue);
|
|
||||||
});
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TResponse InnerDecryptResponseSensitivePropertyBySM<TResponse>(WechatTenpayClient client, TResponse response)
|
|
||||||
where TResponse : WechatTenpayResponse
|
|
||||||
{
|
|
||||||
Utilities.ReflectionHelper.ReplacePropertyStringValue(ref response, (target, currentProp, oldValue) =>
|
|
||||||
{
|
|
||||||
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
|
|
||||||
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
|
|
||||||
.FirstOrDefault(attr => Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(attr.Scheme));
|
|
||||||
if (attribute is null)
|
|
||||||
return (false, oldValue);
|
|
||||||
|
|
||||||
string newValue;
|
|
||||||
switch (attribute.Algorithm)
|
|
||||||
{
|
|
||||||
case Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1:
|
|
||||||
{
|
|
||||||
newValue = Utilities.SM2Utility.Decrypt(
|
|
||||||
privateKey: client.Credentials.MerchantCertificatePrivateKey,
|
|
||||||
cipherText: oldValue,
|
|
||||||
asn1Encoding: true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
throw new WechatTenpayException($"Failed to decrypt response. Unsupported encryption algorithm: \"{attribute.Algorithm}\".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (true, newValue);
|
|
||||||
});
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <para>解密响应中返回的敏感数据。该方法会改变传入的响应模型对象。</para>
|
/// <para>解密响应中返回的敏感数据。该方法会改变传入的响应模型对象。</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -162,24 +94,69 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// [GET] /certificates 接口的响应模型需特殊处理
|
// [GET] /certificates 接口的响应模型需特殊处理
|
||||||
if (response is Models.QueryCertificatesResponse queryCertificatesResponse)
|
if (response is QueryCertificatesResponse queryCertificatesResponse)
|
||||||
{
|
{
|
||||||
return (InnerDecryptResponseSensitiveProperty(client, queryCertificatesResponse) as TResponse)!;
|
return (DecryptResponseSensitiveProperty(client, queryCertificatesResponse) as TResponse)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 遍历并解密被标记为敏感数据的字段
|
// 遍历并解密被标记为敏感数据的字段
|
||||||
bool requireDecrypt = Attribute.IsDefined(response.GetType(), typeof(WechatTenpaySensitiveAttribute));
|
bool requireDecrypt = Attribute.IsDefined(response.GetType(), typeof(WechatTenpaySensitiveAttribute));
|
||||||
if (requireDecrypt)
|
if (!requireDecrypt)
|
||||||
{
|
return response;
|
||||||
switch (client.Credentials.SignScheme)
|
|
||||||
{
|
|
||||||
case Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256:
|
|
||||||
return InnerDecryptResponseSensitivePropertyByRSA(client, response);
|
|
||||||
|
|
||||||
case Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3:
|
string signScheme = client.Credentials.SignScheme;
|
||||||
return InnerDecryptResponseSensitivePropertyBySM(client, response);
|
|
||||||
|
ReflectionHelper.ReplaceObjectStringProperties(response, (_, currentProp, oldValue) =>
|
||||||
|
{
|
||||||
|
if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute)))
|
||||||
|
return (false, oldValue);
|
||||||
|
|
||||||
|
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
|
||||||
|
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
|
||||||
|
.FirstOrDefault(attr => attr.Scheme == signScheme);
|
||||||
|
if (attribute is null)
|
||||||
|
return (false, oldValue);
|
||||||
|
|
||||||
|
string newValue;
|
||||||
|
switch (attribute.Algorithm)
|
||||||
|
{
|
||||||
|
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
|
||||||
|
{
|
||||||
|
newValue = RSAUtility.DecryptWithECB(
|
||||||
|
privateKey: client.Credentials.MerchantCertificatePrivateKey,
|
||||||
|
cipherText: oldValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
|
||||||
|
{
|
||||||
|
newValue = RSAUtility.DecryptWithECB(
|
||||||
|
privateKey: client.Credentials.MerchantCertificatePrivateKey,
|
||||||
|
cipherText: oldValue,
|
||||||
|
paddingMode: "PKCS1PADDING"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1:
|
||||||
|
{
|
||||||
|
newValue = SM2Utility.Decrypt(
|
||||||
|
privateKey: client.Credentials.MerchantCertificatePrivateKey,
|
||||||
|
cipherText: oldValue,
|
||||||
|
asn1Encoding: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new WechatTenpayException($"Failed to decrypt response. Unsupported encryption algorithm: \"{attribute.Algorithm}\".");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return (true, newValue);
|
||||||
|
});
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
@ -1,172 +1,122 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
|
||||||
{
|
{
|
||||||
internal static partial class ReflectionHelper
|
internal static partial class ReflectionHelper
|
||||||
{
|
{
|
||||||
private static readonly Hashtable _cache = new Hashtable();
|
public static void ReplaceObjectStringProperties(object targetObj, ReplaceObjectStringPropertiesReplacementDelegate replacement)
|
||||||
|
|
||||||
private static PropertyInfo[] GetTypedProperties(Type type)
|
|
||||||
{
|
{
|
||||||
if (type is null) throw new ArgumentNullException(nameof(type));
|
if (targetObj is null) throw new ArgumentNullException(nameof(targetObj));
|
||||||
|
|
||||||
string key = type.FullName ?? type.AssemblyQualifiedName!;
|
|
||||||
PropertyInfo[]? properties = (PropertyInfo[]?)_cache[key];
|
|
||||||
if (properties is null)
|
|
||||||
{
|
|
||||||
properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
|
||||||
_cache[key] = properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ReplacePropertyStringValue<T>(ref T obj, ReplacePropertyStringValueReplacementHandler replacement)
|
|
||||||
{
|
|
||||||
InnerReplacePropertyStringValue(ref obj, replacement);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void InnerReplacePropertyStringValue<T>(ref T obj, ReplacePropertyStringValueReplacementHandler replacement)
|
|
||||||
{
|
|
||||||
if (obj is null) throw new ArgumentNullException(nameof(obj));
|
|
||||||
if (replacement is null) throw new ArgumentNullException(nameof(replacement));
|
if (replacement is null) throw new ArgumentNullException(nameof(replacement));
|
||||||
|
|
||||||
Type objType = obj.GetType();
|
ISet<object> visited = new HashSet<object>(ReferenceEqualityComparer.Instance); // 处理循环引用问题
|
||||||
if (!objType.IsClass)
|
InnerReplaceObjectStringProperties(ref targetObj, null, replacement, visited);
|
||||||
throw new NotSupportedException();
|
|
||||||
|
|
||||||
if (objType.IsArray || obj is IList || obj is IDictionary)
|
|
||||||
{
|
|
||||||
InnerReplaceEachCollectionPropertyStringValue(ref obj, objType, replacement, null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var childProp in GetTypedProperties(objType))
|
|
||||||
{
|
|
||||||
if (!childProp.CanWrite)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Type propType = childProp.PropertyType;
|
|
||||||
if (propType == typeof(string))
|
|
||||||
{
|
|
||||||
string value = (string)childProp.GetValue(obj, null)!;
|
|
||||||
if (value is null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var result = replacement(obj, childProp, value);
|
|
||||||
if (result.Modified)
|
|
||||||
{
|
|
||||||
childProp.SetValue(obj, result.NewValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (propType.IsClass)
|
|
||||||
{
|
|
||||||
object? value = childProp.GetValue(obj, null);
|
|
||||||
if (value is null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
InnerReplacePropertyStringValue(ref value, replacement);
|
|
||||||
childProp.SetValue(obj, value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
object? value = childProp.GetValue(obj, null);
|
|
||||||
if (value is null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
InnerReplaceEachCollectionPropertyStringValue(ref value, propType, replacement, childProp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void InnerReplaceEachCollectionPropertyStringValue<T>(ref T obj, Type objType, ReplacePropertyStringValueReplacementHandler replacement, PropertyInfo? currentProp)
|
partial class ReflectionHelper
|
||||||
|
{
|
||||||
|
public delegate (bool IsModified, string NewValue) ReplaceObjectStringPropertiesReplacementDelegate(object currentObj, PropertyInfo? currentProp, string oldValue);
|
||||||
|
|
||||||
|
private static void InnerReplaceObjectStringProperties(ref object currentObj, PropertyInfo? currentProp, ReplaceObjectStringPropertiesReplacementDelegate replacement, ISet<object> visited)
|
||||||
{
|
{
|
||||||
if (objType.IsArray)
|
if (currentObj is null) throw new ArgumentNullException(nameof(currentObj));
|
||||||
{
|
if (replacement is null) throw new ArgumentNullException(nameof(replacement));
|
||||||
var array = (obj as Array)!;
|
|
||||||
|
|
||||||
for (int i = 0, len = array.Length; i < len; i++)
|
if (!visited.Add(currentObj)) return;
|
||||||
|
|
||||||
|
Type type = currentObj.GetType();
|
||||||
|
|
||||||
|
// 跳过基元类型、枚举类型、抽象或接口类型,及部分 CLR 内置类型
|
||||||
|
if (type.IsPrimitive ||
|
||||||
|
type.IsEnum ||
|
||||||
|
type.IsAbstract ||
|
||||||
|
type.IsInterface ||
|
||||||
|
type == typeof(Guid) ||
|
||||||
|
type == typeof(DateTime) ||
|
||||||
|
type == typeof(DateTimeOffset) ||
|
||||||
|
#if NET5_0_OR_GREATER
|
||||||
|
type == typeof(DateOnly) ||
|
||||||
|
type == typeof(TimeOnly) ||
|
||||||
|
#endif
|
||||||
|
type == typeof(TimeSpan))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理数组类型
|
||||||
|
if (type.IsArray)
|
||||||
|
{
|
||||||
|
Array currentObjAsArray = (Array)currentObj;
|
||||||
|
for (int i = 0; i < currentObjAsArray.Length; i++)
|
||||||
{
|
{
|
||||||
object? element = array.GetValue(i);
|
object? element = currentObjAsArray.GetValue(i);
|
||||||
if (element is null)
|
if (element is null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Type elementType = element.GetType();
|
Type elementType = element.GetType();
|
||||||
if (elementType == typeof(string))
|
if (elementType == typeof(string))
|
||||||
{
|
{
|
||||||
if (currentProp is null)
|
if (currentObjAsArray.IsReadOnly)
|
||||||
continue;
|
continue;
|
||||||
if (!currentProp.CanWrite)
|
if ((string)element == string.Empty)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var oldValue = (string)element!;
|
var res = replacement(currentObjAsArray, currentProp, (string)element);
|
||||||
var resHandler = replacement(obj!, currentProp, oldValue);
|
if (res.IsModified)
|
||||||
if (resHandler.Modified && !array.IsReadOnly)
|
currentObjAsArray.SetValue(res.NewValue, i);
|
||||||
{
|
|
||||||
array.SetValue(resHandler.NewValue, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (elementType.IsClass)
|
|
||||||
{
|
|
||||||
InnerReplacePropertyStringValue(ref element, replacement);
|
|
||||||
//if (!array.IsReadOnly)
|
|
||||||
//{
|
|
||||||
// array.SetValue(element, i);
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
continue;
|
InnerReplaceObjectStringProperties(ref element, currentProp, replacement, visited);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (obj is IList)
|
|
||||||
{
|
|
||||||
var list = (obj as IList)!;
|
|
||||||
|
|
||||||
for (int i = 0, len = list.Count; i < len; i++)
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理列表类型
|
||||||
|
if (currentObj is IList)
|
||||||
|
{
|
||||||
|
IList currentObjAsList = (IList)currentObj;
|
||||||
|
for (int i = 0; i < currentObjAsList.Count; i++)
|
||||||
{
|
{
|
||||||
object? element = list[i];
|
object? element = currentObjAsList[i];
|
||||||
if (element is null)
|
if (element is null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Type elementType = element.GetType();
|
Type elementType = element.GetType();
|
||||||
if (elementType == typeof(string))
|
if (elementType == typeof(string))
|
||||||
{
|
{
|
||||||
if (currentProp is null)
|
if (currentObjAsList.IsReadOnly)
|
||||||
continue;
|
continue;
|
||||||
if (!currentProp.CanWrite)
|
if ((string)element == string.Empty)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var oldValue = (string)element!;
|
var res = replacement(currentObjAsList, currentProp, (string)element);
|
||||||
var resHandler = replacement(obj, currentProp, oldValue);
|
if (res.IsModified)
|
||||||
if (resHandler.Modified && !list.IsReadOnly)
|
currentObjAsList[i] = res.NewValue;
|
||||||
{
|
|
||||||
list[i] = resHandler.NewValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (elementType.IsClass)
|
|
||||||
{
|
|
||||||
InnerReplacePropertyStringValue(ref element, replacement);
|
|
||||||
//if (!list.IsReadOnly)
|
|
||||||
//{
|
|
||||||
// list[i] = element;
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
continue;
|
InnerReplaceObjectStringProperties(ref element, currentProp, replacement, visited);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (obj is IDictionary)
|
|
||||||
{
|
|
||||||
var dict = (obj as IDictionary)!;
|
|
||||||
|
|
||||||
foreach (DictionaryEntry entry in dict)
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理字典类型
|
||||||
|
if (currentObj is IDictionary)
|
||||||
|
{
|
||||||
|
IDictionary currentObjAsDictionary = (IDictionary)currentObj;
|
||||||
|
foreach (DictionaryEntry entry in currentObjAsDictionary)
|
||||||
{
|
{
|
||||||
object? entryValue = entry.Value;
|
object? entryValue = entry.Value;
|
||||||
if (entryValue is null)
|
if (entryValue is null)
|
||||||
@ -175,37 +125,137 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
|
|||||||
Type entryValueType = entryValue.GetType();
|
Type entryValueType = entryValue.GetType();
|
||||||
if (entryValueType == typeof(string))
|
if (entryValueType == typeof(string))
|
||||||
{
|
{
|
||||||
if (currentProp is null)
|
if (currentObjAsDictionary.IsReadOnly)
|
||||||
continue;
|
continue;
|
||||||
if (!currentProp.CanWrite)
|
if ((string)entryValue == string.Empty)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
string oldValue = (string)entryValue!;
|
var res = replacement(currentObjAsDictionary, currentProp, (string)entryValue);
|
||||||
var resHandler = replacement(obj, currentProp, oldValue);
|
if (res.IsModified)
|
||||||
if (resHandler.Modified && !dict.IsReadOnly)
|
currentObjAsDictionary[entry.Key] = res.NewValue;
|
||||||
{
|
|
||||||
dict[entry.Key] = resHandler.NewValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (entryValueType.IsClass)
|
|
||||||
{
|
|
||||||
InnerReplacePropertyStringValue(ref entryValue, replacement);
|
|
||||||
//if (!dict.IsReadOnly)
|
|
||||||
//{
|
|
||||||
// dict[entry.Key] = entryValue;
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
continue;
|
InnerReplaceObjectStringProperties(ref entryValue, currentProp, replacement, visited);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历属性
|
||||||
|
foreach (PropertyInfo property in GetWritableProperties(type))
|
||||||
|
{
|
||||||
|
Type propertyType = property.PropertyType;
|
||||||
|
if (propertyType == typeof(string))
|
||||||
|
{
|
||||||
|
string? propertyValue = GetPropertyValue<string>(currentObj, property);
|
||||||
|
if (propertyValue is null || propertyValue == string.Empty)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var res = replacement(currentObj, property, propertyValue);
|
||||||
|
if (res.IsModified)
|
||||||
|
SetPropertyValue(currentObj, property, res.NewValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
object? propertyValue = GetPropertyValue<object>(currentObj, property);
|
||||||
|
if (propertyValue is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
InnerReplaceObjectStringProperties(ref propertyValue, property, replacement, visited);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
partial class ReflectionHelper
|
partial class ReflectionHelper
|
||||||
{
|
{
|
||||||
public delegate (bool Modified, string NewValue) ReplacePropertyStringValueReplacementHandler(object target, PropertyInfo currentProp, string oldValue);
|
private static readonly IDictionary<Type, PropertyInfo[]> _propsCache = new Dictionary<Type, PropertyInfo[]>(capacity: 128);
|
||||||
|
private static readonly Hashtable _getterCache = new Hashtable(capacity: 128);
|
||||||
|
private static readonly Hashtable _setterCache = new Hashtable(capacity: 128);
|
||||||
|
|
||||||
|
private static PropertyInfo[] GetWritableProperties(Type type)
|
||||||
|
{
|
||||||
|
if (type is null) throw new ArgumentNullException(nameof(type));
|
||||||
|
|
||||||
|
if (!_propsCache.TryGetValue(type, out PropertyInfo[]? properties))
|
||||||
|
{
|
||||||
|
properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||||
|
.Where(e => e.CanRead && e.CanWrite)
|
||||||
|
.ToArray();
|
||||||
|
_propsCache[type] = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T? GetPropertyValue<T>(object targetObj, PropertyInfo property)
|
||||||
|
{
|
||||||
|
// 提供比 PropertyInfo.GetValue() 更快的属性取值方法
|
||||||
|
// 只可针对热点类型使用,否则可能会更慢
|
||||||
|
|
||||||
|
if (targetObj is null) throw new ArgumentNullException(nameof(targetObj));
|
||||||
|
if (property is null) throw new ArgumentNullException(nameof(property));
|
||||||
|
|
||||||
|
if (!property.CanRead)
|
||||||
|
throw new InvalidOperationException($"Property '{property.Name}' of type '{typeof(T).FullName}' does not have a getter.");
|
||||||
|
|
||||||
|
Func<object, T>? getter = _getterCache[property] as Func<object, T>;
|
||||||
|
if (getter is null)
|
||||||
|
{
|
||||||
|
ParameterExpression targetExpr = Expression.Parameter(typeof(object));
|
||||||
|
UnaryExpression castTargetExpr = Expression.Convert(targetExpr, targetObj.GetType());
|
||||||
|
MemberExpression getPropertyValueExpr = Expression.Property(castTargetExpr, property);
|
||||||
|
UnaryExpression castPropertyValueExpr = Expression.Convert(getPropertyValueExpr, typeof(T));
|
||||||
|
getter = Expression.Lambda<Func<object, T>>(castPropertyValueExpr, targetExpr).Compile();
|
||||||
|
_getterCache[property] = getter;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getter.Invoke(targetObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetPropertyValue<T>(object targetObj, PropertyInfo property, T? value)
|
||||||
|
{
|
||||||
|
// 提供比 PropertyInfo.SetValue() 更快的属性赋值方法
|
||||||
|
// 只可针对热点类型使用,否则可能会更慢
|
||||||
|
|
||||||
|
if (targetObj is null) throw new ArgumentNullException(nameof(targetObj));
|
||||||
|
if (property is null) throw new ArgumentNullException(nameof(property));
|
||||||
|
|
||||||
|
if (!property.CanWrite)
|
||||||
|
throw new InvalidOperationException($"Property '{property.Name}' of type '{typeof(T).FullName}' does not have a setter.");
|
||||||
|
|
||||||
|
Action<object, T?>? setter = _setterCache[property] as Action<object, T?>;
|
||||||
|
if (setter is null)
|
||||||
|
{
|
||||||
|
ParameterExpression targetExpr = Expression.Parameter(typeof(object));
|
||||||
|
ParameterExpression propertyValueExpr = Expression.Parameter(typeof(T));
|
||||||
|
UnaryExpression castTargetExpr = Expression.Convert(targetExpr, targetObj.GetType());
|
||||||
|
UnaryExpression castPropertyValueExpr = Expression.Convert(propertyValueExpr, property.PropertyType);
|
||||||
|
MethodCallExpression setPropertyValueExpr = Expression.Call(castTargetExpr, property.GetSetMethod()!, castPropertyValueExpr);
|
||||||
|
setter = Expression.Lambda<Action<object, T?>>(setPropertyValueExpr, targetExpr, propertyValueExpr).Compile();
|
||||||
|
_setterCache[property] = setter;
|
||||||
|
}
|
||||||
|
|
||||||
|
setter.Invoke(targetObj, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
partial class ReflectionHelper
|
||||||
|
{
|
||||||
|
#if NET5_0_OR_GREATER
|
||||||
|
#else
|
||||||
|
private sealed class ReferenceEqualityComparer : IEqualityComparer<object?>, IEqualityComparer
|
||||||
|
{
|
||||||
|
public static ReferenceEqualityComparer Instance { get; } = new ReferenceEqualityComparer();
|
||||||
|
|
||||||
|
private ReferenceEqualityComparer() { }
|
||||||
|
|
||||||
|
public new bool Equals(object? x, object? y) => ReferenceEquals(x, y);
|
||||||
|
|
||||||
|
public int GetHashCode(object? obj) => RuntimeHelpers.GetHashCode(obj!);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ using Xunit;
|
|||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
|
||||||
{
|
{
|
||||||
|
using SKIT.FlurlHttpClient.Internal;
|
||||||
|
|
||||||
public partial class TestCase_ResponseDecryptionTests
|
public partial class TestCase_ResponseDecryptionTests
|
||||||
{
|
{
|
||||||
// 此处测试的 RSA/SM2 证书/公钥/私钥是自签名生成的,仅供执行 RSA/SM2 相关的单元测试,不能用于调用微信支付 API。
|
// 此处测试的 RSA/SM2 证书/公钥/私钥是自签名生成的,仅供执行 RSA/SM2 相关的单元测试,不能用于调用微信支付 API。
|
||||||
@ -1151,8 +1153,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
|
|||||||
|
|
||||||
private static T SetMockResponseRawStatusAsOk<T>(T response) where T : WechatTenpayResponse
|
private static T SetMockResponseRawStatusAsOk<T>(T response) where T : WechatTenpayResponse
|
||||||
{
|
{
|
||||||
FieldInfo fieldInfo = typeof(CommonResponseBase).GetField("_InternalRawStatus", BindingFlags.Instance | BindingFlags.NonPublic)!;
|
var accessor = _UnsafeAccessor.VisitCommonResponse(response);
|
||||||
fieldInfo.SetValue(response, (int)HttpStatusCode.OK);
|
accessor.RawStatus = (int)HttpStatusCode.OK;
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user