From 50f024644b7748a168ea890f9e5f06b6e3192c3c Mon Sep 17 00:00:00 2001 From: zuohuaijun Date: Thu, 29 May 2025 13:20:28 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=98=8E1=E3=80=81=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E9=92=89=E9=92=89=E6=8E=A5=E5=8F=A3=E6=9C=8D=E5=8A=A1=20=202?= =?UTF-8?q?=E3=80=81=E4=BC=98=E5=8C=96=E8=A1=8C=E6=94=BF=E5=8C=BA=E5=88=92?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E6=9C=8D=E5=8A=A1=20=203=E3=80=81=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0token=E9=BB=91=E5=90=8D=E5=8D=95=20=204=E3=80=81?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E6=B6=88=E8=B4=B9=E5=A2=9E=E5=8A=A0=E9=87=8D?= =?UTF-8?q?=E8=AF=95=20=205=E3=80=81=E4=BC=98=E5=8C=96=E7=A7=9F=E6=88=B7?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E6=9C=8D=E5=8A=A1=20=206=E3=80=81=E5=8D=87?= =?UTF-8?q?=E7=BA=A7=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Configuration/Database.Development.json | 2 +- .../Admin.NET.Core/Admin.NET.Core.csproj | 6 +- Admin.NET/Admin.NET.Core/Const/CacheConst.cs | 5 + Admin.NET/Admin.NET.Core/Entity/SysRegion.cs | 2 +- .../Admin.NET.Core/EventBus/EventConsumer.cs | 16 +- .../Service/Auth/SysAuthService.cs | 11 +- .../Service/Region/SysRegionService.cs | 8 +- .../Service/Tenant/SysTenantService.cs | 4 +- .../Admin.NET.Web.Core/Handlers/JwtHandler.cs | 9 + .../Service/DingTalkService.cs | 12 + .../Dto/DingTalkCreateAndDeliverInput.cs | 316 ++++++++++++++++++ .../Dto/DingTalkCreateAndDeliverOutput.cs | 32 ++ .../Service/Dto/DingTalkRoleListOutput.cs | 24 ++ .../Service/Dto/DingTalkRoleListResult.cs | 22 ++ .../Service/Dto/DingTalkRoleResult.cs | 18 + .../Dto/DingTalkRoleSimplelistOutput.cs | 24 ++ .../Dto/DingTalkRoleSimplelistResult.cs | 18 + .../Dto/GetDingTalkCurrentRoleListInput.cs | 20 ++ .../GetDingTalkCurrentRoleSimplelistInput.cs | 25 ++ .../Service/IDingTalkApi.cs | 42 ++- README.md | 1 + Web/package.json | 10 +- Web/src/views/system/tenant/index.vue | 2 +- 23 files changed, 605 insertions(+), 24 deletions(-) create mode 100644 Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkCreateAndDeliverInput.cs create mode 100644 Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkCreateAndDeliverOutput.cs create mode 100644 Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleListOutput.cs create mode 100644 Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleListResult.cs create mode 100644 Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleResult.cs create mode 100644 Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleSimplelistOutput.cs create mode 100644 Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/DingTalkRoleSimplelistResult.cs create mode 100644 Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/GetDingTalkCurrentRoleListInput.cs create mode 100644 Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/Dto/GetDingTalkCurrentRoleSimplelistInput.cs 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: '' }); }; // 打开编辑页面