feat(openai): 新增第三方客服接入相关接口

This commit is contained in:
Fu Diwei 2021-10-09 15:17:35 +08:00
parent cf49b5fe83
commit 3d432ceb85
19 changed files with 398 additions and 30 deletions

View File

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Events
{
/// <summary>
/// <para>表示 userEnter 事件的数据。</para>
/// <para>REF: https://developers.weixin.qq.com/doc/aispeech/platform/thirdkefu/recivemsg.html </para>
/// </summary>
public class UserEnterEvent : WechatOpenAIEvent, WechatOpenAIEvent.Serialization.IXmlSerializable
{
public static class Types
{
public class Content
{
/// <summary>
/// 获取或设置消息。
/// </summary>
[System.Xml.Serialization.XmlElement("msg")]
public string Message { get; set; } = default!;
}
}
/// <summary>
/// 获取或设置用户 ID。
/// </summary>
[System.Xml.Serialization.XmlElement("userid")]
public string UserId { get; set; } = default!;
/// <summary>
/// 获取或设置对话来源。
/// </summary>
[System.Xml.Serialization.XmlElement("from")]
public string From { get; set; } = default!;
/// <summary>
/// 获取或设置消息内容。
/// </summary>
[System.Xml.Serialization.XmlElement("content")]
public Types.Content? Content { get; set; }
/// <summary>
/// 获取或设置客服接入状态。
/// </summary>
[System.Xml.Serialization.XmlElement("status")]
public int Status { get; set; }
/// <summary>
/// 获取或设置渠道。
/// </summary>
[System.Xml.Serialization.XmlElement("channel", IsNullable = true)]
public string? Channel { get; set; }
/// <summary>
/// 获取或设置用户评价。
/// </summary>
[System.Xml.Serialization.XmlElement("assessment", IsNullable = true)]
public int? Assessment { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Events
{
/// <summary>
/// <para>表示 userQuit 事件的数据。</para>
/// <para>REF: https://developers.weixin.qq.com/doc/aispeech/platform/thirdkefu/recivemsg.html </para>
/// </summary>
public class UserQuitEvent : UserEnterEvent
{
}
}

View File

@ -98,18 +98,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
try
{
using var stream = new MemoryStream();
using var writer = new System.Xml.XmlTextWriter(stream, Encoding.UTF8);
writer.Formatting = System.Xml.Formatting.None;
XmlSerializer xmlSerializer = new XmlSerializer(typeof(TEvent), new XmlRootAttribute("xml"));
XmlSerializerNamespaces xmlNamespace = new XmlSerializerNamespaces();
xmlNamespace.Add(string.Empty, string.Empty);
xmlSerializer.Serialize(writer, callbackModel, xmlNamespace);
writer.Flush();
xml = Encoding.UTF8.GetString(stream.ToArray());
xml = Regex.Replace(xml, "\\s+<\\w+ (xsi|d2p1):nil=\"true\"[^>]*/>", string.Empty, RegexOptions.IgnoreCase);
xml = Regex.Replace(xml, "<\\?xml[^>]*\\?>", string.Empty, RegexOptions.IgnoreCase);
xml = Utilities.XmlUtility.Serialize(callbackModel);
}
catch (Exception ex)
{

View File

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Flurl;
using Flurl.Http;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI
{
public static class WechatOpenAIClientExecuteOpenApiThirdKfExtensions
{
/// <summary>
/// <para>异步调用 [POST] /openapi/sendmsg/{TOKEN} 接口。</para>
/// <para>REF: https://developers.weixin.qq.com/doc/aispeech/platform/thirdkefu/sendmsg.html </para>
/// </summary>
/// <param name="client"></param>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<Models.OpenApiSendMessageResponse> ExecuteOpenApiSendMessageAsync(this WechatOpenAIClient client, Models.OpenApiSendMessageRequest request, CancellationToken cancellationToken = default)
{
if (client is null) throw new ArgumentNullException(nameof(client));
if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "openapi", "sendmsg", client.Credentials.PushToken!);
return await client.SendRequestWithJsonAsync<Models.OpenApiSendMessageResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
/// <summary>
/// <para>异步调用 [POST] /openapi/kefustate/get/{TOKEN} 接口。</para>
/// <para>REF: https://developers.weixin.qq.com/doc/aispeech/platform/thirdkefu/getstate.html </para>
/// </summary>
/// <param name="client"></param>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<Models.OpenApiKefuStateGetResponse> ExecuteOpenApiKefuStateGetAsync(this WechatOpenAIClient client, Models.OpenApiKefuStateGetRequest request, CancellationToken cancellationToken = default)
{
if (client is null) throw new ArgumentNullException(nameof(client));
if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "openapi", "kefustate", "get", client.Credentials.PushToken!);
return await client.SendRequestWithJsonAsync<Models.OpenApiKefuStateGetResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
/// <summary>
/// <para>异步调用 [POST] /openapi/kefustate/change/{TOKEN} 接口。</para>
/// <para>REF: https://developers.weixin.qq.com/doc/aispeech/platform/thirdkefu/getstate.html </para>
/// </summary>
/// <param name="client"></param>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<Models.OpenApiKefuStateChangeResponse> ExecuteOpenApiKefuStateChangeAsync(this WechatOpenAIClient client, Models.OpenApiKefuStateChangeRequest request, CancellationToken cancellationToken = default)
{
if (client is null) throw new ArgumentNullException(nameof(client));
if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "openapi", "kefustate", "change", client.Credentials.PushToken!);
return await client.SendRequestWithJsonAsync<Models.OpenApiKefuStateChangeResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
}
}

View File

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models
{
/// <summary>
/// <para>表示 [POST] /openapi/kefustate/change/{TOKEN} 接口的请求。</para>
/// </summary>
[XmlRoot("xml")]
public class OpenApiKefuStateChangeRequest : WechatOpenAIRequestEncryptedXmlable
{
/// <summary>
/// 获取或设置用户的 OpenId。
/// </summary>
[XmlElement("openid")]
public string OpenId { get; set; } = string.Empty;
/// <summary>
/// 获取或设置客服接入状态。
/// </summary>
[XmlElement("kefustate")]
public string State { get; set; } = string.Empty;
/// <summary>
/// 获取或设置有效期(单位:秒)。
/// <para>默认值1800</para>
/// </summary>
[XmlElement("expires")]
public int ExpiresIn { get; set; } = 1800;
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models
{
/// <summary>
/// <para>表示 [POST] /openapi/kefustate/change/{TOKEN} 接口的响应。</para>
/// </summary>
public class OpenApiKefuStateChangeResponse : WechatOpenAIResponse
{
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models
{
/// <summary>
/// <para>表示 [POST] /openapi/kefustate/get/{TOKEN} 接口的请求。</para>
/// </summary>
[XmlRoot("xml")]
public class OpenApiKefuStateGetRequest : WechatOpenAIRequestEncryptedXmlable
{
/// <summary>
/// 获取或设置用户的 OpenId。
/// </summary>
[XmlElement("openid")]
public string OpenId { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models
{
/// <summary>
/// <para>表示 [POST] /openapi/kefustate/get/{TOKEN} 接口的响应。</para>
/// </summary>
public class OpenApiKefuStateGetResponse : WechatOpenAIResponse
{
/// <summary>
/// 获取或设置客户接入状态。
/// </summary>
[Newtonsoft.Json.JsonProperty("kefustate")]
[System.Text.Json.Serialization.JsonPropertyName("kefustate")]
public string State { get; set; } = default!;
}
}

View File

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models
{
/// <summary>
/// <para>表示 [POST] /openapi/sendmsg/{TOKEN} 接口的请求。</para>
/// </summary>
[XmlRoot("xml")]
public class OpenApiSendMessageRequest : WechatOpenAIRequestEncryptedXmlable
{
/// <summary>
/// 获取或设置用户的 OpenId。
/// </summary>
[XmlElement("openid")]
public string OpenId { get; set; } = string.Empty;
/// <summary>
/// 获取或设置消息内容。
/// </summary>
[XmlElement("msg")]
public string Message { get; set; } = string.Empty;
/// <summary>
/// 获取或设置渠道。
/// </summary>
[XmlElement("channel")]
public int Channel { get; set; }
/// <summary>
/// 获取或设置事件。
/// </summary>
[XmlElement("event")]
public string? Event { get; set; }
/// <summary>
/// 获取或设置客服人员的昵称。
/// </summary>
[XmlElement("kefuname")]
public string? KfName { get; set; }
/// <summary>
/// 获取或设置客服人员的头像 URL。
/// </summary>
[XmlElement("kefuavatar")]
public string? KfAvatarUrl { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models
{
/// <summary>
/// <para>表示 [POST] /openapi/sendmsg/{TOKEN} 接口的响应。</para>
/// </summary>
public class OpenApiSendMessageResponse : WechatOpenAIResponse
{
}
}

View File

@ -0,0 +1,37 @@
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Serialization;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
{
internal static class XmlUtility
{
public static string Serialize(Type type, object obj)
{
string xml;
using var stream = new MemoryStream();
using var writer = new System.Xml.XmlTextWriter(stream, Encoding.UTF8);
writer.Formatting = System.Xml.Formatting.None;
XmlSerializer xmlSerializer = new XmlSerializer(type, new XmlRootAttribute("xml"));
XmlSerializerNamespaces xmlNamespace = new XmlSerializerNamespaces();
xmlNamespace.Add(string.Empty, string.Empty);
xmlSerializer.Serialize(writer, obj, xmlNamespace);
writer.Flush();
xml = Encoding.UTF8.GetString(stream.ToArray());
xml = Regex.Replace(xml, "\\s+<\\w+ (xsi|d2p1):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);
}
}
}

View File

@ -112,6 +112,15 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
{
try
{
if (data is WechatOpenAIRequestEncryptedXmlable xmlData)
{
if (xmlData.AppId == null)
xmlData.AppId = Credentials.AppId;
string xml = Utilities.XmlUtility.Serialize(xmlData);
data = new { encrypt = Utilities.WxBizMsgCryptor.AESEncrypt(plainText: xml, encodingAESKey: Credentials.PushEncodingAESKey!, appId: xmlData.AppId!) };
}
using IFlurlResponse flurlResponse = await base.SendRequestWithJsonAsync(flurlRequest, data, cancellationToken).ConfigureAwait(false);
return await GetResposneAsync<T>(flurlResponse).ConfigureAwait(false);
}

View File

@ -12,6 +12,8 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[System.Xml.Serialization.XmlIgnore]
[System.Xml.Serialization.SoapIgnore]
public virtual int? Timeout { get; set; }
/// <summary>
@ -19,6 +21,8 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[System.Xml.Serialization.XmlIgnore]
[System.Xml.Serialization.SoapIgnore]
public virtual string? RequestId { get; set; }
/// <summary>
@ -26,6 +30,8 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[System.Xml.Serialization.XmlIgnore]
[System.Xml.Serialization.SoapIgnore]
public virtual string? BotId { get; set; }
/// <summary>
@ -33,6 +39,17 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
[System.Xml.Serialization.XmlIgnore]
[System.Xml.Serialization.SoapIgnore]
public virtual string? AccessToken { get; set; }
}
public abstract class WechatOpenAIRequestEncryptedXmlable : WechatOpenAIRequest
{
/// <summary>
/// 获取或设置微信 AppId。如果不指定将使用构造 <see cref="WechatOpenAIClient"/> 时的 <see cref="WechatOpenAIClientOptions.AppId"/> 参数。
/// </summary>
[System.Xml.Serialization.XmlElement("appid")]
public string? AppId { get; set; }
}
}

View File

@ -32,16 +32,30 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
/// <summary>
/// 获取微信智能对话 API 返回的错误码。
/// </summary>
[Newtonsoft.Json.JsonProperty("code")]
[System.Text.Json.Serialization.JsonPropertyName("code")]
public virtual int ErrorCode { get; set; }
[Newtonsoft.Json.JsonProperty("errcode")]
[System.Text.Json.Serialization.JsonPropertyName("errcode")]
public virtual int? ErrorCode { get; set; }
/// <summary>
/// 获取微信智能对话 API 返回的错误描述。
/// 获取微信智能对话 API 返回的错误信息。
/// </summary>
[Newtonsoft.Json.JsonProperty("errmsg")]
[System.Text.Json.Serialization.JsonPropertyName("errmsg")]
public virtual string? ErrorMessage { get; set; }
/// <summary>
/// 获取微信智能对话 API 返回的状态码。
/// </summary>
[Newtonsoft.Json.JsonProperty("code")]
[System.Text.Json.Serialization.JsonPropertyName("code")]
public virtual int? Code { get; set; }
/// <summary>
/// 获取微信智能对话 API 返回的调用信息。
/// </summary>
[Newtonsoft.Json.JsonProperty("msg")]
[System.Text.Json.Serialization.JsonPropertyName("msg")]
public virtual string? ErrorMessage { get; set; }
public virtual string? Message { get; set; }
/// <summary>
/// 获取微信智能对话 API 返回的请求唯一标识。
@ -51,12 +65,12 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
public virtual string? RequestId { get; set; }
/// <summary>
/// 获取一个值,该值指示调用微信 API 是否成功(即 HTTP 状态码为 200、且 errcode 值为 0
/// 获取一个值,该值指示调用微信 API 是否成功(即 HTTP 状态码为 200、且 code 和 errcode 值为 0
/// </summary>
/// <returns></returns>
public virtual bool IsSuccessful()
{
return RawStatus == 200 && ErrorCode == 0;
return RawStatus == 200 && ErrorCode.GetValueOrDefault() == 0 && Code.GetValueOrDefault() == 0;
}
}

View File

@ -1,6 +1,4 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat
namespace SKIT.FlurlHttpClient.Wechat
{
/// <summary>
/// SKIT.FlurlHttpClient.Wechat 请求接口。

View File

@ -1,16 +1,9 @@
using System;
using System.Collections.Generic;
namespace SKIT.FlurlHttpClient.Wechat
namespace SKIT.FlurlHttpClient.Wechat
{
/// <summary>
/// SKIT.FlurlHttpClient.Wechat 响应接口。
/// </summary>
public interface IWechatResponse : ICommonResponse
{
/// <summary>
/// 获取微信 API 返回的错误描述。
/// </summary>
string? ErrorMessage { get; set; }
}
}

View File

@ -0,0 +1,13 @@
<xml>
<userid>用户的唯一ID通常为用户在微信的openid</userid>
<appid>应用的appid</appid>
<content>
<msg>机器人或用户的的内容</msg>
</content>
<event>userEnter</event>
<from>0</from>
<status>0</status>
<channel>渠0</channel>
<assessment>0</assessment>
<createtime>1234567890</createtime>
</xml>

View File

@ -0,0 +1,10 @@
<xml>
<userid>用户的唯一ID通常为用户在微信的openid</userid>
<appid>应用的appid</appid>
<event>userQuit</event>
<from>0</from>
<status>0</status>
<channel>渠0</channel>
<assessment>0</assessment>
<createtime>1234567890</createtime>
</xml>

View File

@ -11,6 +11,7 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="ModelSamples/**/*.json" />
<Content Include="EventSamples/**/*.xml" />
</ItemGroup>
<ItemGroup>