diff --git a/Admin.NET/Admin.NET.Application/Configuration/Database.Development.json b/Admin.NET/Admin.NET.Application/Configuration/Database.Development.json
index 12d72888..7ea0723b 100644
--- a/Admin.NET/Admin.NET.Application/Configuration/Database.Development.json
+++ b/Admin.NET/Admin.NET.Application/Configuration/Database.Development.json
@@ -27,7 +27,7 @@
//],
"DbSettings": {
"EnableInitDb": true, // 启用库初始化(若实体没有变化建议关闭)
- "EnableInitView": true, // 启用视图初始化
+ "EnableInitView": true, // 启用视图初始化(若实体和视图没有变化建议关闭)
"EnableDiffLog": false, // 启用库表差异日志
"EnableUnderLine": false, // 启用驼峰转下划线
"EnableConnEncrypt": false // 启用数据库连接串加密(国密SM2加解密)
diff --git a/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj b/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj
index e1c096ba..6ec553c6 100644
--- a/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj
+++ b/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj
@@ -31,7 +31,7 @@
-
+
@@ -54,9 +54,9 @@
-
+
-
+
diff --git a/Admin.NET/Admin.NET.Core/Const/CacheConst.cs b/Admin.NET/Admin.NET.Core/Const/CacheConst.cs
index 2fface7b..8a141f90 100644
--- a/Admin.NET/Admin.NET.Core/Const/CacheConst.cs
+++ b/Admin.NET/Admin.NET.Core/Const/CacheConst.cs
@@ -16,6 +16,11 @@ public class CacheConst
///
public const string KeyUserToken = "sys_user_token:";
+ ///
+ /// Token黑名单
+ ///
+ public const string KeyTokenBlacklist = "sys_token_blacklist:";
+
///
/// 用户接口缓存(接口集合)
///
diff --git a/Admin.NET/Admin.NET.Core/Entity/SysRegion.cs b/Admin.NET/Admin.NET.Core/Entity/SysRegion.cs
index cb3aa4f6..94dda924 100644
--- a/Admin.NET/Admin.NET.Core/Entity/SysRegion.cs
+++ b/Admin.NET/Admin.NET.Core/Entity/SysRegion.cs
@@ -12,7 +12,7 @@ namespace Admin.NET.Core;
[SugarTable(null, "系统行政地区表")]
[SysTable]
[SugarIndex("index_{table}_N", nameof(Name), OrderByType.Asc)]
-[SugarIndex("index_{table}_C", nameof(Code), OrderByType.Asc)]
+[SugarIndex("index_{table}_C", nameof(Code), OrderByType.Asc, IsUnique = true)]
public partial class SysRegion : EntityBaseId
{
///
diff --git a/Admin.NET/Admin.NET.Core/EventBus/EventConsumer.cs b/Admin.NET/Admin.NET.Core/EventBus/EventConsumer.cs
index 0f774b1a..53e70918 100644
--- a/Admin.NET/Admin.NET.Core/EventBus/EventConsumer.cs
+++ b/Admin.NET/Admin.NET.Core/EventBus/EventConsumer.cs
@@ -54,13 +54,21 @@ public class EventConsumer : IDisposable
}
_consumerCts = new CancellationTokenSource();
var ct = _consumerCts.Token;
- _consumerTask = Task.Factory.StartNew(() =>
+ _consumerTask = Task.Factory.StartNew(async () =>
{
while (!ct.IsCancellationRequested)
{
- var cr = Consumer.TakeOne(10);
- if (cr == null) continue;
- Received?.Invoke(this, cr);
+ try
+ {
+ var cr = Consumer.TakeOne(10);
+ if (cr == null) continue;
+ Received?.Invoke(this, cr);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"消息消费异常: {ex.Message}");
+ await Task.Delay(1000);
+ }
}
}, ct, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
diff --git a/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs b/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs
index fdb7829b..731bf0f2 100644
--- a/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs
+++ b/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs
@@ -343,10 +343,17 @@ public class SysAuthService : IDynamicApiController, ITransient
[DisplayName("退出系统")]
public async void Logout()
{
- if (string.IsNullOrWhiteSpace(_userManager.Account))
+ var httpContext = _httpContextAccessor.HttpContext ?? throw Oops.Oh(ErrorCodeEnum.D1016);
+
+ var accessToken = httpContext.Request.Headers.Authorization.ToString();
+ if (string.IsNullOrWhiteSpace(accessToken) || string.IsNullOrWhiteSpace(_userManager.Account))
throw Oops.Oh(ErrorCodeEnum.D1011);
- // 发布系统退出事件
+ // 写入Token黑名单
+ var tokenExpire = await _sysConfigService.GetTokenExpire();
+ _sysCacheService.Set($"{CacheConst.KeyTokenBlacklist}:{accessToken}", _userManager.Account, TimeSpan.FromMinutes(tokenExpire));
+
+ // 发布系统退出事件
await _eventPublisher.PublishAsync(UserEventTypeEnum.Logout, _userManager);
// 退出Swagger/设置无效Token响应头
diff --git a/Admin.NET/Admin.NET.Core/Service/Region/SysRegionService.cs b/Admin.NET/Admin.NET.Core/Service/Region/SysRegionService.cs
index 3353101a..68f023a4 100644
--- a/Admin.NET/Admin.NET.Core/Service/Region/SysRegionService.cs
+++ b/Admin.NET/Admin.NET.Core/Service/Region/SysRegionService.cs
@@ -203,7 +203,7 @@ public class SysRegionService : IDynamicApiController, ITransient
Level = 1,
Pid = 0,
};
- if (municipalityList.Any(m => province.Name.StartsWith(m))) province.Name += "(省)";
+ //if (municipalityList.Any(u => province.Name.StartsWith(u))) province.Name += "(省)";
list.Add(province);
if (input.Level <= 1) continue;
@@ -221,7 +221,11 @@ public class SysRegionService : IDynamicApiController, ITransient
Name = cityName,
Level = 2
};
- if (municipalityList.Any(m => city.Name.StartsWith(m))) city.Name += "(地)";
+ if (municipalityList.Any(u => city.Name.StartsWith(u)))
+ {
+ city.Name = "市辖区";
+ if (province.Code == city.Code) city.Code = province.Code.Substring(0, 2) + "0100";
+ }
list.Add(city);
if (input.Level <= 2) continue;
diff --git a/Admin.NET/Admin.NET.Core/Service/Tenant/SysTenantService.cs b/Admin.NET/Admin.NET.Core/Service/Tenant/SysTenantService.cs
index 9126cf69..6c1cdb3d 100644
--- a/Admin.NET/Admin.NET.Core/Service/Tenant/SysTenantService.cs
+++ b/Admin.NET/Admin.NET.Core/Service/Tenant/SysTenantService.cs
@@ -76,8 +76,8 @@ public class SysTenantService : IDynamicApiController, ITransient
public async Task> Page(PageTenantInput input)
{
return await _sysTenantRep.AsQueryable()
- .LeftJoin((u, a) => u.UserId == a.Id)
- .LeftJoin((u, a, b) => u.OrgId == b.Id)
+ .LeftJoin((u, a) => u.UserId == a.Id).ClearFilter()
+ .LeftJoin((u, a, b) => u.OrgId == b.Id).ClearFilter()
.WhereIF(!string.IsNullOrWhiteSpace(input.Phone), (u, a) => a.Phone.Contains(input.Phone.Trim()))
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), (u, a, b) => b.Name.Contains(input.Name.Trim()))
.WhereIF(!input.IncludeDefault, u => u.Id.ToString() != SqlSugarConst.MainConfigId) // 排除默认主库/主租户
diff --git a/Admin.NET/Admin.NET.Web.Core/Handlers/JwtHandler.cs b/Admin.NET/Admin.NET.Web.Core/Handlers/JwtHandler.cs
index a331ca7d..e016d255 100644
--- a/Admin.NET/Admin.NET.Web.Core/Handlers/JwtHandler.cs
+++ b/Admin.NET/Admin.NET.Web.Core/Handlers/JwtHandler.cs
@@ -57,6 +57,15 @@ namespace Admin.NET.Web.Core
return;
}
+ // 验证Token黑名单
+ var accessToken = httpContext.Request.Headers.Authorization.ToString();
+ if (sysCacheService.ExistKey($"{CacheConst.KeyTokenBlacklist}:{accessToken}"))
+ {
+ context.Fail(new AuthorizationFailureReason(this, "令牌已失效,请重新登录。"));
+ context.GetCurrentHttpContext().SignoutToSwagger();
+ return;
+ }
+
// 验证Token版本号
var userId = httpContext.User.FindFirst(ClaimConst.UserId)?.Value;
var tokenVersion1 = httpContext.User.FindFirst(ClaimConst.TokenVersion)?.Value;
diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/DingTalkService.cs b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/DingTalkService.cs
index c622a9cd..7c0847fe 100644
--- a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/DingTalkService.cs
+++ b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/DingTalkService.cs
@@ -72,4 +72,16 @@ public class DingTalkService : IDynamicApiController, IScoped
{
return await _dingTalkApi.DingTalkSendInteractiveCards(token, input);
}
+
+ ///
+ /// 创建并投放钉钉消息卡片 🔖
+ ///
+ ///
+ ///
+ ///
+ [DisplayName("给指定用户发送钉钉消息卡片")]
+ public async Task DingTalkCreateAndDeliver(string token, DingTalkCreateAndDeliverInput input)
+ {
+ return await _dingTalkApi.DingTalkCreateAndDeliver(token, input);
+ }
}
\ No newline at end of file
diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkCreateAndDeliverInput.cs b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkCreateAndDeliverInput.cs
new file mode 100644
index 00000000..f07ae21b
--- /dev/null
+++ b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkCreateAndDeliverInput.cs
@@ -0,0 +1,316 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Plugin.DingTalk;
+
+public class DingTalkCreateAndDeliverInput
+{
+ ///
+ /// 卡片创建者的userId
+ ///
+ public string? userId { get; set; }
+
+ ///
+ /// 卡片内容模板ID
+ ///
+ [Required]
+ public string cardTemplateId { get; set; }
+
+ ///
+ /// 外部卡片实例Id
+ ///
+ [Required]
+ public string outTrackId { get; set; }
+
+ ///
+ /// 卡片回调的类型:STREAM:stream模式 HTTP:http模式
+ ///
+ public string? callbackType { get; set; }
+
+ ///
+ /// 卡片回调HTTP模式时的路由 Key,用于查询注册的 callbackUrl。
+ ///
+ public string? callbackRouteKey { get; set; }
+
+ ///
+ /// 卡片数据
+ ///
+ [Required]
+ public DingTalk_CardData cardData { get; set; }
+
+ ///
+ /// 用户的私有数据
+ ///
+ public PrivateData? crivateData { get; set; }
+
+ ///
+ /// 动态数据源配置
+ ///
+ public OpenDynamicDataConfig? openDynamicDataConfig { get; set; }
+
+ ///
+ /// IM单聊酷应用场域信息
+ ///
+ public OpenSpaceModel? imSingleOpenSpaceModel { get; set; }
+
+ ///
+ /// IM群聊场域信息。
+ ///
+ public OpenSpaceModel? imGroupOpenSpaceModel { get; set; }
+
+ ///
+ /// IM机器人单聊场域信息。
+ ///
+ public OpenSpaceModel? imRobotOpenSpaceModel { get; set; }
+
+ ///
+ /// 协作场域信息
+ ///
+ public OpenSpaceModel? coFeedOpenSpaceModel { get; set; }
+
+ ///
+ /// 吊顶场域信息
+ ///
+ public OpenSpaceModel? topOpenSpaceModel { get; set; }
+
+ ///
+ /// 表示场域及其场域id
+ ///
+ ///
+ /// 其格式为dtv1.card//spaceType1.spaceId1;spaceType2.spaceId2_1;spaceType2.spaceId2_2;spaceType3.spaceId3
+ ///
+ [Required]
+ public string openSpaceId { get; set; }
+
+ ///
+ /// 单聊酷应用场域投放参数。
+ ///
+ public DingTalkOpenDeliverModel? imSingleOpenDeliverModel { get; set; }
+
+ ///
+ /// 群聊投放参数。
+ ///
+ public DingTalkOpenDeliverModel? imGroupOpenDeliverModel { get; set; }
+
+ ///
+ /// IM机器人单聊投放参数。
+ ///
+ public DingTalkOpenDeliverModel? imRobotOpenDeliverModel { get; set; }
+
+ ///
+ /// 吊顶投放参数。
+ ///
+ public DingTalkOpenDeliverModel? topOpenDeliverModel { get; set; }
+
+ ///
+ /// 协作投放参数。
+ ///
+ public DingTalkOpenDeliverModel? coFeedOpenDeliverModel { get; set; }
+
+ ///
+ /// 文档投放参数
+ ///
+ public DingTalkOpenDeliverModel? docOpenDeliverModel { get; set; }
+
+ ///
+ /// 用户userId类型:1(默认):userId模式 2:unionId模式
+ ///
+ public int UserIdType { get; set; }
+}
+
+public class DingTalk_CardData
+{
+ public DingTalk_CardParamMap cardParamMap { get; set; }
+}
+
+///
+/// 卡片模板内容替换参数
+///
+public class DingTalk_CardParamMap
+{
+ ///
+ /// 片模板内容替换参数
+ ///
+ [Newtonsoft.Json.JsonProperty("sys_full_json_obj")]
+ [System.Text.Json.Serialization.JsonPropertyName("sys_full_json_obj")]
+ public string sysFullJsonObj { get; set; }
+}
+
+public class PrivateData
+{
+ public Dictionary key { get; set; } = new Dictionary();
+}
+
+public class OpenDynamicDataConfig
+{
+ ///
+ /// 动态数据源配置列表。
+ ///
+ public List? dynamicDataSourceConfigs { get; set; }
+}
+
+public class DynamicDataSourceConfig
+{
+ ///
+ /// 数据源的唯一 ID, 调用方指定。
+ ///
+ public string? dynamicDataSourceId { get; set; }
+
+ ///
+ /// 回调数据源时回传的固定参数。 示例
+ ///
+ public Dictionary? constParams { get; set; }
+
+ ///
+ /// 数据源拉取配置。
+ ///
+ public PullConfig? pullConfig { get; set; }
+}
+
+public class PullConfig
+{
+ ///
+ /// 拉取策略,可选值:NONE:不拉取,无动态数据 INTERVAL:间隔拉取ONCE:只拉取一次
+ ///
+ public string pullStrategy { get; set; }
+
+ ///
+ /// 拉取的间隔时间。
+ ///
+ public int interval { get; set; }
+
+ ///
+ /// 拉取的间隔时间的单位, 可选值:SECONDS:秒 MINUTES:分钟 HOURS:小时 DAYS:天
+ ///
+ public string timeUnit { get; set; }
+}
+
+public class OpenSpaceModel
+{
+ ///
+ /// 吊顶场域属性,通过增加spaeType使卡片支持吊顶场域。
+ ///
+ public string? spaceType { get; set; }
+
+ ///
+ /// 卡片标题。
+ ///
+ public string? title { get; set; }
+
+ ///
+ /// 酷应用编码。
+ ///
+ public string? coolAppCode { get; set; }
+
+ ///
+ /// 是否支持转发, 默认false。
+ ///
+ public bool? supportForward { get; set; }
+
+ ///
+ /// 支持国际化的LastMessage。
+ ///
+ public Dictionary? lastMessageI18n { get; set; }
+
+ ///
+ /// 支持卡片消息可被搜索字段。
+ ///
+ public SearchSupport? searchSupport { get; set; }
+
+ ///
+ /// 通知信息。
+ ///
+ public Notification? notification { get; set; }
+}
+
+public class SearchSupport
+{
+ ///
+ /// 类型的icon,供搜索展示使用。
+ ///
+ public string searchIcon { get; set; }
+
+ ///
+ /// 卡片类型名。
+ ///
+ public string searchTypeName { get; set; }
+
+ ///
+ /// 供消息展示与搜索的字段。
+ ///
+ public string searchDesc { get; set; }
+}
+
+public class Notification
+{
+ ///
+ /// 供消息展示与搜索的字段。
+ ///
+ public string alertContent { get; set; }
+
+ ///
+ /// 是否关闭推送通知:true:关闭 false:不关闭
+ ///
+ public bool notificationOff { get; set; }
+}
+
+public class DingTalkOpenDeliverModel
+{
+ ///
+ /// 用于发送卡片的机器人编码。
+ ///
+ public string robotCode { get; set; }
+
+ ///
+ /// 消息@人。格式:{"key":"value"}。key:用户的userId value:用户名
+ ///
+ public Dictionary atUserIds { get; set; }
+
+ ///
+ /// 指定接收人的userId。
+ ///
+ public List recipients { get; set; }
+
+ ///
+ /// 扩展字段,示例如下:{"key":"value"}
+ ///
+ public Dictionary extension { get; set; }
+
+ ///
+ /// IM机器人单聊若未设置其他投放属性,需设置spaeType为IM_ROBOT。
+ ///
+ public string spaceType { get; set; }
+
+ ///
+ /// 过期时间戳。若使用topOpenDeliverModel对象,则该字段必填。
+ ///
+ public long expiredTimeMillis { get; set; }
+
+ ///
+ /// 可以查看该吊顶卡片的userId。
+ ///
+ public List userIds { get; set; }
+
+ ///
+ /// 可以查看该吊顶卡片的设备:android|ios|win|mac。
+ ///
+ public List platforms { get; set; }
+
+ ///
+ /// 业务标识。
+ ///
+ public string bizTag { get; set; }
+
+ ///
+ /// 协作场域下的排序时间。
+ ///
+ public long gmtTimeLine { get; set; }
+
+ ///
+ /// 员工userId信息
+ ///
+ public string userId { get; set; }
+}
\ No newline at end of file
diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkCreateAndDeliverOutput.cs b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkCreateAndDeliverOutput.cs
new file mode 100644
index 00000000..159468a7
--- /dev/null
+++ b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkCreateAndDeliverOutput.cs
@@ -0,0 +1,32 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Plugin.DingTalk;
+
+public class DingTalkCreateAndDeliverOutput
+{
+ ///
+ /// 返回结果
+ ///
+ public bool success { get; set; }
+
+ ///
+ /// 创建卡片结果
+ ///
+ public DingTalkCreateAndDeliverResult result { get; set; }
+
+ public string code { get; set; }
+ public string requestid { get; set; }
+ public string message { get; set; }
+}
+
+public class DingTalkCreateAndDeliverResult
+{
+ ///
+ /// 用于业务方后续查看已读列表的查询key
+ ///
+ public string processQueryKey { get; set; }
+}
\ No newline at end of file
diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleListOutput.cs b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleListOutput.cs
new file mode 100644
index 00000000..cfaeca94
--- /dev/null
+++ b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleListOutput.cs
@@ -0,0 +1,24 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Plugin.DingTalk;
+
+public class DingTalkRoleListOutput
+{
+ ///
+ /// 是否还有更多数据
+ ///
+ [JsonProperty("hasMore")]
+ [System.Text.Json.Serialization.JsonPropertyName("hasMore")]
+ public bool hasMore { get; set; }
+
+ ///
+ /// 角色组列表
+ ///
+ [JsonProperty("list")]
+ [System.Text.Json.Serialization.JsonPropertyName("list")]
+ public List list { get; set; }
+}
\ No newline at end of file
diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleListResult.cs b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleListResult.cs
new file mode 100644
index 00000000..2775ea49
--- /dev/null
+++ b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleListResult.cs
@@ -0,0 +1,22 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Plugin.DingTalk;
+
+public class DingTalkRoleListResult
+{
+ [JsonProperty("groupId")]
+ [System.Text.Json.Serialization.JsonPropertyName("groupId")]
+ public long groupId { get; set; }
+
+ [JsonProperty("name")]
+ [System.Text.Json.Serialization.JsonPropertyName("name")]
+ public string name { get; set; }
+
+ [JsonProperty("roles")]
+ [System.Text.Json.Serialization.JsonPropertyName("roles")]
+ public List roles { get; set; }
+}
\ No newline at end of file
diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleResult.cs b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleResult.cs
new file mode 100644
index 00000000..0040e540
--- /dev/null
+++ b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleResult.cs
@@ -0,0 +1,18 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Plugin.DingTalk;
+
+public class DingTalkRoleResult
+{
+ [JsonProperty("id")]
+ [System.Text.Json.Serialization.JsonPropertyName("id")]
+ public long id { get; set; }
+
+ [JsonProperty("name")]
+ [System.Text.Json.Serialization.JsonPropertyName("name")]
+ public string name { get; set; }
+}
\ No newline at end of file
diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleSimplelistOutput.cs b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleSimplelistOutput.cs
new file mode 100644
index 00000000..c9ac44bb
--- /dev/null
+++ b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleSimplelistOutput.cs
@@ -0,0 +1,24 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Plugin.DingTalk;
+
+public class DingTalkRoleSimplelistOutput
+{
+ ///
+ /// 是否还有更多数据
+ ///
+ [JsonProperty("hasMore")]
+ [System.Text.Json.Serialization.JsonPropertyName("hasMore")]
+ public bool hasMore { get; set; }
+
+ ///
+ /// 角色组列表
+ ///
+ [JsonProperty("list")]
+ [System.Text.Json.Serialization.JsonPropertyName("list")]
+ public List list { get; set; }
+}
\ No newline at end of file
diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleSimplelistResult.cs b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleSimplelistResult.cs
new file mode 100644
index 00000000..546d28fd
--- /dev/null
+++ b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleSimplelistResult.cs
@@ -0,0 +1,18 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Plugin.DingTalk;
+
+public class DingTalkRoleSimplelistResult
+{
+ [JsonProperty("userid")]
+ [System.Text.Json.Serialization.JsonPropertyName("userid")]
+ public string userid { get; set; }
+
+ [JsonProperty("name")]
+ [System.Text.Json.Serialization.JsonPropertyName("name")]
+ public string name { get; set; }
+}
\ No newline at end of file
diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/GetDingTalkCurrentRoleListInput.cs b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/GetDingTalkCurrentRoleListInput.cs
new file mode 100644
index 00000000..df6b2ad7
--- /dev/null
+++ b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/GetDingTalkCurrentRoleListInput.cs
@@ -0,0 +1,20 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Plugin.DingTalk;
+
+public class GetDingTalkCurrentRoleListInput
+{
+ ///
+ /// 分页游标,从0开始。根据返回结果里的next_cursor是否为空来判断是否还有下一页,且再次调用时offset设置成next_cursor的值。
+ ///
+ public int? Offset { get; set; }
+
+ ///
+ /// 分页大小,最大50。
+ ///
+ public int? Size { get; set; }
+}
\ No newline at end of file
diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/GetDingTalkCurrentRoleSimplelistInput.cs b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/GetDingTalkCurrentRoleSimplelistInput.cs
new file mode 100644
index 00000000..fa5a30fd
--- /dev/null
+++ b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/GetDingTalkCurrentRoleSimplelistInput.cs
@@ -0,0 +1,25 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Plugin.DingTalk;
+
+public class GetDingTalkCurrentRoleSimplelistInput
+{
+ ///
+ /// 角色id
+ ///
+ public long role_id { get; set; }
+
+ ///
+ /// 分页游标,从0开始。根据返回结果里的next_cursor是否为空来判断是否还有下一页,且再次调用时offset设置成next_cursor的值。
+ ///
+ public int? Offset { get; set; }
+
+ ///
+ /// 分页大小,最大50。
+ ///
+ public int? Size { get; set; }
+}
\ No newline at end of file
diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/IDingTalkApi.cs b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/IDingTalkApi.cs
index 0db3c320..4695433e 100644
--- a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/IDingTalkApi.cs
+++ b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/IDingTalkApi.cs
@@ -25,7 +25,7 @@ public interface IDingTalkApi : IHttpDeclarative
///
[Post("https://oapi.dingtalk.com/topapi/smartwork/hrm/employee/queryonjob")]
Task> GetDingTalkCurrentEmployeesList([Query] string access_token,
- [Body, Required] GetDingTalkCurrentEmployeesListInput input);
+ [Body(ContentType = "application/json", UseStringContent = true), Required] GetDingTalkCurrentEmployeesListInput input);
///
/// 获取员工花名册字段信息
@@ -35,7 +35,7 @@ public interface IDingTalkApi : IHttpDeclarative
///
[Post("https://oapi.dingtalk.com/topapi/smartwork/hrm/employee/v2/list")]
Task>> GetDingTalkCurrentEmployeesRosterList([Query] string access_token,
- [Body, Required] GetDingTalkCurrentEmployeesRosterListInput input);
+ [Body(ContentType = "application/json", UseStringContent = true), Required] GetDingTalkCurrentEmployeesRosterListInput input);
///
/// 发送钉钉互动卡片
@@ -43,10 +43,15 @@ public interface IDingTalkApi : IHttpDeclarative
/// 调用该接口的访问凭证
///
///
+ ///
+ /// 钉钉官方文档显示接口不再支持新应用接入, 已接入的应用可继续调用
+ /// 推荐更新接口https://open.dingtalk.com/document/orgapp/create-and-deliver-cards?spm=ding_open_doc.document.0.0.67fc50988Pf0mc
+ ///
[Post("https://api.dingtalk.com/v1.0/im/interactiveCards/send")]
+ [Obsolete]
Task DingTalkSendInteractiveCards(
[Header("x-acs-dingtalk-access-token")] string token,
- [Body] DingTalkSendInteractiveCardsInput input);
+ [Body(ContentType = "application/json", UseStringContent = true)] DingTalkSendInteractiveCardsInput input);
///
/// 获取钉钉卡片消息读取状态
@@ -58,4 +63,35 @@ public interface IDingTalkApi : IHttpDeclarative
Task GetDingTalkCardMessageReadStatus(
[Header("x-acs-dingtalk-access-token")] string token,
[Query] GetDingTalkCardMessageReadStatusInput input);
+
+ ///
+ /// 获取角色列表
+ ///
+ /// 调用该接口的应用凭证
+ ///
+ ///
+ [Post("https://oapi.dingtalk.com/topapi/role/list")]
+ Task> GetDingTalkRoleList([Query] string access_token,
+ [Body(ContentType = "application/json", UseStringContent = true), Required] GetDingTalkCurrentRoleListInput input);
+
+ ///
+ /// 获取指定角色的员工列表
+ ///
+ /// 调用该接口的应用凭证
+ ///
+ ///
+ [Post("https://oapi.dingtalk.com/topapi/role/simplelist")]
+ Task> GetDingTalkRoleSimplelist([Query] string access_token,
+ [Body(ContentType = "application/json", UseStringContent = true), Required] GetDingTalkCurrentRoleSimplelistInput input);
+
+ ///
+ /// 创建并投放钉钉消息卡片
+ ///
+ ///
+ ///
+ ///
+ [Post("https://api.dingtalk.com/v1.0/card/instances/createAndDeliver")]
+ Task DingTalkCreateAndDeliver(
+ [Header("x-acs-dingtalk-access-token")] string token,
+ [Body(ContentType = "application/json", UseStringContent = true)] DingTalkCreateAndDeliverInput input);
}
\ No newline at end of file
diff --git a/README.md b/README.md
index 36d16b61..5271ffb3 100644
--- a/README.md
+++ b/README.md
@@ -89,6 +89,7 @@
23. ES 日志:通过 NEST 组件实现日志存取到 Elasticsearch 日志系统。
24. 开放授权:支持OAuth 2.0开放标准授权登录,比如微信。
25. APIJSON:适配腾讯APIJSON协议,支持后端0代码,[使用文档](https://github.com/liaozb/APIJSON.NET)。
+26. 数据库视图:基于SqlSugar生成查询SQL + 表实体维护视图,可维护性更强。
## 🎀捐赠支持
```
diff --git a/Web/package.json b/Web/package.json
index 2b446a38..b6a52890 100644
--- a/Web/package.json
+++ b/Web/package.json
@@ -2,7 +2,7 @@
"name": "admin.net.pro",
"type": "module",
"version": "2.4.33",
- "lastBuildTime": "2025.05.28",
+ "lastBuildTime": "2025.05.29",
"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",
"author": "zuohuaijun",
"license": "MIT",
@@ -68,7 +68,7 @@
"splitpanes": "^4.0.4",
"vcrontab-3": "^3.3.22",
"vform3-builds": "^3.0.10",
- "vue": "^3.5.15",
+ "vue": "^3.5.16",
"vue-clipboard3": "^2.0.0",
"vue-demi": "0.14.10",
"vue-draggable-plus": "^0.6.0",
@@ -89,14 +89,14 @@
"@iconify/vue": "^5.0.0",
"@plugin-web-update-notification/vite": "^2.0.0",
"@types/lodash-es": "^4.17.12",
- "@types/node": "^22.15.23",
+ "@types/node": "^22.15.24",
"@types/nprogress": "^0.2.3",
"@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^8.33.0",
"@typescript-eslint/parser": "^8.33.0",
"@vitejs/plugin-vue": "^5.2.4",
"@vitejs/plugin-vue-jsx": "^4.2.0",
- "@vue/compiler-sfc": "^3.5.15",
+ "@vue/compiler-sfc": "^3.5.16",
"code-inspector-plugin": "^0.20.11",
"eslint": "^9.27.0",
"eslint-plugin-vue": "^10.1.0",
@@ -104,7 +104,7 @@
"less": "^4.3.0",
"openapi-ts-request": "^1.5.0",
"prettier": "^3.5.3",
- "rollup-plugin-visualizer": "^6.0.0",
+ "rollup-plugin-visualizer": "^6.0.1",
"sass": "^1.89.0",
"terser": "^5.40.0",
"typescript": "^5.8.3",
diff --git a/Web/src/views/system/tenant/index.vue b/Web/src/views/system/tenant/index.vue
index 4da775c7..a9de8c55 100644
--- a/Web/src/views/system/tenant/index.vue
+++ b/Web/src/views/system/tenant/index.vue
@@ -199,7 +199,7 @@ const resetQuery = async () => {
// 打开新增页面
const handleAdd = () => {
state.title = '添加租户';
- editTenantRef.value?.openDialog({ tenantType: 0, orderNo: 100 });
+ editTenantRef.value?.openDialog({ tenantType: 0, orderNo: 100, host: '' });
};
// 打开编辑页面