feat(openai): 升级公共组件

This commit is contained in:
Fu Diwei 2024-01-29 23:12:04 +08:00 committed by RHQYZ
parent 5a110785f8
commit fcc5e91510
28 changed files with 278 additions and 440 deletions

View File

@ -4,7 +4,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Events
/// <para>表示 userEnter 事件的数据。</para>
/// <para>REF: https://developers.weixin.qq.com/doc/aispeech/confapi/thirdkefu/recivemsg.html </para>
/// </summary>
public class UserEnterEvent : WechatOpenAIEvent, WechatOpenAIEvent.Serialization.IXmlSerializable
public class UserEnterEvent : WechatOpenAIEvent
{
public static class Types
{

View File

@ -1,24 +0,0 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Exceptions
{
public class WechatOpenAIEventSerializationException : WechatOpenAIException
{
/// <inheritdoc/>
internal WechatOpenAIEventSerializationException()
{
}
/// <inheritdoc/>
internal WechatOpenAIEventSerializationException(string message)
: base(message)
{
}
/// <inheritdoc/>
internal WechatOpenAIEventSerializationException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@ -1,24 +0,0 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Exceptions
{
public class WechatOpenAIRequestTimeoutException : WechatOpenAIException
{
/// <inheritdoc/>
internal WechatOpenAIRequestTimeoutException()
{
}
/// <inheritdoc/>
internal WechatOpenAIRequestTimeoutException(string message)
: base(message)
{
}
/// <inheritdoc/>
internal WechatOpenAIRequestTimeoutException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@ -7,19 +7,19 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
/// </summary>
public static class WechatOpenAIClientEventExtensions
{
private static TEvent InnerDeserializeEventFromXml<TEvent>(this WechatOpenAIClient client, string callbackXml)
private static TEvent InnerDeserializeEventFromXml<TEvent>(this WechatOpenAIClient client, string webhookXml)
where TEvent : WechatOpenAIEvent
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (callbackXml == null) throw new ArgumentNullException(callbackXml);
if (client is null) throw new ArgumentNullException(nameof(client));
if (webhookXml is null) throw new ArgumentNullException(webhookXml);
try
{
if (!Utilities.WxMsgCryptor.TryParseXml(callbackXml, out string? encryptedXml))
throw new Exceptions.WechatOpenAIEventSerializationException("Failed to encrypt event data, because of empty encrypted data.");
if (!Utilities.WxMsgCryptor.TryParseXml(webhookXml, out string? encryptedXml))
throw new WechatOpenAIException("Failed to decrypt event data, because of the encrypted data is empty.");
callbackXml = Utilities.WxMsgCryptor.AESDecrypt(cipherText: encryptedXml!, encodingAESKey: client.Credentials.EncodingAESKey!, out _);
return Utilities.XmlUtility.Deserialize<TEvent>(callbackXml);
webhookXml = Utilities.WxMsgCryptor.AESDecrypt(cipherText: encryptedXml!, encodingAESKey: client.Credentials.EncodingAESKey!, out _);
return Utilities.XmlHelper.Deserialize<TEvent>(webhookXml);
}
catch (WechatOpenAIException)
{
@ -27,7 +27,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
}
catch (Exception ex)
{
throw new Exceptions.WechatOpenAIEventSerializationException("Failed to deserialize event data. Please see the inner exception for more details.", ex);
throw new WechatOpenAIException("Failed to deserialize event data. Please see the inner exception for more details.", ex);
}
}
@ -36,23 +36,23 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
/// </summary>
/// <typeparam name="TEvent"></typeparam>
/// <param name="client"></param>
/// <param name="callbackXml"></param>
/// <param name="webhookXml"></param>
/// <returns></returns>
public static TEvent DeserializeEventFromXml<TEvent>(this WechatOpenAIClient client, string callbackXml)
where TEvent : WechatOpenAIEvent, WechatOpenAIEvent.Serialization.IXmlSerializable, new()
public static TEvent DeserializeEventFromXml<TEvent>(this WechatOpenAIClient client, string webhookXml)
where TEvent : WechatOpenAIEvent, new()
{
return InnerDeserializeEventFromXml<TEvent>(client, callbackXml);
return InnerDeserializeEventFromXml<TEvent>(client, webhookXml);
}
/// <summary>
/// <para>从 XML 反序列化得到 <see cref="WechatOpenAIEvent"/> 对象。</para>
/// </summary>
/// <param name="client"></param>
/// <param name="callbackXml"></param>
/// <param name="webhookXml"></param>
/// <returns></returns>
public static WechatOpenAIEvent DeserializeEventFromXml(this WechatOpenAIClient client, string callbackXml)
public static WechatOpenAIEvent DeserializeEventFromXml(this WechatOpenAIClient client, string webhookXml)
{
return InnerDeserializeEventFromXml<WechatOpenAIEvent>(client, callbackXml);
return InnerDeserializeEventFromXml<WechatOpenAIEvent>(client, webhookXml);
}
/// <summary>
@ -60,26 +60,26 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
/// </summary>
/// <typeparam name="TEvent"></typeparam>
/// <param name="client"></param>
/// <param name="callbackModel"></param>
/// <param name="webhookEvent"></param>
/// <returns></returns>
public static string SerializeEventToXml<TEvent>(this WechatOpenAIClient client, TEvent callbackModel)
where TEvent : WechatOpenAIEvent, WechatOpenAIEvent.Serialization.IXmlSerializable, new()
public static string SerializeEventToXml<TEvent>(this WechatOpenAIClient client, TEvent webhookEvent)
where TEvent : WechatOpenAIEvent, new()
{
string xml;
try
{
xml = Utilities.XmlUtility.Serialize(callbackModel);
xml = Utilities.XmlHelper.Serialize(webhookEvent);
}
catch (Exception ex)
{
throw new Exceptions.WechatOpenAIEventSerializationException("Failed to serialize event data. Please see the inner exception for more details.", ex);
throw new WechatOpenAIException("Failed to serialize event data. Please see the inner exception for more details.", ex);
}
if (string.IsNullOrEmpty(client.Credentials.EncodingAESKey))
throw new Exceptions.WechatOpenAIEventSerializationException("Failed to encrypt event data, because there is no encoding AES key.");
throw new WechatOpenAIException("Failed to encrypt event data, because the push encoding AES key is not set.");
if (string.IsNullOrEmpty(client.Credentials.Token))
throw new Exceptions.WechatOpenAIEventSerializationException("Failed to encrypt event data, because there is no token.");
throw new WechatOpenAIException("Failed to encrypt event data, because the push token is not set.");
try
{
@ -93,7 +93,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
}
catch (Exception ex)
{
throw new Exceptions.WechatOpenAIEventSerializationException("Failed to encrypt event data. Please see the inner exception for more details.", ex);
throw new WechatOpenAIException("Failed to encrypt event data. Please see the inner exception for more details.", ex);
}
return xml;

View File

@ -22,9 +22,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "batchimportskill", client.Credentials.Token!);
.CreateFlurlRequest(request, HttpMethod.Post, "batchimportskill", client.Credentials.Token!);
return await client.SendRequestWithJsonAsync<Models.BatchImportSkillResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
return await client.SendFlurlRequestAsJsonAsync<Models.BatchImportSkillResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
/// <summary>
@ -41,9 +41,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "publish", client.Credentials.Token!);
.CreateFlurlRequest(request, HttpMethod.Post, "publish", client.Credentials.Token!);
return await client.SendRequestWithJsonAsync<Models.PublishResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
return await client.SendFlurlRequestAsJsonAsync<Models.PublishResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
/// <summary>
@ -60,9 +60,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "publish_progress", client.Credentials.Token!);
.CreateFlurlRequest(request, HttpMethod.Post, "publish_progress", client.Credentials.Token!);
return await client.SendRequestWithJsonAsync<Models.PublishProgressResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
return await client.SendFlurlRequestAsJsonAsync<Models.PublishProgressResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
/// <summary>
@ -79,9 +79,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "setautoreply", client.Credentials.Token!);
.CreateFlurlRequest(request, HttpMethod.Post, "setautoreply", client.Credentials.Token!);
return await client.SendRequestWithJsonAsync<Models.SetAutoReplyResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
return await client.SendFlurlRequestAsJsonAsync<Models.SetAutoReplyResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
/// <summary>
@ -98,9 +98,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "label", "batchset", client.Credentials.Token!);
.CreateFlurlRequest(request, HttpMethod.Post, "label", "batchset", client.Credentials.Token!);
return await client.SendRequestWithJsonAsync<Models.LabelBatchSetResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
return await client.SendFlurlRequestAsJsonAsync<Models.LabelBatchSetResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
}
}

