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 @@
| 商户平台(微信支付)模块 | [](https://www.nuget.org/packages/SKIT.FlurlHttpClient.Wechat.TenpayV3)
[](https://www.nuget.org/packages/SKIT.FlurlHttpClient.Wechat.TenpayV3) | [开发文档](./docs/WechatTenpayV3/README.md)|[示例项目](./docs/WechatTenpayV3/Sample.md) |
| 企业微信(企业号)模块 | [](https://www.nuget.org/packages/SKIT.FlurlHttpClient.Wechat.Work)
[](https://www.nuget.org/packages/SKIT.FlurlHttpClient.Wechat.Work) | [开发文档](./docs/WechatWork/README.md) |
| 广告平台(广点通)模块 | [](https://www.nuget.org/packages/SKIT.FlurlHttpClient.Wechat.Ads)
[](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