diff --git a/aspnet-core/LINGYUN.MicroService.WechatManagement.sln b/aspnet-core/LINGYUN.MicroService.WechatManagement.sln
index 48004e76a..22ef88c5e 100644
--- a/aspnet-core/LINGYUN.MicroService.WechatManagement.sln
+++ b/aspnet-core/LINGYUN.MicroService.WechatManagement.sln
@@ -111,6 +111,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Identity.Sessio
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Telemetry.SkyWalking", "framework\telemetry\LINGYUN.Abp.Telemetry.SkyWalking\LINGYUN.Abp.Telemetry.SkyWalking.csproj", "{7CF83493-6AF5-9C6D-01A7-AC7FC11BC2CE}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Telemetry.OpenTelemetry", "framework\telemetry\LINGYUN.Abp.Telemetry.OpenTelemetry\LINGYUN.Abp.Telemetry.OpenTelemetry.csproj", "{164E4514-4E84-F13A-E24E-2A743753CDB3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Identity.WeChat.Work", "framework\wechat\LINGYUN.Abp.Identity.WeChat.Work\LINGYUN.Abp.Identity.WeChat.Work.csproj", "{CCC8A29C-1FC1-044F-895A-EC6894CDC8E5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.WeChat.Work.ExternalContact", "framework\wechat\LINGYUN.Abp.WeChat.Work.ExternalContact\LINGYUN.Abp.WeChat.Work.ExternalContact.csproj", "{3A6D0EA9-FA3F-4252-87D9-31845D57180D}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.TestsBase", "tests\LINGYUN.Abp.TestBase\LINGYUN.Abp.TestsBase.csproj", "{757279EC-708D-D1AD-5E9E-7B757F3F7785}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.WeChat.Work.Tests", "tests\LINGYUN.Abp.WeChat.Work.Tests\LINGYUN.Abp.WeChat.Work.Tests.csproj", "{9DC2A982-8FA2-93BE-5FE1-3D7F7C105C39}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.WeChat.Work.ExternalContact.Tests", "tests\LINGYUN.Abp.WeChat.Work.ExternalContact.Tests\LINGYUN.Abp.WeChat.Work.ExternalContact.Tests.csproj", "{6628FB84-66C0-4023-9AA0-AAC4C0CC5917}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.WeChat.Work.Handlers", "framework\wechat\LINGYUN.Abp.WeChat.Work.Handlers\LINGYUN.Abp.WeChat.Work.Handlers.csproj", "{E0DFCAD9-8AFE-A816-10F7-B4CA6691E910}"
EndProject
Global
@@ -283,6 +297,30 @@ Global
{7CF83493-6AF5-9C6D-01A7-AC7FC11BC2CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7CF83493-6AF5-9C6D-01A7-AC7FC11BC2CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7CF83493-6AF5-9C6D-01A7-AC7FC11BC2CE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {164E4514-4E84-F13A-E24E-2A743753CDB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {164E4514-4E84-F13A-E24E-2A743753CDB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {164E4514-4E84-F13A-E24E-2A743753CDB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {164E4514-4E84-F13A-E24E-2A743753CDB3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CCC8A29C-1FC1-044F-895A-EC6894CDC8E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CCC8A29C-1FC1-044F-895A-EC6894CDC8E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CCC8A29C-1FC1-044F-895A-EC6894CDC8E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CCC8A29C-1FC1-044F-895A-EC6894CDC8E5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3A6D0EA9-FA3F-4252-87D9-31845D57180D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3A6D0EA9-FA3F-4252-87D9-31845D57180D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3A6D0EA9-FA3F-4252-87D9-31845D57180D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3A6D0EA9-FA3F-4252-87D9-31845D57180D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {757279EC-708D-D1AD-5E9E-7B757F3F7785}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {757279EC-708D-D1AD-5E9E-7B757F3F7785}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {757279EC-708D-D1AD-5E9E-7B757F3F7785}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {757279EC-708D-D1AD-5E9E-7B757F3F7785}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9DC2A982-8FA2-93BE-5FE1-3D7F7C105C39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9DC2A982-8FA2-93BE-5FE1-3D7F7C105C39}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9DC2A982-8FA2-93BE-5FE1-3D7F7C105C39}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9DC2A982-8FA2-93BE-5FE1-3D7F7C105C39}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6628FB84-66C0-4023-9AA0-AAC4C0CC5917}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6628FB84-66C0-4023-9AA0-AAC4C0CC5917}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6628FB84-66C0-4023-9AA0-AAC4C0CC5917}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6628FB84-66C0-4023-9AA0-AAC4C0CC5917}.Release|Any CPU.Build.0 = Release|Any CPU
{E0DFCAD9-8AFE-A816-10F7-B4CA6691E910}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E0DFCAD9-8AFE-A816-10F7-B4CA6691E910}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E0DFCAD9-8AFE-A816-10F7-B4CA6691E910}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -343,6 +381,12 @@ Global
{CC8DDC8C-CC0C-4534-8D9F-2C345E065869} = {52701ECE-3EBD-45EC-AD2C-0AAB15322311}
{80EBBECC-EF11-4E5E-91DA-EEECED832F21} = {73ED64BB-7C39-42EA-B821-3DD697B9C36A}
{7CF83493-6AF5-9C6D-01A7-AC7FC11BC2CE} = {FFACB4F0-33E0-4F8B-A97E-8FFFA10C12E6}
+ {164E4514-4E84-F13A-E24E-2A743753CDB3} = {FFACB4F0-33E0-4F8B-A97E-8FFFA10C12E6}
+ {CCC8A29C-1FC1-044F-895A-EC6894CDC8E5} = {EDBB7BC1-46F0-4803-A572-7F8FEF433BE2}
+ {3A6D0EA9-FA3F-4252-87D9-31845D57180D} = {EDBB7BC1-46F0-4803-A572-7F8FEF433BE2}
+ {757279EC-708D-D1AD-5E9E-7B757F3F7785} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ {9DC2A982-8FA2-93BE-5FE1-3D7F7C105C39} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ {6628FB84-66C0-4023-9AA0-AAC4C0CC5917} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{E0DFCAD9-8AFE-A816-10F7-B4CA6691E910} = {EDBB7BC1-46F0-4803-A572-7F8FEF433BE2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/FodyWeavers.xml b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/FodyWeavers.xml
new file mode 100644
index 000000000..1715698cc
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/FodyWeavers.xsd b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/FodyWeavers.xsd
new file mode 100644
index 000000000..3f3946e28
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN.Abp.WeChat.Work.ExternalContact.csproj b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN.Abp.WeChat.Work.ExternalContact.csproj
new file mode 100644
index 000000000..730bd0b71
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN.Abp.WeChat.Work.ExternalContact.csproj
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ netstandard2.0;netstandard2.1;net8.0;net9.0
+ LINGYUN.Abp.WeChat.Work.ExternalContact
+ LINGYUN.Abp.WeChat.Work.ExternalContact
+ false
+ false
+ false
+ True
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/AbpWeChatWorkExternalContactModule.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/AbpWeChatWorkExternalContactModule.cs
new file mode 100644
index 000000000..dd18a596b
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/AbpWeChatWorkExternalContactModule.cs
@@ -0,0 +1,78 @@
+using LINGYUN.Abp.WeChat.Common;
+using LINGYUN.Abp.WeChat.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+using LINGYUN.Abp.WeChat.Work.Localization;
+using Volo.Abp.Localization;
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact;
+
+[DependsOn(typeof(AbpWeChatWorkModule))]
+public class AbpWeChatWorkExternalContactModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Configure(options =>
+ {
+ // 企业客户变更事件
+ options.MapEvent("change_external_contact", context =>
+ {
+ var changeType = context.GetMessageData("ChangeType");
+ return changeType switch
+ {
+ "add_external_contact" => context.GetWeChatMessage(),
+ "edit_external_contact" => context.GetWeChatMessage(),
+ "add_half_external_contact" => context.GetWeChatMessage(),
+ "del_external_contact" => context.GetWeChatMessage(),
+ "del_follow_user" => context.GetWeChatMessage(),
+ "transfer_fail" => context.GetWeChatMessage(),
+ _ => throw new AbpWeChatException($"Contact change event change_external_contact:{changeType} is not mounted!"),
+ };
+ });
+ // 客户群变更事件
+ options.MapEvent("change_external_chat", context =>
+ {
+ var changeType = context.GetMessageData("ChangeType");
+ switch (changeType)
+ {
+ case "create": return context.GetWeChatMessage();
+ case "update":
+ // 客户群变更事件
+ var updateDetail = context.GetMessageData("UpdateDetail");
+ return updateDetail switch
+ {
+ "add_member" => context.GetWeChatMessage(),
+ "del_member" => context.GetWeChatMessage(),
+ "change_owner" => context.GetWeChatMessage(),
+ "change_name" => context.GetWeChatMessage(),
+ "change_notice" => context.GetWeChatMessage(),
+ _ => throw new AbpWeChatException($"Contact change event change_external_chat:{changeType}:{updateDetail} is not mounted!"),
+ };
+ case "dismiss": return context.GetWeChatMessage();
+ default: throw new AbpWeChatException($"Contact change event change_external_chat:{changeType} is not mounted!");
+ }
+ });
+ // 企业客户标签事件
+ options.MapEvent("change_external_tag", context =>
+ {
+ var changeType = context.GetMessageData("ChangeType");
+ return changeType switch
+ {
+ "create" => context.GetWeChatMessage(),
+ "update" => context.GetWeChatMessage(),
+ "delete" => context.GetWeChatMessage(),
+ "shuffle" => context.GetWeChatMessage(),
+ _ => throw new AbpWeChatException($"Contact change event change_external_tag:{changeType} is not mounted!"),
+ };
+ });
+ });
+
+ Configure(options =>
+ {
+ options.Resources
+ .Get()
+ .AddVirtualJson("/LINGYUN/Abp/WeChat/Work/ExternalContact/Localization/Resources");
+ });
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/IWeChatWorkAttachmentProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/IWeChatWorkAttachmentProvider.cs
new file mode 100644
index 000000000..e35baeb31
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/IWeChatWorkAttachmentProvider.cs
@@ -0,0 +1,24 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Response;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments;
+///
+/// 附件资源接口
+///
+public interface IWeChatWorkAttachmentProvider
+{
+ ///
+ /// 上传附件资源
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task UploadAsync(
+ WeChatWorkUploadAttachmentRequest request,
+ CancellationToken cancellationToken = default);
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Models/AttachmentType.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Models/AttachmentType.cs
new file mode 100644
index 000000000..096e7bf8f
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Models/AttachmentType.cs
@@ -0,0 +1,20 @@
+using System.ComponentModel;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Models;
+///
+/// 附件类型
+///
+[Description("附件类型")]
+public enum AttachmentType
+{
+ ///
+ /// 朋友圈
+ ///
+ [Description("朋友圈")]
+ Moments = 1,
+ ///
+ /// 商品图册
+ ///
+ [Description("商品图册")]
+ ProductCatalogue = 2,
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Models/MediaType.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Models/MediaType.cs
new file mode 100644
index 000000000..d122f663c
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Models/MediaType.cs
@@ -0,0 +1,25 @@
+using System.ComponentModel;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Models;
+///
+/// 媒体文件类型
+///
+[Description("媒体文件类型")]
+public enum MediaType
+{
+ ///
+ /// 图片
+ ///
+ [Description("图片")]
+ Image = 1,
+ ///
+ /// 视频
+ ///
+ [Description("视频")]
+ Video = 2,
+ ///
+ /// 普通文件
+ ///
+ [Description("普通文件")]
+ File = 3,
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Request/WeChatWorkUploadAttachmentRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Request/WeChatWorkUploadAttachmentRequest.cs
new file mode 100644
index 000000000..e1ac87d55
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Request/WeChatWorkUploadAttachmentRequest.cs
@@ -0,0 +1,41 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Models;
+using Volo.Abp;
+using Volo.Abp.Content;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Request;
+///
+/// 上传附件资源请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkUploadAttachmentRequest : WeChatWorkRequest
+{
+ ///
+ /// 媒体文件类型
+ ///
+ [NotNull]
+ public MediaType MediaType { get; }
+ ///
+ /// 附件类型
+ ///
+ [NotNull]
+ public AttachmentType AttachmentType { get; }
+ ///
+ /// 媒体文件
+ ///
+ [NotNull]
+ public IRemoteStreamContent Content { get; }
+ public WeChatWorkUploadAttachmentRequest(
+ MediaType mediaType,
+ AttachmentType attachmentType,
+ IRemoteStreamContent content)
+ {
+ Check.NotNull(content, nameof(content));
+
+ MediaType = mediaType;
+ AttachmentType = attachmentType;
+ Content = content;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Response/WeChatWorkUploadAttachmentResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Response/WeChatWorkUploadAttachmentResponse.cs
new file mode 100644
index 000000000..46f1045f7
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/Response/WeChatWorkUploadAttachmentResponse.cs
@@ -0,0 +1,35 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Response;
+///
+/// 上传附件资源响应结果
+///
+///
+/// 详情见:
+///
+public class WeChatWorkUploadAttachmentResponse : WeChatWorkResponse
+{
+ ///
+ /// 媒体文件类型,分别有图片(image)、语音(voice)、视频(video),普通文件(file)
+ ///
+ [NotNull]
+ [JsonProperty("type")]
+ [JsonPropertyName("type")]
+ public string MediaType { get; set; }
+ ///
+ /// 媒体文件上传后获取的唯一标识,三天有效,可使用获取临时素材接口获取
+ ///
+ [NotNull]
+ [JsonProperty("media_id")]
+ [JsonPropertyName("media_id")]
+ public string MediaId { get; set; }
+ ///
+ /// 媒体文件上传时间戳
+ ///
+ [NotNull]
+ [JsonProperty("created_at")]
+ [JsonPropertyName("created_at")]
+ public long CreatedAt { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/WeChatWorkAttachmentProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/WeChatWorkAttachmentProvider.cs
new file mode 100644
index 000000000..d2e9d5610
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Attachments/WeChatWorkAttachmentProvider.cs
@@ -0,0 +1,41 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Response;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Features;
+using LINGYUN.Abp.WeChat.Work.Token;
+using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Features;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments;
+
+[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)]
+public class WeChatWorkAttachmentProvider : IWeChatWorkAttachmentProvider, ISingletonDependency
+{
+ protected IHttpClientFactory HttpClientFactory { get; }
+ protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; }
+
+ public WeChatWorkAttachmentProvider(
+ IHttpClientFactory httpClientFactory,
+ IWeChatWorkTokenProvider weChatWorkTokenProvider)
+ {
+ HttpClientFactory = httpClientFactory;
+ WeChatWorkTokenProvider = weChatWorkTokenProvider;
+ }
+
+ public async virtual Task UploadAsync(
+ WeChatWorkUploadAttachmentRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.UploadAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/IWeChatWorkExternalContactProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/IWeChatWorkExternalContactProvider.cs
new file mode 100644
index 000000000..4efe1d1a1
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/IWeChatWorkExternalContactProvider.cs
@@ -0,0 +1,24 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Response;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts;
+///
+/// 外部联系人接口
+///
+public interface IWeChatWorkExternalContactProvider
+{
+ ///
+ /// 获取已服务的外部联系人
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task GetExternalContactListAsync(
+ WeChatWorkGetExternalContactListRequest request,
+ CancellationToken cancellationToken = default);
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/Models/ExternalContactInfo.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/Models/ExternalContactInfo.cs
new file mode 100644
index 000000000..2e53003fc
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/Models/ExternalContactInfo.cs
@@ -0,0 +1,73 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Models;
+///
+/// 外部联系人
+///
+public class ExternalContactInfo
+{
+ ///
+ /// 是否被成员标记为客户
+ ///
+ [NotNull]
+ [JsonProperty("is_customer")]
+ [JsonPropertyName("is_customer")]
+ public bool IsCustomer { get; set; }
+ ///
+ /// 外部联系人临时ID
+ ///
+ ///
+ ///
+ /// 外部联系人临时id是一个外部联系人的唯一标识,企业可根据此id对外部联系人进行去重统计。
+ /// 但外部联系人临时id仅在一轮遍历查询(从首个分页查询开始到最后一个分页查询完毕)中唯一;
+ /// 每次请求首个数据分页(cursor为空)时,返回的外部联系人临时id和next_cursor将发生变化。
+ ///
+ [NotNull]
+ [JsonProperty("tmp_openid")]
+ [JsonPropertyName("tmp_openid")]
+ public string TmpOpenId { get; set; }
+ ///
+ /// 外部联系人的externaluserid(如果是客户才返回)
+ ///
+ [CanBeNull]
+ [JsonProperty("external_userid")]
+ [JsonPropertyName("external_userid")]
+ public string? ExternalUserId { get; set; }
+ ///
+ /// 脱敏后的外部联系人昵称(如果是其他外部联系人才返回)
+ ///
+ [CanBeNull]
+ [JsonProperty("name")]
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
+ ///
+ /// 添加此外部联系人的企业成员或外部联系人所在群聊的群主userid
+ ///
+ [CanBeNull]
+ [JsonProperty("follow_userid")]
+ [JsonPropertyName("follow_userid")]
+ public string? FollowUserId { get; set; }
+ ///
+ /// 外部联系人所在的群聊ID(如果群聊被成员标记为客户群才返回)
+ ///
+ [CanBeNull]
+ [JsonProperty("chat_id")]
+ [JsonPropertyName("chat_id")]
+ public string? ChatId { get; set; }
+ ///
+ /// 外部联系人所在群聊的群名(如果群聊未被成员标记为客户群才返回)
+ ///
+ [CanBeNull]
+ [JsonProperty("chat_name")]
+ [JsonPropertyName("chat_name")]
+ public string? ChatName { get; set; }
+ ///
+ /// 外部联系人首次添加/进群的时间
+ ///
+ [NotNull]
+ [JsonProperty("add_time")]
+ [JsonPropertyName("add_time")]
+ public long AddTime { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/Request/WeChatWorkGetExternalContactListRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/Request/WeChatWorkGetExternalContactListRequest.cs
new file mode 100644
index 000000000..de2874273
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/Request/WeChatWorkGetExternalContactListRequest.cs
@@ -0,0 +1,36 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Request;
+///
+/// 获取已服务的外部联系人请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetExternalContactListRequest : WeChatWorkRequest
+{
+ ///
+ /// 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填
+ ///
+ ///
+ /// cursor具有有效期,请勿缓存后使用
+ ///
+ [CanBeNull]
+ [JsonProperty("cursor")]
+ [JsonPropertyName("cursor")]
+ public string? Cursor { get; }
+ ///
+ /// 返回的最大记录数,整型,默认为1000
+ ///
+ [CanBeNull]
+ [JsonProperty("limit")]
+ [JsonPropertyName("limit")]
+ public int? Limit { get; }
+ public WeChatWorkGetExternalContactListRequest(string? cursor = null, int? limit = 1000)
+ {
+ Cursor = cursor;
+ Limit = limit;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/Response/WeChatWorkGetExternalContactListResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/Response/WeChatWorkGetExternalContactListResponse.cs
new file mode 100644
index 000000000..4492b93ca
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/Response/WeChatWorkGetExternalContactListResponse.cs
@@ -0,0 +1,29 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Response;
+///
+/// 获取已服务的外部联系人响应结果
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetExternalContactListResponse : WeChatWorkResponse
+{
+ ///
+ /// 外部联系人列表
+ ///
+ [NotNull]
+ [JsonProperty("info_list")]
+ [JsonPropertyName("info_list")]
+ public ExternalContactInfo[] InfoList { get; set; }
+ ///
+ /// 分页游标,再下次请求时填写以获取之后分页的记录,如果已经没有更多的数据则返回空,有效期为4小时
+ ///
+ [CanBeNull]
+ [JsonProperty("next_cursor")]
+ [JsonPropertyName("next_cursor")]
+ public string? NextCursor { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/WeChatWorkExternalContactProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/WeChatWorkExternalContactProvider.cs
new file mode 100644
index 000000000..52179841b
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/WeChatWorkExternalContactProvider.cs
@@ -0,0 +1,44 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Response;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Features;
+using LINGYUN.Abp.WeChat.Work.Token;
+using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Features;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts;
+
+[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)]
+public class WeChatWorkExternalContactProvider : IWeChatWorkExternalContactProvider, ISingletonDependency
+{
+ protected IHttpClientFactory HttpClientFactory { get; }
+ protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; }
+
+ public WeChatWorkExternalContactProvider(
+ IHttpClientFactory httpClientFactory,
+ IWeChatWorkTokenProvider weChatWorkTokenProvider)
+ {
+ HttpClientFactory = httpClientFactory;
+ WeChatWorkTokenProvider = weChatWorkTokenProvider;
+ }
+
+ public async virtual Task GetExternalContactListAsync(
+ WeChatWorkGetExternalContactListRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.GetExternalContactListAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/IWeChatWorkCustomerProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/IWeChatWorkCustomerProvider.cs
new file mode 100644
index 000000000..caba3bf25
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/IWeChatWorkCustomerProvider.cs
@@ -0,0 +1,62 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers;
+///
+/// 客户管理接口
+///
+public interface IWeChatWorkCustomerProvider
+{
+ ///
+ /// 获取客户列表
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 企业成员的userid
+ ///
+ ///
+ Task GetCustomerListAsync(
+ string userId,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 批量获取客户详情
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task BulkGetCustomerAsync(
+ WeChatWorkBulkGetCustomerRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 获取客户详情
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 外部联系人的userid,注意不是企业成员的账号
+ /// 上次请求返回的next_cursor
+ ///
+ ///
+ Task GetCustomerAsync(
+ string externalUserid,
+ string? cursor = null,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 修改客户备注信息
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task UpdateCustomerRemarkAsync(
+ WeChatWorkUpdateCustomerRemarkRequest request,
+ CancellationToken cancellationToken = default);
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/IWeChatWorkCustomerStrategyProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/IWeChatWorkCustomerStrategyProvider.cs
new file mode 100644
index 000000000..c7dd5cead
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/IWeChatWorkCustomerStrategyProvider.cs
@@ -0,0 +1,87 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers;
+///
+/// 客户联系规则组管理
+///
+///
+/// 详情见:
+///
+public interface IWeChatWorkCustomerStrategyProvider
+{
+ ///
+ /// 获取规则组列表
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task GetCustomerStrategyListAsync(
+ WeChatWorkGetCustomerStrategyListRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 获取规则组
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task GetCustomerStrategyAsync(
+ WeChatWorkGetCustomerStrategyRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 获取规则组管理范围
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task GetCustomerStrategyRangeAsync(
+ WeChatWorkGetCustomerStrategyRangeRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 创建新的规则组
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task CreateCustomerStrategyAsync(
+ WeChatWorkCreateCustomerStrategyRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 编辑规则组及其管理范围
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task UpdateCustomerStrategyAsync(
+ WeChatWorkUpdateCustomerStrategyRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 删除规则组
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task DeleteCustomerStrategyAsync(
+ WeChatWorkDeleteCustomerStrategyRequest request,
+ CancellationToken cancellationToken = default);
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategy.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategy.cs
new file mode 100644
index 000000000..27b81293a
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategy.cs
@@ -0,0 +1,18 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+///
+/// 规则组
+///
+public class CustomerStrategy
+{
+ ///
+ /// 规则组id
+ ///
+ [NotNull]
+ [JsonProperty("strategy_id")]
+ [JsonPropertyName("strategy_id")]
+ public int StrategyId { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyInfo.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyInfo.cs
new file mode 100644
index 000000000..dec0bdb1f
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyInfo.cs
@@ -0,0 +1,46 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+///
+/// 规则组详情
+///
+public class CustomerStrategyInfo : CustomerStrategy
+{
+ ///
+ /// 父规则组id, 如果当前规则组没父规则组,则为0
+ ///
+ [NotNull]
+ [JsonProperty("parent_id")]
+ [JsonPropertyName("parent_id")]
+ public int ParentId { get; set; }
+ ///
+ /// 规则组名称
+ ///
+ [NotNull]
+ [JsonProperty("strategy_name")]
+ [JsonPropertyName("strategy_name")]
+ public string StrategyName { get; set; }
+ ///
+ /// 规则组创建时间戳
+ ///
+ [NotNull]
+ [JsonProperty("create_time")]
+ [JsonPropertyName("create_time")]
+ public long CreateTime { get; set; }
+ ///
+ /// 规则组管理员userid列表
+ ///
+ [NotNull]
+ [JsonProperty("admin_list")]
+ [JsonPropertyName("admin_list")]
+ public string[] AdminList { get; set; }
+ ///
+ /// 规则组权限
+ ///
+ [NotNull]
+ [JsonProperty("privilege")]
+ [JsonPropertyName("privilege")]
+ public CustomerStrategyPrivilege Privilege { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyPrivilege.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyPrivilege.cs
new file mode 100644
index 000000000..03ce9e057
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyPrivilege.cs
@@ -0,0 +1,183 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+///
+/// 规则组权限
+///
+public class CustomerStrategyPrivilege
+{
+ ///
+ /// 查看客户列表,基础权限,不可取消
+ ///
+ [NotNull]
+ [JsonProperty("view_customer_list")]
+ [JsonPropertyName("view_customer_list")]
+ public bool ViewCustomerList { get; set; }
+ ///
+ /// 查看客户统计数据,基础权限,不可取消
+ ///
+ [NotNull]
+ [JsonProperty("view_customer_data")]
+ [JsonPropertyName("view_customer_data")]
+ public bool ViewCustomerData { get; set; }
+ ///
+ /// 查看群聊列表,基础权限,不可取消
+ ///
+ [NotNull]
+ [JsonProperty("view_room_list")]
+ [JsonPropertyName("view_room_list")]
+ public bool ViewRoomList { get; set; }
+ ///
+ /// 可使用联系我,基础权限,不可取消
+ ///
+ [NotNull]
+ [JsonProperty("contact_me")]
+ [JsonPropertyName("contact_me")]
+ public bool ContactMe { get; set; }
+ ///
+ /// 可加入群聊,基础权限,不可取消
+ ///
+ [NotNull]
+ [JsonProperty("join_room")]
+ [JsonPropertyName("join_room")]
+ public bool JoinRoom { get; set; }
+ ///
+ /// 允许分享客户给其他成员,默认为true
+ ///
+ [NotNull]
+ [JsonProperty("share_customer")]
+ [JsonPropertyName("share_customer")]
+ public bool ShareCustomer { get; set; }
+ ///
+ /// 允许分配离职成员客户,默认为true
+ ///
+ [NotNull]
+ [JsonProperty("oper_resign_customer")]
+ [JsonPropertyName("oper_resign_customer")]
+ public bool OperResignCustomer { get; set; }
+ ///
+ /// 允许分配离职成员客户群,默认为true
+ ///
+ [NotNull]
+ [JsonProperty("oper_resign_group")]
+ [JsonPropertyName("oper_resign_group")]
+ public bool OperResignGroup { get; set; }
+ ///
+ /// 允许给企业客户发送消息,默认为true
+ ///
+ [NotNull]
+ [JsonProperty("send_customer_msg")]
+ [JsonPropertyName("send_customer_msg")]
+ public bool SendCustomerMsg { get; set; }
+ ///
+ /// 允许配置欢迎语,默认为true
+ ///
+ [NotNull]
+ [JsonProperty("edit_welcome_msg")]
+ [JsonPropertyName("edit_welcome_msg")]
+ public bool EditWelcomeMsg { get; set; }
+ ///
+ /// 允许查看成员联系客户统计,默认为true
+ ///
+ [NotNull]
+ [JsonProperty("view_behavior_data")]
+ [JsonPropertyName("view_behavior_data")]
+ public bool ViewBehaviorData { get; set; }
+ ///
+ /// 允许查看群聊数据统计,默认为true
+ ///
+ [NotNull]
+ [JsonProperty("view_room_data")]
+ [JsonPropertyName("view_room_data")]
+ public bool ViewRoomData { get; set; }
+ ///
+ /// 允许发送消息到企业的客户群,默认为true
+ ///
+ [NotNull]
+ [JsonProperty("send_group_msg")]
+ [JsonPropertyName("send_group_msg")]
+ public bool SendGroupMsg { get; set; }
+ ///
+ /// 允许对企业客户群进行去重,默认为true
+ ///
+ [NotNull]
+ [JsonProperty("room_deduplication")]
+ [JsonPropertyName("room_deduplication")]
+ public bool RoomDeduplication { get; set; }
+ ///
+ /// 配置快捷回复,默认为true
+ ///
+ [NotNull]
+ [JsonProperty("rapid_reply")]
+ [JsonPropertyName("rapid_reply")]
+ public bool RapidReply { get; set; }
+ ///
+ /// 转接在职成员的客户,默认为true
+ ///
+ [NotNull]
+ [JsonProperty("onjob_customer_transfer")]
+ [JsonPropertyName("onjob_customer_transfer")]
+ public bool OnjobCustomerTransfer { get; set; }
+ ///
+ /// 编辑企业成员防骚扰规则,默认为true
+ ///
+ [NotNull]
+ [JsonProperty("edit_anti_spam_rule")]
+ [JsonPropertyName("edit_anti_spam_rule")]
+ public bool EditAntiSpamRule { get; set; }
+ ///
+ /// 导出客户列表,默认为true
+ ///
+ [NotNull]
+ [JsonProperty("export_customer_list")]
+ [JsonPropertyName("export_customer_list")]
+ public bool ExportCustomerList { get; set; }
+ ///
+ /// 导出成员客户统计,默认为true
+ ///
+ [NotNull]
+ [JsonProperty("export_customer_data")]
+ [JsonPropertyName("export_customer_data")]
+ public bool ExportCustomerData { get; set; }
+ ///
+ /// 导出客户群列表,默认为true
+ ///
+ [NotNull]
+ [JsonProperty("export_customer_group_list")]
+ [JsonPropertyName("export_customer_group_list")]
+ public bool ExportCustomerGroupList { get; set; }
+ ///
+ /// 配置企业客户标签,默认为true
+ ///
+ [NotNull]
+ [JsonProperty("manage_customer_tag")]
+ [JsonPropertyName("manage_customer_tag")]
+ public bool ManageCustomerTag { get; set; }
+
+ public static CustomerStrategyPrivilege Default()
+ {
+ return new CustomerStrategyPrivilege
+ {
+ ViewCustomerList = true,
+ ViewCustomerData = true,
+ ViewRoomList = true,
+ ContactMe = true,
+ JoinRoom = true,
+ ShareCustomer = true,
+ EditWelcomeMsg = true,
+ ViewBehaviorData = true,
+ ViewRoomData = true,
+ SendGroupMsg = true,
+ RoomDeduplication = true,
+ RapidReply = true,
+ OnjobCustomerTransfer = true,
+ EditAntiSpamRule = true,
+ ExportCustomerList = true,
+ ExportCustomerData = true,
+ ExportCustomerGroupList = true,
+ ManageCustomerTag = true,
+ };
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyRange.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyRange.cs
new file mode 100644
index 000000000..f02127f27
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyRange.cs
@@ -0,0 +1,61 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+///
+/// 规则组管理范围
+///
+public class CustomerStrategyRange
+{
+ ///
+ /// 节点类型
+ ///
+ [NotNull]
+ [JsonProperty("type")]
+ [JsonPropertyName("type")]
+ public CustomerStrategyRangeType Type { get; set; }
+ ///
+ /// 管理范围内配置的成员userid,仅 Type为 时返回
+ ///
+ [CanBeNull]
+ [JsonProperty("userid")]
+ [JsonPropertyName("userid")]
+ public string? UserId { get; set; }
+ ///
+ /// 管理范围内配置的部门partyid,仅 Type为 时返回
+ ///
+ [CanBeNull]
+ [JsonProperty("partyid")]
+ [JsonPropertyName("partyid")]
+ public int? PartyId { get; set; }
+
+ public CustomerStrategyRange()
+ {
+
+ }
+
+ private CustomerStrategyRange(
+ CustomerStrategyRangeType type,
+ string? userId = null,
+ int? partyId = null)
+ {
+ Type = type;
+ UserId = userId;
+ PartyId = partyId;
+ }
+
+ public static CustomerStrategyRange Member(string userId)
+ {
+ Check.NotNullOrWhiteSpace(userId, nameof(userId));
+
+ return new CustomerStrategyRange(CustomerStrategyRangeType.Member, userId);
+ }
+ public static CustomerStrategyRange Party(int partyId)
+ {
+ Check.NotDefaultOrNull(partyId, nameof(partyId));
+
+ return new CustomerStrategyRange(CustomerStrategyRangeType.Part, partyId: partyId);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyRangeType.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyRangeType.cs
new file mode 100644
index 000000000..eb99edafd
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/CustomerStrategyRangeType.cs
@@ -0,0 +1,20 @@
+using System.ComponentModel;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+///
+/// 节点类型
+///
+[Description("节点类型")]
+public enum CustomerStrategyRangeType
+{
+ ///
+ /// 成员
+ ///
+ [Description("成员")]
+ Member = 1,
+ ///
+ /// 部门
+ ///
+ [Description("部门")]
+ Part = 2
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactGender.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactGender.cs
new file mode 100644
index 000000000..609f42df1
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactGender.cs
@@ -0,0 +1,25 @@
+using System.ComponentModel;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+///
+/// 外部联系人性别
+///
+[Description("外部联系人性别")]
+public enum ExternalContactGender
+{
+ ///
+ /// 未知
+ ///
+ [Description("未知")]
+ None = 0,
+ ///
+ /// 男性
+ ///
+ [Description("男性")]
+ Male = 1,
+ ///
+ /// 女性
+ ///
+ [Description("女性")]
+ FeMale = 2,
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactInfo.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactInfo.cs
new file mode 100644
index 000000000..cf26d5659
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactInfo.cs
@@ -0,0 +1,88 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+///
+/// 外部联系人信息
+///
+public class ExternalContactInfo
+{
+ ///
+ /// 外部联系人的userid
+ ///
+ [NotNull]
+ [JsonProperty("external_userid")]
+ [JsonPropertyName("external_userid")]
+ public string ExternalUserId { get; set; }
+ ///
+ /// 外部联系人的名称
+ ///
+ ///
+ /// 如果是微信用户,则返回其微信昵称。
+ /// 如果是企业微信联系人,则返回其设置对外展示的别名或实名
+ ///
+ [NotNull]
+ [JsonProperty("name")]
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+ ///
+ /// 外部联系人头像
+ ///
+ [CanBeNull]
+ [JsonProperty("avatar")]
+ [JsonPropertyName("avatar")]
+ public string? Avatar { get; set; }
+ ///
+ /// 外部联系人的类型
+ ///
+ [NotNull]
+ [JsonProperty("type")]
+ [JsonPropertyName("type")]
+ public ExternalContactType Type { get; set; }
+ ///
+ /// 外部联系人性别
+ ///
+ [NotNull]
+ [JsonProperty("gender")]
+ [JsonPropertyName("gender")]
+ public ExternalContactGender Gender { get; set; }
+ ///
+ /// 外部联系人在微信开放平台的唯一身份标识(微信unionid),通过此字段企业可将外部联系人与公众号/小程序用户关联起来。
+ /// 仅当联系人类型是微信用户,且企业绑定了微信开发者ID有此字段
+ ///
+ [CanBeNull]
+ [JsonProperty("unionid")]
+ [JsonPropertyName("unionid")]
+ public string? UnionId { get; set; }
+ ///
+ /// 外部联系人的职位,如果外部企业或用户选择隐藏职位,则不返回,仅当联系人类型是企业微信用户时有此字段
+ ///
+ [CanBeNull]
+ [JsonProperty("position")]
+ [JsonPropertyName("position")]
+ public string? Position { get; set; }
+ ///
+ /// 外部联系人所在企业的简称,仅当联系人类型是企业微信用户时有此字段
+ ///
+ [CanBeNull]
+ [JsonProperty("corp_name")]
+ [JsonPropertyName("corp_name")]
+ public string? CorpName { get; set; }
+ ///
+ /// 外部联系人所在企业的主体名称,仅当联系人类型是企业微信用户时有此字段
+ /// 仅企业自建应用可获取
+ ///
+ [CanBeNull]
+ [JsonProperty("corp_full_name")]
+ [JsonPropertyName("corp_full_name")]
+ public string? CorpFullName { get; set; }
+ ///
+ /// 外部联系人的自定义展示信息
+ ///
+ [CanBeNull]
+ [JsonProperty("external_profile")]
+ [JsonPropertyName("external_profile")]
+ public ExternalProfile? ExternalProfile { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactList.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactList.cs
new file mode 100644
index 000000000..7ace31afb
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactList.cs
@@ -0,0 +1,25 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+///
+/// 外部联系人列表
+///
+public class ExternalContactList
+{
+ ///
+ /// 客户的基本信息
+ ///
+ [NotNull]
+ [JsonProperty("external_contact")]
+ [JsonPropertyName("external_contact")]
+ public ExternalContactInfo ExternalContact { get; set; }
+ ///
+ /// 企业成员客户跟进信息
+ ///
+ [NotNull]
+ [JsonProperty("follow_info")]
+ [JsonPropertyName("follow_info")]
+ public FollowUser FollowUser { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactType.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactType.cs
new file mode 100644
index 000000000..7545230a7
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/ExternalContactType.cs
@@ -0,0 +1,20 @@
+using System.ComponentModel;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+///
+/// 外部联系人类型
+///
+[Description("外部联系人类型")]
+public enum ExternalContactType
+{
+ ///
+ /// 微信用户
+ ///
+ [Description("微信用户")]
+ WeChat = 1,
+ ///
+ /// 企业微信用户
+ ///
+ [Description("企业微信用户")]
+ WeChatWork = 2,
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUser.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUser.cs
new file mode 100644
index 000000000..6225a3b76
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUser.cs
@@ -0,0 +1,92 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+///
+/// 添加了外部联系人的企业成员
+///
+public class FollowUser
+{
+ ///
+ /// 添加了此外部联系人的企业成员userid
+ ///
+ [NotNull]
+ [JsonProperty("userid")]
+ [JsonPropertyName("userid")]
+ public string UserId { get; set; }
+ ///
+ /// 发起添加的userid
+ /// 如果成员主动添加,为成员的userid;
+ /// 如果是客户主动添加,则为客户的外部联系人userid;
+ /// 如果是内部成员共享/管理员分配,则为对应的成员/管理员userid
+ ///
+ [NotNull]
+ [JsonProperty("oper_userid")]
+ [JsonPropertyName("oper_userid")]
+ public string OperUserId { get; set; }
+ ///
+ /// 企业自定义的state参数,用于区分客户具体是通过哪个「联系我」或获客链接添加
+ ///
+ [CanBeNull]
+ [JsonProperty("state")]
+ [JsonPropertyName("state")]
+ public string? State { get; set; }
+ ///
+ /// 该成员对此外部联系人的备注
+ ///
+ [CanBeNull]
+ [JsonProperty("remark")]
+ [JsonPropertyName("remark")]
+ public string? Remark { get; set; }
+ ///
+ /// 该成员对此外部联系人的描述
+ ///
+ [CanBeNull]
+ [JsonProperty("description")]
+ [JsonPropertyName("description")]
+ public string? Description { get; set; }
+ ///
+ /// 该成员添加此外部联系人的时间
+ ///
+ [NotNull]
+ [JsonProperty("createtime")]
+ [JsonPropertyName("createtime")]
+ public long Createtime { get; set; }
+ ///
+ /// 外部联系人所打标签列表
+ ///
+ [CanBeNull]
+ [JsonProperty("tags")]
+ [JsonPropertyName("tags")]
+ public List? Tags { get; set; }
+ ///
+ /// 该成员对此微信客户备注的企业名称(仅微信客户有该字段)
+ ///
+ [CanBeNull]
+ [JsonProperty("remark_corp_name")]
+ [JsonPropertyName("remark_corp_name")]
+ public string? RemarkCorpName { get; set; }
+ ///
+ /// 该成员对此客户备注的手机号码,代开发自建应用需要管理员授权才可以获取
+ ///
+ [CanBeNull]
+ [JsonProperty("remark_mobiles")]
+ [JsonPropertyName("remark_mobiles")]
+ public List? RemarkMobiles { get; set; }
+ ///
+ /// 该成员添加此客户的来源
+ ///
+ [NotNull]
+ [JsonProperty("add_way")]
+ [JsonPropertyName("add_way")]
+ public FollowUserAddWay AddWay { get; set; }
+ ///
+ /// 该成员添加此客户的来源add_way为10时,对应的视频号信息
+ ///
+ [CanBeNull]
+ [JsonProperty("wechat_channels")]
+ [JsonPropertyName("wechat_channels")]
+ public FollowUserWechatChannel? WechatChannel { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserAddWay.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserAddWay.cs
new file mode 100644
index 000000000..79caed25c
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserAddWay.cs
@@ -0,0 +1,125 @@
+using System.ComponentModel;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+///
+/// 客户来源
+///
+[Description("客户来源")]
+public enum FollowUserAddWay
+{
+ ///
+ /// 未知来源
+ ///
+ [Description("未知来源")]
+ None = 0,
+ ///
+ /// 扫描二维码
+ ///
+ [Description("扫描二维码")]
+ ScanQrCode = 1,
+ ///
+ /// 搜索手机号
+ ///
+ [Description("搜索手机号")]
+ SearchPhoneNumber = 2,
+ ///
+ /// 名片分享
+ ///
+ [Description("名片分享")]
+ SharedCard = 3,
+ ///
+ /// 群聊
+ ///
+ [Description("群聊")]
+ GroupChat = 4,
+ ///
+ /// 手机通讯录
+ ///
+ [Description("手机通讯录")]
+ PhoneBook = 5,
+ ///
+ /// 微信联系人
+ ///
+ [Description("微信联系人")]
+ WeChatContact = 6,
+ ///
+ /// 安装第三方应用时自动添加的客服人员
+ ///
+ [Description("安装第三方应用时自动添加的客服人员")]
+ InstallThirdPartyApp = 8,
+ ///
+ /// 搜索邮箱
+ ///
+ [Description("搜索邮箱")]
+ SearchEmail = 9,
+ ///
+ /// 视频号添加
+ ///
+ [Description("视频号添加")]
+ WechatChannel = 10,
+ ///
+ /// 通过日程参与人添加
+ ///
+ [Description("通过日程参与人添加")]
+ SchedulePart = 11,
+ ///
+ /// 通过会议参与人添加
+ ///
+ [Description("通过会议参与人添加")]
+ MeetPart = 12,
+ ///
+ /// 添加微信好友对应的企业微信
+ ///
+ [Description("添加微信好友对应的企业微信")]
+ WeChatFriend = 13,
+ ///
+ /// 通过智慧硬件专属客服添加
+ ///
+ [Description("通过智慧硬件专属客服添加")]
+ SmartHardware = 14,
+ ///
+ /// 通过上门服务客服添加
+ ///
+ [Description("通过上门服务客服添加")]
+ DoorService = 15,
+ ///
+ /// 通过获客链接添加
+ ///
+ [Description("通过获客链接添加")]
+ CustomerAcqLink = 16,
+ ///
+ /// 通过定制开发添加
+ ///
+ [Description("通过定制开发添加")]
+ CustomDevelopment = 17,
+ ///
+ /// 通过需求回复添加
+ ///
+ [Description("通过需求回复添加")]
+ DemandResponse = 18,
+ ///
+ /// 通过第三方售前客服添加
+ ///
+ [Description("通过第三方售前客服添加")]
+ ThirdPartyPreSales = 21,
+ ///
+ /// 通过可能的商务伙伴添加
+ ///
+ [Description("通过可能的商务伙伴添加")]
+ PotentialBusPart = 22,
+ ///
+ /// 通过接受微信账号收到的好友申请添加
+ ///
+ [Description("通过接受微信账号收到的好友申请添加")]
+ WeChatFriendRequest = 24,
+ ///
+ /// 内部成员共享
+ ///
+ [Description("内部成员共享")]
+ SharedByInternalMembers = 201,
+ ///
+ /// 管理员/负责人分配
+ ///
+ [Description("管理员/负责人分配")]
+ AllocationByAdmin = 202
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserTag.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserTag.cs
new file mode 100644
index 000000000..d11bfbd9c
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserTag.cs
@@ -0,0 +1,39 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+///
+/// 外部联系人所打标签
+///
+public class FollowUserTag
+{
+ ///
+ /// 该成员添加此外部联系人所打标签的分组名称
+ ///
+ [CanBeNull]
+ [JsonProperty("group_name")]
+ [JsonPropertyName("group_name")]
+ public string? GroupName { get; set; }
+ ///
+ /// 该成员添加此外部联系人所打标签名称
+ ///
+ [NotNull]
+ [JsonProperty("tag_name")]
+ [JsonPropertyName("tag_name")]
+ public string TagName { get; set; }
+ ///
+ /// 该成员添加此外部联系人所打标签类型
+ ///
+ [NotNull]
+ [JsonProperty("type")]
+ [JsonPropertyName("type")]
+ public FollowUserTagType Type { get; set; }
+ ///
+ /// 该成员添加此外部联系人所打企业标签的id,用户自定义类型标签(type=2)不返回
+ ///
+ [CanBeNull]
+ [JsonProperty("tag_id")]
+ [JsonPropertyName("tag_id")]
+ public string? TagId { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserTagType.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserTagType.cs
new file mode 100644
index 000000000..d8a91a4f6
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserTagType.cs
@@ -0,0 +1,25 @@
+using System.ComponentModel;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+///
+/// 标签类型
+///
+[Description("标签类型")]
+public enum FollowUserTagType
+{
+ ///
+ /// 企业设置
+ ///
+ [Description("企业设置")]
+ EnterpriseSettings = 1,
+ ///
+ /// 用户自定义
+ ///
+ [Description("用户自定义")]
+ UserCustom = 2,
+ ///
+ /// 规则组标签
+ ///
+ [Description("规则组标签")]
+ RuleGroupTags = 3,
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserWechatChannel.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserWechatChannel.cs
new file mode 100644
index 000000000..b1a1b6b26
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserWechatChannel.cs
@@ -0,0 +1,25 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+///
+/// 视频号信息
+///
+public class FollowUserWechatChannel
+{
+ ///
+ /// 视频号名称
+ ///
+ [NotNull]
+ [JsonProperty("nickname")]
+ [JsonPropertyName("nickname")]
+ public string NickName { get; set; }
+ ///
+ /// 视频号添加场景
+ ///
+ [NotNull]
+ [JsonProperty("source")]
+ [JsonPropertyName("source")]
+ public FollowUserWechatChannelSource Source { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserWechatChannelSource.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserWechatChannelSource.cs
new file mode 100644
index 000000000..fa8eb3433
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Models/FollowUserWechatChannelSource.cs
@@ -0,0 +1,30 @@
+using System.ComponentModel;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+///
+/// 视频号添加场景
+///
+[Description("视频号添加场景")]
+public enum FollowUserWechatChannelSource
+{
+ ///
+ /// 未知
+ ///
+ [Description("未知")]
+ None = 0,
+ ///
+ /// 视频号主页
+ ///
+ [Description("视频号主页")]
+ Home = 1,
+ ///
+ /// 视频号直播间
+ ///
+ [Description("视频号直播间")]
+ LiveRoom = 2,
+ ///
+ /// 视频号留资服务
+ ///
+ [Description("视频号留资服务")]
+ RetentionService = 3
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkBulkGetCustomerRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkBulkGetCustomerRequest.cs
new file mode 100644
index 000000000..48b84ab1a
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkBulkGetCustomerRequest.cs
@@ -0,0 +1,55 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request;
+///
+/// 创建新的规则组请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkBulkGetCustomerRequest : WeChatWorkRequest
+{
+ ///
+ /// 企业成员的userid列表,字符串类型,最多支持100个
+ ///
+ [NotNull]
+ [JsonProperty("userid_list")]
+ [JsonPropertyName("userid_list")]
+ public List UserIds { get; }
+ ///
+ /// 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填
+ ///
+ [CanBeNull]
+ [JsonProperty("cursor")]
+ [JsonPropertyName("cursor")]
+ public string? Cursor { get; }
+ ///
+ /// 返回的最大记录数,整型,最大值100,默认值50,超过最大值时取最大值
+ ///
+ [CanBeNull]
+ [JsonProperty("limit")]
+ [JsonPropertyName("limit")]
+ public int Limit { get; }
+ public WeChatWorkBulkGetCustomerRequest(
+ List userIds,
+ string? cursor = null,
+ int limit = 50)
+ {
+ Check.NotNullOrEmpty(userIds, nameof(userIds));
+ Check.Range(limit, nameof(limit), 1, 100);
+
+ if (userIds.Count > 100)
+ {
+ throw new ArgumentException("The maximum number of userIds allowed in the list is only 100!");
+ }
+
+ UserIds = userIds;
+ Cursor = cursor;
+ Limit = limit;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkCreateCustomerStrategyRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkCreateCustomerStrategyRequest.cs
new file mode 100644
index 000000000..1fa6506cb
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkCreateCustomerStrategyRequest.cs
@@ -0,0 +1,74 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+using Newtonsoft.Json;
+using System;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request;
+///
+/// 批量获取客户详情请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkCreateCustomerStrategyRequest : WeChatWorkRequest
+{
+ ///
+ /// 父规则组id
+ ///
+ [CanBeNull]
+ [JsonProperty("parent_id")]
+ [JsonPropertyName("parent_id")]
+ public int? ParentId { get; set; }
+ ///
+ /// 规则组名称
+ ///
+ [NotNull]
+ [JsonProperty("strategy_name")]
+ [JsonPropertyName("strategy_name")]
+ public string StrategyName { get; }
+ ///
+ /// 规则组管理员userid列表
+ ///
+ [NotNull]
+ [JsonProperty("admin_list")]
+ [JsonPropertyName("admin_list")]
+ public string[] AdminList { get; }
+ ///
+ /// 规则组权限
+ ///
+ [NotNull]
+ [JsonProperty("privilege")]
+ [JsonPropertyName("privilege")]
+ public CustomerStrategyPrivilege Privilege { get; }
+ ///
+ /// 规则组管理范围
+ ///
+ [NotNull]
+ [JsonProperty("range")]
+ [JsonPropertyName("range")]
+ public CustomerStrategyRange[] Range { get; }
+ public WeChatWorkCreateCustomerStrategyRequest(
+ string strategyName,
+ string[] adminList,
+ CustomerStrategyPrivilege? privilege = null,
+ CustomerStrategyRange[]? range = null)
+ {
+ Check.NotNullOrWhiteSpace(strategyName, nameof(strategyName));
+ Check.NotNullOrEmpty(adminList, nameof(adminList));
+
+ if (adminList.Length > 20)
+ {
+ throw new ArgumentException("Up to 20 admin list can be configured at a time!");
+ }
+ if (range != null && range.Length > 100)
+ {
+ throw new ArgumentException("Up to 100 management range can be configured at a time!");
+ }
+
+ StrategyName = strategyName;
+ AdminList = adminList;
+ Privilege = privilege ?? CustomerStrategyPrivilege.Default();
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkDeleteCustomerStrategyRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkDeleteCustomerStrategyRequest.cs
new file mode 100644
index 000000000..961e2fed6
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkDeleteCustomerStrategyRequest.cs
@@ -0,0 +1,28 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request;
+///
+/// 删除规则组请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkDeleteCustomerStrategyRequest : WeChatWorkRequest
+{
+ ///
+ /// 规则组id
+ ///
+ [NotNull]
+ [JsonProperty("strategy_id")]
+ [JsonPropertyName("strategy_id")]
+ public int StrategyId { get; }
+ public WeChatWorkDeleteCustomerStrategyRequest(int strategyId)
+ {
+ Check.NotDefaultOrNull(strategyId, nameof(strategyId));
+
+ StrategyId = strategyId;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkGetCustomerStrategyListRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkGetCustomerStrategyListRequest.cs
new file mode 100644
index 000000000..1b5240c7b
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkGetCustomerStrategyListRequest.cs
@@ -0,0 +1,39 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request;
+///
+/// 获取规则组列表请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetCustomerStrategyListRequest : WeChatWorkRequest
+{
+ ///
+ /// 分页查询游标,首次调用可不填
+ ///
+ [CanBeNull]
+ [JsonProperty("cursor")]
+ [JsonPropertyName("cursor")]
+ public string? Cursor { get; }
+ ///
+ /// 分页大小,默认为1000,最大不超过1000
+ ///
+ [CanBeNull]
+ [JsonProperty("limit")]
+ [JsonPropertyName("limit")]
+ public int? Limit { get; }
+ public WeChatWorkGetCustomerStrategyListRequest(string? cursor = null, int? limit = 1000)
+ {
+ if (limit.HasValue)
+ {
+ Check.Range(limit.Value, nameof(limit), 1, 1000);
+ }
+
+ Cursor = cursor;
+ Limit = limit;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkGetCustomerStrategyRangeRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkGetCustomerStrategyRangeRequest.cs
new file mode 100644
index 000000000..415990088
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkGetCustomerStrategyRangeRequest.cs
@@ -0,0 +1,47 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request;
+///
+/// 获取规则组管理范围请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetCustomerStrategyRangeRequest : WeChatWorkRequest
+{
+ ///
+ /// 规则组id
+ ///
+ [NotNull]
+ [JsonProperty("strategy_id")]
+ [JsonPropertyName("strategy_id")]
+ public int StrategyId { get; }
+ ///
+ /// 分页查询游标,首次调用可不填
+ ///
+ [CanBeNull]
+ [JsonProperty("cursor")]
+ [JsonPropertyName("cursor")]
+ public string? Cursor { get; }
+ ///
+ /// 每个分页的成员/部门节点数,默认为1000,最大为1000
+ ///
+ [CanBeNull]
+ [JsonProperty("limit")]
+ [JsonPropertyName("limit")]
+ public int? Limit { get; }
+ public WeChatWorkGetCustomerStrategyRangeRequest(int strategyId, string? cursor = null, int? limit = 1000)
+ {
+ if (limit.HasValue)
+ {
+ Check.Range(limit.Value, nameof(limit), 1, 1000);
+ }
+
+ StrategyId = strategyId;
+ Cursor = cursor;
+ Limit = limit;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkGetCustomerStrategyRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkGetCustomerStrategyRequest.cs
new file mode 100644
index 000000000..1efbfb196
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkGetCustomerStrategyRequest.cs
@@ -0,0 +1,25 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request;
+///
+/// 获取规则组请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetCustomerStrategyRequest : WeChatWorkRequest
+{
+ ///
+ /// 规则组id
+ ///
+ [NotNull]
+ [JsonProperty("strategy_id")]
+ [JsonPropertyName("strategy_id")]
+ public int StrategyId { get; }
+ public WeChatWorkGetCustomerStrategyRequest(int strategyId)
+ {
+ StrategyId = strategyId;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkUpdateCustomerRemarkRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkUpdateCustomerRemarkRequest.cs
new file mode 100644
index 000000000..9501a670d
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkUpdateCustomerRemarkRequest.cs
@@ -0,0 +1,86 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request;
+///
+/// 修改客户备注信息请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkUpdateCustomerRemarkRequest : WeChatWorkRequest
+{
+ ///
+ /// 企业成员的userid
+ ///
+ [NotNull]
+ [JsonProperty("userid")]
+ [JsonPropertyName("userid")]
+ public string UserId { get; }
+ ///
+ /// 外部联系人userid
+ ///
+ [NotNull]
+ [JsonProperty("external_userid")]
+ [JsonPropertyName("external_userid")]
+ public string ExternalUserId { get; }
+ ///
+ /// 此用户对外部联系人的备注,最多20个字符
+ ///
+ [CanBeNull]
+ [StringLength(20)]
+ [JsonProperty("remark")]
+ [JsonPropertyName("remark")]
+ public string? Remark { get; }
+ ///
+ /// 此用户对外部联系人的描述,最多150个字符
+ ///
+ [CanBeNull]
+ [StringLength(150)]
+ [JsonProperty("description")]
+ [JsonPropertyName("description")]
+ public string? Description { get; }
+ ///
+ /// 此用户对外部联系人备注的所属公司名称,最多20个字符
+ ///
+ [CanBeNull]
+ [StringLength(20)]
+ [JsonProperty("remark_company")]
+ [JsonPropertyName("remark_company")]
+ public string? RemarkCompany { get; }
+ ///
+ /// 此用户对外部联系人备注的手机号
+ ///
+ [CanBeNull]
+ [JsonProperty("remark_mobiles")]
+ [JsonPropertyName("remark_mobiles")]
+ public List? RemarkMobiles { get; }
+ ///
+ /// 备注图片的mediaid
+ ///
+ [CanBeNull]
+ [JsonProperty("remark_pic_mediaid")]
+ [JsonPropertyName("remark_pic_mediaid")]
+ public string? RemarkPictureMediaId { get; }
+ public WeChatWorkUpdateCustomerRemarkRequest(
+ string userId,
+ string externalUserId,
+ string? remark = null,
+ string? description = null,
+ string? remarkCompany = null,
+ List? remarkMobiles = null,
+ string? remarkPictureMediaId = null)
+ {
+ UserId = Check.NotNullOrWhiteSpace(userId, nameof(userId));
+ ExternalUserId = Check.NotNullOrWhiteSpace(externalUserId, nameof(externalUserId));
+ Remark = Check.Length(remark, nameof(remark), 20);
+ Description = Check.Length(description, nameof(description), 150);
+ RemarkCompany = Check.Length(remarkCompany, nameof(remarkCompany), 20);
+ RemarkMobiles = remarkMobiles;
+ RemarkPictureMediaId = remarkPictureMediaId;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkUpdateCustomerStrategyRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkUpdateCustomerStrategyRequest.cs
new file mode 100644
index 000000000..5167d3130
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Request/WeChatWorkUpdateCustomerStrategyRequest.cs
@@ -0,0 +1,75 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request;
+///
+/// 编辑规则组及其管理范围请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkUpdateCustomerStrategyRequest : WeChatWorkRequest
+{
+ ///
+ /// 规则组id
+ ///
+ [NotNull]
+ [JsonProperty("strategy_id")]
+ [JsonPropertyName("strategy_id")]
+ public int StrategyId { get; }
+ ///
+ /// 规则组名称
+ ///
+ [CanBeNull]
+ [JsonProperty("strategy_name")]
+ [JsonPropertyName("strategy_name")]
+ public string? StrategyName { get; set; }
+ ///
+ /// 规则组管理员userid列表
+ ///
+ [CanBeNull]
+ [JsonProperty("admin_list")]
+ [JsonPropertyName("admin_list")]
+ public string[]? AdminList { get; set; }
+ ///
+ /// 规则组权限
+ ///
+ [CanBeNull]
+ [JsonProperty("privilege")]
+ [JsonPropertyName("privilege")]
+ public CustomerStrategyPrivilege? Privilege { get; set; }
+ ///
+ /// 新增管理范围
+ ///
+ [NotNull]
+ [JsonProperty("range_add")]
+ [JsonPropertyName("range_add")]
+ public List CreateRange { get; private set; }
+ ///
+ /// 删除管理范围
+ ///
+ [NotNull]
+ [JsonProperty("range_del")]
+ [JsonPropertyName("range_del")]
+ public List DeleteRange { get; private set; }
+ public WeChatWorkUpdateCustomerStrategyRequest(
+ int strategyId,
+ string? strategyName = null,
+ string[]? adminList = null,
+ CustomerStrategyPrivilege? privilege = null)
+ {
+ Check.NotDefaultOrNull(strategyId, nameof(strategyId));
+
+ StrategyId = strategyId;
+ StrategyName = strategyName;
+ AdminList = adminList;
+ Privilege = privilege;
+
+ CreateRange = new List();
+ DeleteRange = new List();
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkBulkGetCustomerResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkBulkGetCustomerResponse.cs
new file mode 100644
index 000000000..75780677a
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkBulkGetCustomerResponse.cs
@@ -0,0 +1,48 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response;
+///
+/// 批量获取客户详情响应参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkBulkGetCustomerResponse : WeChatWorkResponse
+{
+ ///
+ /// 外部联系人的userid列表
+ ///
+ [NotNull]
+ [JsonProperty("external_contact_list")]
+ [JsonPropertyName("external_contact_list")]
+ public List ExternalUserId { get; set; } = new List();
+ ///
+ /// 分页游标,再下次请求时填写以获取之后分页的记录,如果已经没有更多的数据则返回空
+ ///
+ [CanBeNull]
+ [JsonProperty("next_cursor")]
+ [JsonPropertyName("next_cursor")]
+ public string? NextCursor { get; set; }
+ ///
+ /// 若请求中所有userid都无有效互通许可,接口直接报错701008。如果部分userid无有效互通许可,接口返回成功
+ ///
+ [CanBeNull]
+ [JsonProperty("fail_info")]
+ [JsonPropertyName("fail_info")]
+ public WeChatWorkBulkGetCustomerFailInfo? FailInfo { get; set; }
+}
+
+public class WeChatWorkBulkGetCustomerFailInfo
+{
+ ///
+ /// 无许可的userid列表
+ ///
+ [NotNull]
+ [JsonProperty("next_cursor")]
+ [JsonPropertyName("next_cursor")]
+ public List UnlicensedUseridList { get; set; } = new List();
+}
\ No newline at end of file
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkCreateCustomerStrategyResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkCreateCustomerStrategyResponse.cs
new file mode 100644
index 000000000..f6aec2f00
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkCreateCustomerStrategyResponse.cs
@@ -0,0 +1,21 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response;
+///
+/// 创建新的规则组响应参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkCreateCustomerStrategyResponse : WeChatWorkResponse
+{
+ ///
+ /// 规则组id
+ ///
+ [NotNull]
+ [JsonProperty("strategy_id")]
+ [JsonPropertyName("strategy_id")]
+ public int StrategyId { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerListResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerListResponse.cs
new file mode 100644
index 000000000..4f16db664
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerListResponse.cs
@@ -0,0 +1,22 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response;
+///
+/// 获取客户列表响应参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetCustomerListResponse : WeChatWorkResponse
+{
+ ///
+ /// 外部联系人的userid列表
+ ///
+ [NotNull]
+ [JsonProperty("external_userid")]
+ [JsonPropertyName("external_userid")]
+ public List ExternalUserId { get; set; } = new List();
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerResponse.cs
new file mode 100644
index 000000000..d465abe38
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerResponse.cs
@@ -0,0 +1,37 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response;
+///
+/// 获取客户详情响应参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetCustomerResponse : WeChatWorkResponse
+{
+ ///
+ /// 外部联系人信息
+ ///
+ [NotNull]
+ [JsonProperty("external_contact")]
+ [JsonPropertyName("external_contact")]
+ public ExternalContactInfo ExternalContact { get; set; }
+ ///
+ /// 添加了此外部联系人的企业成员
+ ///
+ [NotNull]
+ [JsonProperty("follow_user")]
+ [JsonPropertyName("follow_user")]
+ public List FollowUser { get; set; }
+ ///
+ /// 分页的cursor,当跟进人多于500人时返回
+ ///
+ [CanBeNull]
+ [JsonProperty("next_cursor")]
+ [JsonPropertyName("next_cursor")]
+ public string? NextCursor { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerStrategyListResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerStrategyListResponse.cs
new file mode 100644
index 000000000..b386da41c
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerStrategyListResponse.cs
@@ -0,0 +1,29 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response;
+///
+/// 获取规则组列表响应参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetCustomerStrategyListResponse : WeChatWorkResponse
+{
+ ///
+ /// 规则组列表
+ ///
+ [NotNull]
+ [JsonProperty("strategy")]
+ [JsonPropertyName("strategy")]
+ public CustomerStrategy[] Strategy { get; set; }
+ ///
+ /// 分页游标,用于查询下一个分页的数据,无更多数据时不返回
+ ///
+ [CanBeNull]
+ [JsonProperty("next_cursor")]
+ [JsonPropertyName("next_cursor")]
+ public string? NextCursor { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerStrategyRangeResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerStrategyRangeResponse.cs
new file mode 100644
index 000000000..0c7c715cd
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerStrategyRangeResponse.cs
@@ -0,0 +1,30 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response;
+///
+/// 获取规则组管理范围响应参数
+///
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetCustomerStrategyRangeResponse : WeChatWorkResponse
+{
+ ///
+ /// 规则组管理范围
+ ///
+ [NotNull]
+ [JsonProperty("range")]
+ [JsonPropertyName("range")]
+ public CustomerStrategyRange[] Range { get; set; }
+ ///
+ /// 分页游标,用于查询下一个分页的数据,无更多数据时不返回
+ ///
+ [CanBeNull]
+ [JsonProperty("next_cursor")]
+ [JsonPropertyName("next_cursor")]
+ public string? NextCursor { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerStrategyResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerStrategyResponse.cs
new file mode 100644
index 000000000..0b3eba400
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/Response/WeChatWorkGetCustomerStrategyResponse.cs
@@ -0,0 +1,22 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response;
+///
+/// 获取规则组详情响应参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetCustomerStrategyResponse : WeChatWorkResponse
+{
+ ///
+ /// 规则组详情
+ ///
+ [NotNull]
+ [JsonProperty("strategy")]
+ [JsonPropertyName("strategy")]
+ public CustomerStrategyInfo Strategy { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerProvider.cs
new file mode 100644
index 000000000..eb242ed94
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerProvider.cs
@@ -0,0 +1,93 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Features;
+using LINGYUN.Abp.WeChat.Work.Token;
+using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Features;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers;
+
+[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)]
+public class WeChatWorkCustomerProvider : IWeChatWorkCustomerProvider, ISingletonDependency
+{
+ protected IHttpClientFactory HttpClientFactory { get; }
+ protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; }
+
+ public WeChatWorkCustomerProvider(
+ IHttpClientFactory httpClientFactory,
+ IWeChatWorkTokenProvider weChatWorkTokenProvider)
+ {
+ HttpClientFactory = httpClientFactory;
+ WeChatWorkTokenProvider = weChatWorkTokenProvider;
+ }
+
+ public async virtual Task GetCustomerListAsync(
+ string userId,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNullOrWhiteSpace(userId, nameof(userId));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.GetCustomerListAsync(token.AccessToken, userId, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task BulkGetCustomerAsync(
+ WeChatWorkBulkGetCustomerRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.BulkGetCustomerAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task GetCustomerAsync(
+ string externalUserid,
+ string? cursor = null,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNullOrWhiteSpace(externalUserid, nameof(externalUserid));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.GetCustomerAsync(token.AccessToken, externalUserid, cursor, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task UpdateCustomerRemarkAsync(
+ WeChatWorkUpdateCustomerRemarkRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.UpdateCustomerRemarkAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerStrategyProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerStrategyProvider.cs
new file mode 100644
index 000000000..f2984bb5f
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerStrategyProvider.cs
@@ -0,0 +1,111 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Response;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Features;
+using LINGYUN.Abp.WeChat.Work.Token;
+using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Features;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers;
+
+[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)]
+public class WeChatWorkCustomerStrategyProvider : IWeChatWorkCustomerStrategyProvider, ISingletonDependency
+{
+ protected IHttpClientFactory HttpClientFactory { get; }
+ protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; }
+
+ public WeChatWorkCustomerStrategyProvider(
+ IHttpClientFactory httpClientFactory,
+ IWeChatWorkTokenProvider weChatWorkTokenProvider)
+ {
+ HttpClientFactory = httpClientFactory;
+ WeChatWorkTokenProvider = weChatWorkTokenProvider;
+ }
+
+ public async virtual Task GetCustomerStrategyListAsync(
+ WeChatWorkGetCustomerStrategyListRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.GetCustomerStrategyListAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task GetCustomerStrategyAsync(
+ WeChatWorkGetCustomerStrategyRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.GetCustomerStrategyAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task GetCustomerStrategyRangeAsync(
+ WeChatWorkGetCustomerStrategyRangeRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.GetCustomerStrategyRangeAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task CreateCustomerStrategyAsync(
+ WeChatWorkCreateCustomerStrategyRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.CreateCustomerStrategyAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task UpdateCustomerStrategyAsync(
+ WeChatWorkUpdateCustomerStrategyRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.UpdateCustomerStrategyAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task DeleteCustomerStrategyAsync(
+ WeChatWorkDeleteCustomerStrategyRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.DeleteCustomerStrategyAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Features/WeChatWorkExternalContactFeatureDefinitionProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Features/WeChatWorkExternalContactFeatureDefinitionProvider.cs
new file mode 100644
index 000000000..d516615eb
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Features/WeChatWorkExternalContactFeatureDefinitionProvider.cs
@@ -0,0 +1,31 @@
+using LINGYUN.Abp.WeChat.Work.Features;
+using LINGYUN.Abp.WeChat.Work.Localization;
+using Volo.Abp.Features;
+using Volo.Abp.Localization;
+using Volo.Abp.Validation.StringValues;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Features;
+public class WeChatWorkExternalContactFeatureDefinitionProvider : FeatureDefinitionProvider
+{
+ public override void Define(IFeatureDefinitionContext context)
+ {
+ var weChatFeature = context.GetGroupOrNull(WeChatWorkFeatureNames.GroupName);
+ if (weChatFeature == null)
+ {
+ return;
+ }
+
+ var group = weChatFeature.AddFeature(WeChatWorkExternalContactFeatureNames.GroupName);
+ group.CreateChild(
+ WeChatWorkExternalContactFeatureNames.Enable,
+ defaultValue: "false",
+ displayName: L("Features:ExternalContactEnable"),
+ description: L("Features:ExternalContactEnableDesc"),
+ valueType: new ToggleStringValueType(new BooleanValueValidator()));
+ }
+
+ private static LocalizableString L(string name)
+ {
+ return LocalizableString.Create(name);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Features/WeChatWorkExternalContactFeatureNames.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Features/WeChatWorkExternalContactFeatureNames.cs
new file mode 100644
index 000000000..9a9a1b259
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Features/WeChatWorkExternalContactFeatureNames.cs
@@ -0,0 +1,14 @@
+using LINGYUN.Abp.WeChat.Work.Features;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Features;
+///
+/// 企业微信客户联系模块功能
+///
+public static class WeChatWorkExternalContactFeatureNames
+{
+ public const string GroupName = WeChatWorkFeatureNames.GroupName + ".ExternalContact";
+ ///
+ /// 启用企业微信客户联系
+ ///
+ public const string Enable = GroupName + ".Enable";
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/IWeChatWorkFollowUserProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/IWeChatWorkFollowUserProvider.cs
new file mode 100644
index 000000000..5772d7a9e
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/IWeChatWorkFollowUserProvider.cs
@@ -0,0 +1,20 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Follows.Response;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Follows;
+///
+/// 企业服务人员管理接口
+///
+public interface IWeChatWorkFollowUserProvider
+{
+ ///
+ /// 获取配置了客户联系功能的成员列表
+ ///
+ ///
+ /// 详情见:
+ ///
+ ///
+ ///
+ Task GetFollowUserListAsync(CancellationToken cancellationToken = default);
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/Response/WeChatWorkGetFollowUserListResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/Response/WeChatWorkGetFollowUserListResponse.cs
new file mode 100644
index 000000000..ff0c5d728
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/Response/WeChatWorkGetFollowUserListResponse.cs
@@ -0,0 +1,22 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Follows.Response;
+///
+/// 获取配置了客户联系功能的成员列表响应参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetFollowUserListResponse : WeChatWorkResponse
+{
+ ///
+ /// 配置了客户联系功能的成员userid列表
+ ///
+ [NotNull]
+ [JsonProperty("follow_user")]
+ [JsonPropertyName("follow_user")]
+ public List FollowUser { get; set; } = new List();
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/WeChatWorkFollowUserProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/WeChatWorkFollowUserProvider.cs
new file mode 100644
index 000000000..0391d6b7e
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/WeChatWorkFollowUserProvider.cs
@@ -0,0 +1,38 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Features;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Follows.Response;
+using LINGYUN.Abp.WeChat.Work.Token;
+using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Features;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Follows;
+
+[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)]
+public class WeChatWorkFollowUserProvider : IWeChatWorkFollowUserProvider, ISingletonDependency
+{
+ protected IHttpClientFactory HttpClientFactory { get; }
+ protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; }
+
+ public WeChatWorkFollowUserProvider(
+ IHttpClientFactory httpClientFactory,
+ IWeChatWorkTokenProvider weChatWorkTokenProvider)
+ {
+ HttpClientFactory = httpClientFactory;
+ WeChatWorkTokenProvider = weChatWorkTokenProvider;
+ }
+
+ public async virtual Task GetFollowUserListAsync(CancellationToken cancellationToken = default)
+ {
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.GetFollowUserListAsync(token.AccessToken, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/IWeChatWorkGroupChatProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/IWeChatWorkGroupChatProvider.cs
new file mode 100644
index 000000000..d0cf7d078
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/IWeChatWorkGroupChatProvider.cs
@@ -0,0 +1,48 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Response;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats;
+///
+/// 客户群管理接口
+///
+public interface IWeChatWorkGroupChatProvider
+{
+ ///
+ /// 获取客户群列表
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task GetGroupChatListAsync(
+ WeChatWorkGetGroupChatListRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 获取客户群详情
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task GetGroupChatAsync(
+ WeChatWorkGetGroupChatRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 客户群opengid转换
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task OpengIdToChatIdAsync(
+ WeChatWorkOpengIdToChatIdRequest request,
+ CancellationToken cancellationToken = default);
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChat.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChat.cs
new file mode 100644
index 000000000..e9d6f34bb
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChat.cs
@@ -0,0 +1,25 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models;
+///
+/// 客户群
+///
+public class GroupChat
+{
+ ///
+ /// 客户群ID
+ ///
+ [NotNull]
+ [JsonProperty("chat_id")]
+ [JsonPropertyName("chat_id")]
+ public string ChatId { get; set; }
+ ///
+ /// 客户群跟进状态
+ ///
+ [NotNull]
+ [JsonProperty("status")]
+ [JsonPropertyName("status")]
+ public GroupChatStatus Status { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatInfo.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatInfo.cs
new file mode 100644
index 000000000..84c173240
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatInfo.cs
@@ -0,0 +1,67 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models;
+///
+/// 客户群详情
+///
+public class GroupChatInfo
+{
+ ///
+ /// 客户群ID
+ ///
+ [NotNull]
+ [JsonProperty("chat_id")]
+ [JsonPropertyName("chat_id")]
+ public string ChatId { get; set; }
+ ///
+ /// 群名
+ ///
+ [NotNull]
+ [JsonProperty("name")]
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+ ///
+ /// 群主ID
+ ///
+ [NotNull]
+ [JsonProperty("owner")]
+ [JsonPropertyName("owner")]
+ public string Owner { get; set; }
+ ///
+ /// 群的创建时间
+ ///
+ [NotNull]
+ [JsonProperty("create_time")]
+ [JsonPropertyName("create_time")]
+ public long CreateTime { get; set; }
+ ///
+ /// 群公告
+ ///
+ [CanBeNull]
+ [JsonProperty("notice")]
+ [JsonPropertyName("notice")]
+ public string Notice { get; set; }
+ ///
+ /// 群成员列表
+ ///
+ [NotNull]
+ [JsonProperty("member_list")]
+ [JsonPropertyName("member_list")]
+ public GroupChatMember[] MemberList { get; set; }
+ ///
+ /// 群管理员列表
+ ///
+ [NotNull]
+ [JsonProperty("admin_list")]
+ [JsonPropertyName("admin_list")]
+ public GroupChatManager[] AdminList { get; set; }
+ ///
+ /// 当前群成员版本号。可以配合客户群变更事件减少主动调用本接口的次数
+ ///
+ [NotNull]
+ [JsonProperty("member_version")]
+ [JsonPropertyName("member_version")]
+ public string MemberVersion { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatInvitor.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatInvitor.cs
new file mode 100644
index 000000000..201decd88
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatInvitor.cs
@@ -0,0 +1,18 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models;
+///
+/// 邀请者
+///
+public class GroupChatInvitor
+{
+ ///
+ /// 邀请者的userid
+ ///
+ [NotNull]
+ [JsonProperty("userid")]
+ [JsonPropertyName("userid")]
+ public string UserId { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatManager.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatManager.cs
new file mode 100644
index 000000000..1a53a0781
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatManager.cs
@@ -0,0 +1,18 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models;
+///
+/// 群管理员
+///
+public class GroupChatManager
+{
+ ///
+ /// 群管理员userid
+ ///
+ [NotNull]
+ [JsonProperty("userid")]
+ [JsonPropertyName("userid")]
+ public string UserId { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatMember.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatMember.cs
new file mode 100644
index 000000000..937f07841
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatMember.cs
@@ -0,0 +1,70 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models;
+///
+/// 群成员
+///
+public class GroupChatMember
+{
+ ///
+ /// 群成员id
+ ///
+ [NotNull]
+ [JsonProperty("userid")]
+ [JsonPropertyName("userid")]
+ public string UserId { get; set; }
+ ///
+ /// 成员类型
+ ///
+ [NotNull]
+ [JsonProperty("type")]
+ [JsonPropertyName("type")]
+ public GroupChatMemberType Type { get; set; }
+ ///
+ /// 外部联系人在微信开放平台的唯一身份标识(微信unionid),通过此字段企业可将外部联系人与公众号/小程序用户关联起来。
+ /// 仅当群成员类型是微信用户(包括企业成员未添加好友),且企业绑定了微信开发者ID有此字段
+ ///
+ [CanBeNull]
+ [JsonProperty("unionid")]
+ [JsonPropertyName("unionid")]
+ public string? UnionId { get; set; }
+ ///
+ /// 入群时间
+ ///
+ [NotNull]
+ [JsonProperty("join_time")]
+ [JsonPropertyName("join_time")]
+ public long JoinTime { get; set; }
+ ///
+ /// 入群方式
+ ///
+ [NotNull]
+ [JsonProperty("join_scene")]
+ [JsonPropertyName("join_scene")]
+ public GroupChatMemberJoinScene JoinScene { get; set; }
+ ///
+ /// 邀请者。目前仅当是由本企业内部成员邀请入群时会返回该值
+ ///
+ [CanBeNull]
+ [JsonProperty("invitor")]
+ [JsonPropertyName("invitor")]
+ public GroupChatInvitor? Invitor { get; set; }
+ ///
+ /// 在群里的昵称
+ ///
+ [CanBeNull]
+ [JsonProperty("group_nickname")]
+ [JsonPropertyName("group_nickname")]
+ public string? GroupNickname { get; set; }
+ ///
+ /// 名字。仅当 need_name = 1 时返回
+ /// 如果是微信用户,则返回其在微信中设置的名字
+ /// 如果是企业微信联系人,则返回其设置对外展示的别名或实名
+ ///
+ [CanBeNull]
+ [JsonProperty("name")]
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatMemberJoinScene.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatMemberJoinScene.cs
new file mode 100644
index 000000000..8163b711c
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatMemberJoinScene.cs
@@ -0,0 +1,25 @@
+using System.ComponentModel;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models;
+///
+/// 入群方式
+///
+[Description("入群方式")]
+public enum GroupChatMemberJoinScene
+{
+ ///
+ /// 由群成员邀请入群(直接邀请入群)
+ ///
+ [Description("直接邀请入群")]
+ DirectInvitation = 1,
+ ///
+ /// 由群成员邀请入群(通过邀请链接入群)
+ ///
+ [Description("通过邀请链接入群")]
+ InvitationLink = 2,
+ ///
+ /// 通过扫描群二维码入群
+ ///
+ [Description("通过扫描群二维码入群")]
+ ScanQrCode = 3,
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatMemberType.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatMemberType.cs
new file mode 100644
index 000000000..f8f4a6219
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatMemberType.cs
@@ -0,0 +1,20 @@
+using System.ComponentModel;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models;
+///
+/// 成员类型
+///
+[Description("成员类型")]
+public enum GroupChatMemberType
+{
+ ///
+ /// 企业成员
+ ///
+ [Description("企业成员")]
+ Internal = 1,
+ ///
+ /// 外部联系人
+ ///
+ [Description("外部联系人")]
+ External = 2
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatStatus.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatStatus.cs
new file mode 100644
index 000000000..5393313cd
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/GroupChatStatus.cs
@@ -0,0 +1,30 @@
+using System.ComponentModel;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models;
+///
+/// 客户群跟进状态
+///
+[Description("客户群跟进状态")]
+public enum GroupChatStatus
+{
+ ///
+ /// 跟进人正常
+ ///
+ [Description("跟进人正常")]
+ Normal = 0,
+ ///
+ /// 跟进人离职
+ ///
+ [Description("跟进人离职")]
+ Leaves = 1,
+ ///
+ /// 离职继承中
+ ///
+ [Description("离职继承中")]
+ Resiging = 2,
+ ///
+ /// 离职继承完成
+ ///
+ [Description(" 离职继承完成")]
+ ResignCompleted = 3,
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/OwnerFilter.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/OwnerFilter.cs
new file mode 100644
index 000000000..bd0bc24c8
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/OwnerFilter.cs
@@ -0,0 +1,31 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models;
+///
+/// 群主过滤
+///
+public class OwnerFilter
+{
+ ///
+ /// 用户ID列表。最多100个
+ ///
+ [NotNull]
+ [JsonProperty("userid_list")]
+ [JsonPropertyName("userid_list")]
+ public string[] UserIdList { get; }
+ public OwnerFilter(string[] userIdList)
+ {
+ Check.NotNullOrEmpty(userIdList, nameof(userIdList));
+
+ if (userIdList.Length > 100)
+ {
+ throw new ArgumentException("The maximum number of parameters allowed for group owner filtering is only 100!");
+ }
+
+ UserIdList = userIdList;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/StatusFilter.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/StatusFilter.cs
new file mode 100644
index 000000000..e4d78e910
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Models/StatusFilter.cs
@@ -0,0 +1,30 @@
+using System.ComponentModel;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models;
+///
+/// 客户群跟进状态过滤
+///
+[Description("客户群跟进状态过滤")]
+public enum StatusFilter
+{
+ ///
+ /// 所有列表
+ ///
+ [Description("所有列表")]
+ All = 0,
+ ///
+ /// 跟进人离职
+ ///
+ [Description("跟进人离职")]
+ Leaves = 1,
+ ///
+ /// 离职继承中
+ ///
+ [Description("离职继承中")]
+ Resiging = 2,
+ ///
+ /// 离职继承完成
+ ///
+ [Description(" 离职继承完成")]
+ ResignCompleted = 3,
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Request/WeChatWorkGetGroupChatListRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Request/WeChatWorkGetGroupChatListRequest.cs
new file mode 100644
index 000000000..5c86a3e9d
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Request/WeChatWorkGetGroupChatListRequest.cs
@@ -0,0 +1,50 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Request;
+///
+/// 获取客户群列表请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetGroupChatListRequest : WeChatWorkRequest
+{
+ ///
+ /// 客户群跟进状态过滤
+ ///
+ [CanBeNull]
+ [JsonProperty("status_filter")]
+ [JsonPropertyName("status_filter")]
+ public StatusFilter? StatusFilter { get; set; }
+ ///
+ /// 客户群跟进状态过滤
+ ///
+ [CanBeNull]
+ [JsonProperty("owner_filter")]
+ [JsonPropertyName("owner_filter")]
+ public OwnerFilter[]? OwnerFilter { get; set; }
+ ///
+ /// 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用不填
+ ///
+ [CanBeNull]
+ [JsonProperty("cursor")]
+ [JsonPropertyName("cursor")]
+ public string? Cursor { get; set; }
+ ///
+ /// 分页,预期请求的数据量,取值范围 1 ~ 1000
+ ///
+ [CanBeNull]
+ [JsonProperty("limit")]
+ [JsonPropertyName("limit")]
+ public int Limit { get; }
+ public WeChatWorkGetGroupChatListRequest(int limit = 1000)
+ {
+ Check.Range(limit, nameof(limit), 1, 1000);
+
+ Limit = limit;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Request/WeChatWorkGetGroupChatRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Request/WeChatWorkGetGroupChatRequest.cs
new file mode 100644
index 000000000..4949e0d87
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Request/WeChatWorkGetGroupChatRequest.cs
@@ -0,0 +1,36 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Request;
+///
+/// 获取客户群详情
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetGroupChatRequest : WeChatWorkRequest
+{
+ ///
+ /// 客户群ID
+ ///
+ [NotNull]
+ [JsonProperty("chat_id")]
+ [JsonPropertyName("chat_id")]
+ public string ChatId { get; }
+ ///
+ /// 是否需要返回群成员的名字group_chat.member_list.name。0-不返回;1-返回。默认不返回
+ ///
+ [CanBeNull]
+ [JsonProperty("need_name")]
+ [JsonPropertyName("need_name")]
+ public int? NeedName { get; }
+ public WeChatWorkGetGroupChatRequest(string chatId, bool needName = false)
+ {
+ Check.NotNullOrWhiteSpace(chatId, nameof(chatId));
+
+ ChatId = chatId;
+ NeedName = needName ? 1 : 0;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Request/WeChatWorkOpengIdToChatIdRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Request/WeChatWorkOpengIdToChatIdRequest.cs
new file mode 100644
index 000000000..0c3343a3f
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Request/WeChatWorkOpengIdToChatIdRequest.cs
@@ -0,0 +1,28 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Request;
+///
+/// 客户群opengid转换请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkOpengIdToChatIdRequest : WeChatWorkRequest
+{
+ ///
+ /// 小程序在微信获取到的群ID
+ ///
+ [NotNull]
+ [JsonProperty("opengid")]
+ [JsonPropertyName("opengid")]
+ public string OpengId { get; }
+ public WeChatWorkOpengIdToChatIdRequest(string opengId)
+ {
+ Check.NotNullOrWhiteSpace(opengId, nameof(opengId));
+
+ OpengId = opengId;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Response/WeChatWorkGetGroupChatListResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Response/WeChatWorkGetGroupChatListResponse.cs
new file mode 100644
index 000000000..1a0d2b81e
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Response/WeChatWorkGetGroupChatListResponse.cs
@@ -0,0 +1,29 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Response;
+///
+/// 获取客户群列表响应结果
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetGroupChatListResponse : WeChatWorkResponse
+{
+ ///
+ /// 客户群列表
+ ///
+ [NotNull]
+ [JsonProperty("group_chat_list")]
+ [JsonPropertyName("group_chat_list")]
+ public GroupChat[] GroupChatList { get; set; }
+ ///
+ /// 分页游标,下次请求时填写以获取之后分页的记录。如果该字段返回空则表示已没有更多数据
+ ///
+ [CanBeNull]
+ [JsonProperty("next_cursor")]
+ [JsonPropertyName("next_cursor")]
+ public string? NextCursor { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Response/WeChatWorkGetGroupChatResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Response/WeChatWorkGetGroupChatResponse.cs
new file mode 100644
index 000000000..8f58bbfb3
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Response/WeChatWorkGetGroupChatResponse.cs
@@ -0,0 +1,22 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Response;
+///
+/// 获取客户群详情响应结果
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetGroupChatResponse : WeChatWorkResponse
+{
+ ///
+ /// 客户群详情
+ ///
+ [NotNull]
+ [JsonProperty("group_chat")]
+ [JsonPropertyName("group_chat")]
+ public GroupChatInfo GroupChat { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Response/WeChatWorkOpengIdToChatIdResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Response/WeChatWorkOpengIdToChatIdResponse.cs
new file mode 100644
index 000000000..ab1b981d5
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/Response/WeChatWorkOpengIdToChatIdResponse.cs
@@ -0,0 +1,21 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Response;
+///
+/// 客户群opengid转换响应结果
+///
+///
+/// 详情见:
+///
+public class WeChatWorkOpengIdToChatIdResponse : WeChatWorkResponse
+{
+ ///
+ /// 客户群ID,可以用来调用获取客户群详情
+ ///
+ [NotNull]
+ [JsonProperty("chat_id")]
+ [JsonPropertyName("chat_id")]
+ public string ChatId { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/WeChatWorkGroupChatProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/WeChatWorkGroupChatProvider.cs
new file mode 100644
index 000000000..6600d08a9
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/WeChatWorkGroupChatProvider.cs
@@ -0,0 +1,76 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Features;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Response;
+using LINGYUN.Abp.WeChat.Work.Token;
+using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Features;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats;
+
+[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)]
+public class WeChatWorkGroupChatProvider : IWeChatWorkGroupChatProvider, ISingletonDependency
+{
+ protected IHttpClientFactory HttpClientFactory { get; }
+ protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; }
+
+ public WeChatWorkGroupChatProvider(
+ IHttpClientFactory httpClientFactory,
+ IWeChatWorkTokenProvider weChatWorkTokenProvider)
+ {
+ HttpClientFactory = httpClientFactory;
+ WeChatWorkTokenProvider = weChatWorkTokenProvider;
+ }
+
+ public async virtual Task GetGroupChatListAsync(
+ WeChatWorkGetGroupChatListRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.GetGroupChatListAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task GetGroupChatAsync(
+ WeChatWorkGetGroupChatRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.GetGroupChatAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task OpengIdToChatIdAsync(
+ WeChatWorkOpengIdToChatIdRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.OpengIdToChatIdAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Localization/Resources/en.json b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Localization/Resources/en.json
new file mode 100644
index 000000000..555a57daa
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Localization/Resources/en.json
@@ -0,0 +1,7 @@
+{
+ "culture": "en",
+ "texts": {
+ "Features:ExternalContactEnable": "Enable External Contact",
+ "Features:ExternalContactEnableDesc": "Enable the ability to provide the application with an Enterprise wechat customer contact interface."
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Localization/Resources/zh-Hans.json b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Localization/Resources/zh-Hans.json
new file mode 100644
index 000000000..038e57240
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Localization/Resources/zh-Hans.json
@@ -0,0 +1,7 @@
+{
+ "culture": "zh-Hans",
+ "texts": {
+ "Features:ExternalContactEnable": "启用客户联系",
+ "Features:ExternalContactEnableDesc": "启用以使应用拥有企业微信客户联系接口的能力."
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChaDelMemberEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChaDelMemberEvent.cs
new file mode 100644
index 000000000..c5ff046e8
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChaDelMemberEvent.cs
@@ -0,0 +1,25 @@
+using LINGYUN.Abp.WeChat.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using System.Xml.Serialization;
+using Volo.Abp.EventBus;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 客户群成员退群事件推送
+///
+[EventName("external_chat_del_member")]
+public class ExternalChaDelMemberEvent : ExternalChatChangeMemberEvent
+{
+ ///
+ /// 当是成员退群时有值。表示成员的退群方式
+ /// 0 - 自己退群
+ /// 1 - 群主/群管理员移出
+ ///
+ [XmlElement("QuitScene")]
+ public ExternalChatMemberQuitScene QuitScene { get; set; }
+
+ public override WeChatMessageEto ToEto()
+ {
+ return new WeChatWorkEventMessageEto(this);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatAddMemberEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatAddMemberEvent.cs
new file mode 100644
index 000000000..ebd36da90
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatAddMemberEvent.cs
@@ -0,0 +1,25 @@
+using LINGYUN.Abp.WeChat.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using System.Xml.Serialization;
+using Volo.Abp.EventBus;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 客户群成员入群事件推送
+///
+[EventName("external_chat_add_member")]
+public class ExternalChatAddMemberEvent : ExternalChatChangeMemberEvent
+{
+ ///
+ /// 当是成员入群时有值。表示成员的入群方式
+ /// 0 - 由成员邀请入群(包括直接邀请入群和通过邀请链接入群)
+ /// 3 - 通过扫描群二维码入群
+ ///
+ [XmlElement("JoinScene")]
+ public ExternalChatMemberJoinScene JoinScene { get; set; }
+
+ public override WeChatMessageEto ToEto()
+ {
+ return new WeChatWorkEventMessageEto(this);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeEvent.cs
new file mode 100644
index 000000000..9e7ab1eb6
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeEvent.cs
@@ -0,0 +1,21 @@
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using System.Xml.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 客户群变更事件推送
+///
+public abstract class ExternalChatChangeEvent : WeChatWorkEventMessage
+{
+ ///
+ /// 变更类型
+ ///
+ [XmlElement("ChangeType")]
+ public string ChangeType { get; set; }
+ ///
+ /// 群ID
+ ///
+ [XmlElement("ChatId")]
+ public string ChatId { get; set; }
+}
+
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeMemberEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeMemberEvent.cs
new file mode 100644
index 000000000..b64e10b84
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeMemberEvent.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using System.Xml.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 客户群成员变更事件推送
+///
+public abstract class ExternalChatChangeMemberEvent : ExternalChatUpdateEvent
+{
+ ///
+ /// 成员变更数量
+ ///
+ [XmlElement("MemChangeCnt")]
+ public int MemChangeCnt { get; set; }
+ ///
+ /// 变更的成员列表
+ ///
+ [XmlElement("MemChangeList")]
+ public List MemChangeList { get; set; } = new List();
+ ///
+ /// 变更前的群成员版本号
+ ///
+ [XmlElement("LastMemVer")]
+ public string LastMemVer { get; set; }
+ ///
+ /// 变更后的群成员版本号
+ ///
+ [XmlElement("CurMemVer")]
+ public string CurMemVer { get; set; }
+}
+
+public class ExternalChatChangeMember
+{
+ ///
+ /// 成员Id
+ ///
+ [XmlElement("Item")]
+ public string UserId { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeNameEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeNameEvent.cs
new file mode 100644
index 000000000..e3480f2a7
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeNameEvent.cs
@@ -0,0 +1,16 @@
+using LINGYUN.Abp.WeChat.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using Volo.Abp.EventBus;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 客户群群名变更事件推送
+///
+[EventName("external_chat_change_name")]
+public class ExternalChatChangeNameEvent : ExternalChatUpdateEvent
+{
+ public override WeChatMessageEto ToEto()
+ {
+ return new WeChatWorkEventMessageEto(this);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeNoticeEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeNoticeEvent.cs
new file mode 100644
index 000000000..ec7d5d485
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeNoticeEvent.cs
@@ -0,0 +1,16 @@
+using LINGYUN.Abp.WeChat.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using Volo.Abp.EventBus;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 客户群群公告变更事件推送
+///
+[EventName("external_chat_change_notice")]
+public class ExternalChatChangeNoticeEvent : ExternalChatUpdateEvent
+{
+ public override WeChatMessageEto ToEto()
+ {
+ return new WeChatWorkEventMessageEto(this);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeOwnerEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeOwnerEvent.cs
new file mode 100644
index 000000000..ad955f907
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatChangeOwnerEvent.cs
@@ -0,0 +1,16 @@
+using LINGYUN.Abp.WeChat.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using Volo.Abp.EventBus;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 客户群群主变更事件推送
+///
+[EventName("external_chat_change_owner")]
+public class ExternalChatChangeOwnerEvent : ExternalChatUpdateEvent
+{
+ public override WeChatMessageEto ToEto()
+ {
+ return new WeChatWorkEventMessageEto(this);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatCreateEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatCreateEvent.cs
new file mode 100644
index 000000000..417082072
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatCreateEvent.cs
@@ -0,0 +1,16 @@
+using LINGYUN.Abp.WeChat.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using Volo.Abp.EventBus;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 客户群创建事件推送
+///
+[EventName("external_chat_create")]
+public class ExternalChatCreateEvent : ExternalChatChangeEvent
+{
+ public override WeChatMessageEto ToEto()
+ {
+ return new WeChatWorkEventMessageEto(this);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatDismissEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatDismissEvent.cs
new file mode 100644
index 000000000..e3fa20623
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatDismissEvent.cs
@@ -0,0 +1,16 @@
+using LINGYUN.Abp.WeChat.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using Volo.Abp.EventBus;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 客户群解散事件推送
+///
+[EventName("external_chat_dismiss")]
+public class ExternalChatDismissEvent : ExternalChatChangeEvent
+{
+ public override WeChatMessageEto ToEto()
+ {
+ return new WeChatWorkEventMessageEto(this);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatMemberJoinScene.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatMemberJoinScene.cs
new file mode 100644
index 000000000..1eb129638
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatMemberJoinScene.cs
@@ -0,0 +1,20 @@
+using System.ComponentModel;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 成员的入群方式
+///
+[Description("成员的入群方式")]
+public enum ExternalChatMemberJoinScene
+{
+ ///
+ /// 由成员邀请入群
+ ///
+ [Description("由成员邀请入群")]
+ MemberInvitation = 0,
+ ///
+ /// 通过扫描群二维码入群
+ ///
+ [Description("通过扫描群二维码入群")]
+ ScanQrCode = 3
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatMemberQuitScene.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatMemberQuitScene.cs
new file mode 100644
index 000000000..1a02803ac
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatMemberQuitScene.cs
@@ -0,0 +1,20 @@
+using System.ComponentModel;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 成员的退群方式
+///
+[Description("成员的退群方式")]
+public enum ExternalChatMemberQuitScene
+{
+ ///
+ /// 自己退群
+ ///
+ [Description("自己退群")]
+ UserSelf = 0,
+ ///
+ /// 群主/群管理员移出
+ ///
+ [Description("群主/群管理员移出")]
+ Admin = 1
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatUpdateEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatUpdateEvent.cs
new file mode 100644
index 000000000..7b981dcdf
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalChatUpdateEvent.cs
@@ -0,0 +1,19 @@
+using System.Xml.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 客户群变更事件推送
+///
+public abstract class ExternalChatUpdateEvent : ExternalChatChangeEvent
+{
+ ///
+ /// 变更详情。目前有以下几种:
+ /// add_member : 成员入群
+ /// del_member : 成员退群
+ /// change_owner : 群主变更
+ /// change_name : 群名变更
+ /// change_notice : 群公告变更
+ ///
+ [XmlElement("UpdateDetail")]
+ public string UpdateDetail { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactChangeEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactChangeEvent.cs
new file mode 100644
index 000000000..9d6fe930c
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactChangeEvent.cs
@@ -0,0 +1,25 @@
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using System.Xml.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 企业客户变更事件推送
+///
+public abstract class ExternalContactChangeEvent : WeChatWorkEventMessage
+{
+ ///
+ /// 变更类型
+ ///
+ [XmlElement("ChangeType")]
+ public string ChangeType { get; set; }
+ ///
+ /// 企业服务人员的UserID
+ ///
+ [XmlElement("UserID")]
+ public string UserId { get; set; }
+ ///
+ /// 外部联系人的userid,注意不是企业成员的账号
+ ///
+ [XmlElement("ExternalUserID")]
+ public string ExternalUserId { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactCreateEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactCreateEvent.cs
new file mode 100644
index 000000000..03ddacffb
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactCreateEvent.cs
@@ -0,0 +1,28 @@
+using LINGYUN.Abp.WeChat.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using System.Xml.Serialization;
+using Volo.Abp.EventBus;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 添加企业客户事件推送
+///
+[EventName("external_contact_create")]
+public class ExternalContactCreateEvent : ExternalContactChangeEvent
+{
+ ///
+ /// 添加此用户的「联系我」方式配置的state参数,或在获客链接中指定的customer_channel参数,可用于识别添加此用户的渠道
+ ///
+ [XmlElement("State")]
+ public string State { get; set; }
+ ///
+ /// 欢迎语code,可用于发送欢迎语
+ ///
+ [XmlElement("WelcomeCode")]
+ public string WelcomeCode { get; set; }
+
+ public override WeChatMessageEto ToEto()
+ {
+ return new WeChatWorkEventMessageEto(this);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactCreateHalfEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactCreateHalfEvent.cs
new file mode 100644
index 000000000..943b84f86
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactCreateHalfEvent.cs
@@ -0,0 +1,28 @@
+using LINGYUN.Abp.WeChat.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using System.Xml.Serialization;
+using Volo.Abp.EventBus;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 外部联系人免验证添加成员事件
+///
+[EventName("external_contact_create_half")]
+public class ExternalContactCreateHalfEvent : ExternalContactChangeEvent
+{
+ ///
+ /// 添加此用户的「联系我」方式配置的state参数,或在获客链接中指定的customer_channel参数,可用于识别添加此用户的渠道
+ ///
+ [XmlElement("State")]
+ public string State { get; set; }
+ ///
+ /// 欢迎语code,可用于发送欢迎语
+ ///
+ [XmlElement("WelcomeCode")]
+ public string WelcomeCode { get; set; }
+
+ public override WeChatMessageEto ToEto()
+ {
+ return new WeChatWorkEventMessageEto(this);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactDeleteEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactDeleteEvent.cs
new file mode 100644
index 000000000..c67b70d30
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactDeleteEvent.cs
@@ -0,0 +1,23 @@
+using LINGYUN.Abp.WeChat.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using System.Xml.Serialization;
+using Volo.Abp.EventBus;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 删除企业客户事件
+///
+[EventName("external_contact_delete")]
+public class ExternalContactDeleteEvent : ExternalContactChangeEvent
+{
+ ///
+ /// 删除客户的操作来源,DELETE_BY_TRANSFER表示此客户是因在职继承自动被转接成员删除
+ ///
+ [XmlElement("Source")]
+ public string Source { get; set; }
+
+ public override WeChatMessageEto ToEto()
+ {
+ return new WeChatWorkEventMessageEto(this);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactDeleteFollowUserEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactDeleteFollowUserEvent.cs
new file mode 100644
index 000000000..cdd043073
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactDeleteFollowUserEvent.cs
@@ -0,0 +1,16 @@
+using LINGYUN.Abp.WeChat.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using Volo.Abp.EventBus;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 删除跟进成员事件
+///
+[EventName("external_contact_del_follow_user")]
+public class ExternalContactDeleteFollowUserEvent : ExternalContactChangeEvent
+{
+ public override WeChatMessageEto ToEto()
+ {
+ return new WeChatWorkEventMessageEto(this);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactTransferFailEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactTransferFailEvent.cs
new file mode 100644
index 000000000..dd60b40a4
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactTransferFailEvent.cs
@@ -0,0 +1,23 @@
+using LINGYUN.Abp.WeChat.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using System.Xml.Serialization;
+using Volo.Abp.EventBus;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 客户接替失败事件
+///
+[EventName("external_contact_transfer_fail")]
+public class ExternalContactTransferFailEvent : ExternalContactChangeEvent
+{
+ ///
+ /// 接替失败的原因, customer_refused-客户拒绝, customer_limit_exceed-接替成员的客户数达到上限
+ ///
+ [XmlElement("FailReason")]
+ public string FailReason { get; set; }
+
+ public override WeChatMessageEto ToEto()
+ {
+ return new WeChatWorkEventMessageEto(this);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactUpdateEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactUpdateEvent.cs
new file mode 100644
index 000000000..e1f9a777c
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalContactUpdateEvent.cs
@@ -0,0 +1,17 @@
+using LINGYUN.Abp.WeChat.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using Volo.Abp.EventBus;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 编辑企业客户事件推送
+///
+[EventName("external_contact_update")]
+public class ExternalContactUpdateEvent : ExternalContactChangeEvent
+{
+ public override WeChatMessageEto ToEto()
+ {
+ return new WeChatWorkEventMessageEto(this);
+ }
+}
+
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagChangeEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagChangeEvent.cs
new file mode 100644
index 000000000..e6bc43cb7
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagChangeEvent.cs
@@ -0,0 +1,20 @@
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using System.Xml.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 企业客户标签变更事件推送
+///
+public abstract class ExternalTagChangeEvent : WeChatWorkEventMessage
+{
+ ///
+ /// 变更类型
+ ///
+ [XmlElement("ChangeType")]
+ public string ChangeType { get; set; }
+ ///
+ /// 标签或标签组所属的规则组id,只回调给“客户联系”应用
+ ///
+ [XmlElement("StrategyId")]
+ public string StrategyId { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagCreateEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagCreateEvent.cs
new file mode 100644
index 000000000..cd7e0ff33
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagCreateEvent.cs
@@ -0,0 +1,16 @@
+using LINGYUN.Abp.WeChat.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using Volo.Abp.EventBus;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 企业客户标签创建事件推送
+///
+[EventName("change_external_tag_create")]
+public class ExternalTagCreateEvent : ExternalTagChangeEvent
+{
+ public override WeChatMessageEto ToEto()
+ {
+ return new WeChatWorkEventMessageEto(this);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagDeleteEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagDeleteEvent.cs
new file mode 100644
index 000000000..730b10915
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagDeleteEvent.cs
@@ -0,0 +1,16 @@
+using LINGYUN.Abp.WeChat.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using Volo.Abp.EventBus;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 企业客户标签删除事件推送
+///
+[EventName("change_external_tag_delete")]
+public class ExternalTagDeleteEvent : ExternalTagChangeEvent
+{
+ public override WeChatMessageEto ToEto()
+ {
+ return new WeChatWorkEventMessageEto(this);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagShuffleEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagShuffleEvent.cs
new file mode 100644
index 000000000..390a0f7d9
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagShuffleEvent.cs
@@ -0,0 +1,16 @@
+using LINGYUN.Abp.WeChat.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using Volo.Abp.EventBus;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 企业客户标签重排事件推送
+///
+[EventName("change_external_tag_shuffle")]
+public class ExternalTagShuffleEvent : ExternalTagChangeEvent
+{
+ public override WeChatMessageEto ToEto()
+ {
+ return new WeChatWorkEventMessageEto(this);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagUpdateEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagUpdateEvent.cs
new file mode 100644
index 000000000..1e728d5c2
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Messages/Models/ExternalTagUpdateEvent.cs
@@ -0,0 +1,16 @@
+using LINGYUN.Abp.WeChat.Common.Messages;
+using LINGYUN.Abp.WeChat.Work.Common.Messages;
+using Volo.Abp.EventBus;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Messages.Models;
+///
+/// 企业客户标签变更事件推送
+///
+[EventName("change_external_tag_update")]
+public class ExternalTagUpdateEvent : ExternalTagChangeEvent
+{
+ public override WeChatMessageEto ToEto()
+ {
+ return new WeChatWorkEventMessageEto(this);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalAttribute.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalAttribute.cs
new file mode 100644
index 000000000..ef161f3c2
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalAttribute.cs
@@ -0,0 +1,25 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Models;
+///
+/// 成员对外属性
+///
+public abstract class ExternalAttribute
+{
+ ///
+ /// 属性名称: 需要先确保在管理端有创建该属性,否则会忽略
+ ///
+ [NotNull]
+ [JsonProperty("name")]
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+ ///
+ /// 属性类型
+ ///
+ [NotNull]
+ [JsonProperty("type")]
+ [JsonPropertyName("type")]
+ public ExternalAttributeType Type { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalAttributeDeserializeFactory.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalAttributeDeserializeFactory.cs
new file mode 100644
index 000000000..fb0975217
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalAttributeDeserializeFactory.cs
@@ -0,0 +1,35 @@
+using Newtonsoft.Json.Linq;
+using System;
+using System.Text.Json;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Models;
+internal static class ExternalAttributeDeserializeFactory
+{
+ ///
+ /// 根据属性类型创建属性(System.Text.Json)
+ ///
+ public static ExternalAttribute CreateExternalAttribute(ExternalAttributeType type, JsonElement configElement)
+ {
+ return type switch
+ {
+ ExternalAttributeType.Text => JsonSerializer.Deserialize(configElement.GetRawText())!,
+ ExternalAttributeType.Web => JsonSerializer.Deserialize(configElement.GetRawText())!,
+ ExternalAttributeType.MiniProgram => JsonSerializer.Deserialize(configElement.GetRawText())!,
+ _ => throw new NotSupportedException($"Attribute type {type} is not supported for the time being"),
+ };
+ }
+
+ ///
+ /// 根据属性类型创建属性(Newtonsoft.Json)
+ ///
+ public static ExternalAttribute CreateExternalAttribute(ExternalAttributeType type, JToken configToken)
+ {
+ return type switch
+ {
+ ExternalAttributeType.Text => configToken.ToObject()!,
+ ExternalAttributeType.Web => configToken.ToObject()!,
+ ExternalAttributeType.MiniProgram => configToken.ToObject()!,
+ _ => throw new NotSupportedException($"Attribute type {type} is not supported for the time being"),
+ };
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalAttributeType.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalAttributeType.cs
new file mode 100644
index 000000000..8b003cf05
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalAttributeType.cs
@@ -0,0 +1,25 @@
+using System.ComponentModel;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Models;
+///
+/// 属性类型
+///
+[Description("属性类型")]
+public enum ExternalAttributeType
+{
+ ///
+ /// 文本
+ ///
+ [Description("文本")]
+ Text = 0,
+ ///
+ /// 网页
+ ///
+ [Description("网页")]
+ Web = 1,
+ ///
+ /// 小程序
+ ///
+ [Description("小程序")]
+ MiniProgram = 2
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalMiniProgramAttribute.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalMiniProgramAttribute.cs
new file mode 100644
index 000000000..5935b1ebf
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalMiniProgramAttribute.cs
@@ -0,0 +1,43 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Models;
+///
+/// 小程序类型属性
+///
+public class ExternalMiniProgramAttribute : ExternalAttribute
+{
+ ///
+ /// 小程序
+ ///
+ [NotNull]
+ [JsonProperty("miniprogram")]
+ [JsonPropertyName("miniprogram")]
+ public ExternalMiniProgramModel MiniProgram { get; set; }
+}
+
+public class ExternalMiniProgramModel
+{
+ ///
+ /// 小程序appid,必须是有在本企业安装授权的小程序,否则会被忽略
+ ///
+ [NotNull]
+ [JsonProperty("appid")]
+ [JsonPropertyName("appid")]
+ public string AppId { get; set; }
+ ///
+ /// 小程序的展示标题,长度限制12个UTF8字符
+ ///
+ [NotNull]
+ [JsonProperty("title")]
+ [JsonPropertyName("title")]
+ public string Title { get; set; }
+ ///
+ /// 小程序的页面路径
+ ///
+ [NotNull]
+ [JsonProperty("pagepath")]
+ [JsonPropertyName("pagepath")]
+ public string PagePath { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalProfile.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalProfile.cs
new file mode 100644
index 000000000..2b625199c
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalProfile.cs
@@ -0,0 +1,36 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Models;
+///
+/// 成员对外信息
+///
+[System.Text.Json.Serialization.JsonConverter(typeof(ExternalProfileSystemTextJsonConverter))]
+[Newtonsoft.Json.JsonConverter(typeof(ExternalProfileNewtonsoftJsonConverter))]
+public class ExternalProfile
+{
+ ///
+ /// 企业对外简称,需从已认证的企业简称中选填。可在“我的企业”页中查看企业简称认证状态。
+ ///
+ [NotNull]
+ [JsonProperty("external_corp_name")]
+ [JsonPropertyName("external_corp_name")]
+ public string ExternalCorpName { get; set; }
+ ///
+ /// 视频号属性。须从企业绑定到企业微信的视频号中选择,可在“我的企业”页中查看绑定的视频号。
+ /// 第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取
+ ///
+ [NotNull]
+ [JsonProperty("wechat_channels")]
+ [JsonPropertyName("wechat_channels")]
+ public List WechatChannels { get; set; }
+ ///
+ /// 属性列表
+ ///
+ [NotNull]
+ [JsonProperty("external_attr")]
+ [JsonPropertyName("external_attr")]
+ public List ExternalAttributes { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalTextAttribute.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalTextAttribute.cs
new file mode 100644
index 000000000..b8f3f7d70
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalTextAttribute.cs
@@ -0,0 +1,29 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Models;
+///
+/// 文本类型属性
+///
+public class ExternalTextAttribute : ExternalAttribute
+{
+ ///
+ /// 文本
+ ///
+ [NotNull]
+ [JsonProperty("text")]
+ [JsonPropertyName("text")]
+ public ExternalTextModel Text { get; set; }
+}
+
+public class ExternalTextModel
+{
+ ///
+ /// 文本
+ ///
+ [NotNull]
+ [JsonProperty("value")]
+ [JsonPropertyName("value")]
+ public string Value { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalWebAttribute.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalWebAttribute.cs
new file mode 100644
index 000000000..e882697a2
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/ExternalWebAttribute.cs
@@ -0,0 +1,36 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Models;
+///
+/// 网页类型属性
+///
+public class ExternalWebAttribute : ExternalAttribute
+{
+ ///
+ /// 网页
+ ///
+ [NotNull]
+ [JsonProperty("web")]
+ [JsonPropertyName("web")]
+ public ExternalWebModel Web { get; set; }
+}
+
+public class ExternalWebModel
+{
+ ///
+ /// 网页的url,必须包含http或者https头
+ ///
+ [NotNull]
+ [JsonProperty("url")]
+ [JsonPropertyName("url")]
+ public string Url { get; set; }
+ ///
+ /// 网页的展示标题,长度限制12个UTF8字符
+ ///
+ [NotNull]
+ [JsonProperty("title")]
+ [JsonPropertyName("title")]
+ public string Title { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/WechatChannel.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/WechatChannel.cs
new file mode 100644
index 000000000..953c929fa
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/WechatChannel.cs
@@ -0,0 +1,25 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Models;
+///
+/// 视频号属性
+///
+public class WechatChannel
+{
+ ///
+ /// 视频号名字(设置后,成员将对外展示该视频号)
+ ///
+ [NotNull]
+ [JsonProperty("nickname")]
+ [JsonPropertyName("nickname")]
+ public string NickName { get; set; }
+ ///
+ /// 对外展示视频号状态
+ ///
+ [NotNull]
+ [JsonProperty("status")]
+ [JsonPropertyName("status")]
+ public WechatChannelStatus Status { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/WechatChannelStatus.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/WechatChannelStatus.cs
new file mode 100644
index 000000000..3d994e996
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Models/WechatChannelStatus.cs
@@ -0,0 +1,20 @@
+using System.ComponentModel;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Models;
+///
+/// 视频号状态
+///
+[Description("视频号状态")]
+public enum WechatChannelStatus
+{
+ ///
+ /// 已确认
+ ///
+ [Description("已确认")]
+ Confirmed = 0,
+ ///
+ /// 待确认
+ ///
+ [Description("待确认")]
+ UnConfirmed = 1,
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/IWeChatWorkCropTagProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/IWeChatWorkCropTagProvider.cs
new file mode 100644
index 000000000..f943e1e3c
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/IWeChatWorkCropTagProvider.cs
@@ -0,0 +1,72 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Response;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags;
+///
+/// 客户标签管理
+///
+public interface IWeChatWorkCropTagProvider
+{
+ ///
+ /// 获取企业标签库
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task GetCropTagListAsync(
+ WeChatWorkGetCropTagListRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 添加企业客户标签
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task CreateCropTagAsync(
+ WeChatWorkCreateCropTagRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 编辑企业客户标签
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task UpdateCropTagAsync(
+ WeChatWorkUpdateCropTagRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 删除企业客户标签
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task DeleteCropTagAsync(
+ WeChatWorkDeleteCropTagRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 编辑客户企业标签
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task MarkCropTagAsync(
+ WeChatWorkMarkCropTagRequest request,
+ CancellationToken cancellationToken = default);
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/IWeChatWorkStrategyTagProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/IWeChatWorkStrategyTagProvider.cs
new file mode 100644
index 000000000..794b6e007
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/IWeChatWorkStrategyTagProvider.cs
@@ -0,0 +1,60 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Response;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags;
+///
+/// 规则组标签管理
+///
+public interface IWeChatWorkStrategyTagProvider
+{
+ ///
+ /// 获取指定规则组下的企业客户标签
+ ///
+ ///
+ /// 详情见: https://developer.work.weixin.qq.com/document/path/94882#%E8%8E%B7%E5%8F%96%E6%8C%87%E5%AE%9A%E8%A7%84%E5%88%99%E7%BB%84%E4%B8%8B%E7%9A%84%E4%BC%81%E4%B8%9A%E5%AE%A2%E6%88%B7%E6%A0%87%E7%AD%BE
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task GetStrategyTagListAsync(
+ WeChatWorkGetStrategyTagListRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 为指定规则组创建企业客户标签
+ ///
+ ///
+ /// 详情见: https://developer.work.weixin.qq.com/document/path/94882#%E8%8E%B7%E5%8F%96%E6%8C%87%E5%AE%9A%E8%A7%84%E5%88%99%E7%BB%84%E4%B8%8B%E7%9A%84%E4%BC%81%E4%B8%9A%E5%AE%A2%E6%88%B7%E6%A0%87%E7%AD%BE
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task CreateStrategyTagAsync(
+ WeChatWorkCreateStrategyTagRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 编辑指定规则组下的企业客户标签
+ ///
+ ///
+ /// 详情见: https://developer.work.weixin.qq.com/document/path/94882#%E7%BC%96%E8%BE%91%E6%8C%87%E5%AE%9A%E8%A7%84%E5%88%99%E7%BB%84%E4%B8%8B%E7%9A%84%E4%BC%81%E4%B8%9A%E5%AE%A2%E6%88%B7%E6%A0%87%E7%AD%BE
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task UpdateStrategyTagAsync(
+ WeChatWorkUpdateStrategyTagRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 删除指定规则组下的企业客户标签
+ ///
+ ///
+ /// 详情见: https://developer.work.weixin.qq.com/document/path/94882#%E5%88%A0%E9%99%A4%E6%8C%87%E5%AE%9A%E8%A7%84%E5%88%99%E7%BB%84%E4%B8%8B%E7%9A%84%E4%BC%81%E4%B8%9A%E5%AE%A2%E6%88%B7%E6%A0%87%E7%AD%BE
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task DeleteStrategyTagAsync(
+ WeChatWorkDeleteStrategyTagRequest request,
+ CancellationToken cancellationToken = default);
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/CropTag.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/CropTag.cs
new file mode 100644
index 000000000..ebcf7ba48
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/CropTag.cs
@@ -0,0 +1,15 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models;
+public class CropTag : Tag
+{
+ ///
+ /// 标签是否已经被删除,只在指定tag_id/group_id进行查询时返回
+ ///
+ [NotNull]
+ [JsonProperty("deleted")]
+ [JsonPropertyName("deleted")]
+ public bool Deleted { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/CropTagGroup.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/CropTagGroup.cs
new file mode 100644
index 000000000..db8f9770e
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/CropTagGroup.cs
@@ -0,0 +1,22 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models;
+public class CropTagGroup : TagGroup
+{
+ ///
+ /// 标签组是否已经被删除,只在指定tag_id进行查询时返回
+ ///
+ [NotNull]
+ [JsonProperty("deleted")]
+ [JsonPropertyName("deleted")]
+ public bool Deleted { get; set; }
+ ///
+ /// 标签列表
+ ///
+ [NotNull]
+ [JsonProperty("tag")]
+ [JsonPropertyName("tag")]
+ public CropTag[] Tag { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/StrategyTag.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/StrategyTag.cs
new file mode 100644
index 000000000..f0ebd22c8
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/StrategyTag.cs
@@ -0,0 +1,4 @@
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models;
+public class StrategyTag : Tag
+{
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/StrategyTagGroup.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/StrategyTagGroup.cs
new file mode 100644
index 000000000..7f49f7e41
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/StrategyTagGroup.cs
@@ -0,0 +1,22 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models;
+public class StrategyTagGroup : TagGroup
+{
+ ///
+ /// 标签组所属的规则组id
+ ///
+ [NotNull]
+ [JsonProperty("strategy_id")]
+ [JsonPropertyName("strategy_id")]
+ public int StrategyId { get; set; }
+ ///
+ /// 标签列表
+ ///
+ [NotNull]
+ [JsonProperty("tag")]
+ [JsonPropertyName("tag")]
+ public StrategyTag[] Tag { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/Tag.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/Tag.cs
new file mode 100644
index 000000000..e625fcb66
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/Tag.cs
@@ -0,0 +1,36 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models;
+public abstract class Tag
+{
+ ///
+ /// 标签id
+ ///
+ [NotNull]
+ [JsonProperty("id")]
+ [JsonPropertyName("id")]
+ public string Id { get; set; }
+ ///
+ /// 标签名称
+ ///
+ [NotNull]
+ [JsonProperty("name")]
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+ ///
+ /// 标签创建时间
+ ///
+ [NotNull]
+ [JsonProperty("create_time")]
+ [JsonPropertyName("create_time")]
+ public long CreateTime { get; set; }
+ ///
+ /// 标签排序的次序值,order值大的排序靠前。有效的值范围是[0, 2^32)
+ ///
+ [NotNull]
+ [JsonProperty("order")]
+ [JsonPropertyName("order")]
+ public int Order { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/TagGroup.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/TagGroup.cs
new file mode 100644
index 000000000..6fcd9ddac
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Models/TagGroup.cs
@@ -0,0 +1,36 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models;
+public abstract class TagGroup
+{
+ ///
+ /// 标签组id
+ ///
+ [NotNull]
+ [JsonProperty("group_id")]
+ [JsonPropertyName("group_id")]
+ public string GroupId { get; set; }
+ ///
+ /// 标签组名称
+ ///
+ [NotNull]
+ [JsonProperty("group_name")]
+ [JsonPropertyName("group_name")]
+ public string GroupName { get; set; }
+ ///
+ /// 标签组创建时间
+ ///
+ [NotNull]
+ [JsonProperty("create_time")]
+ [JsonPropertyName("create_time")]
+ public long CreateTime { get; set; }
+ ///
+ /// 标签组排序的次序值,order值大的排序靠前。有效的值范围是[0, 2^32)
+ ///
+ [NotNull]
+ [JsonProperty("order")]
+ [JsonPropertyName("order")]
+ public int Order { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkCreateCropTagRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkCreateCropTagRequest.cs
new file mode 100644
index 000000000..061aa937a
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkCreateCropTagRequest.cs
@@ -0,0 +1,100 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request;
+///
+/// 添加企业客户标签请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkCreateCropTagRequest : WeChatWorkRequest
+{
+ ///
+ /// 标签组id
+ ///
+ [CanBeNull]
+ [JsonProperty("group_id")]
+ [JsonPropertyName("group_id")]
+ public string? GroupId { get; set; }
+
+ private string? _groupName;
+ ///
+ /// 标签组名称,最长为30个字符
+ ///
+ [CanBeNull]
+ [JsonProperty("group_name")]
+ [JsonPropertyName("group_name")]
+ public string? GroupName {
+ get => _groupName;
+ set {
+
+ Check.Length(value, nameof(GroupName), 30);
+ _groupName = value;
+ }
+ }
+ ///
+ /// 标签组排序的次序值,order值大的排序靠前。有效的值范围是[0, 2^32)
+ ///
+ [CanBeNull]
+ [JsonProperty("order")]
+ [JsonPropertyName("order")]
+ public int? Order { get; set; }
+ ///
+ /// 授权方安装的应用agentid。仅旧的第三方多应用套件需要填此参数
+ ///
+ [CanBeNull]
+ [JsonProperty("agentid")]
+ [JsonPropertyName("agentid")]
+ public string? AgentId { get; set; }
+ ///
+ /// 添加的标签组
+ ///
+ [CanBeNull]
+ [JsonProperty("tag")]
+ [JsonPropertyName("tag")]
+ public List Tag { get; }
+
+ public WeChatWorkCreateCropTagRequest()
+ {
+ Tag = new List();
+ }
+
+ protected override void Validate()
+ {
+ if (GroupName.IsNullOrWhiteSpace() &&
+ Tag.IsNullOrEmpty())
+ {
+ throw new ArgumentException("The name of the tag group or the tag list cannot be empty at the same time!");
+ }
+ }
+}
+
+public class NewCropTag
+{
+ ///
+ /// 添加的标签名称,最长为30个字符
+ ///
+ [NotNull]
+ [JsonProperty("name")]
+ [JsonPropertyName("name")]
+ public string Name { get; }
+ ///
+ /// 标签次序值。order值大的排序靠前。有效的值范围是[0, 2^32)
+ ///
+ [CanBeNull]
+ [JsonProperty("order")]
+ [JsonPropertyName("order")]
+ public int? Order { get; set; }
+ public NewCropTag(string name, int? order = null)
+ {
+ Check.NotNullOrWhiteSpace(name, nameof(name), 30);
+
+ Name = name;
+ Order = order;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkCreateStrategyTagRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkCreateStrategyTagRequest.cs
new file mode 100644
index 000000000..a1e7a1bb1
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkCreateStrategyTagRequest.cs
@@ -0,0 +1,88 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request;
+public class WeChatWorkCreateStrategyTagRequest : WeChatWorkRequest
+{
+ ///
+ /// 规则组id
+ ///
+ [NotNull]
+ [JsonProperty("strategy_id")]
+ [JsonPropertyName("strategy_id")]
+ public int StrategyId { get; }
+ ///
+ /// 标签组id
+ ///
+ [CanBeNull]
+ [JsonProperty("group_id")]
+ [JsonPropertyName("group_id")]
+ public string? GroupId { get; set; }
+
+ private string? _groupName;
+ ///
+ /// 标签组名称,最长为30个字符
+ ///
+ [CanBeNull]
+ [JsonProperty("group_name")]
+ [JsonPropertyName("group_name")]
+ public string? GroupName {
+ get => _groupName;
+ set {
+
+ Check.Length(value, nameof(GroupName), 30);
+ _groupName = value;
+ }
+ }
+ ///
+ /// 标签组排序的次序值,order值大的排序靠前。有效的值范围是[0, 2^32)
+ ///
+ [CanBeNull]
+ [JsonProperty("order")]
+ [JsonPropertyName("order")]
+ public int? Order { get; set; }
+ ///
+ /// 添加的标签组
+ ///
+ [CanBeNull]
+ [JsonProperty("tag")]
+ [JsonPropertyName("tag")]
+ public List Tag { get; }
+
+ public WeChatWorkCreateStrategyTagRequest(int strategyId)
+ {
+ Check.NotDefaultOrNull(strategyId, nameof(strategyId));
+
+ StrategyId = strategyId;
+
+ Tag = new List();
+ }
+}
+
+public class NewStrategyTag
+{
+ ///
+ /// 添加的标签名称,最长为30个字符
+ ///
+ [NotNull]
+ [JsonProperty("name")]
+ [JsonPropertyName("name")]
+ public string Name { get; }
+ ///
+ /// 标签次序值。order值大的排序靠前。有效的值范围是[0, 2^32)
+ ///
+ [CanBeNull]
+ [JsonProperty("order")]
+ [JsonPropertyName("order")]
+ public int? Order { get; set; }
+ public NewStrategyTag(string name, int? order = null)
+ {
+ Check.NotNullOrWhiteSpace(name, nameof(name), 30);
+
+ Name = name;
+ Order = order;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkDeleteCropTagRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkDeleteCropTagRequest.cs
new file mode 100644
index 000000000..c5b86b07e
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkDeleteCropTagRequest.cs
@@ -0,0 +1,53 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request;
+///
+/// 删除企业客户标签请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkDeleteCropTagRequest : WeChatWorkRequest
+{
+ ///
+ /// 标签组的id列表
+ ///
+ [CanBeNull]
+ [JsonProperty("group_id")]
+ [JsonPropertyName("group_id")]
+ public string[]? GroupId { get; }
+ ///
+ /// 标签组的id列表
+ ///
+ [CanBeNull]
+ [JsonProperty("tag_id")]
+ [JsonPropertyName("tag_id")]
+ public string[]? TagId { get; }
+ ///
+ /// 授权方安装的应用agentid。仅旧的第三方多应用套件需要填此参数
+ ///
+ [CanBeNull]
+ [JsonProperty("agentid")]
+ [JsonPropertyName("agentid")]
+ public string? AgentId { get; set; }
+
+ private WeChatWorkDeleteCropTagRequest(
+ string[]? groupId = null,
+ string[]? tagId = null)
+ {
+ GroupId = groupId;
+ TagId = tagId;
+ }
+
+ public static WeChatWorkDeleteCropTagRequest Tag(string[] tagId)
+ {
+ return new WeChatWorkDeleteCropTagRequest(tagId: tagId);
+ }
+
+ public static WeChatWorkDeleteCropTagRequest Group(string[] groupId)
+ {
+ return new WeChatWorkDeleteCropTagRequest(groupId);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkDeleteStrategyTagRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkDeleteStrategyTagRequest.cs
new file mode 100644
index 000000000..018ed9505
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkDeleteStrategyTagRequest.cs
@@ -0,0 +1,40 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request;
+public class WeChatWorkDeleteStrategyTagRequest : WeChatWorkRequest
+{
+ ///
+ /// 标签组的id列表
+ ///
+ [CanBeNull]
+ [JsonProperty("group_id")]
+ [JsonPropertyName("group_id")]
+ public string[]? GroupId { get; }
+ ///
+ /// 标签组的id列表
+ ///
+ [CanBeNull]
+ [JsonProperty("tag_id")]
+ [JsonPropertyName("tag_id")]
+ public string[]? TagId { get; }
+
+ private WeChatWorkDeleteStrategyTagRequest(
+ string[]? groupId = null,
+ string[]? tagId = null)
+ {
+ GroupId = groupId;
+ TagId = tagId;
+ }
+
+ public static WeChatWorkDeleteStrategyTagRequest Tag(string[] tagId)
+ {
+ return new WeChatWorkDeleteStrategyTagRequest(tagId: tagId);
+ }
+
+ public static WeChatWorkDeleteStrategyTagRequest Group(string[] groupId)
+ {
+ return new WeChatWorkDeleteStrategyTagRequest(groupId);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkGetCropTagListRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkGetCropTagListRequest.cs
new file mode 100644
index 000000000..051a09701
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkGetCropTagListRequest.cs
@@ -0,0 +1,33 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request;
+///
+/// 获取企业标签库请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetCropTagListRequest : WeChatWorkRequest
+{
+ ///
+ /// 要查询的标签id
+ ///
+ [CanBeNull]
+ [JsonProperty("tag_id")]
+ [JsonPropertyName("tag_id")]
+ public string[]? TagId { get; }
+ ///
+ /// 要查询的标签组id,返回该标签组以及其下的所有标签信息
+ ///
+ [CanBeNull]
+ [JsonProperty("group_id")]
+ [JsonPropertyName("group_id")]
+ public string[]? GroupId { get; }
+ public WeChatWorkGetCropTagListRequest(string[]? tagId = null, string[]? groupId = null)
+ {
+ TagId = tagId;
+ GroupId = groupId;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkGetStrategyTagListRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkGetStrategyTagListRequest.cs
new file mode 100644
index 000000000..e17cc35d0
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkGetStrategyTagListRequest.cs
@@ -0,0 +1,41 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request;
+public class WeChatWorkGetStrategyTagListRequest : WeChatWorkRequest
+{
+ ///
+ /// 规则组id
+ ///
+ [NotNull]
+ [JsonProperty("strategy_id")]
+ [JsonPropertyName("strategy_id")]
+ public int StrategyId { get; }
+ ///
+ /// 要查询的标签id
+ ///
+ [CanBeNull]
+ [JsonProperty("tag_id")]
+ [JsonPropertyName("tag_id")]
+ public string[]? TagId { get; }
+ ///
+ /// 要查询的标签组id,返回该标签组以及其下的所有标签信息
+ ///
+ [CanBeNull]
+ [JsonProperty("group_id")]
+ [JsonPropertyName("group_id")]
+ public string[]? GroupId { get; }
+ public WeChatWorkGetStrategyTagListRequest(
+ int strategyId,
+ string[]? tagId = null,
+ string[]? groupId = null)
+ {
+ Check.NotDefaultOrNull(strategyId, nameof(strategyId));
+
+ StrategyId = strategyId;
+ TagId = tagId;
+ GroupId = groupId;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkMarkCropTagRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkMarkCropTagRequest.cs
new file mode 100644
index 000000000..fc90029c3
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkMarkCropTagRequest.cs
@@ -0,0 +1,65 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request;
+///
+/// 编辑客户企业标签请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkMarkCropTagRequest : WeChatWorkRequest
+{
+ ///
+ /// 添加外部联系人的userid
+ ///
+ [NotNull]
+ [JsonProperty("userid")]
+ [JsonPropertyName("userid")]
+ public string UserId { get; }
+ ///
+ /// 外部联系人userid
+ ///
+ [NotNull]
+ [JsonProperty("external_userid")]
+ [JsonPropertyName("external_userid")]
+ public string ExternalUserId { get; }
+ ///
+ /// 要标记的标签列表
+ ///
+ [NotNull]
+ [JsonProperty("add_tag")]
+ [JsonPropertyName("add_tag")]
+ public List CreateTag { get; }
+ ///
+ /// 要移除的标签列表
+ ///
+ [NotNull]
+ [JsonProperty("remove_tag")]
+ [JsonPropertyName("remove_tag")]
+ public List RemoveTag { get; }
+ public WeChatWorkMarkCropTagRequest(string userId, string externalUserId)
+ {
+ Check.NotNullOrWhiteSpace(userId, nameof(userId));
+ Check.NotNullOrWhiteSpace(externalUserId, nameof(externalUserId));
+
+ UserId = userId;
+ ExternalUserId = externalUserId;
+
+ CreateTag = new List();
+ RemoveTag = new List();
+ }
+
+ protected override void Validate()
+ {
+ if (CreateTag.IsNullOrEmpty() &&
+ RemoveTag.IsNullOrEmpty())
+ {
+ throw new ArgumentException("CreateTag and RemoveTag cannot be empty simultaneously!");
+ }
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkUpdateCropTagRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkUpdateCropTagRequest.cs
new file mode 100644
index 000000000..b79ec152c
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkUpdateCropTagRequest.cs
@@ -0,0 +1,52 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request;
+///
+/// 编辑企业客户标签请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkUpdateCropTagRequest : WeChatWorkRequest
+{
+ ///
+ /// 标签或标签组的id
+ ///
+ [NotNull]
+ [JsonProperty("id")]
+ [JsonPropertyName("id")]
+ public string Id { get; }
+ ///
+ /// 新的标签或标签组名称,最长为30个字符
+ ///
+ [CanBeNull]
+ [JsonProperty("name")]
+ [JsonPropertyName("name")]
+ public string? Name { get; }
+ ///
+ /// 标签/标签组的次序值。order值大的排序靠前。有效的值范围是[0, 2^32)
+ ///
+ [CanBeNull]
+ [JsonProperty("order")]
+ [JsonPropertyName("order")]
+ public int? Order { get; set; }
+ ///
+ /// 授权方安装的应用agentid。仅旧的第三方多应用套件需要填此参数
+ ///
+ [CanBeNull]
+ [JsonProperty("agentid")]
+ [JsonPropertyName("agentid")]
+ public string? AgentId { get; set; }
+
+ public WeChatWorkUpdateCropTagRequest(string id, string? name = null)
+ {
+ Check.NotNullOrWhiteSpace(id, nameof(id));
+ Check.Length(name, nameof(name), 30);
+
+ Id = id;
+ Name = name;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkUpdateStrategyTagRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkUpdateStrategyTagRequest.cs
new file mode 100644
index 000000000..b8f1f1a84
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Request/WeChatWorkUpdateStrategyTagRequest.cs
@@ -0,0 +1,39 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request;
+public class WeChatWorkUpdateStrategyTagRequest : WeChatWorkRequest
+{
+ ///
+ /// 标签或标签组的id
+ ///
+ [NotNull]
+ [JsonProperty("id")]
+ [JsonPropertyName("id")]
+ public string Id { get; }
+ ///
+ /// 新的标签或标签组名称,最长为30个字符
+ ///
+ [CanBeNull]
+ [JsonProperty("name")]
+ [JsonPropertyName("name")]
+ public string? Name { get; }
+ ///
+ /// 标签/标签组的次序值。order值大的排序靠前。有效的值范围是[0, 2^32)
+ ///
+ [CanBeNull]
+ [JsonProperty("order")]
+ [JsonPropertyName("order")]
+ public int? Order { get; set; }
+
+ public WeChatWorkUpdateStrategyTagRequest(string id, string? name = null)
+ {
+ Check.NotNullOrWhiteSpace(id, nameof(id));
+ Check.Length(name, nameof(name), 30);
+
+ Id = id;
+ Name = name;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkCreateCropTagResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkCreateCropTagResponse.cs
new file mode 100644
index 000000000..d10cb037d
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkCreateCropTagResponse.cs
@@ -0,0 +1,22 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Response;
+///
+/// 添加企业客户标签响应参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkCreateCropTagResponse : WeChatWorkResponse
+{
+ ///
+ /// 标签组
+ ///
+ [NotNull]
+ [JsonProperty("tag_group")]
+ [JsonPropertyName("tag_group")]
+ public CropTagGroup TagGroup { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkCreateStrategyTagResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkCreateStrategyTagResponse.cs
new file mode 100644
index 000000000..6f40217d5
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkCreateStrategyTagResponse.cs
@@ -0,0 +1,22 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Response;
+///
+/// 为指定规则组创建企业客户标签
+///
+///
+/// 详情见: https://developer.work.weixin.qq.com/document/path/94882#%E4%B8%BA%E6%8C%87%E5%AE%9A%E8%A7%84%E5%88%99%E7%BB%84%E5%88%9B%E5%BB%BA%E4%BC%81%E4%B8%9A%E5%AE%A2%E6%88%B7%E6%A0%87%E7%AD%BE
+///
+public class WeChatWorkCreateStrategyTagResponse : WeChatWorkResponse
+{
+ ///
+ /// 标签组
+ ///
+ [NotNull]
+ [JsonProperty("tag_group")]
+ [JsonPropertyName("tag_group")]
+ public StrategyTagGroup TagGroup { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkGetCropTagListResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkGetCropTagListResponse.cs
new file mode 100644
index 000000000..b71bcb1a1
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkGetCropTagListResponse.cs
@@ -0,0 +1,22 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Response;
+///
+/// 获取企业标签库响应参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetCropTagListResponse : WeChatWorkResponse
+{
+ ///
+ /// 标签组列表
+ ///
+ [NotNull]
+ [JsonProperty("tag_group")]
+ [JsonPropertyName("tag_group")]
+ public StrategyTagGroup[] TagGroup { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkGetStrategyTagListResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkGetStrategyTagListResponse.cs
new file mode 100644
index 000000000..1a77eaa69
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/Response/WeChatWorkGetStrategyTagListResponse.cs
@@ -0,0 +1,22 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Response;
+///
+/// 获取指定规则组下的企业客户标签响应参数
+///
+///
+/// 详情见: https://developer.work.weixin.qq.com/document/path/94882#%E8%8E%B7%E5%8F%96%E6%8C%87%E5%AE%9A%E8%A7%84%E5%88%99%E7%BB%84%E4%B8%8B%E7%9A%84%E4%BC%81%E4%B8%9A%E5%AE%A2%E6%88%B7%E6%A0%87%E7%AD%BE
+///
+public class WeChatWorkGetStrategyTagListResponse : WeChatWorkResponse
+{
+ ///
+ /// 标签组列表
+ ///
+ [NotNull]
+ [JsonProperty("tag_group")]
+ [JsonPropertyName("tag_group")]
+ public StrategyTagGroup[] TagGroup { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/WeChatWorkCropTagProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/WeChatWorkCropTagProvider.cs
new file mode 100644
index 000000000..b73400b14
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/WeChatWorkCropTagProvider.cs
@@ -0,0 +1,108 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Features;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Response;
+using LINGYUN.Abp.WeChat.Work.Token;
+using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Features;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags;
+
+[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)]
+public class WeChatWorkCropTagProvider : IWeChatWorkCropTagProvider, ISingletonDependency
+{
+ protected IHttpClientFactory HttpClientFactory { get; }
+ protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; }
+
+ public WeChatWorkCropTagProvider(
+ IHttpClientFactory httpClientFactory,
+ IWeChatWorkTokenProvider weChatWorkTokenProvider)
+ {
+ HttpClientFactory = httpClientFactory;
+ WeChatWorkTokenProvider = weChatWorkTokenProvider;
+ }
+
+ public async virtual Task GetCropTagListAsync(
+ WeChatWorkGetCropTagListRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.GetCropTagListAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task CreateCropTagAsync(
+ WeChatWorkCreateCropTagRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.CreateCropTagAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task UpdateCropTagAsync(
+ WeChatWorkUpdateCropTagRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.UpdateCropTagAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task DeleteCropTagAsync(
+ WeChatWorkDeleteCropTagRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.DeleteCropTagAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task MarkCropTagAsync(
+ WeChatWorkMarkCropTagRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.MarkCropTagAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/WeChatWorkStrategyTagProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/WeChatWorkStrategyTagProvider.cs
new file mode 100644
index 000000000..49fe5189e
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/WeChatWorkStrategyTagProvider.cs
@@ -0,0 +1,92 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Features;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Response;
+using LINGYUN.Abp.WeChat.Work.Token;
+using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Features;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags;
+
+[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)]
+public class WeChatWorkStrategyTagProvider : IWeChatWorkStrategyTagProvider, ISingletonDependency
+{
+ protected IHttpClientFactory HttpClientFactory { get; }
+ protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; }
+
+ public WeChatWorkStrategyTagProvider(
+ IHttpClientFactory httpClientFactory,
+ IWeChatWorkTokenProvider weChatWorkTokenProvider)
+ {
+ HttpClientFactory = httpClientFactory;
+ WeChatWorkTokenProvider = weChatWorkTokenProvider;
+ }
+
+ public async virtual Task GetStrategyTagListAsync(
+ WeChatWorkGetStrategyTagListRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.GetStrategyTagListAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task CreateStrategyTagAsync(
+ WeChatWorkCreateStrategyTagRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.CreateStrategyTagAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task UpdateStrategyTagAsync(
+ WeChatWorkUpdateStrategyTagRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.UpdateStrategyTagAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task DeleteStrategyTagAsync(
+ WeChatWorkDeleteStrategyTagRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.DeleteStrategyTagAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/IWeChatWorkEmployExtendProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/IWeChatWorkEmployExtendProvider.cs
new file mode 100644
index 000000000..9622d4e54
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/IWeChatWorkEmployExtendProvider.cs
@@ -0,0 +1,48 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers;
+///
+/// 在职继承接口
+///
+public interface IWeChatWorkEmployExtendProvider
+{
+ ///
+ /// 分配在职成员的客户
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task TransferCustomerAsync(
+ WeChatWorkTransferCustomerRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 查询客户接替状态
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task GetTransferResultAsync(
+ WeChatWorkGetTransferResultRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 分配在职成员的客户群
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task GroupChatOnjobTransferAsync(
+ WeChatWorkGroupChatOnjobTransferRequest request,
+ CancellationToken cancellationToken = default);
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/IWeChatWorkResignExtendProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/IWeChatWorkResignExtendProvider.cs
new file mode 100644
index 000000000..3a837d768
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/IWeChatWorkResignExtendProvider.cs
@@ -0,0 +1,60 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers;
+///
+/// 离职继承接口
+///
+public interface IWeChatWorkResignExtendProvider
+{
+ ///
+ /// 获取待分配的离职成员列表
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task GetUnassignedListAsync(
+ WeChatWorkGetUnassignedListRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 分配离职成员的客户
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task ResignedTransferCustomerAsync(
+ WeChatWorkResignedTransferCustomerRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 查询客户接替状态
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task GetResignedTransferResultAsync(
+ WeChatWorkGetResignedTransferResultRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 分配离职成员的客户群
+ ///
+ ///
+ /// 详情见:
+ ///
+ /// 请求参数
+ ///
+ ///
+ Task GroupChatTransferAsync(
+ WeChatWorkGroupChatTransferRequest request,
+ CancellationToken cancellationToken = default);
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/GroupChatTransferFailed.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/GroupChatTransferFailed.cs
new file mode 100644
index 000000000..a39fb70d4
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/GroupChatTransferFailed.cs
@@ -0,0 +1,32 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models;
+///
+/// 没能成功继承的群详情
+///
+public class GroupChatTransferFailed
+{
+ ///
+ /// 没能成功继承的群ID
+ ///
+ [NotNull]
+ [JsonProperty("chat_id")]
+ [JsonPropertyName("chat_id")]
+ public string ChatId { get; set; }
+ ///
+ /// 没能成功继承的群,错误码
+ ///
+ [NotNull]
+ [JsonProperty("errcode")]
+ [JsonPropertyName("errcode")]
+ public int ErrCode { get; set; }
+ ///
+ /// 没能成功继承的群,错误描述
+ ///
+ [NotNull]
+ [JsonProperty("errmsg")]
+ [JsonPropertyName("errmsg")]
+ public string ErrMsg { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/TransferCustomer.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/TransferCustomer.cs
new file mode 100644
index 000000000..3402836b0
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/TransferCustomer.cs
@@ -0,0 +1,22 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models;
+public class TransferCustomer
+{
+ ///
+ /// 客户的外部联系人userid
+ ///
+ [NotNull]
+ [JsonProperty("external_userid")]
+ [JsonPropertyName("external_userid")]
+ public string ExternalUserid { get; set; }
+ ///
+ /// 对此客户进行分配的结果, 具体可参考全局错误码, 0表示成功发起接替,待24小时后自动接替,并不代表最终接替成功
+ ///
+ [NotNull]
+ [JsonProperty("errcode")]
+ [JsonPropertyName("errcode")]
+ public int ErrCode { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/TransferCustomerResult.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/TransferCustomerResult.cs
new file mode 100644
index 000000000..c76f89df7
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/TransferCustomerResult.cs
@@ -0,0 +1,29 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models;
+public class TransferCustomerResult
+{
+ ///
+ /// 客户的外部联系人userid
+ ///
+ [NotNull]
+ [JsonProperty("external_userid")]
+ [JsonPropertyName("external_userid")]
+ public string ExternalUserid { get; set; }
+ ///
+ /// 接替状态
+ ///
+ [NotNull]
+ [JsonProperty("status")]
+ [JsonPropertyName("status")]
+ public TransferStatus Status { get; set; }
+ ///
+ /// 接替客户的时间,如果是等待接替状态,则为未来的自动接替时间
+ ///
+ [NotNull]
+ [JsonProperty("takeover_time")]
+ [JsonPropertyName("takeover_time")]
+ public long TakeoverTime { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/TransferStatus.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/TransferStatus.cs
new file mode 100644
index 000000000..acab61ff8
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/TransferStatus.cs
@@ -0,0 +1,30 @@
+using System.ComponentModel;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models;
+///
+/// 接替状态
+///
+[Description("接替状态")]
+public enum TransferStatus
+{
+ ///
+ /// 接替完毕
+ ///
+ [Description("接替完毕")]
+ Completed = 1,
+ ///
+ /// 等待接替
+ ///
+ [Description("等待接替")]
+ Pending = 2,
+ ///
+ /// 客户拒绝
+ ///
+ [Description("客户拒绝")]
+ CustomerReject = 3,
+ ///
+ /// 接替成员客户达到上限
+ ///
+ [Description("接替成员客户达到上限")]
+ ReachesLimit = 4
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/UnassignedCustomerInfo.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/UnassignedCustomerInfo.cs
new file mode 100644
index 000000000..f4767e63e
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/UnassignedCustomerInfo.cs
@@ -0,0 +1,29 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models;
+public class UnassignedCustomerInfo
+{
+ ///
+ /// 离职成员的userid
+ ///
+ [NotNull]
+ [JsonProperty("handover_userid")]
+ [JsonPropertyName("handover_userid")]
+ public string HandoverUserid { get; set; }
+ ///
+ /// 外部联系人userid
+ ///
+ [NotNull]
+ [JsonProperty("external_userid")]
+ [JsonPropertyName("external_userid")]
+ public string ExternalUserid { get; set; }
+ ///
+ /// 成员离职时间
+ ///
+ [NotNull]
+ [JsonProperty("dimission_time")]
+ [JsonPropertyName("dimission_time")]
+ public long DimissionTime { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/UnassignedTransferCustomer.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/UnassignedTransferCustomer.cs
new file mode 100644
index 000000000..f8942edf9
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/UnassignedTransferCustomer.cs
@@ -0,0 +1,25 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models;
+///
+/// 分配离职成员客户详情
+///
+public class UnassignedTransferCustomer
+{
+ ///
+ /// 客户的外部联系人userid
+ ///
+ [NotNull]
+ [JsonProperty("external_userid")]
+ [JsonPropertyName("external_userid")]
+ public string ExternalUserid { get; set; }
+ ///
+ /// 对此客户进行分配的结果,0表示开始分配流程,待24小时后自动接替,并不代表最终分配成功
+ ///
+ [NotNull]
+ [JsonProperty("errcode")]
+ [JsonPropertyName("errcode")]
+ public int ErrCode { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/UnassignedTransferCustomerResult.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/UnassignedTransferCustomerResult.cs
new file mode 100644
index 000000000..8379d2f65
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Models/UnassignedTransferCustomerResult.cs
@@ -0,0 +1,32 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models;
+///
+/// 分配离职成员客户接替状态结果
+///
+public class UnassignedTransferCustomerResult
+{
+ ///
+ /// 转接客户的外部联系人userid
+ ///
+ [NotNull]
+ [JsonProperty("external_userid")]
+ [JsonPropertyName("external_userid")]
+ public string ExternalUserid { get; set; }
+ ///
+ /// 接替状态
+ ///
+ [NotNull]
+ [JsonProperty("status")]
+ [JsonPropertyName("status")]
+ public TransferStatus Status { get; set; }
+ ///
+ /// 接替客户的时间,如果是等待接替状态,则为未来的自动接替时间
+ ///
+ [NotNull]
+ [JsonProperty("takeover_time")]
+ [JsonPropertyName("takeover_time")]
+ public long TakeoverTime { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGetResignedTransferResultRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGetResignedTransferResultRequest.cs
new file mode 100644
index 000000000..64960ea47
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGetResignedTransferResultRequest.cs
@@ -0,0 +1,48 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request;
+///
+/// 查询离职成员的客户接替状态请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetResignedTransferResultRequest : WeChatWorkRequest
+{
+ ///
+ /// 原添加成员的userid
+ ///
+ [NotNull]
+ [JsonProperty("handover_userid")]
+ [JsonPropertyName("handover_userid")]
+ public string HandOverUserId { get; }
+ ///
+ /// 接替成员的userid
+ ///
+ [NotNull]
+ [JsonProperty("takeover_userid")]
+ [JsonPropertyName("takeover_userid")]
+ public string TakeOverUserId { get; }
+ ///
+ /// 分页查询的cursor,每个分页返回的数据不会超过1000条;不填或为空表示获取第一个分页
+ ///
+ [CanBeNull]
+ [JsonProperty("cursor")]
+ [JsonPropertyName("cursor")]
+ public string? Cursor { get; }
+ public WeChatWorkGetResignedTransferResultRequest(
+ string handOverUserId,
+ string takeOverUserId,
+ string? cursor = null)
+ {
+ Check.NotNullOrWhiteSpace(handOverUserId, nameof(handOverUserId));
+ Check.NotNullOrWhiteSpace(takeOverUserId, nameof(takeOverUserId));
+
+ HandOverUserId = handOverUserId;
+ TakeOverUserId = takeOverUserId;
+ Cursor = cursor;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGetTransferResultRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGetTransferResultRequest.cs
new file mode 100644
index 000000000..2ea1f6298
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGetTransferResultRequest.cs
@@ -0,0 +1,48 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request;
+///
+/// 查询客户接替状态请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetTransferResultRequest : WeChatWorkRequest
+{
+ ///
+ /// 原跟进成员的userid
+ ///
+ [NotNull]
+ [JsonProperty("handover_userid")]
+ [JsonPropertyName("handover_userid")]
+ public string HandOverUserId { get; }
+ ///
+ /// 接替成员的userid
+ ///
+ [NotNull]
+ [JsonProperty("takeover_userid")]
+ [JsonPropertyName("takeover_userid")]
+ public string TakeOverUserId { get; }
+ ///
+ /// 分页查询的cursor,每个分页返回的数据不会超过1000条;不填或为空表示获取第一个分页
+ ///
+ [CanBeNull]
+ [JsonProperty("cursor")]
+ [JsonPropertyName("cursor")]
+ public string? Cursor { get; }
+ public WeChatWorkGetTransferResultRequest(
+ string handOverUserId,
+ string takeOverUserId,
+ string? cursor = null)
+ {
+ Check.NotNullOrWhiteSpace(handOverUserId, nameof(handOverUserId));
+ Check.NotNullOrWhiteSpace(takeOverUserId, nameof(takeOverUserId));
+
+ HandOverUserId = handOverUserId;
+ TakeOverUserId = takeOverUserId;
+ Cursor = cursor;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGetUnassignedListRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGetUnassignedListRequest.cs
new file mode 100644
index 000000000..75c102ad9
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGetUnassignedListRequest.cs
@@ -0,0 +1,27 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request;
+public class WeChatWorkGetUnassignedListRequest : WeChatWorkRequest
+{
+ ///
+ /// 分页查询游标,字符串类型,适用于数据量较大的情况,如果使用该参数则无需填写page_id,该参数由上一次调用返回
+ ///
+ [CanBeNull]
+ [JsonProperty("cursor")]
+ [JsonPropertyName("cursor")]
+ public string? Cursor { get; set; }
+ ///
+ /// 每次返回的最大记录数,默认为1000,最大值为1000
+ ///
+ [CanBeNull]
+ [JsonProperty("page_size")]
+ [JsonPropertyName("page_size")]
+ public int? PageSize { get; set; }
+ public WeChatWorkGetUnassignedListRequest(string? cursor = null, int? pageSize = 1000)
+ {
+ Cursor = cursor;
+ PageSize = pageSize;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGroupChatOnjobTransferRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGroupChatOnjobTransferRequest.cs
new file mode 100644
index 000000000..dda7a5ed2
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGroupChatOnjobTransferRequest.cs
@@ -0,0 +1,43 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request;
+///
+/// 分配在职成员的客户群请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGroupChatOnjobTransferRequest : WeChatWorkRequest
+{
+ ///
+ /// 新群主ID
+ ///
+ [NotNull]
+ [JsonProperty("new_owner")]
+ [JsonPropertyName("new_owner")]
+ public string NewOwner { get; }
+ ///
+ /// 需要转群主的客户群ID列表。取值范围: 1 ~ 100
+ ///
+ [NotNull]
+ [JsonProperty("chat_id_list")]
+ [JsonPropertyName("chat_id_list")]
+ public string[] ChatIdList { get; }
+ public WeChatWorkGroupChatOnjobTransferRequest(string newOwner, string[] chatIdList)
+ {
+ Check.NotNullOrWhiteSpace(newOwner, nameof(newOwner));
+ Check.NotNullOrEmpty(chatIdList, nameof(chatIdList));
+
+ if (chatIdList.Length < 1 || chatIdList.Length > 100)
+ {
+ throw new ArgumentException("The list of customer group ids that need to be transferred to the group owner must have more than 1 item or less than 100 items");
+ }
+
+ NewOwner = newOwner;
+ ChatIdList = chatIdList;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGroupChatTransferRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGroupChatTransferRequest.cs
new file mode 100644
index 000000000..364427048
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkGroupChatTransferRequest.cs
@@ -0,0 +1,43 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request;
+///
+/// 分配离职成员的客户群请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGroupChatTransferRequest : WeChatWorkRequest
+{
+ ///
+ /// 新群主ID
+ ///
+ [NotNull]
+ [JsonProperty("new_owner")]
+ [JsonPropertyName("new_owner")]
+ public string NewOwner { get; }
+ ///
+ /// 需要转群主的客户群ID列表。取值范围: 1 ~ 100
+ ///
+ [NotNull]
+ [JsonProperty("chat_id_list")]
+ [JsonPropertyName("chat_id_list")]
+ public string[] ChatIdList { get; }
+ public WeChatWorkGroupChatTransferRequest(string newOwner, string[] chatIdList)
+ {
+ Check.NotNullOrWhiteSpace(newOwner, nameof(newOwner));
+ Check.NotNullOrEmpty(chatIdList, nameof(chatIdList));
+
+ if (chatIdList.Length < 1 || chatIdList.Length > 100)
+ {
+ throw new ArgumentException("The list of customer group ids that need to be transferred to the group owner must have more than 1 item or less than 100 items");
+ }
+
+ NewOwner = newOwner;
+ ChatIdList = chatIdList;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkResignedTransferCustomerRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkResignedTransferCustomerRequest.cs
new file mode 100644
index 000000000..60ed2712d
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkResignedTransferCustomerRequest.cs
@@ -0,0 +1,52 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request;
+///
+/// 分配离职成员的客户请求参数
+///
+public class WeChatWorkResignedTransferCustomerRequest : WeChatWorkRequest
+{
+ ///
+ /// 原跟进成员的userid
+ ///
+ [NotNull]
+ [JsonProperty("handover_userid")]
+ [JsonPropertyName("handover_userid")]
+ public string HandoverUserId { get; }
+ ///
+ /// 接替成员的userid
+ ///
+ [NotNull]
+ [JsonProperty("takeover_userid")]
+ [JsonPropertyName("takeover_userid")]
+ public string TakeoverUserId { get; }
+ ///
+ /// 客户的external_userid列表,每次最多分配100个客户
+ ///
+ [NotNull]
+ [JsonProperty("external_userid")]
+ [JsonPropertyName("external_userid")]
+ public string[] ExternalUserId { get; }
+ public WeChatWorkResignedTransferCustomerRequest(string handoverUserId, string takeoverUserId, string[] externalUserId)
+ {
+ Check.NotNullOrWhiteSpace(handoverUserId, nameof(handoverUserId));
+ Check.NotNullOrWhiteSpace(takeoverUserId, nameof(takeoverUserId));
+ Check.NotNullOrEmpty(externalUserId, nameof(externalUserId));
+
+ HandoverUserId = handoverUserId;
+ TakeoverUserId = takeoverUserId;
+ ExternalUserId = externalUserId;
+ }
+
+ protected override void Validate()
+ {
+ if (ExternalUserId.Length > 100)
+ {
+ throw new ArgumentException("Transfer a maximum of 100 customers at a time!");
+ }
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkTransferCustomerRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkTransferCustomerRequest.cs
new file mode 100644
index 000000000..61fee48e4
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Request/WeChatWorkTransferCustomerRequest.cs
@@ -0,0 +1,60 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request;
+///
+/// 分配在职成员的客户请求参数
+///
+///
+/// 详情见:
+///
+public class WeChatWorkTransferCustomerRequest : WeChatWorkRequest
+{
+ ///
+ /// 原跟进成员的userid
+ ///
+ [NotNull]
+ [JsonProperty("handover_userid")]
+ [JsonPropertyName("handover_userid")]
+ public string HandoverUserId { get; }
+ ///
+ /// 接替成员的userid
+ ///
+ [NotNull]
+ [JsonProperty("takeover_userid")]
+ [JsonPropertyName("takeover_userid")]
+ public string TakeoverUserId { get; }
+ ///
+ /// 客户的external_userid列表,每次最多分配100个客户
+ ///
+ [NotNull]
+ [JsonProperty("external_userid")]
+ [JsonPropertyName("external_userid")]
+ public string[] ExternalUserId { get; }
+ ///
+ /// 转移成功后发给客户的消息,最多200个字符,不填则使用默认文案
+ ///
+ [CanBeNull]
+ [JsonProperty("transfer_success_msg")]
+ [JsonPropertyName("transfer_success_msg")]
+ public string? TransferSuccessMsg { get; set; }
+ public WeChatWorkTransferCustomerRequest(string handoverUserId, string takeoverUserId, string[] externalUserId, string? transferSuccessMsg = null)
+ {
+ Check.NotNullOrWhiteSpace(handoverUserId, nameof(handoverUserId));
+ Check.NotNullOrWhiteSpace(takeoverUserId, nameof(takeoverUserId));
+ Check.NotNullOrEmpty(externalUserId, nameof(externalUserId));
+ Check.Length(transferSuccessMsg, nameof(transferSuccessMsg), 200);
+
+ HandoverUserId = handoverUserId;
+ TakeoverUserId = takeoverUserId;
+ ExternalUserId = externalUserId;
+ TransferSuccessMsg = transferSuccessMsg;
+ }
+
+ protected override void Validate()
+ {
+ Check.Length(TransferSuccessMsg, nameof(TransferSuccessMsg), 200);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGetResignedTransferResultResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGetResignedTransferResultResponse.cs
new file mode 100644
index 000000000..256950e43
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGetResignedTransferResultResponse.cs
@@ -0,0 +1,29 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response;
+///
+/// 查询离职人员客户接替状态响应结果
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetResignedTransferResultResponse : WeChatWorkResponse
+{
+ ///
+ /// 转接客户
+ ///
+ [NotNull]
+ [JsonProperty("customer")]
+ [JsonPropertyName("customer")]
+ public UnassignedTransferCustomerResult[] Customer { get; set; }
+ ///
+ /// 下个分页的起始cursor
+ ///
+ [CanBeNull]
+ [JsonProperty("next_cursor")]
+ [JsonPropertyName("next_cursor")]
+ public string? NextCursor { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGetTransferResultResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGetTransferResultResponse.cs
new file mode 100644
index 000000000..7aed70bf7
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGetTransferResultResponse.cs
@@ -0,0 +1,29 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response;
+///
+/// 查询客户接替状态响应结果
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetTransferResultResponse : WeChatWorkResponse
+{
+ ///
+ /// 分配客户
+ ///
+ [NotNull]
+ [JsonProperty("customer")]
+ [JsonPropertyName("customer")]
+ public TransferCustomerResult[] Customer { get; set; }
+ ///
+ /// 下个分页的起始cursor
+ ///
+ [CanBeNull]
+ [JsonProperty("next_cursor")]
+ [JsonPropertyName("next_cursor")]
+ public string? NextCursor { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGetUnassignedListResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGetUnassignedListResponse.cs
new file mode 100644
index 000000000..66e42c82b
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGetUnassignedListResponse.cs
@@ -0,0 +1,36 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response;
+///
+/// 获取待分配的离职成员列表响应结果
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGetUnassignedListResponse : WeChatWorkResponse
+{
+ ///
+ /// 待分配离职成员的客户
+ ///
+ [NotNull]
+ [JsonProperty("info")]
+ [JsonPropertyName("info")]
+ public UnassignedCustomerInfo[] Info { get; set; }
+ ///
+ /// 是否是最后一条记录
+ ///
+ [NotNull]
+ [JsonProperty("is_last")]
+ [JsonPropertyName("is_last")]
+ public bool IsLast { get; set; }
+ ///
+ /// 分页查询游标,已经查完则返回空(""),使用page_id作为查询参数时不返回
+ ///
+ [CanBeNull]
+ [JsonProperty("next_cursor")]
+ [JsonPropertyName("next_cursor")]
+ public string? NextCursor { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGroupChatOnjobTransferResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGroupChatOnjobTransferResponse.cs
new file mode 100644
index 000000000..7f05c077d
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGroupChatOnjobTransferResponse.cs
@@ -0,0 +1,22 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response;
+///
+/// 分配在职成员的客户群响应结果
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGroupChatOnjobTransferResponse : WeChatWorkResponse
+{
+ ///
+ /// 没能成功继承的群
+ ///
+ [NotNull]
+ [JsonProperty("failed_chat_list")]
+ [JsonPropertyName("failed_chat_list")]
+ public GroupChatTransferFailed[] FailedChatList { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGroupChatTransferResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGroupChatTransferResponse.cs
new file mode 100644
index 000000000..ad0112bf4
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkGroupChatTransferResponse.cs
@@ -0,0 +1,22 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response;
+///
+/// 分配离职成员的客户群响应结果
+///
+///
+/// 详情见:
+///
+public class WeChatWorkGroupChatTransferResponse : WeChatWorkResponse
+{
+ ///
+ /// 没能成功继承的群
+ ///
+ [NotNull]
+ [JsonProperty("failed_chat_list")]
+ [JsonPropertyName("failed_chat_list")]
+ public GroupChatTransferFailed[] FailedChatList { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkResignedTransferCustomerResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkResignedTransferCustomerResponse.cs
new file mode 100644
index 000000000..255fdab7d
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkResignedTransferCustomerResponse.cs
@@ -0,0 +1,22 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response;
+///
+/// 分配离职成员的客户响应结果
+///
+///
+/// 详情见:
+///
+public class WeChatWorkResignedTransferCustomerResponse : WeChatWorkResponse
+{
+ ///
+ /// 分配离职成员客户结果
+ ///
+ [NotNull]
+ [JsonProperty("customer")]
+ [JsonPropertyName("customer")]
+ public UnassignedTransferCustomer[] Customer { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkTransferCustomerResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkTransferCustomerResponse.cs
new file mode 100644
index 000000000..638d3b4ef
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/Response/WeChatWorkTransferCustomerResponse.cs
@@ -0,0 +1,22 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Models;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response;
+///
+/// 分配在职成员的客户响应结果
+///
+///
+/// 详情见:
+///
+public class WeChatWorkTransferCustomerResponse : WeChatWorkResponse
+{
+ ///
+ /// 分配客户
+ ///
+ [NotNull]
+ [JsonProperty("customer")]
+ [JsonPropertyName("customer")]
+ public TransferCustomer[] Customer { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/WeChatWorkEmployExtendProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/WeChatWorkEmployExtendProvider.cs
new file mode 100644
index 000000000..63baaeeb1
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/WeChatWorkEmployExtendProvider.cs
@@ -0,0 +1,76 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Features;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response;
+using LINGYUN.Abp.WeChat.Work.Token;
+using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Features;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers;
+
+[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)]
+public class WeChatWorkEmployExtendProvider : IWeChatWorkEmployExtendProvider, ISingletonDependency
+{
+ protected IHttpClientFactory HttpClientFactory { get; }
+ protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; }
+
+ public WeChatWorkEmployExtendProvider(
+ IHttpClientFactory httpClientFactory,
+ IWeChatWorkTokenProvider weChatWorkTokenProvider)
+ {
+ HttpClientFactory = httpClientFactory;
+ WeChatWorkTokenProvider = weChatWorkTokenProvider;
+ }
+
+ public async virtual Task TransferCustomerAsync(
+ WeChatWorkTransferCustomerRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.AssignCustomerAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task GetTransferResultAsync(
+ WeChatWorkGetTransferResultRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.GetTransferResultAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task GroupChatOnjobTransferAsync(
+ WeChatWorkGroupChatOnjobTransferRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.OnjobTransferAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/WeChatWorkResignExtendProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/WeChatWorkResignExtendProvider.cs
new file mode 100644
index 000000000..3ff4fae55
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/LINGYUN/Abp/WeChat/Work/ExternalContact/Transfers/WeChatWorkResignExtendProvider.cs
@@ -0,0 +1,92 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Features;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Response;
+using LINGYUN.Abp.WeChat.Work.Token;
+using Microsoft.Extensions.DependencyInjection;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Features;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers;
+
+[RequiresFeature(WeChatWorkExternalContactFeatureNames.Enable)]
+public class WeChatWorkResignExtendProvider : IWeChatWorkResignExtendProvider, ISingletonDependency
+{
+ protected IHttpClientFactory HttpClientFactory { get; }
+ protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; }
+
+ public WeChatWorkResignExtendProvider(
+ IHttpClientFactory httpClientFactory,
+ IWeChatWorkTokenProvider weChatWorkTokenProvider)
+ {
+ HttpClientFactory = httpClientFactory;
+ WeChatWorkTokenProvider = weChatWorkTokenProvider;
+ }
+
+ public async virtual Task GetUnassignedListAsync(
+ WeChatWorkGetUnassignedListRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.GetUnassignedListAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task ResignedTransferCustomerAsync(
+ WeChatWorkResignedTransferCustomerRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.ResignedTransferCustomerAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task GetResignedTransferResultAsync(
+ WeChatWorkGetResignedTransferResultRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.GetResignedTransferResultAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+
+ public async virtual Task GroupChatTransferAsync(
+ WeChatWorkGroupChatTransferRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ Check.NotNull(request, nameof(request));
+
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateWeChatWorkApiClient();
+
+ using var response = await client.GroupChatTransferAsync(token.AccessToken, request, cancellationToken);
+
+ var wechatResponse = await response.DeserializeObjectAsync();
+ wechatResponse.ThrowIfNotSuccess();
+ return wechatResponse;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/Newtonsoft/Json/ExternalProfileNewtonsoftJsonConverter.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/Newtonsoft/Json/ExternalProfileNewtonsoftJsonConverter.cs
new file mode 100644
index 000000000..e3521bde5
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/Newtonsoft/Json/ExternalProfileNewtonsoftJsonConverter.cs
@@ -0,0 +1,70 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Models;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+
+namespace Newtonsoft.Json;
+internal class ExternalProfileNewtonsoftJsonConverter : JsonConverter
+{
+ public override bool CanWrite => true;
+
+ public override void WriteJson(JsonWriter writer, ExternalProfile? value, JsonSerializer serializer)
+ {
+ writer.WriteStartObject();
+
+ if (value?.ExternalCorpName != null)
+ {
+ writer.WritePropertyName("external_corp_name");
+ serializer.Serialize(writer, value.ExternalCorpName);
+ }
+
+ if (value?.WechatChannels != null)
+ {
+ writer.WritePropertyName("wechat_channels");
+ serializer.Serialize(writer, value.WechatChannels, value.WechatChannels.GetType());
+ }
+
+ if (value?.ExternalAttributes != null)
+ {
+ writer.WritePropertyName("external_attr");
+ serializer.Serialize(writer, value.ExternalAttributes, value.ExternalAttributes.GetType());
+ }
+
+ writer.WriteEndObject();
+ }
+
+ public override ExternalProfile? ReadJson(JsonReader reader, Type objectType, ExternalProfile? existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ var jObject = JObject.Load(reader);
+
+ var externalProfile = new ExternalProfile
+ {
+ WechatChannels = new List(),
+ ExternalAttributes = new List()
+ };
+
+ if (jObject.TryGetValue("external_corp_name", out var externalCorpNameToken))
+ {
+ externalProfile.ExternalCorpName = externalCorpNameToken.ToString();
+ }
+ if (jObject.TryGetValue("wechat_channels", out var wechatChannelsToken) && wechatChannelsToken.Type == JTokenType.Array)
+ {
+ externalProfile.WechatChannels = wechatChannelsToken.ToObject>(serializer)!;
+ }
+ if (jObject.TryGetValue("external_attr", out var externalAttrsToken) && externalAttrsToken.Type == JTokenType.Array)
+ {
+ foreach ( var externalAttrToken in externalAttrsToken)
+ {
+ var typeToken = externalAttrToken.SelectToken("type");
+ if (typeToken != null)
+ {
+ var type = typeToken.Value();
+ externalProfile.ExternalAttributes.Add(
+ ExternalAttributeDeserializeFactory.CreateExternalAttribute(type, externalAttrToken));
+ }
+ }
+ }
+
+ return externalProfile;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/README.md b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/README.md
new file mode 100644
index 000000000..1ff6cd2c2
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/README.md
@@ -0,0 +1,29 @@
+# LINGYUN.Abp.WeChat.Work.ExternalContact
+
+企业微信客户联系模块,提供企业微信应用开发的客户联系功能实现。
+
+## 功能特性
+
+
+## 模块引用
+
+```csharp
+[DependsOn(typeof(AbpWeChatWorkExternalContactModule))]
+public class YouProjectModule : AbpModule
+{
+ // other
+}
+```
+
+## 配置项
+
+
+## 消息处理
+
+
+## 事件处理
+
+
+## 更多文档
+
+* [企业微信客户联系文档](https://developer.work.weixin.qq.com/document/path/92109)
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Attachments.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Attachments.cs
new file mode 100644
index 000000000..43d71ccc7
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Attachments.cs
@@ -0,0 +1,33 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Attachments.Request;
+using LINGYUN.Abp.WeChat.Work.Utils;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Http;
+internal static partial class HttpClientWeChatWorkRequestExtensions
+{
+ public async static Task UploadAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkUploadAttachmentRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/media/upload_attachment");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+ urlBuilder.AppendFormat("&media_type={0}", request.MediaType.ToString().ToLowerInvariant());
+ urlBuilder.AppendFormat("&attachment_type={0}", request.AttachmentType);
+
+ var fileBytes = await request.Content.GetStream().GetAllBytesAsync();
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = WeChatWorkHttpContentBuildHelper.BuildUploadMediaContent("media", fileBytes, request.Content.FileName)
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Contacts.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Contacts.cs
new file mode 100644
index 000000000..fde7c64cf
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Contacts.cs
@@ -0,0 +1,28 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Request;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Http;
+internal static partial class HttpClientWeChatWorkRequestExtensions
+{
+ public async static Task GetExternalContactListAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkGetExternalContactListRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/contact_list");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson())
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Customers.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Customers.cs
new file mode 100644
index 000000000..2a3ce8111
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Customers.cs
@@ -0,0 +1,89 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Http;
+internal static partial class HttpClientWeChatWorkRequestExtensions
+{
+ public async static Task GetCustomerListAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ string userId,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/list");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+ urlBuilder.AppendFormat("&userid={0}", userId);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Get,
+ urlBuilder.ToString());
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task BulkGetCustomerAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkBulkGetCustomerRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/batch/get_by_user");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson())
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task GetCustomerAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ string externalUserid,
+ string? cursor = null,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/get");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+ urlBuilder.AppendFormat("&external_userid={0}", externalUserid);
+ if (!cursor.IsNullOrWhiteSpace())
+ {
+ urlBuilder.AppendFormat("&cursor={0}", cursor);
+ }
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Get,
+ urlBuilder.ToString());
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task UpdateCustomerRemarkAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkUpdateCustomerRemarkRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/remark");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson())
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Follows.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Follows.cs
new file mode 100644
index 000000000..868b0e865
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Follows.cs
@@ -0,0 +1,23 @@
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Http;
+internal static partial class HttpClientWeChatWorkRequestExtensions
+{
+ public async static Task GetFollowUserListAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/get_follow_user_list");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Get,
+ urlBuilder.ToString());
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.GroupChats.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.GroupChats.cs
new file mode 100644
index 000000000..c8a2d3e80
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.GroupChats.cs
@@ -0,0 +1,69 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Request;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Http;
+internal static partial class HttpClientWeChatWorkRequestExtensions
+{
+ public async static Task GetGroupChatListAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkGetGroupChatListRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/groupchat/list");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson())
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task GetGroupChatAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkGetGroupChatRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/groupchat/get");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson())
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task OpengIdToChatIdAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkOpengIdToChatIdRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/opengid_to_chatid");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson())
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Strategies.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Strategies.cs
new file mode 100644
index 000000000..833baac6b
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Strategies.cs
@@ -0,0 +1,128 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Http;
+internal static partial class HttpClientWeChatWorkRequestExtensions
+{
+ public async static Task GetCustomerStrategyListAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkGetCustomerStrategyListRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/customer_strategy/list");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task GetCustomerStrategyAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkGetCustomerStrategyRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/customer_strategy/get");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task GetCustomerStrategyRangeAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkGetCustomerStrategyRangeRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/customer_strategy/get_range");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task CreateCustomerStrategyAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkCreateCustomerStrategyRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/customer_strategy/create");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task UpdateCustomerStrategyAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkUpdateCustomerStrategyRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/customer_strategy/edit");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task DeleteCustomerStrategyAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkDeleteCustomerStrategyRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/customer_strategy/del");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Tags.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Tags.cs
new file mode 100644
index 000000000..8f6fb24b0
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Tags.cs
@@ -0,0 +1,188 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Http;
+internal static partial class HttpClientWeChatWorkRequestExtensions
+{
+ public async static Task GetCropTagListAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkGetCropTagListRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/get_corp_tag_list");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task CreateCropTagAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkCreateCropTagRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/add_corp_tag");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task UpdateCropTagAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkUpdateCropTagRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/edit_corp_tag");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task DeleteCropTagAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkDeleteCropTagRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/del_corp_tag");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task MarkCropTagAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkMarkCropTagRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/mark_tag");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task GetStrategyTagListAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkGetStrategyTagListRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/get_strategy_tag_list");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task CreateStrategyTagAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkCreateStrategyTagRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/add_strategy_tag");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task UpdateStrategyTagAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkUpdateStrategyTagRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/edit_strategy_tag");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task DeleteStrategyTagAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkDeleteStrategyTagRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/del_strategy_tag");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Transfers.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Transfers.cs
new file mode 100644
index 000000000..3b32435bb
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Transfers.cs
@@ -0,0 +1,148 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Transfers.Request;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Http;
+internal static partial class HttpClientWeChatWorkRequestExtensions
+{
+ public async static Task AssignCustomerAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkTransferCustomerRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/transfer_customer");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task GetTransferResultAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkGetTransferResultRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/transfer_result");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task OnjobTransferAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkGroupChatOnjobTransferRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/groupchat/onjob_transfer");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task GetUnassignedListAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkGetUnassignedListRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/get_unassigned_list");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task ResignedTransferCustomerAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkResignedTransferCustomerRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/resigned/transfer_customer");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task GetResignedTransferResultAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkGetResignedTransferResultRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/resigned/transfer_result");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+
+ public async static Task GroupChatTransferAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkGroupChatTransferRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/externalcontact/groupchat/transfer");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Post,
+ urlBuilder.ToString())
+ {
+ Content = new StringContent(request.SerializeToJson()),
+ };
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.cs
new file mode 100644
index 000000000..072b15046
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpClientWeChatWorkRequestExtensions.cs
@@ -0,0 +1,5 @@
+namespace System.Net.Http;
+internal static partial class HttpClientWeChatWorkRequestExtensions
+{
+
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpResponseDeserializeExtensions.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpResponseDeserializeExtensions.cs
new file mode 100644
index 000000000..f3abd415d
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Net/Http/HttpResponseDeserializeExtensions.cs
@@ -0,0 +1,19 @@
+using LINGYUN.Abp.WeChat.Work;
+using Newtonsoft.Json;
+using System.Threading.Tasks;
+using Volo.Abp.Http.Client;
+
+namespace System.Net.Http;
+internal static class HttpResponseDeserializeExtensions
+{
+ public async static Task DeserializeObjectAsync(this HttpResponseMessage response) where T : WeChatWorkResponse
+ {
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new AbpRemoteCallException($"Wechat work request error: {response.StatusCode} - {response.ReasonPhrase}");
+ }
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ return JsonConvert.DeserializeObject(responseContent)!;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Text/Json/Serialization/ExternalProfileSystemTextJsonConverter.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Text/Json/Serialization/ExternalProfileSystemTextJsonConverter.cs
new file mode 100644
index 000000000..ec532e9f1
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.ExternalContact/System/Text/Json/Serialization/ExternalProfileSystemTextJsonConverter.cs
@@ -0,0 +1,63 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Models;
+using System.Collections.Generic;
+
+namespace System.Text.Json.Serialization;
+internal class ExternalProfileSystemTextJsonConverter : JsonConverter
+{
+ public override ExternalProfile Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ using var doc = JsonDocument.ParseValue(ref reader);
+ var root = doc.RootElement;
+
+ var externalProfile = new ExternalProfile
+ {
+ WechatChannels = new List(),
+ ExternalAttributes = new List()
+ };
+
+ if (root.TryGetProperty("external_corp_name", out var corpNameElement))
+ {
+ externalProfile.ExternalCorpName = corpNameElement.GetRawText();
+ }
+ if (root.TryGetProperty("wechat_channels", out var wechatChannelsElement) && wechatChannelsElement.ValueKind == JsonValueKind.Array)
+ {
+ externalProfile.WechatChannels = JsonSerializer.Deserialize>(wechatChannelsElement.GetRawText(), options)!;
+ }
+ if (root.TryGetProperty("external_attr", out var externalAttrsElement) && externalAttrsElement.ValueKind == JsonValueKind.Array)
+ {
+ foreach ( var externalAttrElement in externalAttrsElement.EnumerateArray())
+ {
+ if (externalAttrElement.TryGetProperty("type", out var typeElement) && typeElement.ValueKind != JsonValueKind.Null)
+ {
+ var type = typeElement.Deserialize();
+
+ externalProfile.ExternalAttributes.Add(
+ ExternalAttributeDeserializeFactory.CreateExternalAttribute(type, externalAttrElement));
+ }
+ }
+ }
+
+ return externalProfile;
+ }
+
+ public override void Write(Utf8JsonWriter writer, ExternalProfile value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+
+ writer.WritePropertyName("external_corp_name");
+ JsonSerializer.Serialize(writer, value.ExternalCorpName, options);
+
+ if (value.WechatChannels != null)
+ {
+ writer.WritePropertyName("wechat_channels");
+ JsonSerializer.Serialize(writer, value.WechatChannels, value.WechatChannels.GetType(), options);
+ }
+ if (value.ExternalAttributes != null)
+ {
+ writer.WritePropertyName("external_attr");
+ JsonSerializer.Serialize(writer, value.ExternalAttributes, value.ExternalAttributes.GetType(), options);
+ }
+
+ writer.WriteEndObject();
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Utils/HttpContentBuildHelper.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Utils/WeChatWorkHttpContentBuildHelper.cs
similarity index 96%
rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Utils/HttpContentBuildHelper.cs
rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Utils/WeChatWorkHttpContentBuildHelper.cs
index 190a1ff89..1542c47da 100644
--- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Utils/HttpContentBuildHelper.cs
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Utils/WeChatWorkHttpContentBuildHelper.cs
@@ -5,7 +5,7 @@ using System.Text;
namespace LINGYUN.Abp.WeChat.Work.Utils;
-internal static class HttpContentBuildHelper
+public static class WeChatWorkHttpContentBuildHelper
{
public static HttpContent BuildUploadMediaContent(
string mediaName,
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Media.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Media.cs
index fb4c2e77e..8ab66dcdc 100644
--- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Media.cs
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Media.cs
@@ -41,7 +41,7 @@ internal static partial class HttpClientWeChatWorkRequestExtensions
HttpMethod.Post,
urlBuilder.ToString())
{
- Content = HttpContentBuildHelper.BuildUploadMediaContent("media", fileBytes, request.Content.FileName)
+ Content = WeChatWorkHttpContentBuildHelper.BuildUploadMediaContent("media", fileBytes, request.Content.FileName)
};
return await client.SendAsync(httpRequest, cancellationToken);
@@ -61,7 +61,7 @@ internal static partial class HttpClientWeChatWorkRequestExtensions
HttpMethod.Post,
urlBuilder.ToString())
{
- Content = HttpContentBuildHelper.BuildUploadMediaContent("file", fileBytes, request.Content.FileName)
+ Content = WeChatWorkHttpContentBuildHelper.BuildUploadMediaContent("file", fileBytes, request.Content.FileName)
};
return await client.SendAsync(httpRequest, cancellationToken);
diff --git a/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/LY.MicroService.WechatManagement.HttpApi.Host.csproj b/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/LY.MicroService.WechatManagement.HttpApi.Host.csproj
index fe684d527..cd0067114 100644
--- a/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/LY.MicroService.WechatManagement.HttpApi.Host.csproj
+++ b/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/LY.MicroService.WechatManagement.HttpApi.Host.csproj
@@ -33,6 +33,7 @@
+
@@ -51,13 +52,15 @@
-
+
+
+
diff --git a/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/WechatManagementHttpApiHostModule.Configure.cs b/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/WechatManagementHttpApiHostModule.Configure.cs
index e73992052..7fcfb0a18 100644
--- a/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/WechatManagementHttpApiHostModule.Configure.cs
+++ b/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/WechatManagementHttpApiHostModule.Configure.cs
@@ -42,6 +42,7 @@ using Volo.Abp.Security.Claims;
using Volo.Abp.SettingManagement;
using Volo.Abp.Threading;
using Volo.Abp.Timing;
+using Volo.Abp.UI.Navigation.Urls;
using Volo.Abp.VirtualFileSystem;
namespace LY.MicroService.WechatManagement;
@@ -177,6 +178,23 @@ public partial class WechatManagementHttpApiHostModule
}
});
}
+
+ private void ConfigureUrls(IConfiguration configuration)
+ {
+ Configure(options =>
+ {
+ var applicationConfiguration = configuration.GetSection("App:Urls:Applications");
+ foreach (var appConfig in applicationConfiguration.GetChildren())
+ {
+ options.Applications[appConfig.Key].RootUrl = appConfig["RootUrl"];
+ foreach (var urlsConfig in appConfig.GetSection("Urls").GetChildren())
+ {
+ options.Applications[appConfig.Key].Urls[urlsConfig.Key] = urlsConfig.Value;
+ }
+ }
+ });
+ }
+
private void ConfigureTiming(IConfiguration configuration)
{
Configure(options =>
diff --git a/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/WechatManagementHttpApiHostModule.cs b/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/WechatManagementHttpApiHostModule.cs
index a72432e76..26abb6d7e 100644
--- a/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/WechatManagementHttpApiHostModule.cs
+++ b/aspnet-core/services/LY.MicroService.WechatManagement.HttpApi.Host/WechatManagementHttpApiHostModule.cs
@@ -8,15 +8,17 @@ using LINGYUN.Abp.EventBus.CAP;
using LINGYUN.Abp.ExceptionHandling.Emailing;
using LINGYUN.Abp.Http.Client.Wrapper;
using LINGYUN.Abp.Identity.Session.AspNetCore;
+using LINGYUN.Abp.Identity.WeChat.Work;
using LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore;
using LINGYUN.Abp.Saas.EntityFrameworkCore;
using LINGYUN.Abp.Serilog.Enrichers.Application;
using LINGYUN.Abp.Serilog.Enrichers.UniqueId;
-using LINGYUN.Abp.Telemetry.SkyWalking;
+using LINGYUN.Abp.Telemetry.OpenTelemetry;
using LINGYUN.Abp.WeChat.MiniProgram;
using LINGYUN.Abp.WeChat.Official;
using LINGYUN.Abp.WeChat.SettingManagement;
using LINGYUN.Abp.WeChat.Work;
+using LINGYUN.Abp.WeChat.Work.ExternalContact;
using LINGYUN.Abp.WeChat.Work.Handlers;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
@@ -31,6 +33,7 @@ using Volo.Abp.DistributedLocking;
using Volo.Abp.EntityFrameworkCore.MySQL;
using Volo.Abp.FeatureManagement.EntityFrameworkCore;
using Volo.Abp.Http.Client.IdentityModel.Web;
+using Volo.Abp.Identity.EntityFrameworkCore;
using Volo.Abp.MailKit;
using Volo.Abp.Modularity;
using Volo.Abp.PermissionManagement.EntityFrameworkCore;
@@ -46,12 +49,15 @@ namespace LY.MicroService.WechatManagement;
typeof(AbpAspNetCoreSerilogModule),
typeof(AbpWeChatWorkApplicationModule),
typeof(AbpWeChatWorkHttpApiModule),
+ typeof(AbpWeChatWorkExternalContactModule),
typeof(AbpWeChatWorkHandlersModule),
typeof(AbpWeChatOfficialApplicationModule),
typeof(AbpWeChatOfficialHttpApiModule),
typeof(AbpWeChatMiniProgramModule),
+ typeof(AbpIdentityWeChatWorkModule),
typeof(AbpWeChatSettingManagementModule),
typeof(AbpSaasEntityFrameworkCoreModule),
+ typeof(AbpIdentityEntityFrameworkCoreModule),
typeof(AbpFeatureManagementEntityFrameworkCoreModule),
typeof(AbpPermissionManagementEntityFrameworkCoreModule),
typeof(AbpSettingManagementEntityFrameworkCoreModule),
@@ -70,7 +76,7 @@ namespace LY.MicroService.WechatManagement;
typeof(AbpHttpClientWrapperModule),
typeof(AbpMailKitModule),
typeof(AbpClaimsMappingModule),
- typeof(AbpTelemetrySkyWalkingModule),
+ typeof(AbpTelemetryOpenTelemetryModule),
typeof(AbpAspNetCoreMvcWrapperModule),
typeof(AbpAspNetCoreHttpOverridesModule),
typeof(AbpIdentitySessionAspNetCoreModule),
@@ -102,6 +108,7 @@ public partial class WechatManagementHttpApiHostModule : AbpModule
ConfigureFeatureManagement();
ConfigureSettingManagement();
ConfigurePermissionManagement();
+ ConfigureUrls(configuration);
ConfigureTiming(configuration);
ConfigureCaching(configuration);
ConfigureAuditing(configuration);
diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/GlobalUsings.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/GlobalUsings.cs
new file mode 100644
index 000000000..bd8299f6f
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/GlobalUsings.cs
@@ -0,0 +1,2 @@
+global using Xunit;
+global using Shouldly;
\ No newline at end of file
diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests.csproj
new file mode 100644
index 000000000..e65ecd7cd
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net9.0
+
+ false
+ Debug;Release;PostgreSQL
+ AnyCPU
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/AbpWeChatWorkExternalContactTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/AbpWeChatWorkExternalContactTestBase.cs
new file mode 100644
index 000000000..e78c65e39
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/AbpWeChatWorkExternalContactTestBase.cs
@@ -0,0 +1,6 @@
+using LINGYUN.Abp.Tests;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact;
+public abstract class AbpWeChatWorkExternalContactTestBase : AbpTestsBase
+{
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/AbpWeChatWorkExternalContactTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/AbpWeChatWorkExternalContactTestModule.cs
new file mode 100644
index 000000000..ab8d15e07
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/AbpWeChatWorkExternalContactTestModule.cs
@@ -0,0 +1,22 @@
+using LINGYUN.Abp.Tests.Features;
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Features;
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact;
+
+[DependsOn(
+ typeof(AbpWeChatWorkExternalContactModule),
+ typeof(AbpWeChatWorkTestModule))]
+public class AbpWeChatWorkExternalContactTestModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Configure(options =>
+ {
+ options.Map(WeChatWorkExternalContactFeatureNames.Enable, (feature) =>
+ {
+ return true.ToString();
+ });
+ });
+ }
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/WeChatWorkExternalContactProvider_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/WeChatWorkExternalContactProvider_Tests.cs
new file mode 100644
index 000000000..d5ed50793
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Contacts/WeChatWorkExternalContactProvider_Tests.cs
@@ -0,0 +1,22 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts.Request;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Contacts;
+
+public class WeChatWorkExternalContactProvider_Tests : AbpWeChatWorkExternalContactTestBase
+{
+ public readonly IWeChatWorkExternalContactProvider _provider;
+ public WeChatWorkExternalContactProvider_Tests()
+ {
+ _provider = GetRequiredService();
+ }
+
+ [Fact]
+ public async virtual Task Should_Get_External_Contact_List()
+ {
+ var res = await _provider.GetExternalContactListAsync(new WeChatWorkGetExternalContactListRequest());
+
+ res.ErrorCode.ShouldBe(0);
+ res.ErrorMessage.ShouldBe("ok");
+ }
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerProvider_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerProvider_Tests.cs
new file mode 100644
index 000000000..7d96f6ade
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerProvider_Tests.cs
@@ -0,0 +1,62 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request;
+using Microsoft.Extensions.Configuration;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers;
+public class WeChatWorkCustomerProvider_Tests : AbpWeChatWorkExternalContactTestBase
+{
+ public readonly IWeChatWorkCustomerProvider _provider;
+ public readonly IConfiguration _configuration;
+ public WeChatWorkCustomerProvider_Tests()
+ {
+ _provider = GetRequiredService();
+ _configuration = GetRequiredService();
+ }
+
+ [Fact]
+ public async virtual Task Should_Get_Customer_List()
+ {
+ var userId = _configuration["WeChat:Work:ExternalContact:Customers:GetCustomerList:UserId"];
+ var res = await _provider.GetCustomerListAsync(userId);
+
+ res.ErrorCode.ShouldBe(0);
+ res.ErrorMessage.ShouldBe("ok");
+ }
+
+ [Fact]
+ public async virtual Task Should_Bulk_Get_Customer()
+ {
+ var userId = _configuration["WeChat:Work:ExternalContact:Customers:BulkGetCustomer:UserId"];
+ var res = await _provider.BulkGetCustomerAsync(
+ new WeChatWorkBulkGetCustomerRequest(
+ new List { userId }));
+
+ res.ErrorCode.ShouldBe(0);
+ res.ErrorMessage.ShouldBe("ok");
+ }
+
+ [Fact]
+ public async virtual Task Should_Get_Customer()
+ {
+ var externalUserid = _configuration["WeChat:Work:ExternalContact:Customers:GetCustomer:ExternalUserid"];
+ var res = await _provider.GetCustomerAsync(externalUserid);
+
+ res.ErrorCode.ShouldBe(0);
+ res.ErrorMessage.ShouldBe("ok");
+ }
+
+ [Fact]
+ public async virtual Task Should_Update_Customer_Remark()
+ {
+ var userId = _configuration["WeChat:Work:ExternalContact:Customers:UpdateCustomerRemark:UserId"];
+ var externalUserid = _configuration["WeChat:Work:ExternalContact:Customers:UpdateCustomerRemark:ExternalUserid"];
+ var res = await _provider.UpdateCustomerRemarkAsync(
+ new WeChatWorkUpdateCustomerRemarkRequest(
+ userId,
+ externalUserid));
+
+ res.ErrorCode.ShouldBe(0);
+ res.ErrorMessage.ShouldBe("ok");
+ }
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerStrategyProvider_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerStrategyProvider_Tests.cs
new file mode 100644
index 000000000..56eeac713
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Customers/WeChatWorkCustomerStrategyProvider_Tests.cs
@@ -0,0 +1,49 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Customers.Request;
+using Microsoft.Extensions.Configuration;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Customers;
+public class WeChatWorkCustomerStrategyProvider_Tests : AbpWeChatWorkExternalContactTestBase
+{
+ public readonly IWeChatWorkCustomerStrategyProvider _provider;
+ public readonly IConfiguration _configuration;
+ public WeChatWorkCustomerStrategyProvider_Tests()
+ {
+ _provider = GetRequiredService();
+ _configuration = GetRequiredService();
+ }
+
+ [Fact]
+ public async virtual Task Should_Get_Customer_Strategy_List()
+ {
+ var res = await _provider.GetCustomerStrategyListAsync(
+ new WeChatWorkGetCustomerStrategyListRequest());
+
+ res.ErrorCode.ShouldBe(0);
+ res.ErrorMessage.ShouldBe("ok");
+ }
+
+ [Fact]
+ public async virtual Task Should_Get_Customer_Strategy()
+ {
+ var exception = await Assert.ThrowsAsync(async () =>
+ await _provider.GetCustomerStrategyAsync(
+ new WeChatWorkGetCustomerStrategyRequest(0)));
+
+ exception.Code.ShouldBe($"WeChatWork:40058");
+ exception.Message.ShouldContain($"invalid strategy_id:0");
+ }
+
+ [Fact]
+ public async virtual Task Should_Get_Customer_Strategy_Range()
+ {
+ var exception = await Assert.ThrowsAsync(async () =>
+ await _provider.GetCustomerStrategyRangeAsync(
+ new WeChatWorkGetCustomerStrategyRangeRequest(0)));
+
+ exception.Code.ShouldBe($"WeChatWork:40058");
+ exception.Message.ShouldContain($"invalid strategy_id:0");
+ }
+
+ // TODO: 其他敏感接口自行实现测试
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/WeChatWorkFollowUserProvider_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/WeChatWorkFollowUserProvider_Tests.cs
new file mode 100644
index 000000000..37ebc3653
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Follows/WeChatWorkFollowUserProvider_Tests.cs
@@ -0,0 +1,20 @@
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Follows;
+public class WeChatWorkFollowUserProvider_Tests : AbpWeChatWorkExternalContactTestBase
+{
+ public readonly IWeChatWorkFollowUserProvider _provider;
+ public WeChatWorkFollowUserProvider_Tests()
+ {
+ _provider = GetRequiredService();
+ }
+
+ [Fact]
+ public async virtual Task Should_Get_Follow_User_List()
+ {
+ var res = await _provider.GetFollowUserListAsync();
+
+ res.ErrorCode.ShouldBe(0);
+ res.ErrorMessage.ShouldBe("ok");
+ }
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/WeChatWorkGroupChatProvider_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/WeChatWorkGroupChatProvider_Tests.cs
new file mode 100644
index 000000000..5a10debdd
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/GroupChats/WeChatWorkGroupChatProvider_Tests.cs
@@ -0,0 +1,47 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats.Request;
+using Microsoft.Extensions.Configuration;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.GroupChats;
+public class WeChatWorkGroupChatProvider_Tests : AbpWeChatWorkExternalContactTestBase
+{
+ public readonly IWeChatWorkGroupChatProvider _provider;
+ public readonly IConfiguration _configuration;
+ public WeChatWorkGroupChatProvider_Tests()
+ {
+ _provider = GetRequiredService();
+ _configuration = GetRequiredService();
+ }
+
+ [Fact]
+ public async virtual Task Should_Get_Group_Chat_List()
+ {
+ var res = await _provider.GetGroupChatListAsync(
+ new WeChatWorkGetGroupChatListRequest());
+
+ res.ErrorCode.ShouldBe(0);
+ res.ErrorMessage.ShouldBe("ok");
+ }
+
+ [Fact]
+ public async virtual Task Should_Get_Group_Chat()
+ {
+ var chatId = _configuration["WeChat:Work:ExternalContact:GroupChats:GetGroupChat:ChatId"];
+ var res = await _provider.GetGroupChatAsync(
+ new WeChatWorkGetGroupChatRequest(chatId, true));
+
+ res.ErrorCode.ShouldBe(0);
+ res.ErrorMessage.ShouldBe("ok");
+ }
+
+ [Fact]
+ public async virtual Task Should_OpengId_To_ChatId()
+ {
+ var opengId = _configuration["WeChat:Work:ExternalContact:GroupChats:GetGroupChat:OpengIdToChatId"];
+ var res = await _provider.OpengIdToChatIdAsync(
+ new WeChatWorkOpengIdToChatIdRequest(opengId));
+
+ res.ErrorCode.ShouldBe(0);
+ res.ErrorMessage.ShouldBe("ok");
+ }
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/WeChatWorkCropTagProvider_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/WeChatWorkCropTagProvider_Tests.cs
new file mode 100644
index 000000000..a9b702fc0
--- /dev/null
+++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.ExternalContact.Tests/LINGYUN/Abp/WeChat/Work/ExternalContact/Tags/WeChatWorkCropTagProvider_Tests.cs
@@ -0,0 +1,90 @@
+using LINGYUN.Abp.WeChat.Work.ExternalContact.Tags.Request;
+using Microsoft.Extensions.Configuration;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.ExternalContact.Tags;
+public class WeChatWorkCropTagProvider_Tests : AbpWeChatWorkExternalContactTestBase
+{
+ public readonly IWeChatWorkCropTagProvider _provider;
+ public readonly IConfiguration _configuration;
+ public WeChatWorkCropTagProvider_Tests()
+ {
+ _provider = GetRequiredService();
+ _configuration = GetRequiredService();
+ }
+
+ [Fact]
+ public async virtual Task Should_Get_Crop_Tag_List()
+ {
+ var res = await _provider.GetCropTagListAsync(
+ new WeChatWorkGetCropTagListRequest());
+
+ res.ErrorCode.ShouldBe(0);
+ res.ErrorMessage.ShouldBe("ok");
+ }
+
+ [Fact]
+ public async virtual Task Should_Create_Crop_Tag()
+ {
+ var res = await _provider.CreateCropTagAsync(
+ new WeChatWorkCreateCropTagRequest()
+ {
+ GroupName = "test",
+ });
+
+ res.ErrorCode.ShouldBe(0);
+ res.ErrorMessage.ShouldBe("ok");
+
+ await _provider.DeleteCropTagAsync(
+ WeChatWorkDeleteCropTagRequest.Group(new string[1] { res.TagGroup.GroupId }));
+ }
+
+ [Fact]
+ public async virtual Task Should_Update_Crop_Tag()
+ {
+ var createRes = await _provider.CreateCropTagAsync(
+ new WeChatWorkCreateCropTagRequest()
+ {
+ GroupName = "test",
+ });
+
+ var updateRes = await _provider.UpdateCropTagAsync(
+ new WeChatWorkUpdateCropTagRequest(createRes.TagGroup.GroupId, "test_update"));
+
+ updateRes.ErrorCode.ShouldBe(0);
+ updateRes.ErrorMessage.ShouldBe("ok");
+
+ await _provider.DeleteCropTagAsync(
+ WeChatWorkDeleteCropTagRequest.Group(new string[1] { createRes.TagGroup.GroupId }));
+ }
+
+ [Fact]
+ public async virtual Task Should_Delete_Crop_Tag()
+ {
+ var createRes = await _provider.CreateCropTagAsync(
+ new WeChatWorkCreateCropTagRequest()
+ {
+ GroupName = "test",
+ });
+
+ var deleteRes = await _provider.DeleteCropTagAsync(
+ WeChatWorkDeleteCropTagRequest.Group(new string[1] { createRes.TagGroup.GroupId }));
+
+ deleteRes.ErrorCode.ShouldBe(0);
+ deleteRes.ErrorMessage.ShouldBe("ok");
+ }
+
+ [Fact]
+ public async virtual Task Should_Mark_Crop_Tag()
+ {
+ var userId = _configuration["WeChat:Work:ExternalContact:Tags:MarkCropTag:UserId"];
+ var externalUserId = _configuration["WeChat:Work:ExternalContact:Tags:MarkCropTag:ExternalUserId"];
+
+ var req = new WeChatWorkMarkCropTagRequest(userId, externalUserId);
+ req.CreateTag.Add("test_tag");
+ var res = await _provider.MarkCropTagAsync(req);
+
+ res.ErrorCode.ShouldBe(0);
+ res.ErrorMessage.ShouldBe("ok");
+ }
+}
diff --git a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.Tests/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.Tests/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkTestModule.cs
index bb888cb1f..a95eb64f5 100644
--- a/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.Tests/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkTestModule.cs
+++ b/aspnet-core/tests/LINGYUN.Abp.WeChat.Work.Tests/LINGYUN/Abp/WeChat/Work/AbpWeChatWorkTestModule.cs
@@ -17,15 +17,14 @@ namespace LINGYUN.Abp.WeChat.Work;
typeof(AbpTestsBaseModule))]
public class AbpWeChatWorkTestModule : AbpModule
{
+ private const string UserSecretsId = "5709C35E-27FF-4F5A-BBFD-F451285AA012";
+
public override void PreConfigureServices(ServiceConfigurationContext context)
{
- var configurationOptions = new AbpConfigurationBuilderOptions
+ context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(builderAction: builder =>
{
- BasePath = @"D:\Projects\Development\Abp\WeChat\Work",
- EnvironmentName = "Test"
- };
-
- context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(configurationOptions));
+ builder.AddUserSecrets(UserSecretsId);
+ }));
}
public override void ConfigureServices(ServiceConfigurationContext context)