View File

@ -22,9 +22,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "sign", client.Credentials.Token!);
.CreateFlurlRequest(request, HttpMethod.Post, "sign", client.Credentials.Token!);
return await client.SendRequestWithJsonAsync<Models.SignResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
return await client.SendFlurlRequestAsJsonAsync<Models.SignResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
/// <summary>
@ -41,9 +41,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "aibot", client.Credentials.Token!);
.CreateFlurlRequest(request, HttpMethod.Post, "aibot", client.Credentials.Token!);
return await client.SendRequestWithJsonAsync<Models.AIBotResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
return await client.SendFlurlRequestAsJsonAsync<Models.AIBotResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
/// <summary>
@ -60,9 +60,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "feedback", client.Credentials.Token!);
.CreateFlurlRequest(request, HttpMethod.Post, "feedback", client.Credentials.Token!);
return await client.SendRequestWithJsonAsync<Models.FeedbackResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
return await client.SendFlurlRequestAsJsonAsync<Models.FeedbackResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
/// <summary>
@ -79,9 +79,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "gethotquerylist", client.Credentials.Token!);
.CreateFlurlRequest(request, HttpMethod.Post, "gethotquerylist", client.Credentials.Token!);
return await client.SendRequestWithJsonAsync<Models.GetHotQueryListResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
return await client.SendFlurlRequestAsJsonAsync<Models.GetHotQueryListResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
/// <summary>
@ -98,9 +98,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "gethotquerydetail", client.Credentials.Token!);
.CreateFlurlRequest(request, HttpMethod.Post, "gethotquerydetail", client.Credentials.Token!);
return await client.SendRequestWithJsonAsync<Models.GetHotQueryDetailResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
return await client.SendFlurlRequestAsJsonAsync<Models.GetHotQueryDetailResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
}
}

View File

