chore: resolve git confict

This commit is contained in:
Fu Diwei
2021-10-19 20:09:13 +08:00
10 changed files with 160 additions and 66 deletions

View File

@@ -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

View File

@@ -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)
{

View File

@@ -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>

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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)
{

View File

@@ -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);
}
}
}

View File

@@ -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)
{

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}