mirror of
https://gitee.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat.git
synced 2025-12-29 09:54:44 +08:00
chore: resolve git confict
This commit is contained in:
@@ -34,6 +34,10 @@
|
||||
|
||||
<summary>SKIT.FlurlHttpClient.Wechat.Api 更新日志</summary>
|
||||
|
||||
- Release 1.10.1
|
||||
|
||||
- **修复**:修复 XmlSerializer 潜在的内存泄漏问题。([GitHub Issue #11](https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat/issues/11))
|
||||
|
||||
- Release 1.10.0
|
||||
|
||||
- **新增**:新增第三方平台申请开通直播相关接口。
|
||||
@@ -44,8 +48,6 @@
|
||||
|
||||
- **新增**:随官方更新小程序联盟推客端相关接口模型。
|
||||
|
||||
- **修复**:修复 XmlSerializer 潜在的内存泄漏问题。([GitHub Issue #11](https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat/issues/11))
|
||||
|
||||
- **修复**:修复 AES 解密结果结尾有冗余的空白字符问题。
|
||||
|
||||
- Release 1.9.0
|
||||
|
||||
@@ -79,10 +79,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Api
|
||||
callbackXml = Utilities.WxBizMsgCryptor.AESDecrypt(cipherText: encryptedXml!, encodingAESKey: client.Credentials.PushEncodingAESKey!, out _);
|
||||
}
|
||||
|
||||
using var reader = new StringReader(callbackXml);
|
||||
|
||||
XmlSerializer xmlSerializer = new XmlSerializer(typeof(TEvent), new XmlRootAttribute("xml"));
|
||||
return (TEvent)xmlSerializer.Deserialize(reader)!;
|
||||
return Utilities.XmlUtility.Deserialize<TEvent>(callbackXml);
|
||||
}
|
||||
catch (WechatApiException)
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat</PackageProjectUrl>
|
||||
<PackageTags>Flurl.Http Wechat Weixin MicroMessage MiniProgram MiniGame MiniStore 微信 公众号 服务号 订阅号 小程序 小游戏 小商店 公众平台 开放平台 微信公众号 微信服务号 微信订阅号 微信小程序 微信小游戏 微信小商店 微信公众平台 微信开放平台</PackageTags>
|
||||
<Version>1.10.0</Version>
|
||||
<Version>1.10.1</Version>
|
||||
<Description>基于 Flurl.Http 的微信 API 客户端,支持公众平台(订阅号、服务号、小程序、小游戏、小商店)、开放平台等平台,支持基础服务、模板消息、订阅消息、客服消息、动态消息、菜单管理、素材管理、留言管理、用户管理、账号管理、数据统计、微信门店、微信小店、智能接口、一物一码、微信发票、微信非税缴费、插件管理、附近的小程序、小程序码、小程序搜索、URL Scheme、URL Link、即时配送、物流助手、直播、生物认证、虚拟支付、开放数据、对局匹配、帧同步、内容安全、安全风控、第三方平台等功能。</Description>
|
||||
<Authors>Fu Diwei</Authors>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
|
||||
@@ -35,6 +35,31 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 CBC 模式加密数据。
|
||||
/// </summary>
|
||||
/// <param name="keyBytes">AES 密钥字节数组。</param>
|
||||
/// <param name="ivBytes">加密使用的初始化向量字节数组。</param>
|
||||
/// <param name="plainBytes">待加密数据字节数组。</param>
|
||||
/// <returns>加密后的数据字节数组。</returns>
|
||||
public static byte[] EncryptWithCBC(byte[] keyBytes, byte[] ivBytes, byte[] plainBytes)
|
||||
{
|
||||
if (keyBytes == null) throw new ArgumentNullException(nameof(keyBytes));
|
||||
if (ivBytes == null) throw new ArgumentNullException(nameof(ivBytes));
|
||||
if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes));
|
||||
|
||||
using (SymmetricAlgorithm aes = Aes.Create())
|
||||
{
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
aes.Key = keyBytes;
|
||||
aes.IV = ivBytes;
|
||||
|
||||
using ICryptoTransform transform = aes.CreateEncryptor();
|
||||
return transform.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 CBC 模式解密数据。
|
||||
/// </summary>
|
||||
@@ -54,5 +79,25 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities
|
||||
);
|
||||
return Encoding.UTF8.GetString(plainBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 CBC 模式加密数据。
|
||||
/// </summary>
|
||||
/// <param name="encodingKey">经 Base64 编码后的 AES 密钥。</param>
|
||||
/// <param name="encodingIV">经 Base64 编码后的 AES 初始化向量。</param>
|
||||
/// <param name="plainText">待加密文本。</param>
|
||||
/// <returns>经 Base64 编码的加密后的数据。</returns>
|
||||
public static string EncryptWithCBC(string encodingKey, string encodingIV, string plainText)
|
||||
{
|
||||
if (encodingKey == null) throw new ArgumentNullException(nameof(encodingKey));
|
||||
if (plainText == null) throw new ArgumentNullException(nameof(plainText));
|
||||
|
||||
byte[] plainBytes = EncryptWithCBC(
|
||||
keyBytes: Convert.FromBase64String(encodingKey),
|
||||
ivBytes: Convert.FromBase64String(encodingIV),
|
||||
plainBytes: Encoding.UTF8.GetBytes(plainText)
|
||||
);
|
||||
return Convert.ToBase64String(plainBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,23 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities
|
||||
internal static class XmlUtility
|
||||
{
|
||||
// REF: https://docs.microsoft.com/zh-cn/dotnet/api/system.xml.serialization.xmlserializer#dynamically-generated-assemblies
|
||||
private static Hashtable _serializers = new Hashtable();
|
||||
private static readonly Hashtable _xmlSerializers = new Hashtable();
|
||||
private static readonly XmlRootAttribute _xmlRoot = new XmlRootAttribute("xml");
|
||||
|
||||
private static XmlSerializer GetTypedSerializer(Type type)
|
||||
{
|
||||
if (type == null) throw new ArgumentNullException(nameof(type));
|
||||
|
||||
string skey = type.AssemblyQualifiedName ?? type.GetHashCode().ToString();
|
||||
XmlSerializer? xmlSerializer = (XmlSerializer?)_xmlSerializers[skey];
|
||||
if (xmlSerializer == null)
|
||||
{
|
||||
xmlSerializer = new XmlSerializer(type, _xmlRoot);
|
||||
_xmlSerializers[skey] = xmlSerializer;
|
||||
}
|
||||
|
||||
return xmlSerializer;
|
||||
}
|
||||
|
||||
public static string Serialize(Type type, object obj)
|
||||
{
|
||||
@@ -24,19 +40,12 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities
|
||||
settings.WriteEndDocumentOnClose = false;
|
||||
settings.NamespaceHandling = NamespaceHandling.OmitDuplicates;
|
||||
|
||||
string skey = type.AssemblyQualifiedName;
|
||||
XmlSerializer? xmlSerializer = (XmlSerializer)_serializers[skey];
|
||||
if (xmlSerializer == null)
|
||||
{
|
||||
xmlSerializer = new XmlSerializer(type, new XmlRootAttribute("xml"));
|
||||
_serializers[skey] = xmlSerializer;
|
||||
}
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
using var writer = XmlWriter.Create(stream, settings);
|
||||
XmlSerializerNamespaces xmlNamespace = new XmlSerializerNamespaces();
|
||||
xmlNamespace.Add(string.Empty, string.Empty);
|
||||
xmlSerializer.Serialize(writer, obj, xmlNamespace);
|
||||
XmlSerializer serializer = GetTypedSerializer(type);
|
||||
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
|
||||
ns.Add(string.Empty, string.Empty);
|
||||
serializer.Serialize(writer, obj, ns);
|
||||
writer.Flush();
|
||||
xml = Encoding.UTF8.GetString(stream.ToArray());
|
||||
xml = Regex.Replace(xml, "\\s*<\\w+ (xsi|d2p1):nil=\"true\"[^>]*/>", string.Empty, RegexOptions.IgnoreCase);
|
||||
@@ -50,5 +59,18 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.Utilities
|
||||
{
|
||||
return Serialize(typeof(T), obj);
|
||||
}
|
||||
|
||||
public static object Deserialize(Type type, string xml)
|
||||
{
|
||||
using var reader = new StringReader(xml);
|
||||
XmlSerializer serializer = GetTypedSerializer(type);
|
||||
return serializer.Deserialize(reader)!;
|
||||
}
|
||||
|
||||
public static T Deserialize<T>(string xml)
|
||||
where T : class
|
||||
{
|
||||
return (T)Deserialize(typeof(T), xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,11 +41,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
||||
throw new Exceptions.WechatOpenAIEventSerializationException("Encrypt event failed, because of empty encrypted data.");
|
||||
|
||||
callbackXml = Utilities.WxBizMsgCryptor.AESDecrypt(cipherText: encryptedXml!, encodingAESKey: client.Credentials.EncodingAESKey!, out _);
|
||||
|
||||
using var reader = new StringReader(callbackXml);
|
||||
|
||||
XmlSerializer xmlSerializer = new XmlSerializer(typeof(TEvent), new XmlRootAttribute("xml"));
|
||||
return (TEvent)xmlSerializer.Deserialize(reader)!;
|
||||
return Utilities.XmlUtility.Deserialize<TEvent>(callbackXml);
|
||||
}
|
||||
catch (WechatOpenAIException)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,23 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
|
||||
internal static class XmlUtility
|
||||
{
|
||||
// REF: https://docs.microsoft.com/zh-cn/dotnet/api/system.xml.serialization.xmlserializer#dynamically-generated-assemblies
|
||||
private static Hashtable _serializers = new Hashtable();
|
||||
private static readonly Hashtable _xmlSerializers = new Hashtable();
|
||||
private static readonly XmlRootAttribute _xmlRoot = new XmlRootAttribute("xml");
|
||||
|
||||
private static XmlSerializer GetTypedSerializer(Type type)
|
||||
{
|
||||
if (type == null) throw new ArgumentNullException(nameof(type));
|
||||
|
||||
string skey = type.AssemblyQualifiedName ?? type.GetHashCode().ToString();
|
||||
XmlSerializer? xmlSerializer = (XmlSerializer?)_xmlSerializers[skey];
|
||||
if (xmlSerializer == null)
|
||||
{
|
||||
xmlSerializer = new XmlSerializer(type, _xmlRoot);
|
||||
_xmlSerializers[skey] = xmlSerializer;
|
||||
}
|
||||
|
||||
return xmlSerializer;
|
||||
}
|
||||
|
||||
public static string Serialize(Type type, object obj)
|
||||
{
|
||||
@@ -24,19 +40,12 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
|
||||
settings.WriteEndDocumentOnClose = false;
|
||||
settings.NamespaceHandling = NamespaceHandling.OmitDuplicates;
|
||||
|
||||
string skey = type.AssemblyQualifiedName;
|
||||
XmlSerializer? xmlSerializer = (XmlSerializer)_serializers[skey];
|
||||
if (xmlSerializer == null)
|
||||
{
|
||||
xmlSerializer = new XmlSerializer(type, new XmlRootAttribute("xml"));
|
||||
_serializers[skey] = xmlSerializer;
|
||||
}
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
using var writer = XmlWriter.Create(stream, settings);
|
||||
XmlSerializerNamespaces xmlNamespace = new XmlSerializerNamespaces();
|
||||
xmlNamespace.Add(string.Empty, string.Empty);
|
||||
xmlSerializer.Serialize(writer, obj, xmlNamespace);
|
||||
XmlSerializer serializer = GetTypedSerializer(type);
|
||||
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
|
||||
ns.Add(string.Empty, string.Empty);
|
||||
serializer.Serialize(writer, obj, ns);
|
||||
writer.Flush();
|
||||
xml = Encoding.UTF8.GetString(stream.ToArray());
|
||||
xml = Regex.Replace(xml, "\\s*<\\w+ (xsi|d2p1):nil=\"true\"[^>]*/>", string.Empty, RegexOptions.IgnoreCase);
|
||||
@@ -50,5 +59,18 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
|
||||
{
|
||||
return Serialize(typeof(T), obj);
|
||||
}
|
||||
|
||||
public static object Deserialize(Type type, string xml)
|
||||
{
|
||||
using var reader = new StringReader(xml);
|
||||
XmlSerializer serializer = GetTypedSerializer(type);
|
||||
return serializer.Deserialize(reader)!;
|
||||
}
|
||||
|
||||
public static T Deserialize<T>(string xml)
|
||||
where T : class
|
||||
{
|
||||
return (T)Deserialize(typeof(T), xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,11 +71,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Work
|
||||
throw new Exceptions.WechatWorkEventSerializationException("Encrypt event failed, because of empty encrypted data.");
|
||||
|
||||
callbackXml = Utilities.WxBizMsgCryptor.AESDecrypt(cipherText: encryptedXml!, encodingAESKey: client.Credentials.PushEncodingAESKey!, out _);
|
||||
|
||||
using var reader = new StringReader(callbackXml);
|
||||
|
||||
XmlSerializer xmlSerializer = new XmlSerializer(typeof(TEvent), new XmlRootAttribute("xml"));
|
||||
return (TEvent)xmlSerializer.Deserialize(reader)!;
|
||||
return Utilities.XmlUtility.Deserialize<TEvent>(callbackXml);
|
||||
}
|
||||
catch (WechatWorkException)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,23 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.Utilities
|
||||
internal static class XmlUtility
|
||||
{
|
||||
// REF: https://docs.microsoft.com/zh-cn/dotnet/api/system.xml.serialization.xmlserializer#dynamically-generated-assemblies
|
||||
private static Hashtable _serializers = new Hashtable();
|
||||
private static readonly Hashtable _xmlSerializers = new Hashtable();
|
||||
private static readonly XmlRootAttribute _xmlRoot = new XmlRootAttribute("xml");
|
||||
|
||||
private static XmlSerializer GetTypedSerializer(Type type)
|
||||
{
|
||||
if (type == null) throw new ArgumentNullException(nameof(type));
|
||||
|
||||
string skey = type.AssemblyQualifiedName ?? type.GetHashCode().ToString();
|
||||
XmlSerializer? xmlSerializer = (XmlSerializer?)_xmlSerializers[skey];
|
||||
if (xmlSerializer == null)
|
||||
{
|
||||
xmlSerializer = new XmlSerializer(type, _xmlRoot);
|
||||
_xmlSerializers[skey] = xmlSerializer;
|
||||
}
|
||||
|
||||
return xmlSerializer;
|
||||
}
|
||||
|
||||
public static string Serialize(Type type, object obj)
|
||||
{
|
||||
@@ -24,19 +40,12 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.Utilities
|
||||
settings.WriteEndDocumentOnClose = false;
|
||||
settings.NamespaceHandling = NamespaceHandling.OmitDuplicates;
|
||||
|
||||
string skey = type.AssemblyQualifiedName;
|
||||
XmlSerializer? xmlSerializer = (XmlSerializer)_serializers[skey];
|
||||
if (xmlSerializer == null)
|
||||
{
|
||||
xmlSerializer = new XmlSerializer(type, new XmlRootAttribute("xml"));
|
||||
_serializers[skey] = xmlSerializer;
|
||||
}
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
using var writer = XmlWriter.Create(stream, settings);
|
||||
XmlSerializerNamespaces xmlNamespace = new XmlSerializerNamespaces();
|
||||
xmlNamespace.Add(string.Empty, string.Empty);
|
||||
xmlSerializer.Serialize(writer, obj, xmlNamespace);
|
||||
XmlSerializer serializer = GetTypedSerializer(type);
|
||||
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
|
||||
ns.Add(string.Empty, string.Empty);
|
||||
serializer.Serialize(writer, obj, ns);
|
||||
writer.Flush();
|
||||
xml = Encoding.UTF8.GetString(stream.ToArray());
|
||||
xml = Regex.Replace(xml, "\\s*<\\w+ (xsi|d2p1):nil=\"true\"[^>]*/>", string.Empty, RegexOptions.IgnoreCase);
|
||||
@@ -50,5 +59,18 @@ namespace SKIT.FlurlHttpClient.Wechat.Work.Utilities
|
||||
{
|
||||
return Serialize(typeof(T), obj);
|
||||
}
|
||||
|
||||
public static object Deserialize(Type type, string xml)
|
||||
{
|
||||
using var reader = new StringReader(xml);
|
||||
XmlSerializer serializer = GetTypedSerializer(type);
|
||||
return serializer.Deserialize(reader)!;
|
||||
}
|
||||
|
||||
public static T Deserialize<T>(string xml)
|
||||
where T : class
|
||||
{
|
||||
return (T)Deserialize(typeof(T), xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.Api.UnitTests
|
||||
@@ -40,13 +33,12 @@ namespace SKIT.FlurlHttpClient.Wechat.Api.UnitTests
|
||||
{
|
||||
string iv = "KEWv/gyiIwAfHvjrLeaX6w==";
|
||||
string key = "YZJqKnNFi0KAiKUc0ggC2g==";
|
||||
string cipherData = "Gu2PVnxVWl+jK4F8c0liG1TiwQfaibvddu8eU1zWDDmVpPlM8ewsDzDr3l8VMY01ifZgFWNsr5QyBG0IKwM6lJNXzZHkaK9AQ4ZkVq7PYwdqNQkrg0QmKzntLMTRVNuY+TqPhXGPrOhup/orxwwCUBqheFPPwVbMeOdwrpVNyOdtsHRWQUefXN5UtDBb40pPHon4DbiHBQb5TjBPMrEF2Q==";
|
||||
string cipherText = "Gu2PVnxVWl+jK4F8c0liGxfkB5Bj3m5HRvwgEIk1Yb+36RZ3Bg7YmUnud/ooiHz0PQroipsH7GCjlGwUeT04NwmrFaP1y3dRYPLpS43ed9QZWcFIFo+8vTs3Zco6S98DUvaNEAs8duhz/BzfBOZaIHMziRqEtPFI0ZDzCgJluBirJ6Wl3UkygZ5/QLo3KA53qGdip7K48Rq8XbCwuidTCw==";
|
||||
|
||||
string actualPlainData = Utilities.AESUtility.DecryptWithCBC(encodingKey: key, encodingIV: iv, encodingCipherText: cipherData).Trim();
|
||||
string actualPhoneNumber = JsonConvert.DeserializeObject<JToken>(actualPlainData)["phoneNumber"].ToObject<string>();
|
||||
string expectedPhoneNumber = "18677245613";
|
||||
|
||||
Assert.Equal(expectedPhoneNumber, actualPhoneNumber, ignoreCase: true);
|
||||
string expectedPlainData = "{\"phoneNumber\":\"186****5613\",\"purePhoneNumber\":\"186****5613\",\"countryCode\":\"86\",\"watermark\":{\"timestamp\":1634545675,\"appid\":\"wxc****17e87e0e0a7\"}}";
|
||||
string actualPlainData = Utilities.AESUtility.DecryptWithCBC(encodingKey: key, encodingIV: iv, encodingCipherText: cipherText);
|
||||
|
||||
Assert.Equal(expectedPlainData, actualPlainData, ignoreCase: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user