@ -25,9 +25,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "getbindlink", client.Credentials.Token!);
.CreateFlurlRequest(request, HttpMethod.Post, "getbindlink", client.Credentials.Token!);
return await client.SendRequestWithJsonAsync<Models.GetBindLinkResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
return await client.SendFlurlRequestAsJsonAsync<Models.GetBindLinkResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
/// <summary>
@ -44,9 +44,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "getbindlist", client.Credentials.Token!);
.CreateFlurlRequest(request, HttpMethod.Post, "getbindlist", client.Credentials.Token!);
return await client.SendRequestWithJsonAsync<Models.GetBindListResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
return await client.SendFlurlRequestAsJsonAsync<Models.GetBindListResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
/// <summary>
@ -63,9 +63,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "unbindmp", client.Credentials.Token!);
.CreateFlurlRequest(request, HttpMethod.Post, "unbindmp", client.Credentials.Token!);
return await client.SendRequestWithJsonAsync<Models.UnbindMpResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
return await client.SendFlurlRequestAsJsonAsync<Models.UnbindMpResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
/// <summary>
@ -82,9 +82,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "geth5link", client.Credentials.Token!);
.CreateFlurlRequest(request, HttpMethod.Post, "geth5link", client.Credentials.Token!);
return await client.SendRequestWithJsonAsync<Models.GetH5LinkResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
return await client.SendFlurlRequestAsJsonAsync<Models.GetH5LinkResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
/// <summary>
@ -100,19 +100,19 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
if (client is null) throw new ArgumentNullException(nameof(client));
if (request is null) throw new ArgumentNullException(nameof(request));
if (request.FileName == null)
if (request.FileName is null)
request.FileName = Guid.NewGuid().ToString("N").ToLower() + ".jpg";
if (request.FileContentType == null)
if (request.FileContentType is null)
request.FileContentType = "image/jpeg";
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "assetsupload", client.Credentials.Token!);
.CreateFlurlRequest(request, HttpMethod.Post, "assetsupload", client.Credentials.Token!);
using var fileContent = new ByteArrayContent(request.FileBytes ?? Array.Empty<byte>());
using var paramContent = new StringContent(
Utilities.WxMsgCryptor.AESEncrypt(
plainText: Utilities.XmlUtility.ConvertFromJson(client.JsonSerializer.Serialize(request)),
plainText: Utilities.XmlHelper.ConvertFromJson(client.JsonSerializer.Serialize(request)),
encodingAESKey: client.Credentials.EncodingAESKey!,
appId: client.Credentials.AppId!
),
@ -124,7 +124,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse(request.FileContentType);
fileContent.Headers.ContentLength = request.FileBytes?.Length;
return await client.SendRequestAsync<Models.AssetsUploadResponse>(flurlReq, httpContent: httpContent, cancellationToken: cancellationToken);
return await client.SendFlurlRequestAsync<Models.AssetsUploadResponse>(flurlReq, httpContent: httpContent, cancellationToken: cancellationToken);
}
}
}

View File

@ -21,13 +21,13 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
if (client is null) throw new ArgumentNullException(nameof(client));
if (request is null) throw new ArgumentNullException(nameof(request));
if (request.AppId == null)
if (request.AppId is null)
request.AppId = client.Credentials.AppId;
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "sendmsg", client.Credentials.Token!);
.CreateFlurlRequest(request, HttpMethod.Post, "sendmsg", client.Credentials.Token!);
return await client.SendRequestWithJsonAsync<Models.SendMessageResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
return await client.SendFlurlRequestAsJsonAsync<Models.SendMessageResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
/// <summary>
@ -43,13 +43,13 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
if (client is null) throw new ArgumentNullException(nameof(client));
if (request is null) throw new ArgumentNullException(nameof(request));
if (request.AppId == null)
if (request.AppId is null)
request.AppId = client.Credentials.AppId;
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "kefustate", "get", client.Credentials.Token!);
.CreateFlurlRequest(request, HttpMethod.Post, "kefustate", "get", client.Credentials.Token!);
return await client.SendRequestWithJsonAsync<Models.KefuStateGetResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
return await client.SendFlurlRequestAsJsonAsync<Models.KefuStateGetResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
/// <summary>
@ -65,13 +65,13 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
if (client is null) throw new ArgumentNullException(nameof(client));
if (request is null) throw new ArgumentNullException(nameof(request));
if (request.AppId == null)
if (request.AppId is null)
request.AppId = client.Credentials.AppId;
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "kefustate", "change", client.Credentials.Token!);
.CreateFlurlRequest(request, HttpMethod.Post, "kefustate", "change", client.Credentials.Token!);
return await client.SendRequestWithJsonAsync<Models.KefuStateChangeResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
return await client.SendFlurlRequestAsJsonAsync<Models.KefuStateChangeResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
}
}

View File

@ -55,7 +55,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("confidence")]
[System.Text.Json.Serialization.JsonPropertyName("confidence")]
public float Confidence { get; set; } = default!;
public decimal Confidence { get; set; } = default!;
/// <summary>
/// 获取或设置消息类型。
@ -156,7 +156,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("confidence")]
[System.Text.Json.Serialization.JsonPropertyName("confidence")]
public double Confidence { get; set; }
public decimal Confidence { get; set; }
}
public class Slot
@ -274,7 +274,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("confidence")]
[System.Text.Json.Serialization.JsonPropertyName("confidence")]
public double Confidence { get; set; }
public decimal Confidence { get; set; }
/// <summary>
/// 获取或设置发起查询的用户 ID。

View File

@ -46,7 +46,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("score")]
[System.Text.Json.Serialization.JsonPropertyName("score")]
public double Score { get; set; }
public decimal Score { get; set; }
}
}

View File

@ -29,7 +29,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("score")]
[System.Text.Json.Serialization.JsonPropertyName("score")]
public double Score { get; set; }
public decimal Score { get; set; }
}
}

View File

