diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/IApprovalTemplateProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/IWeChatWorkApprovalTemplateProvider.cs
similarity index 100%
rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/IApprovalTemplateProvider.cs
rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/IWeChatWorkApprovalTemplateProvider.cs
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/ApprovalTemplateProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/WeChatWorkApprovalTemplateProvider.cs
similarity index 96%
rename from aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/ApprovalTemplateProvider.cs
rename to aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/WeChatWorkApprovalTemplateProvider.cs
index aa7436c10..9d074bffe 100644
--- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/ApprovalTemplateProvider.cs
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Approvals/WeChatWorkApprovalTemplateProvider.cs
@@ -11,12 +11,12 @@ using Volo.Abp.Features;
namespace LINGYUN.Abp.WeChat.Work.Approvals;
[RequiresFeature(WeChatWorkFeatureNames.Enable)]
-public class ApprovalTemplateProvider : IApprovalTemplateProvider, ISingletonDependency
+public class WeChatWorkApprovalTemplateProvider : IApprovalTemplateProvider, ISingletonDependency
{
protected IHttpClientFactory HttpClientFactory { get; }
protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; }
- public ApprovalTemplateProvider(
+ public WeChatWorkApprovalTemplateProvider(
IHttpClientFactory httpClientFactory,
IWeChatWorkTokenProvider weChatWorkTokenProvider)
{
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/IWeChatWorkServerProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/IWeChatWorkServerProvider.cs
new file mode 100644
index 000000000..8f527f4d0
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/IWeChatWorkServerProvider.cs
@@ -0,0 +1,17 @@
+using LINGYUN.Abp.WeChat.Work.Security.Models;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.Security;
+public interface IWeChatWorkServerProvider
+{
+ ///
+ /// 获取企业微信域名IP信息
+ ///
+ ///
+ /// 参考:https://developer.work.weixin.qq.com/document/path/100079
+ ///
+ ///
+ ///
+ Task GetWeChatServerAsync(CancellationToken cancellationToken = default);
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatDomainModel.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatDomainModel.cs
new file mode 100644
index 000000000..0105cb67d
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatDomainModel.cs
@@ -0,0 +1,44 @@
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.Security.Models;
+public class WeChatDomainModel
+{
+ ///
+ /// 域名
+ ///
+ [JsonProperty("domain")]
+ [JsonPropertyName("domain")]
+ public string Domain { get; set; } = default!;
+ ///
+ /// 泛域名
+ ///
+ [JsonProperty("universal_domian")]
+ [JsonPropertyName("universal_domian")]
+ public string UniversalDomian { get; set; }
+ ///
+ /// 协议 如TCP UDP
+ ///
+ [JsonProperty("protocol")]
+ [JsonPropertyName("protocol")]
+ public string Protocol { get; set; } = default!;
+ ///
+ /// 端口号列表
+ ///
+ [JsonProperty("port")]
+ [JsonPropertyName("port")]
+ public List Port { get; set; } = new List();
+ ///
+ /// 是否必要,0-否 1-是, 如果必要的域名或IP被拦截,将导致企业微信的功能出现异常
+ ///
+ [JsonProperty("is_necessary")]
+ [JsonPropertyName("is_necessary")]
+ public int IsNecessary { get; set; } = default!;
+ ///
+ /// IP涉及到的功能的描述信息
+ ///
+ [JsonProperty("description")]
+ [JsonPropertyName("description")]
+ public string Description { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatIpModel.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatIpModel.cs
new file mode 100644
index 000000000..ab8e0e4bc
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatIpModel.cs
@@ -0,0 +1,38 @@
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.Security.Models;
+public class WeChatIpModel
+{
+ ///
+ /// ip地址
+ ///
+ [JsonProperty("ip")]
+ [JsonPropertyName("ip")]
+ public string Ip { get; set; } = default!;
+ ///
+ /// 协议 如TCP UDP
+ ///
+ [JsonProperty("protocol")]
+ [JsonPropertyName("protocol")]
+ public string Protocol { get; set; } = default!;
+ ///
+ /// 端口号列表
+ ///
+ [JsonProperty("port")]
+ [JsonPropertyName("port")]
+ public List Port { get; set; } = new List();
+ ///
+ /// 是否必要,0-否 1-是, 如果必要的域名或IP被拦截,将导致企业微信的功能出现异常
+ ///
+ [JsonProperty("is_necessary")]
+ [JsonPropertyName("is_necessary")]
+ public int IsNecessary { get; set; } = default!;
+ ///
+ /// IP涉及到的功能的描述信息
+ ///
+ [JsonProperty("description")]
+ [JsonPropertyName("description")]
+ public string Description { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatServerDomainModel.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatServerDomainModel.cs
new file mode 100644
index 000000000..dd84447fa
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatServerDomainModel.cs
@@ -0,0 +1,25 @@
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.Security.Models;
+public class WeChatServerDomainModel
+{
+ ///
+ /// 域名列表
+ ///
+ [JsonProperty("domain_list")]
+ [JsonPropertyName("domain_list")]
+ public List Domains { get; }
+ ///
+ /// Ip列表
+ ///
+ [JsonProperty("ip_list")]
+ [JsonPropertyName("ip_list")]
+ public List Ips { get; }
+ public WeChatServerDomainModel(List domains, List ips)
+ {
+ Domains = domains;
+ Ips = ips;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatServerDomainResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatServerDomainResponse.cs
new file mode 100644
index 000000000..3d65b35ba
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/Models/WeChatServerDomainResponse.cs
@@ -0,0 +1,22 @@
+using Newtonsoft.Json;
+using System.Collections.Generic;
+
+namespace LINGYUN.Abp.WeChat.Work.Security.Models;
+public class WeChatServerDomainResponse : WeChatWorkResponse
+{
+ ///
+ /// 域名列表
+ ///
+ [JsonProperty("domain_list")]
+ public List Domains { get; set; } = new List();
+ ///
+ /// Ip列表
+ ///
+ [JsonProperty("ip_list")]
+ public List Ips { get; set; } = new List();
+ public WeChatServerDomainModel ToServerDomain()
+ {
+ ThrowIfNotSuccess();
+ return new WeChatServerDomainModel(Domains, Ips);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/WeChatWorkServerProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/WeChatWorkServerProvider.cs
new file mode 100644
index 000000000..fcca18361
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Security/WeChatWorkServerProvider.cs
@@ -0,0 +1,32 @@
+using LINGYUN.Abp.WeChat.Work.Security.Models;
+using LINGYUN.Abp.WeChat.Work.Token;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+
+namespace LINGYUN.Abp.WeChat.Work.Security;
+public class WeChatWorkServerProvider : IWeChatWorkServerProvider, ISingletonDependency
+{
+ protected IHttpClientFactory HttpClientFactory { get; }
+ protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; }
+
+ public WeChatWorkServerProvider(
+ IHttpClientFactory httpClientFactory,
+ IWeChatWorkTokenProvider weChatWorkTokenProvider)
+ {
+ HttpClientFactory = httpClientFactory;
+ WeChatWorkTokenProvider = weChatWorkTokenProvider;
+ }
+
+ public async virtual Task GetWeChatServerAsync(CancellationToken cancellationToken = default)
+ {
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient);
+
+ using var response = await client.GetServerDomainIpAsync(token.AccessToken, cancellationToken);
+ var serverDomainResponse = await response.DeserializeObjectAsync();
+
+ return serverDomainResponse.ToServerDomain();
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/IWeChatWorkTagProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/IWeChatWorkTagProvider.cs
new file mode 100644
index 000000000..df2aa3dc6
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/IWeChatWorkTagProvider.cs
@@ -0,0 +1,93 @@
+using LINGYUN.Abp.WeChat.Work.Tags.Request;
+using LINGYUN.Abp.WeChat.Work.Tags.Response;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.WeChat.Work.Tags;
+///
+/// 标签管理接口
+///
+public interface IWeChatWorkTagProvider
+{
+ ///
+ /// 创建标签
+ ///
+ ///
+ /// 详情见:https://developer.work.weixin.qq.com/document/path/90210
+ ///
+ /// 创建标签请求参数
+ ///
+ /// 创建标签响应参数
+ Task CreateAsync(
+ WeChatWorkTagCreateRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 更新标签名字
+ ///
+ ///
+ /// 详情见:https://developer.work.weixin.qq.com/document/path/90211
+ ///
+ /// 更新标签名字请求参数
+ ///
+ /// 更新标签名字响应参数
+ Task UpdateAsync(
+ WeChatWorkTagUpdateRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 删除标签
+ ///
+ ///
+ /// 详情见:https://developer.work.weixin.qq.com/document/path/90212
+ ///
+ /// 删除标签请求参数
+ ///
+ /// 删除标签响应参数
+ Task DeleteAsync(
+ WeChatWorkGetTagRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 获取标签成员
+ ///
+ ///
+ /// 详情见:https://developer.work.weixin.qq.com/document/path/90213
+ ///
+ /// 获取标签成员请求参数
+ ///
+ /// 获取标签成员响应参数
+ Task GetMemberAsync(
+ WeChatWorkGetTagRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 增加标签成员
+ ///
+ ///
+ /// 详情见:https://developer.work.weixin.qq.com/document/path/90214
+ ///
+ /// 增加标签成员请求参数
+ ///
+ /// 增加标签成员响应参数
+ Task AddMemberAsync(
+ WeChatWorkTagChangeMemberRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 增加标签成员
+ ///
+ ///
+ /// 详情见:https://developer.work.weixin.qq.com/document/path/90214
+ ///
+ /// 增加标签成员请求参数
+ ///
+ /// 增加标签成员响应参数
+ Task DeleteMemberAsync(
+ WeChatWorkTagChangeMemberRequest request,
+ CancellationToken cancellationToken = default);
+ ///
+ /// 获取标签列表
+ ///
+ ///
+ /// 详情见:https://developer.work.weixin.qq.com/document/path/90214
+ ///
+ ///
+ /// 获取标签列表响应参数
+ Task GetListAsync(CancellationToken cancellationToken = default);
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Models/TagInfo.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Models/TagInfo.cs
new file mode 100644
index 000000000..24cbeffb8
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Models/TagInfo.cs
@@ -0,0 +1,25 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.Tags.Models;
+///
+/// 标签
+///
+public class TagInfo
+{
+ ///
+ /// 标签id
+ ///
+ [NotNull]
+ [JsonProperty("tagid")]
+ [JsonPropertyName("tagid")]
+ public int TagId { get; set; }
+ ///
+ /// 标签名
+ ///
+ [NotNull]
+ [JsonProperty("tagname")]
+ [JsonPropertyName("tagname")]
+ public string TagName { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Models/TagUserInfo.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Models/TagUserInfo.cs
new file mode 100644
index 000000000..720ba0b1b
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Models/TagUserInfo.cs
@@ -0,0 +1,25 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.Tags.Models;
+///
+/// 标签中包含的成员
+///
+public class TagUserInfo
+{
+ ///
+ /// 成员账号
+ ///
+ [NotNull]
+ [JsonProperty("userid")]
+ [JsonPropertyName("userid")]
+ public string UserId { get; set; }
+ ///
+ /// 成员名称
+ ///
+ [NotNull]
+ [JsonProperty("name")]
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkGetTagRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkGetTagRequest.cs
new file mode 100644
index 000000000..f1c4fb22a
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkGetTagRequest.cs
@@ -0,0 +1,23 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.Tags.Request;
+///
+/// 获取标签请求参数
+///
+public class WeChatWorkGetTagRequest
+{
+ ///
+ /// 标签id
+ ///
+ [NotNull]
+ [JsonProperty("tagid")]
+ [JsonPropertyName("tagid")]
+ public int TagId { get; set; }
+ public WeChatWorkGetTagRequest(int tagId)
+ {
+ TagId = Check.Positive(tagId, nameof(tagId));
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkTagChangeMemberRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkTagChangeMemberRequest.cs
new file mode 100644
index 000000000..22aba51f1
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkTagChangeMemberRequest.cs
@@ -0,0 +1,57 @@
+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.Tags.Request;
+///
+/// 标签成员变更请求参数
+///
+public class WeChatWorkTagChangeMemberRequest
+{
+ ///
+ /// 标签id,非负整型,指定此参数时新增的标签会生成对应的标签id,不指定时则以目前最大的id自增。
+ ///
+ [NotNull]
+ [JsonProperty("tagid")]
+ [JsonPropertyName("tagid")]
+ public int TagId { get; set; }
+ ///
+ /// 企业成员ID列表,注意:userlist、partylist不能同时为空,单次请求个数不超过1000
+ ///
+ [CanBeNull]
+ [JsonProperty("userlist")]
+ [JsonPropertyName("userlist")]
+ public List Users { get; set; }
+ ///
+ /// 企业部门ID列表,注意:userlist、partylist不能同时为空,单次请求个数不超过100
+ ///
+ [CanBeNull]
+ [JsonProperty("partylist")]
+ [JsonPropertyName("partylist")]
+ public List Parts { get; set; }
+ public WeChatWorkTagChangeMemberRequest(
+ int tagId,
+ List users = null,
+ List parts = null)
+ {
+ TagId = Check.Positive(tagId, nameof(tagId));
+ Users = users;
+ Parts = parts;
+
+ if (users == null && parts == null)
+ {
+ throw new ArgumentNullException("users/parts", "userlist、partylist不能同时为空!");
+ }
+ if (users.Count > 1000)
+ {
+ throw new ArgumentOutOfRangeException(nameof(users), "企业成员ID列表单次请求个数不超过1000!");
+ }
+ if (parts.Count > 100)
+ {
+ throw new ArgumentOutOfRangeException(nameof(users), "企业部门ID列表单次请求个数不超过100!");
+ }
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkTagCreateRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkTagCreateRequest.cs
new file mode 100644
index 000000000..27a6d6dcd
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkTagCreateRequest.cs
@@ -0,0 +1,33 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.Tags.Request;
+///
+/// 创建标签请求参数
+///
+public class WeChatWorkTagCreateRequest
+{
+ ///
+ /// 标签id,非负整型,指定此参数时新增的标签会生成对应的标签id,不指定时则以目前最大的id自增。
+ ///
+ [NotNull]
+ [JsonProperty("tagid")]
+ [JsonPropertyName("tagid")]
+ public int? TagId { get; set; }
+ ///
+ /// 标签名称,长度限制为32个字以内(汉字或英文字母),标签名不可与其他标签重名。
+ ///
+ [NotNull]
+ [StringLength(32)]
+ [JsonProperty("tagname")]
+ [JsonPropertyName("tagname")]
+ public string TagName { get; set; }
+ public WeChatWorkTagCreateRequest(string tagName, int? tagId = null)
+ {
+ TagName = Check.NotNullOrWhiteSpace(tagName, nameof(tagName), 32);
+ TagId = tagId;
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkTagUpdateRequest.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkTagUpdateRequest.cs
new file mode 100644
index 000000000..902d70e4b
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Request/WeChatWorkTagUpdateRequest.cs
@@ -0,0 +1,33 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
+using Volo.Abp;
+
+namespace LINGYUN.Abp.WeChat.Work.Tags.Request;
+///
+/// 更新标签请求参数
+///
+public class WeChatWorkTagUpdateRequest
+{
+ ///
+ /// 标签id,非负整型,指定此参数时新增的标签会生成对应的标签id,不指定时则以目前最大的id自增。
+ ///
+ [NotNull]
+ [JsonProperty("tagid")]
+ [JsonPropertyName("tagid")]
+ public int TagId { get; set; }
+ ///
+ /// 标签名称,长度限制为32个字以内(汉字或英文字母),标签名不可与其他标签重名。
+ ///
+ [NotNull]
+ [StringLength(32)]
+ [JsonProperty("tagname")]
+ [JsonPropertyName("tagname")]
+ public string TagName { get; set; }
+ public WeChatWorkTagUpdateRequest(int tagId, string tagName)
+ {
+ TagId = Check.Positive(tagId, nameof(tagId));
+ TagName = Check.NotNullOrWhiteSpace(tagName, nameof(tagName), 32);
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagChangeMemberResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagChangeMemberResponse.cs
new file mode 100644
index 000000000..d0e6cd002
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagChangeMemberResponse.cs
@@ -0,0 +1,26 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.Tags.Response;
+///
+/// 标签成员变更响应参数
+///
+public class WeChatWorkTagChangeMemberResponse : WeChatWorkResponse
+{
+ ///
+ /// 若部分userid非法,则返回
+ ///
+ [CanBeNull]
+ [JsonProperty("invalidlist")]
+ [JsonPropertyName("invalidlist")]
+ public string InvalidList { get; set; }
+ ///
+ /// 若部分partylist非法,则返回
+ ///
+ [CanBeNull]
+ [JsonProperty("invalidparty")]
+ [JsonPropertyName("invalidparty")]
+ public List InvalidPart { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagCreateResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagCreateResponse.cs
new file mode 100644
index 000000000..8e0a1f084
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagCreateResponse.cs
@@ -0,0 +1,21 @@
+using JetBrains.Annotations;
+using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.Tags.Response;
+///
+/// 创建标签响应参数
+///
+///
+/// 详情见: https://developer.work.weixin.qq.com/document/path/90210
+///
+public class WeChatWorkTagCreateResponse : WeChatWorkResponse
+{
+ ///
+ /// 标签id
+ ///
+ [NotNull]
+ [JsonProperty("tagid")]
+ [JsonPropertyName("tagid")]
+ public string TagId { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagListResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagListResponse.cs
new file mode 100644
index 000000000..ed905495d
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagListResponse.cs
@@ -0,0 +1,23 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.Tags.Models;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.Tags.Response;
+///
+/// 获取标签列表响应参数
+///
+///
+/// 详情见: https://developer.work.weixin.qq.com/document/path/90216
+///
+public class WeChatWorkTagListResponse : WeChatWorkResponse
+{
+ ///
+ /// 标签列表
+ ///
+ [NotNull]
+ [JsonProperty("taglist")]
+ [JsonPropertyName("taglist")]
+ public List Tags { get; set; }
+}
\ No newline at end of file
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagMemberInfoResponse.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagMemberInfoResponse.cs
new file mode 100644
index 000000000..fce599a69
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/Response/WeChatWorkTagMemberInfoResponse.cs
@@ -0,0 +1,37 @@
+using JetBrains.Annotations;
+using LINGYUN.Abp.WeChat.Work.Tags.Models;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LINGYUN.Abp.WeChat.Work.Tags.Response;
+///
+/// 标签成员响应参数
+///
+///
+/// 详情见: https://developer.work.weixin.qq.com/document/path/90213
+///
+public class WeChatWorkTagMemberInfoResponse : WeChatWorkResponse
+{
+ ///
+ /// 标签名
+ ///
+ [NotNull]
+ [JsonProperty("tagname")]
+ [JsonPropertyName("tagname")]
+ public string TagName { get; set; }
+ ///
+ /// 标签中包含的成员列表
+ ///
+ [NotNull]
+ [JsonProperty("userlist")]
+ [JsonPropertyName("userlist")]
+ public List Users { get; set; }
+ ///
+ /// 标签中包含的部门id列表
+ ///
+ [NotNull]
+ [JsonProperty("partylist")]
+ [JsonPropertyName("partylist")]
+ public List Parts { get; set; }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/WeChatWorkTagProvider.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/WeChatWorkTagProvider.cs
new file mode 100644
index 000000000..3eb40560a
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/LINGYUN/Abp/WeChat/Work/Tags/WeChatWorkTagProvider.cs
@@ -0,0 +1,89 @@
+using LINGYUN.Abp.WeChat.Work.Features;
+using LINGYUN.Abp.WeChat.Work.Tags.Request;
+using LINGYUN.Abp.WeChat.Work.Tags.Response;
+using LINGYUN.Abp.WeChat.Work.Token;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Features;
+
+namespace LINGYUN.Abp.WeChat.Work.Tags;
+
+[RequiresFeature(WeChatWorkFeatureNames.Enable)]
+public class WeChatWorkTagProvider : IWeChatWorkTagProvider, ISingletonDependency
+{
+ protected IHttpClientFactory HttpClientFactory { get; }
+ protected IWeChatWorkTokenProvider WeChatWorkTokenProvider { get; }
+
+ public WeChatWorkTagProvider(
+ IHttpClientFactory httpClientFactory,
+ IWeChatWorkTokenProvider weChatWorkTokenProvider)
+ {
+ HttpClientFactory = httpClientFactory;
+ WeChatWorkTokenProvider = weChatWorkTokenProvider;
+ }
+
+ public async virtual Task AddMemberAsync(WeChatWorkTagChangeMemberRequest request, CancellationToken cancellationToken = default)
+ {
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient);
+
+ using var response = await client.AddTagMemberAsync(token.AccessToken, request, cancellationToken);
+ return await response.DeserializeObjectAsync();
+ }
+
+ public async virtual Task CreateAsync(WeChatWorkTagCreateRequest request, CancellationToken cancellationToken = default)
+ {
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient);
+
+ using var response = await client.CreateTagAsync(token.AccessToken, request, cancellationToken);
+ return await response.DeserializeObjectAsync();
+ }
+
+ public async virtual Task DeleteAsync(WeChatWorkGetTagRequest request, CancellationToken cancellationToken = default)
+ {
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient);
+
+ using var response = await client.DeleteTagAsync(token.AccessToken, request, cancellationToken);
+ return await response.DeserializeObjectAsync();
+ }
+
+ public async virtual Task DeleteMemberAsync(WeChatWorkTagChangeMemberRequest request, CancellationToken cancellationToken = default)
+ {
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient);
+
+ using var response = await client.DeleteTagMemberAsync(token.AccessToken, request, cancellationToken);
+ return await response.DeserializeObjectAsync();
+ }
+
+ public async virtual Task GetListAsync(CancellationToken cancellationToken = default)
+ {
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient);
+
+ using var response = await client.GetTagListAsync(token.AccessToken, cancellationToken);
+ return await response.DeserializeObjectAsync();
+ }
+
+ public async virtual Task GetMemberAsync(WeChatWorkGetTagRequest request, CancellationToken cancellationToken = default)
+ {
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient);
+
+ using var response = await client.GetTagMemberAsync(token.AccessToken, request, cancellationToken);
+ return await response.DeserializeObjectAsync();
+ }
+
+ public async virtual Task UpdateAsync(WeChatWorkTagUpdateRequest request, CancellationToken cancellationToken = default)
+ {
+ var token = await WeChatWorkTokenProvider.GetTokenAsync(cancellationToken);
+ var client = HttpClientFactory.CreateClient(AbpWeChatWorkGlobalConsts.ApiClient);
+
+ using var response = await client.UpdateTagAsync(token.AccessToken, request, cancellationToken);
+ return await response.DeserializeObjectAsync();
+ }
+}
diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Tag.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Tag.cs
new file mode 100644
index 000000000..e9162de12
--- /dev/null
+++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work/System/Net/Http/HttpClientWeChatWorkRequestExtensions.Tag.cs
@@ -0,0 +1,134 @@
+using LINGYUN.Abp.WeChat.Work.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 AddTagMemberAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkTagChangeMemberRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/tag/addtagusers");
+ 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 CreateTagAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkTagCreateRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/tag/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 DeleteTagAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkGetTagRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/tag/delete");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+ urlBuilder.AppendFormat("&tagid={0}", request.TagId);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Get,
+ urlBuilder.ToString());
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+ public async static Task DeleteTagMemberAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkTagChangeMemberRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/tag/deltagusers");
+ 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 GetTagListAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/tag/list");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Get,
+ urlBuilder.ToString());
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+ public async static Task GetTagMemberAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkGetTagRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/tag/get");
+ urlBuilder.AppendFormat("?access_token={0}", accessToken);
+ urlBuilder.AppendFormat("&tagid={0}", request.TagId);
+
+ var httpRequest = new HttpRequestMessage(
+ HttpMethod.Get,
+ urlBuilder.ToString());
+
+ return await client.SendAsync(httpRequest, cancellationToken);
+ }
+ public async static Task UpdateTagAsync(
+ this HttpMessageInvoker client,
+ string accessToken,
+ WeChatWorkTagUpdateRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var urlBuilder = new StringBuilder();
+ urlBuilder.Append("/cgi-bin/tag/update");
+ 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);
+ }
+}