From 038d1d1293c378141b517068173784745cede2f5 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 7 Oct 2021 21:19:24 +0800 Subject: [PATCH] =?UTF-8?q?feat(openai):=20=E5=AF=BC=E5=85=A5=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=E5=BC=80=E6=94=BE=E5=B9=B3=E5=8F=B0=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 1 + README.md | 1 + SKIT.FlurlHttpClient.Wechat.sln | 16 +- .../SKIT.FlurlHttpClient.Wechat.OpenAI.csproj | 37 +++++ .../Settings/Credentials.cs | 29 ++++ .../WechatOpenAIClient.cs | 147 ++++++++++++++++++ .../WechatOpenAIClientOptions.cs | 30 ++++ .../WechatOpenAIEndpoints.cs | 15 ++ .../WechatOpenAIException.cs | 27 ++++ .../WechatOpenAIRequest.cs | 38 +++++ .../WechatOpenAIResponse.cs | 76 +++++++++ ...lHttpClient.Wechat.OpenAI.UnitTests.csproj | 30 ++++ .../TestClients.cs | 18 +++ .../TestConfigs.cs | 33 ++++ .../WechatOpenAIDeclarationTests.cs | 80 ++++++++++ .../appsettings.json | 9 ++ 16 files changed, 586 insertions(+), 1 deletion(-) create mode 100644 src/SKIT.FlurlHttpClient.Wechat.OpenAI/SKIT.FlurlHttpClient.Wechat.OpenAI.csproj create mode 100644 src/SKIT.FlurlHttpClient.Wechat.OpenAI/Settings/Credentials.cs create mode 100644 src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIClient.cs create mode 100644 src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIClientOptions.cs create mode 100644 src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIEndpoints.cs create mode 100644 src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIException.cs create mode 100644 src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIRequest.cs create mode 100644 src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIResponse.cs create mode 100644 test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests.csproj create mode 100644 test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/TestClients.cs create mode 100644 test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/TestConfigs.cs create mode 100644 test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/WechatOpenAIDeclarationTests.cs create mode 100644 test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/appsettings.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8eafc365..af845ba7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -129,6 +129,7 @@ - _tenpayv3_:关于 `SKIT.FlurlHttpClient.Wechat.TenpayV3` 项目的变化; - _work_:关于 `SKIT.FlurlHttpClient.Wechat.Work` 项目的变化; - _wxads_:关于 `SKIT.FlurlHttpClient.Wechat.Ads` 项目的变化; + - _openai_:关于 `SKIT.FlurlHttpClient.Wechat.OpenAI` 项目的变化; - 留空:不属于上述任何情形。 - **建议**:提交记录应遵循最小化原则,避免修改或新增了几十处、却混杂在一起提交,以免为日后搜索查询造成困扰。 diff --git a/README.md b/README.md index facc2873..a5655c82 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ | 商户平台(微信支付)模块 | [![NuGet Version](https://img.shields.io/nuget/v/SKIT.FlurlHttpClient.Wechat.TenpayV3.svg?label=NuGet)](https://www.nuget.org/packages/SKIT.FlurlHttpClient.Wechat.TenpayV3)
[![NuGet Download](https://img.shields.io/nuget/dt/SKIT.FlurlHttpClient.Wechat.TenpayV3.svg?sanitize=true&label=Downloads)](https://www.nuget.org/packages/SKIT.FlurlHttpClient.Wechat.TenpayV3) | [开发文档](./docs/WechatTenpayV3/README.md)|[示例项目](./docs/WechatTenpayV3/Sample.md) | | 企业微信(企业号)模块 | [![NuGet Version](https://img.shields.io/nuget/v/SKIT.FlurlHttpClient.Wechat.Work.svg?label=NuGet)](https://www.nuget.org/packages/SKIT.FlurlHttpClient.Wechat.Work)
[![NuGet Download](https://img.shields.io/nuget/dt/SKIT.FlurlHttpClient.Wechat.Work.svg?sanitize=true&label=Downloads)](https://www.nuget.org/packages/SKIT.FlurlHttpClient.Wechat.Work) | [开发文档](./docs/WechatWork/README.md) | | 广告平台(广点通)模块 | [![NuGet Version](https://img.shields.io/nuget/v/SKIT.FlurlHttpClient.Wechat.Ads.svg?label=NuGet)](https://www.nuget.org/packages/SKIT.FlurlHttpClient.Wechat.Ads)
[![NuGet Download](https://img.shields.io/nuget/dt/SKIT.FlurlHttpClient.Wechat.Ads.svg?sanitize=true&label=Downloads)](https://www.nuget.org/packages/SKIT.FlurlHttpClient.Wechat.Ads) | [开发文档](./docs/WechatAds/README.md) | +| 对话开放平台(智能对话)模块 | | _开发中_ | --- diff --git a/SKIT.FlurlHttpClient.Wechat.sln b/SKIT.FlurlHttpClient.Wechat.sln index 4499bdc0..b39a2587 100644 --- a/SKIT.FlurlHttpClient.Wechat.sln +++ b/SKIT.FlurlHttpClient.Wechat.sln @@ -20,6 +20,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.Ads", "src\SKIT.FlurlHttpClient.Wechat.Ads\SKIT.FlurlHttpClient.Wechat.Ads.csproj", "{7F155EFB-152F-4798-9984-99102B21D2F8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SKIT.FlurlHttpClient.Wechat.OpenAI", "src\SKIT.FlurlHttpClient.Wechat.OpenAI\SKIT.FlurlHttpClient.Wechat.OpenAI.csproj", "{AAE2E9BC-4D0B-4495-8825-DF8C405DB4A0}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C95AF531-CF44-44AA-AC90-F4DF9F941674}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{4C48D9D5-1D7F-4616-A05D-256555C310FC}" @@ -34,11 +36,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.Ads.UnitTests", "test\SKIT.FlurlHttpClient.Wechat.Ads.UnitTests\SKIT.FlurlHttpClient.Wechat.Ads.UnitTests.csproj", "{561E0BFB-7817-41FE-BAF2-D78817679AC1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests", "test\SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests\SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests.csproj", "{9689135B-44BA-4D55-8663-7C669BAFE066}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{35C901ED-C234-4A91-9561-AD89B3BB788D}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5", "samples\SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5\SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5.csproj", "{D1B321C9-3004-4645-A78D-A85C152062FA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net5", "samples\SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net5\SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net5.csproj", "{65E51735-73CE-4E9B-AA65-4BF5E4C8A705}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net5", "samples\SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net5\SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net5.csproj", "{65E51735-73CE-4E9B-AA65-4BF5E4C8A705}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -94,6 +98,14 @@ Global {65E51735-73CE-4E9B-AA65-4BF5E4C8A705}.Debug|Any CPU.Build.0 = Debug|Any CPU {65E51735-73CE-4E9B-AA65-4BF5E4C8A705}.Release|Any CPU.ActiveCfg = Release|Any CPU {65E51735-73CE-4E9B-AA65-4BF5E4C8A705}.Release|Any CPU.Build.0 = Release|Any CPU + {AAE2E9BC-4D0B-4495-8825-DF8C405DB4A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AAE2E9BC-4D0B-4495-8825-DF8C405DB4A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AAE2E9BC-4D0B-4495-8825-DF8C405DB4A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AAE2E9BC-4D0B-4495-8825-DF8C405DB4A0}.Release|Any CPU.Build.0 = Release|Any CPU + {9689135B-44BA-4D55-8663-7C669BAFE066}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9689135B-44BA-4D55-8663-7C669BAFE066}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9689135B-44BA-4D55-8663-7C669BAFE066}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9689135B-44BA-4D55-8663-7C669BAFE066}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -112,6 +124,8 @@ Global {561E0BFB-7817-41FE-BAF2-D78817679AC1} = {C95AF531-CF44-44AA-AC90-F4DF9F941674} {D1B321C9-3004-4645-A78D-A85C152062FA} = {35C901ED-C234-4A91-9561-AD89B3BB788D} {65E51735-73CE-4E9B-AA65-4BF5E4C8A705} = {35C901ED-C234-4A91-9561-AD89B3BB788D} + {AAE2E9BC-4D0B-4495-8825-DF8C405DB4A0} = {3E34ADB9-1F52-4C96-9A42-DE782DE1AAA3} + {9689135B-44BA-4D55-8663-7C669BAFE066} = {C95AF531-CF44-44AA-AC90-F4DF9F941674} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F08ED64E-2517-4B51-A4BE-D33D56CC7B39} diff --git a/src/SKIT.FlurlHttpClient.Wechat.OpenAI/SKIT.FlurlHttpClient.Wechat.OpenAI.csproj b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/SKIT.FlurlHttpClient.Wechat.OpenAI.csproj new file mode 100644 index 00000000..c8fe0e36 --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/SKIT.FlurlHttpClient.Wechat.OpenAI.csproj @@ -0,0 +1,37 @@ + + + + net461; netstandard2.0; net5.0 + 8.0 + enable + true + + + + SKIT.FlurlHttpClient.Wechat.OpenAI + MIT + https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat + Flurl.Http Wechat Weixin MicroMessage WxOpenAI 微信 微信智能对话 微信对话开放平台 微信智能对话开放平台 智能对话平台 + 0.1.0-alpha + 基于 Flurl.Http 的微信对话开放平台(微信智能对话) API 客户端。 + Fu Diwei + git + https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat.git + true + true + snupkg + + + + + + + + + + + + + + + diff --git a/src/SKIT.FlurlHttpClient.Wechat.OpenAI/Settings/Credentials.cs b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/Settings/Credentials.cs new file mode 100644 index 00000000..1dca01a4 --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/Settings/Credentials.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Settings +{ + public class Credentials + { + /// + /// 初始化客户端时 的副本。 + /// + public string ClientId { get; } + + /// + /// 初始化客户端时 的副本。 + /// + public string ClientKey { get; } + + internal Credentials(WechatOpenAIClientOptions options) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + + ClientId = options.ClientId; + ClientKey = options.ClientKey; + } + } +} diff --git a/src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIClient.cs b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIClient.cs new file mode 100644 index 00000000..bdbf0d33 --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIClient.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Flurl.Http; + +namespace SKIT.FlurlHttpClient.Wechat.OpenAI +{ + /// + /// 一个微信智能对话 API HTTP 客户端。 + /// + public class WechatOpenAIClient : CommonClientBase, IWechatClient + { + /// + /// 获取当前客户端使用的微信公众平台凭证。 + /// + public Settings.Credentials Credentials { get; } + + /// + /// 用指定的配置项初始化 类的新实例。 + /// + /// 配置项。 + public WechatOpenAIClient(WechatOpenAIClientOptions options) + : base() + { + if (options == null) throw new ArgumentNullException(nameof(options)); + + Credentials = new Settings.Credentials(options); + + FlurlClient.BaseUrl = options.Endpoints ?? WechatOpenAIEndpoints.DEFAULT; + FlurlClient.WithTimeout(TimeSpan.FromMilliseconds(options.Timeout)); + } + + /// + /// 用指定的微信智能对话 ClientId 和微信智能对话 ClientKey 初始化 类的新实例。 + /// + /// 微信智能对话 ClientId。 + /// 微信智能对话 ClientKey。 + public WechatOpenAIClient(string clientId, string clientKey) + : this(new WechatOpenAIClientOptions() { ClientId = clientId, ClientKey = clientKey }) + { + } + + /// + /// 使用当前客户端生成一个新的 对象。 + /// + /// + /// + /// + /// + public IFlurlRequest CreateRequest(WechatOpenAIRequest request, HttpMethod method, params object[] urlSegments) + { + IFlurlRequest flurlRequest = FlurlClient.Request(urlSegments).WithVerb(method); + + if (request.Timeout.HasValue) + { + flurlRequest.WithTimeout(TimeSpan.FromMilliseconds(request.Timeout.Value)); + } + + if (request.RequestId != null) + { + flurlRequest.WithHeader("request_id", request.RequestId); + } + + if (request.BotId != null) + { + flurlRequest.WithHeader("wxbot_bid", request.BotId); + } + + if (request.AccessToken != null) + { + flurlRequest.WithHeader("X-OPENAI-TOKEN", request.AccessToken); + } + + return flurlRequest; + } + + /// + /// 异步发起请求。 + /// + /// + /// + /// + /// + /// + public async Task SendRequestAsync(IFlurlRequest flurlRequest, HttpContent? httpContent = null, CancellationToken cancellationToken = default) + where T : WechatOpenAIResponse, new() + { + try + { + using IFlurlResponse flurlResponse = await base.SendRequestAsync(flurlRequest, httpContent, cancellationToken).ConfigureAwait(false); + return await GetResposneAsync(flurlResponse).ConfigureAwait(false); + } + catch (FlurlHttpException ex) + { + throw new WechatOpenAIException(ex.Message, ex); + } + } + + /// + /// 异步发起请求。 + /// + /// + /// + /// + /// + /// + public async Task SendRequestWithJsonAsync(IFlurlRequest flurlRequest, object? data = null, CancellationToken cancellationToken = default) + where T : WechatOpenAIResponse, new() + { + try + { + using IFlurlResponse flurlResponse = await base.SendRequestWithJsonAsync(flurlRequest, data, cancellationToken).ConfigureAwait(false); + return await GetResposneAsync(flurlResponse).ConfigureAwait(false); + } + catch (FlurlHttpException ex) + { + throw new WechatOpenAIException(ex.Message, ex); + } + } + + private async Task GetResposneAsync(IFlurlResponse flurlResponse) + where T : WechatOpenAIResponse, new() + { + string contentType = flurlResponse.Headers.GetAll("Content-Type").FirstOrDefault() ?? string.Empty; + bool contentTypeIsNotJson = + (flurlResponse.StatusCode != (int)HttpStatusCode.OK) || + (!contentType.StartsWith("application/json") && !contentType.StartsWith("text/json") && !contentType.StartsWith("text/plain")); + + T result = contentTypeIsNotJson ? new T() : await flurlResponse.GetJsonAsync().ConfigureAwait(false); + result.RawStatus = flurlResponse.StatusCode; + result.RawHeaders = new ReadOnlyDictionary( + flurlResponse.Headers + .GroupBy(e => e.Name) + .ToDictionary( + k => k.Key, + v => string.Join(", ", v.Select(e => e.Value)) + ) + ); + result.RawBytes = await flurlResponse.ResponseMessage.Content.ReadAsByteArrayAsync().ConfigureAwait(false); + return result; + } + } +} diff --git a/src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIClientOptions.cs b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIClientOptions.cs new file mode 100644 index 00000000..12a67690 --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIClientOptions.cs @@ -0,0 +1,30 @@ +namespace SKIT.FlurlHttpClient.Wechat.OpenAI +{ + /// + /// 一个用于构造 时使用的配置项。 + /// + public class WechatOpenAIClientOptions + { + /// + /// 获取或设置请求超时时间(单位:毫秒)。 + /// 默认值:30000 + /// + public int Timeout { get; set; } = 30 * 1000; + + /// + /// 获取或设置微微信智能对话 API 域名。 + /// 默认值: + /// + public string Endpoints { get; set; } = WechatOpenAIEndpoints.DEFAULT; + + /// + /// 获取或设置微信智能对话 ClientId。 + /// + public string ClientId { get; set; } = default!; + + /// + /// 获取或设置微信智能对话 ClientKey。 + /// + public string ClientKey { get; set; } = default!; + } +} diff --git a/src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIEndpoints.cs b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIEndpoints.cs new file mode 100644 index 00000000..df263e5f --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIEndpoints.cs @@ -0,0 +1,15 @@ +using System; + +namespace SKIT.FlurlHttpClient.Wechat.OpenAI +{ + /// + /// 微信智能对话 API 接口域名。 + /// + public static class WechatOpenAIEndpoints + { + /// + /// 主域名(默认)。 + /// + public const string DEFAULT = "https://openaiapi.weixin.qq.com"; + } +} diff --git a/src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIException.cs b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIException.cs new file mode 100644 index 00000000..19ec9f1b --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIException.cs @@ -0,0 +1,27 @@ +using System; + +namespace SKIT.FlurlHttpClient.Wechat.OpenAI +{ + /// + /// 当调用微信智能对话 API 出错时引发的异常。 + /// + public class WechatOpenAIException : CommonExceptionBase + { + /// + public WechatOpenAIException() + { + } + + /// + public WechatOpenAIException(string message) + : base(message) + { + } + + /// + public WechatOpenAIException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIRequest.cs b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIRequest.cs new file mode 100644 index 00000000..6eff316c --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIRequest.cs @@ -0,0 +1,38 @@ +using System; + +namespace SKIT.FlurlHttpClient.Wechat.OpenAI +{ + /// + /// 表示微信智能对话 API 请求的基类。 + /// + public abstract class WechatOpenAIRequest : IWechatRequest + { + /// + /// 获取或设置请求超时时间(单位:毫秒)。如果不指定将使用构造 时的 参数,这在需要指定特定耗时请求(比如上传或下载文件)的超时时间时很有用。 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public virtual int? Timeout { get; set; } + + /// + /// 获取或设置请求唯一标识。如果不指定将有系统自动生成。 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public virtual string? RequestId { get; set; } + + /// + /// 获取或设置 Bot ID。 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public virtual string? BotId { get; set; } + + /// + /// 获取或设置请求令牌。 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public virtual string? AccessToken { get; set; } + } +} diff --git a/src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIResponse.cs b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIResponse.cs new file mode 100644 index 00000000..9e34a089 --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/WechatOpenAIResponse.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; + +namespace SKIT.FlurlHttpClient.Wechat.OpenAI +{ + /// + /// 表示微信智能对话 API 响应的基类。 + /// + public abstract class WechatOpenAIResponse : IWechatResponse + { + /// + /// 获取原始的 HTTP 响应状态码。 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public int RawStatus { get; internal set; } + + /// + /// 获取原始的 HTTP 响应表头集合。 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public IDictionary RawHeaders { get; internal set; } = default!; + + /// + /// 获取原始的 HTTP 响应正文。 + /// + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public byte[] RawBytes { get; internal set; } = default!; + + /// + /// 获取微信智能对话 API 返回的错误码。 + /// + [Newtonsoft.Json.JsonProperty("code")] + [System.Text.Json.Serialization.JsonPropertyName("code")] + public virtual int ErrorCode { get; set; } + + /// + /// 获取微信智能对话 API 返回的错误描述。 + /// + [Newtonsoft.Json.JsonProperty("msg")] + [System.Text.Json.Serialization.JsonPropertyName("msg")] + public virtual string? ErrorMessage { get; set; } + + /// + /// 获取微信智能对话 API 返回的请求唯一标识。 + /// + [Newtonsoft.Json.JsonProperty("request_id")] + [System.Text.Json.Serialization.JsonPropertyName("request_id")] + public virtual string? RequestId { get; set; } + + /// + /// 获取一个值,该值指示调用微信 API 是否成功(即 HTTP 状态码为 200、且 errcode 值为 0)。 + /// + /// + public virtual bool IsSuccessful() + { + return RawStatus == 200 && ErrorCode == 0; + } + } + + /// + /// 表示微信智能对话 API 响应的泛型基类。 + /// + public abstract class WechatOpenAIResponse + where TData : class, new() + { + /// + /// 获取微信智能对话 API 返回的数据。 + /// + [Newtonsoft.Json.JsonProperty("data")] + [System.Text.Json.Serialization.JsonPropertyName("data")] + public virtual TData? Data { get; set; } + } +} diff --git a/test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests.csproj b/test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests.csproj new file mode 100644 index 00000000..1338fd3c --- /dev/null +++ b/test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests.csproj @@ -0,0 +1,30 @@ + + + + netcoreapp3.1 + 8.0 + + + + + + PreserveNewest + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/TestClients.cs b/test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/TestClients.cs new file mode 100644 index 00000000..1aad1387 --- /dev/null +++ b/test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/TestClients.cs @@ -0,0 +1,18 @@ +using System; + +namespace SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests +{ + class TestClients + { + static TestClients() + { + Instance = new WechatOpenAIClient(new WechatOpenAIClientOptions() + { + ClientId = TestConfigs.WechatClientId, + ClientKey = TestConfigs.WechatClientKey + }); + } + + public static readonly WechatOpenAIClient Instance; + } +} diff --git a/test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/TestConfigs.cs b/test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/TestConfigs.cs new file mode 100644 index 00000000..077a5ab6 --- /dev/null +++ b/test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/TestConfigs.cs @@ -0,0 +1,33 @@ +using System; +using System.IO; +using System.Text.Json; + +namespace SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests +{ + class TestConfigs + { + static TestConfigs() + { + // NOTICE: 请在项目根目录下按照 appsettings.json 的格式填入测试参数。 + // WARN: 敏感信息请不要提交到 git! + + using var stream = File.OpenRead("appsettings.json"); + using var json = JsonDocument.Parse(stream); + + var config = json.RootElement.GetProperty("WechatConfig"); + WechatClientId = config.GetProperty("ClientId").GetString(); + WechatClientKey = config.GetProperty("ClientKey").GetString(); + WechatAccessToken = config.GetProperty("AccessToken").GetString(); + + ProjectSourceDirectory = json.RootElement.GetProperty("ProjectSourceDirectory").GetString(); + ProjectTestDirectory = json.RootElement.GetProperty("ProjectTestDirectory").GetString(); + } + + public static readonly string WechatClientId; + public static readonly string WechatClientKey; + public static readonly string WechatAccessToken; + + public static readonly string ProjectSourceDirectory; + public static readonly string ProjectTestDirectory; + } +} diff --git a/test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/WechatOpenAIDeclarationTests.cs b/test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/WechatOpenAIDeclarationTests.cs new file mode 100644 index 00000000..4e6600e4 --- /dev/null +++ b/test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/WechatOpenAIDeclarationTests.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests +{ + public class WechatOpenAIDeclarationTests + { + private static readonly Assembly _assembly = Assembly.Load("SKIT.FlurlHttpClient.Wechat.OpenAI"); + + [Fact(DisplayName = "验证 API 模型命名")] + public void ApiModelsNamingTest() + { + CodeStyleUtil.VerifyApiModelsNaming(_assembly, out var ex); + + if (ex != null) + throw ex; + + Assert.Null(ex); + } + + [Fact(DisplayName = "验证 API 模型定义")] + public void ApiModelsDefinitionTest() + { + string workdir = Path.Combine(TestConfigs.ProjectTestDirectory, "ModelSamples"); + Assert.True(Directory.Exists(workdir)); + + CodeStyleUtil.VerifyApiModelsDefinition(_assembly, workdir, out var ex); + + if (ex != null) + throw ex; + + Assert.Null(ex); + } + + [Fact(DisplayName = "验证 API 事件定义")] + public void ApiEventsDefinitionTest() + { + string workdir = Path.Combine(TestConfigs.ProjectTestDirectory, "EventSamples"); + Assert.True(Directory.Exists(workdir)); + + CodeStyleUtil.VerifyApiEventsDefinition(_assembly, workdir, out var ex); + + if (ex != null) + throw ex; + + Assert.Null(ex); + } + + [Fact(DisplayName = "验证 API 接口命名")] + public void ApiExtensionsNamingTest() + { + CodeStyleUtil.VerifyApiExtensionsNaming(_assembly, out var ex); + + if (ex != null) + throw ex; + + Assert.Null(ex); + } + + [Fact(DisplayName = "验证代码规范")] + public void CodeStyleTest() + { + string workdir = Path.Combine(TestConfigs.ProjectSourceDirectory); + Assert.True(Directory.Exists(workdir)); + + CodeStyleUtil.VerifySourceCodeStyle(workdir, out var ex); + + if (ex != null) + throw ex; + + Assert.Null(ex); + } + } +} diff --git a/test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/appsettings.json b/test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/appsettings.json new file mode 100644 index 00000000..854e66ce --- /dev/null +++ b/test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/appsettings.json @@ -0,0 +1,9 @@ +{ + "WechatConfig": { + "ClientId": "请在此填写用于测试的微信智能对话 ClientId", + "ClientKey": "请在此填写用于测试的微信智能对话 ClientKey", + "AccessToken": "请在此填写用于测试的微信智能对话 AccessToken" + }, + "ProjectSourceDirectory": "请输入当前 SDK 项目所在的目录完整路径,如 C:\\Project\\src\\SKIT.FlurlHttpClient.Wechat.OpenAI\\", + "ProjectTestDirectory": "请输入当前测试项目所在的目录完整路径,如 C:\\Project\\test\\SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests\\" +} \ No newline at end of file