@ -46,45 +46,45 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models
/// 获取或设置是否开通微信门店功能。
/// </summary>
[Newtonsoft.Json.JsonProperty("open_store")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.NumericalBooleanConverter))]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.NumericalBooleanConverter))]
[System.Text.Json.Serialization.JsonPropertyName("open_store")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.NumericalBooleanConverter))]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.NumericalBooleanConverter))]
public bool IsOpenStore { get; set; }
/// <summary>
/// 获取或设置是否开通微信扫商品功能。
/// </summary>
[Newtonsoft.Json.JsonProperty("open_scan")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.NumericalBooleanConverter))]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.NumericalBooleanConverter))]
[System.Text.Json.Serialization.JsonPropertyName("open_scan")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.NumericalBooleanConverter))]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.NumericalBooleanConverter))]
public bool IsOpenScan { get; set; }
/// <summary>
/// 获取或设置是否开通微信支付功能。
/// </summary>
[Newtonsoft.Json.JsonProperty("open_pay")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.NumericalBooleanConverter))]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.NumericalBooleanConverter))]
[System.Text.Json.Serialization.JsonPropertyName("open_pay")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.NumericalBooleanConverter))]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.NumericalBooleanConverter))]
public bool IsOpenPay { get; set; }
/// <summary>
/// 获取或设置是否开通微信卡券功能。
/// </summary>
[Newtonsoft.Json.JsonProperty("open_card")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.NumericalBooleanConverter))]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.NumericalBooleanConverter))]
[System.Text.Json.Serialization.JsonPropertyName("open_card")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.NumericalBooleanConverter))]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.NumericalBooleanConverter))]
public bool IsOpenCard { get; set; }
/// <summary>
/// 获取或设置是否开通微信摇一摇功能。
/// </summary>
[Newtonsoft.Json.JsonProperty("open_shake")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.NumericalBooleanConverter))]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.NumericalBooleanConverter))]
[System.Text.Json.Serialization.JsonPropertyName("open_shake")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.NumericalBooleanConverter))]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.NumericalBooleanConverter))]
public bool IsOpenShake { get; set; }
}

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>net461; netstandard2.0; net6.0</TargetFrameworks>
<LangVersion>8.0</LangVersion>
<LangVersion>10.0</LangVersion>
<Nullable>enable</Nullable>
<NullableReferenceTypes>true</NullableReferenceTypes>
</PropertyGroup>
@ -14,7 +14,7 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat</PackageProjectUrl>
<PackageTags>Flurl.Http Wechat Weixin MicroMessage WechatAI WechatOpenAI WexinAI WeixinOpenAI 微信 微信智能对话 微信对话开放平台 微信智能对话开放平台 智能对话平台</PackageTags>
<Version>2.0.1</Version>
<Version>3.0.0-preview.1</Version>
<Description>基于 Flurl.Http 的微信对话开放平台(微信智能对话) API 客户端,支持智能对话接口、第三方客服接入、机器人配置、公众号/小程序/H5 绑定等功能。</Description>
<Authors>Fu Diwei</Authors>
<RepositoryType>git</RepositoryType>
@ -40,7 +40,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="SKIT.FlurlHttpClient.Common" Version="2.6.0" />
<PackageReference Include="SKIT.FlurlHttpClient.Common" Version="3.0.0-preview.1" />
</ItemGroup>
</Project>

View File

@ -21,7 +21,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Settings
internal Credentials(WechatOpenAIClientOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
if (options is null) throw new ArgumentNullException(nameof(options));
AppId = options.AppId;
Token = options.Token;

View File

@ -1,84 +0,0 @@
using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Serialization;
using Newtonsoft.Json;
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 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)
{
string xml;
var settings = new XmlWriterSettings();
settings.Encoding = Encoding.UTF8;
settings.Indent = false;
settings.OmitXmlDeclaration = true;
settings.WriteEndDocumentOnClose = false;
settings.NamespaceHandling = NamespaceHandling.OmitDuplicates;
using var stream = new MemoryStream();
using var writer = XmlWriter.Create(stream, settings);
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+ ([a-zA-Z0-9]+):nil=\"true\"[^>]*/>", string.Empty, RegexOptions.IgnoreCase);
xml = Regex.Replace(xml, "<\\?xml[^>]*\\?>", string.Empty, RegexOptions.IgnoreCase);
return xml;
}
public static string Serialize<T>(T obj)
where T : class
{
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);
}
public static string ConvertFromJson(string json)
{
XmlDocument xmlDocument = JsonConvert.DeserializeXmlNode(json, "xml")!;
string xml = xmlDocument.InnerXml;
return xml;
}
}
}

View File

@ -16,7 +16,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
/// <returns>信息摘要字节数组。</returns>
public static byte[] Hash(byte[] bytes)
{
if (bytes == null) throw new ArgumentNullException(nameof(bytes));
if (bytes is null) throw new ArgumentNullException(nameof(bytes));
using SHA1 sha = SHA1.Create();
return sha.ComputeHash(bytes);
@ -29,7 +29,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
/// <returns>信息摘要。</returns>
public static string Hash(string message)
{
if (message == null) throw new ArgumentNullException(nameof(message));
if (message is null) throw new ArgumentNullException(nameof(message));
byte[] msgBytes = Encoding.UTF8.GetBytes(message);
byte[] hashBytes = Hash(msgBytes);

View File

@ -60,7 +60,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
private static byte[] Decode2(byte[] decryptedBytes)
{
if (decryptedBytes == null) throw new ArgumentNullException(nameof(decryptedBytes));
if (decryptedBytes is null) throw new ArgumentNullException(nameof(decryptedBytes));
int pad = (int)decryptedBytes[decryptedBytes.Length - 1];
if (pad < 1 || pad > 32)
@ -75,9 +75,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
private static byte[] AESDecrypt(byte[] keyBytes, byte[] ivBytes, byte[] ciperBytes)
{
if (keyBytes == null) throw new ArgumentNullException(nameof(keyBytes));
if (ivBytes == null) throw new ArgumentNullException(nameof(ivBytes));
if (ciperBytes == null) throw new ArgumentNullException(nameof(ciperBytes));
if (keyBytes is null) throw new ArgumentNullException(nameof(keyBytes));
if (ivBytes is null) throw new ArgumentNullException(nameof(ivBytes));
if (ciperBytes is null) throw new ArgumentNullException(nameof(ciperBytes));
using var aes = Aes.Create();
aes.KeySize = 256;
@ -103,9 +103,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
private static string AESEncrypt(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));
if (keyBytes is null) throw new ArgumentNullException(nameof(keyBytes));
if (ivBytes is null) throw new ArgumentNullException(nameof(ivBytes));
if (plainBytes is null) throw new ArgumentNullException(nameof(plainBytes));
using var aes = Aes.Create();
aes.KeySize = AES_KEY_SIZE;
@ -140,8 +140,8 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
/// <returns>解密后的文本内容。</returns>
public static string AESDecrypt(string cipherText, string encodingAESKey, out string appId)
{
if (cipherText == null) throw new ArgumentNullException(nameof(cipherText));
if (encodingAESKey == null) throw new ArgumentNullException(nameof(encodingAESKey));
if (cipherText is null) throw new ArgumentNullException(nameof(cipherText));
if (encodingAESKey is null) throw new ArgumentNullException(nameof(encodingAESKey));
byte[] cipherBytes = Convert.FromBase64String(cipherText);
byte[] keyBytes = Convert.FromBase64String(encodingAESKey + "=");
@ -170,9 +170,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
/// <returns>加密后的文本内容。</returns>
public static string AESEncrypt(string plainText, string encodingAESKey, string appId)
{
if (plainText == null) throw new ArgumentNullException(nameof(plainText));
if (encodingAESKey == null) throw new ArgumentNullException(nameof(encodingAESKey));
if (appId == null) throw new ArgumentNullException(nameof(appId));
if (plainText is null) throw new ArgumentNullException(nameof(plainText));
if (encodingAESKey is null) throw new ArgumentNullException(nameof(encodingAESKey));
if (appId is null) throw new ArgumentNullException(nameof(appId));
byte[] keyBytes = Convert.FromBase64String(encodingAESKey + "=");
byte[] ivBytes = new byte[16];
@ -204,11 +204,11 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
/// <returns>验证结果。</returns>
public static bool VerifySignature(string sToken, string sTimestamp, string sNonce, string sMsgEncrypt, string sMsgSign)
{
if (sToken == null) throw new ArgumentNullException(nameof(sToken));
if (sTimestamp == null) throw new ArgumentNullException(nameof(sTimestamp));
if (sNonce == null) throw new ArgumentNullException(nameof(sNonce));
if (sMsgEncrypt == null) throw new ArgumentNullException(nameof(sMsgEncrypt));
if (sMsgSign == null) throw new ArgumentNullException(nameof(sMsgSign));
if (sToken is null) throw new ArgumentNullException(nameof(sToken));
if (sTimestamp is null) throw new ArgumentNullException(nameof(sTimestamp));
if (sNonce is null) throw new ArgumentNullException(nameof(sNonce));
if (sMsgEncrypt is null) throw new ArgumentNullException(nameof(sMsgEncrypt));
if (sMsgSign is null) throw new ArgumentNullException(nameof(sMsgSign));
string expectedSign = GenerateSignature(sToken, sTimestamp, sNonce, sMsgEncrypt);
return string.Equals(expectedSign, sMsgSign, StringComparison.OrdinalIgnoreCase);
@ -224,10 +224,10 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
/// <returns>签名。</returns>
public static string GenerateSignature(string sToken, string sTimestamp, string sNonce, string sMsgEncrypt)
{
if (sToken == null) throw new ArgumentNullException(nameof(sToken));
if (sTimestamp == null) throw new ArgumentNullException(nameof(sTimestamp));
if (sNonce == null) throw new ArgumentNullException(nameof(sNonce));
if (sMsgEncrypt == null) throw new ArgumentNullException(nameof(sMsgEncrypt));
if (sToken is null) throw new ArgumentNullException(nameof(sToken));
if (sTimestamp is null) throw new ArgumentNullException(nameof(sTimestamp));
if (sNonce is null) throw new ArgumentNullException(nameof(sNonce));
if (sMsgEncrypt is null) throw new ArgumentNullException(nameof(sMsgEncrypt));
List<string> tmp = new List<string>(capacity: 4) { sToken, sTimestamp, sNonce, sMsgEncrypt };
tmp.Sort(StringComparer.Ordinal);
@ -257,7 +257,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
/// <returns>指示是否是有效的 XML 内容。</returns>
public static bool TryParseXml(string xml, out string? encryptedMsg, out string? toUserName)
{
if (xml == null) throw new ArgumentNullException(nameof(xml));
if (xml is null) throw new ArgumentNullException(nameof(xml));
encryptedMsg = null;
toUserName = null;
@ -269,7 +269,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
xmlDoc.LoadXml(xml);
XmlNode? xmlRoot = xmlDoc.FirstChild;
if (xmlRoot == null)
if (xmlRoot is null)
return false;
encryptedMsg = xmlRoot["Encrypt"]?.InnerText?.ToString();
@ -291,8 +291,8 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
/// <returns></returns>
public static string WrapXml(string sToken, string sMsgEncrypt)
{
if (sToken == null) throw new ArgumentNullException(nameof(sToken));
if (sMsgEncrypt == null) throw new ArgumentNullException(nameof(sMsgEncrypt));
if (sToken is null) throw new ArgumentNullException(nameof(sToken));
if (sMsgEncrypt is null) throw new ArgumentNullException(nameof(sMsgEncrypt));
string sTimestamp = DateTimeOffset.Now.ToLocalTime().ToUnixTimeSeconds().ToString();
string sNonce = DateTimeOffset.Now.Ticks.ToString("x");
@ -310,10 +310,10 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
/// <returns></returns>
public static string WrapXml(string sTimestamp, string sNonce, string sMsgEncrypt, string sMsgSign)
{
if (sTimestamp == null) throw new ArgumentNullException(nameof(sTimestamp));
if (sNonce == null) throw new ArgumentNullException(nameof(sNonce));
if (sMsgEncrypt == null) throw new ArgumentNullException(nameof(sMsgEncrypt));
if (sMsgSign == null) throw new ArgumentNullException(nameof(sMsgSign));
if (sTimestamp is null) throw new ArgumentNullException(nameof(sTimestamp));
if (sNonce is null) throw new ArgumentNullException(nameof(sNonce));
if (sMsgEncrypt is null) throw new ArgumentNullException(nameof(sMsgEncrypt));
if (sMsgSign is null) throw new ArgumentNullException(nameof(sMsgSign));
StringBuilder builder = new StringBuilder();
builder.AppendFormat("<xml>");

View File

@ -0,0 +1,40 @@
using System;
using System.Xml;
using Newtonsoft.Json;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
{
using SKIT.FlurlHttpClient.Internal;
internal static class XmlHelper
{
public static string Serialize(object obj, Type type)
{
return _XmlSimpleSerializer.Serialize(obj, type);
}
public static string Serialize<T>(T obj)
where T : class
{
return Serialize(obj, typeof(T));
}
public static object Deserialize(string xml, Type type)
{
return _XmlSimpleSerializer.Deserialize(xml, type);
}
public static T Deserialize<T>(string xml)
where T : class
{
return (T)Deserialize(xml, typeof(T));
}
public static string ConvertFromJson(string json)
{
XmlDocument xmlDocument = JsonConvert.DeserializeXmlNode(json, "xml")!;
string xml = xmlDocument.InnerXml;
return xml;
}
}
}

View File

@ -21,42 +21,37 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
/// </summary>
/// <param name="options">配置项。</param>
public WechatOpenAIClient(WechatOpenAIClientOptions options)
: base()
: this(options, null)
{
if (options == null) throw new ArgumentNullException(nameof(options));
}
/// <summary>
///
/// </summary>
/// <param name="options"></param>
/// <param name="httpClient"></param>
/// <param name="disposeClient"></param>
internal protected WechatOpenAIClient(WechatOpenAIClientOptions options, HttpClient? httpClient, bool disposeClient = true)
: base(httpClient, disposeClient)
{
if (options is null) throw new ArgumentNullException(nameof(options));
Credentials = new Settings.Credentials(options);
FlurlClient.BaseUrl = options.Endpoint ?? WechatOpenAIEndpoints.DEFAULT;
FlurlClient.WithTimeout(TimeSpan.FromMilliseconds(options.Timeout));
}
/// <summary>
/// 用指定的微信智能对话 AppId、Token、EncodingAESKey 初始化 <see cref="WechatOpenAIThirdPartyClient"/> 类的新实例。
/// </summary>
/// <param name="appId">微信智能对话 AppId。</param>
/// <param name="token">微信智能对话 Token。</param>
/// <param name="encodingAESKey">微信智能对话 EncodingAESKey。</param>
public WechatOpenAIClient(string appId, string token, string encodingAESKey)
: this(new WechatOpenAIClientOptions() { AppId = appId, Token = token, EncodingAESKey = encodingAESKey })
{
FlurlClient.WithTimeout(options.Timeout <= 0 ? Timeout.InfiniteTimeSpan : TimeSpan.FromMilliseconds(options.Timeout));
}
/// <summary>
/// 使用当前客户端生成一个新的 <see cref="IFlurlRequest"/> 对象。
/// </summary>
/// <param name="request"></param>
/// <param name="method"></param>
/// <param name="httpMethod"></param>
/// <param name="urlSegments"></param>
/// <returns></returns>
public IFlurlRequest CreateRequest(WechatOpenAIRequest request, HttpMethod method, params object[] urlSegments)
public IFlurlRequest CreateFlurlRequest(WechatOpenAIRequest request, HttpMethod httpMethod, params object[] urlSegments)
{
IFlurlRequest flurlRequest = FlurlClient.Request(urlSegments).WithVerb(method);
if (request.Timeout != null)
{
flurlRequest.WithTimeout(TimeSpan.FromMilliseconds(request.Timeout.Value));
}
IFlurlRequest flurlRequest = base.CreateFlurlRequest(request, httpMethod, urlSegments);
return flurlRequest;
}
@ -69,20 +64,13 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
/// <param name="httpContent"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<T> SendRequestAsync<T>(IFlurlRequest flurlRequest, HttpContent? httpContent = null, CancellationToken cancellationToken = default)
public async Task<T> SendFlurlRequestAsync<T>(IFlurlRequest flurlRequest, HttpContent? httpContent = null, CancellationToken cancellationToken = default)
where T : WechatOpenAIResponse, new()
{
if (flurlRequest == null) throw new ArgumentNullException(nameof(flurlRequest));
if (flurlRequest is null) throw new ArgumentNullException(nameof(flurlRequest));
try
{
using IFlurlResponse flurlResponse = await base.SendRequestAsync(flurlRequest, httpContent, cancellationToken);
return await WrapResponseWithJsonAsync<T>(flurlResponse, cancellationToken);
}
catch (FlurlHttpException ex)
{
throw new WechatOpenAIException(ex.Message, ex);
}
using IFlurlResponse flurlResponse = await base.SendFlurlRequestAsync(flurlRequest, httpContent, cancellationToken);
return await WrapFlurlResponseAsJsonAsync<T>(flurlResponse, cancellationToken);
}
/// <summary>
@ -93,37 +81,26 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
/// <param name="data"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<T> SendRequestWithJsonAsync<T>(IFlurlRequest flurlRequest, object? data = null, CancellationToken cancellationToken = default)
public async Task<T> SendFlurlRequestAsJsonAsync<T>(IFlurlRequest flurlRequest, object? data = null, CancellationToken cancellationToken = default)
where T : WechatOpenAIResponse, new()
{
if (flurlRequest == null) throw new ArgumentNullException(nameof(flurlRequest));
if (flurlRequest is null) throw new ArgumentNullException(nameof(flurlRequest));
try
if (data is WechatOpenAIRequest.Serialization.IEncryptedXmlable)
{
if (data is WechatOpenAIRequest.Serialization.IEncryptedXmlable)
{
string plainXml = Utilities.XmlUtility.ConvertFromJson(JsonSerializer.Serialize(data));
string encryptedXml = Utilities.WxMsgCryptor.AESEncrypt(plainText: plainXml, encodingAESKey: Credentials.EncodingAESKey!, appId: Credentials.AppId!);
data = new { encrypt = encryptedXml };
}
string plainXml = Utilities.XmlHelper.ConvertFromJson(JsonSerializer.Serialize(data));
string encryptedXml = Utilities.WxMsgCryptor.AESEncrypt(plainText: plainXml, encodingAESKey: Credentials.EncodingAESKey!, appId: Credentials.AppId!);
data = new { encrypt = encryptedXml };
}
bool isSimpleRequest = data == null ||
flurlRequest.Verb == HttpMethod.Get ||
flurlRequest.Verb == HttpMethod.Head ||
flurlRequest.Verb == HttpMethod.Options;
using IFlurlResponse flurlResponse = isSimpleRequest ?
await base.SendRequestAsync(flurlRequest, null, cancellationToken) :
await base.SendRequestWithJsonAsync(flurlRequest, data, cancellationToken);
return await WrapResponseWithJsonAsync<T>(flurlResponse, cancellationToken);
}
catch (FlurlHttpTimeoutException ex)
{
throw new Exceptions.WechatOpenAIRequestTimeoutException(ex.Message, ex);
}
catch (FlurlHttpException ex)
{
throw new WechatOpenAIException(ex.Message, ex);
}
bool isSimpleRequest = data is null ||
flurlRequest.Verb == HttpMethod.Get ||
flurlRequest.Verb == HttpMethod.Head ||
flurlRequest.Verb == HttpMethod.Options;
using IFlurlResponse flurlResponse = isSimpleRequest ?
await base.SendFlurlRequestAsync(flurlRequest, null, cancellationToken) :
await base.SendFlurlRequestAsJsonAsync(flurlRequest, data, cancellationToken);
return await WrapFlurlResponseAsJsonAsync<T>(flurlResponse, cancellationToken);
}
}
}

View File

@ -1,38 +1,27 @@
using System;
using System.Xml.Serialization;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI
{
/// <summary>
/// 表示微信智能对话 API 回调通知事件的基类。
/// </summary>
[Serializable]
public class WechatOpenAIEvent
[System.Xml.Serialization.XmlRoot("xml")]
public class WechatOpenAIEvent : ICommonWebhookEvent
{
public static class Serialization
{
[XmlRoot("xml")]
public interface IXmlSerializable
{
}
}
/// <summary>
/// 获取或设置 AppId。
/// </summary>
[XmlElement("appid", IsNullable = true)]
[System.Xml.Serialization.XmlElement("appid", IsNullable = true)]
public string? AppId { get; set; }
/// <summary>
/// 获取或设置事件类型。
/// </summary>
[XmlElement("event", IsNullable = true)]
[System.Xml.Serialization.XmlElement("event", IsNullable = true)]
public string? Event { get; set; }
/// <summary>
/// 获取或设置消息创建时间戳。
/// </summary>
[XmlElement("createtime", IsNullable = true)]
[System.Xml.Serialization.XmlElement("createtime", IsNullable = true)]
public long? CreateTimestamp { get; set; }
}
}

View File

@ -1,11 +1,11 @@
using System;
using System;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI
{
/// <summary>
/// 当调用微信智能对话 API 出错时引发的异常。
/// </summary>
public class WechatOpenAIException : CommonExceptionBase
public class WechatOpenAIException : CommonException
{
/// <inheritdoc/>
public WechatOpenAIException()

View File

@ -3,7 +3,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
/// <summary>
/// 表示微信智能对话 API 请求的基类。
/// </summary>
public abstract class WechatOpenAIRequest : ICommonRequest
public abstract class WechatOpenAIRequest : CommonRequestBase, ICommonRequest
{
public static class Serialization
{
@ -11,14 +11,5 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
{
}
}
/// <summary>
/// 获取或设置请求超时时间(单位:毫秒)。如果不指定将使用构造 <see cref="WechatOpenAIClient"/> 时的 <see cref="WechatOpenAIClientOptions.Timeout"/> 参数,这在需要指定特定耗时请求(比如上传或下载文件)的超时时间时很有用。
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[System.Xml.Serialization.XmlIgnore]
[System.Xml.Serialization.SoapIgnore]
public virtual int? Timeout { get; set; }
}
}

View File

@ -1,60 +1,10 @@
using System.Collections.Generic;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI
{
/// <summary>
/// 表示微信智能对话 API 响应的基类。
/// </summary>
public abstract class WechatOpenAIResponse : ICommonResponse
public abstract class WechatOpenAIResponse : CommonResponseBase, ICommonResponse
{
/// <summary>
///
/// </summary>
int ICommonResponse.RawStatus { get; set; }
/// <summary>
///
/// </summary>
IDictionary<string, string> ICommonResponse.RawHeaders { get; set; } = default!;
/// <summary>
///
/// </summary>
byte[] ICommonResponse.RawBytes { get; set; } = default!;
/// <summary>
/// 获取原始的 HTTP 响应状态码。
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
public int RawStatus
{
get { return ((ICommonResponse)this).RawStatus; }
internal set { ((ICommonResponse)this).RawStatus = value; }
}
/// <summary>
/// 获取原始的 HTTP 响应表头集合。
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
public IDictionary<string, string> RawHeaders
{
get { return ((ICommonResponse)this).RawHeaders; }
internal set { ((ICommonResponse)this).RawHeaders = value; }
}
/// <summary>
/// 获取原始的 HTTP 响应正文。
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
public byte[] RawBytes
{
get { return ((ICommonResponse)this).RawBytes; }
internal set { ((ICommonResponse)this).RawBytes = value; }
}
/// <summary>
/// 获取微信智能对话 API 返回的错误码。
/// </summary>
@ -84,12 +34,15 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
public virtual string? ReturnError { get; set; }
/// <summary>
/// 获取一个值,该值指示调用微信 API 是否成功(即 HTTP 状态码为 200、且 errcode/ret 值都为 0
/// 获取一个值,该值指示调用微信 API 是否成功。
/// <para>
/// (即 HTTP 状态码为 200且 <see cref="ErrorCode"/>、<see cref="ReturnCode"/> 值都为 0
/// </para>
/// </summary>
/// <returns></returns>
public virtual bool IsSuccessful()
public override bool IsSuccessful()
{
return RawStatus == 200 && ErrorCode.GetValueOrDefault() == 0 && ReturnCode.GetValueOrDefault() == 0 && string.IsNullOrEmpty(ReturnError);
return GetRawStatus() == 200 && ErrorCode.GetValueOrDefault() == 0 && ReturnCode.GetValueOrDefault() == 0 && string.IsNullOrEmpty(ReturnError);
}
}

View File

@ -0,0 +1,56 @@
using System;
using System.IO;
using System.Reflection;
using SKIT.FlurlHttpClient.Tools.CodeAnalyzer;
using Xunit;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests
{
public class CodeAnalyzeTests
{
[Fact(DisplayName = "代码质量分析")]
public void CodeAnalyze()
{
Assert.Null(Record.Exception(() =>
{
var options = new TypeDeclarationAnalyzerOptions()
{
SdkAssembly = Assembly.GetAssembly(typeof(WechatOpenAIClient))!,
SdkRequestModelDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.OpenAI.Models",
SdkResponseModelDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.OpenAI.Models",
SdkExecutingExtensionDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.OpenAI",
SdkWebhookEventDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.OpenAI.Events",
ThrowOnNotFoundRequestModelTypes = true,
ThrowOnNotFoundResponseModelTypes = true,
ThrowOnNotFoundExecutingExtensionTypes = true,
ThrowOnNotFoundWebhookEventTypes = true
};
new TypeDeclarationAnalyzer(options).AssertNoIssues();
}));
Assert.Null(Record.Exception(() =>
{
string workdir = Environment.CurrentDirectory;
string projdir = Path.Combine(workdir, "../../../../../");
var options = new SourceFileAnalyzerOptions()
{
SdkAssembly = Assembly.GetAssembly(typeof(WechatOpenAIClient))!,
SdkRequestModelDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.OpenAI.Models",
SdkResponseModelDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.OpenAI.Models",
SdkWebhookEventDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.OpenAI.Events",
ProjectSourceRootDirectory = Path.Combine(projdir, "./src/SKIT.FlurlHttpClient.Wechat.OpenAI/"),
ProjectTestRootDirectory = Path.Combine(projdir, "./test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/"),
ThrowOnNotFoundRequestModelClassCodeFiles = true,
ThrowOnNotFoundResponseModelClassCodeFiles = true,
ThrowOnNotFoundExecutingExtensionClassCodeFiles = true,
ThrowOnNotFoundWebhookEventClassCodeFiles = true,
ThrowOnNotFoundRequestModelSerializationSampleFiles = true,
ThrowOnNotFoundResponseModelSerializationSampleFiles = true,
ThrowOnNotFoundWebhookEventSerializationSampleFiles = true
};
new SourceFileAnalyzer(options).AssertNoIssues();
}));
}
}
}

View File

@ -10,23 +10,23 @@
<ItemGroup>
<None Remove=".gitignore" />
<Content Include="appsettings.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</Content>
<Content Include="appsettings.*.json" Condition="'$(Configuration)' == 'Debug'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="appsettings.*.json" Condition="'$(Configuration)' == 'Debug'">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
</Content>
<Content Include="ModelSamples/**/*.json" />
<Content Include="EventSamples/**/*.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
<PackageReference Include="SKIT.FlurlHttpClient.Tools.CodeAnalyzer" Version="0.1.0-alpha.1" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="SKIT.FlurlHttpClient.Tools.CodeAnalyzer" Version="0.3.0-preview.1" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -1,28 +0,0 @@
using SKIT.FlurlHttpClient.Tools.CodeAnalyzer;
using Xunit;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests
{
public class TestCase_CodeReview
{
[Fact(DisplayName = "测试用例:代码质量分析")]
public void TestCodeAnalyzer()
{
Assert.Null(Record.Exception(() =>
{
CodeAnalyzerOptions options = new CodeAnalyzerOptions()
{
AssemblyName = "SKIT.FlurlHttpClient.Wechat.OpenAI",
WorkDirectoryForSourceCode = TestConfigs.WorkDirectoryForSdk,
WorkDirectoryForTestSample = TestConfigs.WorkDirectoryForTest,
AllowNotFoundEventTypes = true,
AllowNotFoundEventSamples = true
};
CodeAnalyzer analyzer = new CodeAnalyzer(options);
analyzer.Start();
analyzer.Assert();
analyzer.Flush();
}));
}
}
}

View File

@ -17,14 +17,11 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests
using var stream = File.OpenRead("appsettings.local.json");
using var jdoc = JsonDocument.Parse(stream);
var config = jdoc.RootElement.GetProperty("TestConfig");
var config = jdoc.RootElement.GetProperty("TestConfigs");
WechatAppId = config.GetProperty("AppId").GetString()!;
WechatToken = config.GetProperty("Token").GetString()!;
WechatEncodingAESKey = config.GetProperty("EncodingAESKey").GetString()!;
WechatAccessToken = config.GetProperty("AccessToken").GetString()!;
WorkDirectoryForSdk = jdoc.RootElement.GetProperty("WorkDirectoryForSdk").GetString()!;
WorkDirectoryForTest = jdoc.RootElement.GetProperty("WorkDirectoryForTest").GetString()!;
}
catch (Exception ex)
{
@ -36,8 +33,5 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests
public static readonly string WechatToken;
public static readonly string WechatEncodingAESKey;
public static readonly string WechatAccessToken;
public static readonly string WorkDirectoryForSdk;
public static readonly string WorkDirectoryForTest;
}
}

View File

@ -1,10 +1,8 @@
{
"TestConfig": {
"TestConfigs": {
"AppId": "请在此填写用于测试的微信智能对话 AppId",
"Token": "请在此填写用于测试的微信智能对话 Token",
"EncodingAESKey": "请在此填写用于测试的微信智能对话 EncodingAESKey",
"AccessToken": "请在此填写用于测试的微信智能对话 AccessToken"
},
"WorkDirectoryForSdk": "请输入当前 SDK 项目所在的目录完整路径,如 C:\\Project\\src\\SKIT.FlurlHttpClient.Wechat.OpenAI\\",
"WorkDirectoryForTest": "请输入当前测试项目所在的目录完整路径,如 C:\\Project\\test\\SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests\\"
}
}