diff --git a/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj b/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj index df59286c..ae198a5b 100644 --- a/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj +++ b/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj @@ -26,7 +26,7 @@ - + @@ -37,7 +37,7 @@ - + diff --git a/Admin.NET/Admin.NET.Core/Const/CacheConst.cs b/Admin.NET/Admin.NET.Core/Const/CacheConst.cs index 758e8ce0..6114a8d7 100644 --- a/Admin.NET/Admin.NET.Core/Const/CacheConst.cs +++ b/Admin.NET/Admin.NET.Core/Const/CacheConst.cs @@ -100,4 +100,9 @@ public class CacheConst /// 系统字典缓存 /// public const string KeyDict = "sys_dict:"; + + /// + /// Excel临时文件缓存 + /// + public const string KeyExcelTemp = "sys_excel_temp:"; } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/GenderEnum.cs b/Admin.NET/Admin.NET.Core/Enum/GenderEnum.cs index 3284532c..5b5a7904 100644 --- a/Admin.NET/Admin.NET.Core/Enum/GenderEnum.cs +++ b/Admin.NET/Admin.NET.Core/Enum/GenderEnum.cs @@ -7,26 +7,32 @@ namespace Admin.NET.Core; /// -/// 性别枚举 +/// 性别枚举(GB/T 2261.1-2003) /// [Description("性别枚举")] public enum GenderEnum { /// - /// 男 + /// 未知的性别 /// - [Description("男")] + [Description("未知的性别")] + Unknown = 0, + + /// + /// 男性 + /// + [Description("男性")] Male = 1, /// - /// 女 + /// 女性 /// - [Description("女")] + [Description("女性")] Female = 2, /// - /// 其他 + /// 未说明的性别 /// - [Description("其他")] - Other = 3 + [Description("未说明的性别")] + Unspecified = 9 } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Job/EnumToDictJob.cs b/Admin.NET/Admin.NET.Core/Job/EnumToDictJob.cs index 95848948..3e0d5661 100644 --- a/Admin.NET/Admin.NET.Core/Job/EnumToDictJob.cs +++ b/Admin.NET/Admin.NET.Core/Job/EnumToDictJob.cs @@ -70,7 +70,7 @@ public class EnumToDictJob : IJob }); try { - db.Ado.BeginTran(); + db.BeginTran(); if (uSysDictType.Count > 0) await db.Updateable(uSysDictType).ExecuteCommandAsync(stoppingToken); @@ -78,11 +78,11 @@ public class EnumToDictJob : IJob if (uSysDictData.Count > 0) await db.Updateable(uSysDictData).ExecuteCommandAsync(stoppingToken); - db.Ado.CommitTran(); + db.CommitTran(); } catch (Exception error) { - db.Ado.RollbackTran(); + db.RollbackTran(); Log.Error($"{context.Trigger.Description}更新枚举转换字典入库错误:" + _jsonSerializer.Serialize(error)); throw new Exception($"{context.Trigger.Description}更新枚举转换字典入库错误"); } @@ -123,7 +123,7 @@ public class EnumToDictJob : IJob }); try { - db.Ado.BeginTran(); + db.BeginTran(); if (iDictType.Count > 0) await db.Insertable(iDictType).ExecuteCommandAsync(stoppingToken); @@ -131,11 +131,11 @@ public class EnumToDictJob : IJob if (iDictData.Count > 0) await db.Insertable(iDictData).ExecuteCommandAsync(stoppingToken); - db.Ado.CommitTran(); + db.CommitTran(); } catch (Exception error) { - db.Ado.RollbackTran(); + db.RollbackTran(); Log.Error($"{context.Trigger.Description}新增枚举转换字典入库错误:" + _jsonSerializer.Serialize(error)); throw new Exception($"{context.Trigger.Description}新增枚举转换字典入库错误"); } diff --git a/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs b/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs index 6ddead17..46c79d2a 100644 --- a/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs @@ -144,6 +144,10 @@ public class SysCacheService : IDynamicApiController, ISingleton [DisplayName("获取缓存值")] public object GetValue(string key) { + // 若Key经过URL编码则进行解码 + if (Regex.IsMatch(key, @"%[0-9a-fA-F]{2}")) + key = HttpUtility.UrlDecode(key); + return _cacheProvider.Cache == Cache.Default ? _cacheProvider.Cache.Get($"{_cacheOptions.Prefix}{key}") : _cacheProvider.Cache.Get($"{_cacheOptions.Prefix}{key}"); diff --git a/Admin.NET/Admin.NET.Core/Service/Common/SysCommonService.cs b/Admin.NET/Admin.NET.Core/Service/Common/SysCommonService.cs index 91813ae2..792fc444 100644 --- a/Admin.NET/Admin.NET.Core/Service/Common/SysCommonService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Common/SysCommonService.cs @@ -121,4 +121,23 @@ public class SysCommonService : IDynamicApiController, ITransient } return apiList; } + + /// + /// 下载标记错误的临时 Excel(全局) + /// + /// + [DisplayName("下载标记错误的临时 Excel")] + public async Task DownloadErrorExcelTemp([FromQuery] string fileName = null) + { + var userId = App.User?.FindFirst(ClaimConst.UserId)?.Value; + var resultStream = App.GetRequiredService().Get(CacheConst.KeyExcelTemp + userId); + + if (resultStream == null) + throw Oops.Oh("错误标记文件已过期。"); + + return await Task.FromResult(new FileStreamResult(resultStream, "application/octet-stream") + { + FileDownloadName = $"{(string.IsNullOrEmpty(fileName) ? "错误标记_" + DateTime.Now.ToString("yyyyMMddhhmmss") : fileName)}.xlsx" + }); + } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictDataInput.cs b/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictDataInput.cs index eccdff36..b9a1b183 100644 --- a/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictDataInput.cs +++ b/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictDataInput.cs @@ -11,6 +11,7 @@ public class DictDataInput : BaseIdInput /// /// 状态 /// + [Dict("StatusEnum")] public StatusEnum Status { get; set; } } diff --git a/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictTypeInput.cs b/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictTypeInput.cs index 7884ada2..fc6327e7 100644 --- a/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictTypeInput.cs +++ b/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictTypeInput.cs @@ -11,6 +11,7 @@ public class DictTypeInput : BaseIdInput /// /// 状态 /// + [Dict("StatusEnum")] public StatusEnum Status { get; set; } } diff --git a/Admin.NET/Admin.NET.Core/Service/Dict/SysDictDataService.cs b/Admin.NET/Admin.NET.Core/Service/Dict/SysDictDataService.cs index dd99cf59..dc6dfc4c 100644 --- a/Admin.NET/Admin.NET.Core/Service/Dict/SysDictDataService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Dict/SysDictDataService.cs @@ -1,4 +1,4 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 // // 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 // @@ -16,8 +16,8 @@ public class SysDictDataService : IDynamicApiController, ITransient private readonly SysCacheService _sysCacheService; private readonly SqlSugarRepository _sysDictDataRep; - public SysDictDataService(SqlSugarRepository sysDictDataRep, - SysCacheService sysCacheService) + public SysDictDataService(SqlSugarRepository sysDictDataRep + , SysCacheService sysCacheService) { _sysDictDataRep = sysDictDataRep; _sysCacheService = sysCacheService; @@ -60,8 +60,7 @@ public class SysDictDataService : IDynamicApiController, ITransient public async Task AddDictData(AddDictDataInput input) { var isExist = await _sysDictDataRep.IsAnyAsync(u => u.Code == input.Code && u.DictTypeId == input.DictTypeId); - if (isExist) - throw Oops.Oh(ErrorCodeEnum.D3003); + if (isExist) throw Oops.Oh(ErrorCodeEnum.D3003); await _sysDictDataRep.InsertAsync(input.Adapt()); } @@ -98,9 +97,7 @@ public class SysDictDataService : IDynamicApiController, ITransient [DisplayName("删除字典值")] public async Task DeleteDictData(DeleteDictDataInput input) { - var dictData = await _sysDictDataRep.GetFirstAsync(u => u.Id == input.Id); - if (dictData == null) - throw Oops.Oh(ErrorCodeEnum.D3004); + var dictData = await _sysDictDataRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D3004); var dictTypeCode = await _sysDictDataRep.AsQueryable().Where(u => u.DictTypeId == dictData.Id).Select(u => u.DictType.Code).FirstAsync(); _sysCacheService.Remove($"{CacheConst.KeyDict}{dictTypeCode}"); @@ -128,18 +125,13 @@ public class SysDictDataService : IDynamicApiController, ITransient [DisplayName("修改字典值状态")] public async Task SetStatus(DictDataInput input) { - var dictData = await _sysDictDataRep.GetFirstAsync(u => u.Id == input.Id); - if (dictData == null) - throw Oops.Oh(ErrorCodeEnum.D3004); - - if (!Enum.IsDefined(typeof(StatusEnum), input.Status)) - throw Oops.Oh(ErrorCodeEnum.D3005); + var dictData = await _sysDictDataRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D3004); var dictTypeCode = await _sysDictDataRep.AsQueryable().Where(u => u.DictTypeId == dictData.Id).Select(u => u.DictType.Code).FirstAsync(); _sysCacheService.Remove($"{CacheConst.KeyDict}{dictTypeCode}"); dictData.Status = input.Status; - await _sysDictDataRep.UpdateAsync(dictData); + await _sysDictDataRep.AsUpdateable(dictData).UpdateColumns(u => new { u.Status }, true).ExecuteCommandAsync(); } /// @@ -156,13 +148,9 @@ public class SysDictDataService : IDynamicApiController, ITransient if (dictDataList == null) { dictDataList = await _sysDictDataRep.AsQueryable() - .Where(u => u.DictTypeId == dictTypeId) - .OrderBy(u => new { u.OrderNo, u.Code }) - .ToListAsync(); - + .Where(u => u.DictTypeId == dictTypeId).OrderBy(u => new { u.OrderNo, u.Code }).ToListAsync(); _sysCacheService.Set($"{CacheConst.KeyDict}{dictType.Code}", dictDataList); } - return dictDataList; } diff --git a/Admin.NET/Admin.NET.Core/Service/Dict/SysDictTypeService.cs b/Admin.NET/Admin.NET.Core/Service/Dict/SysDictTypeService.cs index b205336c..da75928a 100644 --- a/Admin.NET/Admin.NET.Core/Service/Dict/SysDictTypeService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Dict/SysDictTypeService.cs @@ -60,10 +60,7 @@ public class SysDictTypeService : IDynamicApiController, ITransient [DisplayName("获取字典类型-值列表")] public async Task> GetDataList([FromQuery] GetDataDictTypeInput input) { - var dictType = await _sysDictTypeRep.GetFirstAsync(u => u.Code == input.Code); - if (dictType == null) - throw Oops.Oh(ErrorCodeEnum.D3000); - + var dictType = await _sysDictTypeRep.GetFirstAsync(u => u.Code == input.Code) ?? throw Oops.Oh(ErrorCodeEnum.D3000); return await _sysDictDataService.GetDictDataListByDictTypeId(dictType.Id); } @@ -77,8 +74,7 @@ public class SysDictTypeService : IDynamicApiController, ITransient public async Task AddDictType(AddDictTypeInput input) { var isExist = await _sysDictTypeRep.IsAnyAsync(u => u.Code == input.Code); - if (isExist) - throw Oops.Oh(ErrorCodeEnum.D3001); + if (isExist) throw Oops.Oh(ErrorCodeEnum.D3001); await _sysDictTypeRep.InsertAsync(input.Adapt()); } @@ -94,12 +90,10 @@ public class SysDictTypeService : IDynamicApiController, ITransient public async Task UpdateDictType(UpdateDictTypeInput input) { var isExist = await _sysDictTypeRep.IsAnyAsync(u => u.Id == input.Id); - if (!isExist) - throw Oops.Oh(ErrorCodeEnum.D3000); + if (!isExist) throw Oops.Oh(ErrorCodeEnum.D3000); isExist = await _sysDictTypeRep.IsAnyAsync(u => u.Code == input.Code && u.Id != input.Id); - if (isExist) - throw Oops.Oh(ErrorCodeEnum.D3001); + if (isExist) throw Oops.Oh(ErrorCodeEnum.D3001); _sysCacheService.Remove($"{CacheConst.KeyDict}{input.Code}"); await _sysDictTypeRep.UpdateAsync(input.Adapt()); @@ -115,9 +109,7 @@ public class SysDictTypeService : IDynamicApiController, ITransient [DisplayName("删除字典类型")] public async Task DeleteDictType(DeleteDictTypeInput input) { - var dictType = await _sysDictTypeRep.GetFirstAsync(u => u.Id == input.Id); - if (dictType == null) - throw Oops.Oh(ErrorCodeEnum.D3000); + var dictType = await _sysDictTypeRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D3000); // 删除字典值 await _sysDictTypeRep.DeleteAsync(dictType); @@ -144,17 +136,12 @@ public class SysDictTypeService : IDynamicApiController, ITransient [DisplayName("修改字典类型状态")] public async Task SetStatus(DictTypeInput input) { - var dictType = await _sysDictTypeRep.GetFirstAsync(u => u.Id == input.Id); - if (dictType == null) - throw Oops.Oh(ErrorCodeEnum.D3000); - - if (!Enum.IsDefined(typeof(StatusEnum), input.Status)) - throw Oops.Oh(ErrorCodeEnum.D3005); + var dictType = await _sysDictTypeRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D3000); _sysCacheService.Remove($"{CacheConst.KeyDict}{dictType.Code}"); dictType.Status = input.Status; - await _sysDictTypeRep.UpdateAsync(dictType); + await _sysDictTypeRep.AsUpdateable(dictType).UpdateColumns(u => new { u.Status }, true).ExecuteCommandAsync(); } /// diff --git a/Admin.NET/Admin.NET.Core/Service/Enum/SysEnumService.cs b/Admin.NET/Admin.NET.Core/Service/Enum/SysEnumService.cs index e8e204bf..a15b1138 100644 --- a/Admin.NET/Admin.NET.Core/Service/Enum/SysEnumService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Enum/SysEnumService.cs @@ -49,7 +49,7 @@ public class SysEnumService : IDynamicApiController, ITransient { string description = type.Name; var attrs = type.GetCustomAttributes(typeof(DescriptionAttribute), false); - if (attrs.Any()) + if (attrs.Length != 0) { var att = ((DescriptionAttribute[])attrs)[0]; description = att.Description; diff --git a/Admin.NET/Admin.NET.Core/Service/Job/DbJobPersistence.cs b/Admin.NET/Admin.NET.Core/Service/Job/DbJobPersistence.cs index deadcd83..03679de3 100644 --- a/Admin.NET/Admin.NET.Core/Service/Job/DbJobPersistence.cs +++ b/Admin.NET/Admin.NET.Core/Service/Job/DbJobPersistence.cs @@ -42,14 +42,14 @@ public class DbJobPersistence : IJobPersistence var jobBuilder = schedulerBuilder.GetJobBuilder(); // 加载数据库数据 - var dbDetail = await db.Queryable().FirstAsync(u => u.JobId == jobBuilder.JobId); + var dbDetail = await db.Queryable().FirstAsync(u => u.JobId == jobBuilder.JobId, stoppingToken); if (dbDetail == null) continue; // 同步数据库数据 jobBuilder.LoadFrom(dbDetail); // 获取作业的所有数据库的触发器 - var dbTriggers = await db.Queryable().Where(u => u.JobId == jobBuilder.JobId).ToListAsync(); + var dbTriggers = await db.Queryable().Where(u => u.JobId == jobBuilder.JobId).ToListAsync(stoppingToken); // 遍历所有作业触发器 foreach (var (_, triggerBuilder) in schedulerBuilder.GetEnumerable()) { @@ -73,24 +73,16 @@ public class DbJobPersistence : IJobPersistence } // 获取数据库所有通过脚本创建的作业 - var allDbScriptJobs = await db.Queryable().Where(u => u.CreateType != JobCreateTypeEnum.BuiltIn).ToListAsync(); + var allDbScriptJobs = await db.Queryable().Where(u => u.CreateType != JobCreateTypeEnum.BuiltIn).ToListAsync(stoppingToken); foreach (var dbDetail in allDbScriptJobs) { // 动态创建作业 - Type jobType; - switch (dbDetail.CreateType) + Type jobType = dbDetail.CreateType switch { - case JobCreateTypeEnum.Script: - jobType = dynamicJobCompiler.BuildJob(dbDetail.ScriptCode); - break; - - case JobCreateTypeEnum.Http: - jobType = typeof(HttpJob); - break; - - default: - throw new NotSupportedException(); - } + JobCreateTypeEnum.Script => dynamicJobCompiler.BuildJob(dbDetail.ScriptCode), + JobCreateTypeEnum.Http => typeof(HttpJob), + _ => throw new NotSupportedException(), + }; // 动态构建的 jobType 的程序集名称为随机名称,需重新设置 dbDetail.AssemblyName = jobType.Assembly.FullName!.Split(',')[0]; @@ -131,25 +123,23 @@ public class DbJobPersistence : IJobPersistence /// public async Task OnChangedAsync(PersistenceContext context) { - using (var scope = _serviceScopeFactory.CreateScope()) + using var scope = _serviceScopeFactory.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService().CopyNew(); + + var jobDetail = context.JobDetail.Adapt(); + switch (context.Behavior) { - var db = scope.ServiceProvider.GetRequiredService().CopyNew(); + case PersistenceBehavior.Appended: + await db.Insertable(jobDetail).ExecuteCommandAsync(); + break; - var jobDetail = context.JobDetail.Adapt(); - switch (context.Behavior) - { - case PersistenceBehavior.Appended: - await db.Insertable(jobDetail).ExecuteCommandAsync(); - break; + case PersistenceBehavior.Updated: + await db.Updateable(jobDetail).WhereColumns(u => new { u.JobId }).IgnoreColumns(u => new { u.Id, u.CreateType, u.ScriptCode }).ExecuteCommandAsync(); + break; - case PersistenceBehavior.Updated: - await db.Updateable(jobDetail).WhereColumns(u => new { u.JobId }).IgnoreColumns(u => new { u.Id, u.CreateType, u.ScriptCode }).ExecuteCommandAsync(); - break; - - case PersistenceBehavior.Removed: - await db.Deleteable().Where(u => u.JobId == jobDetail.JobId).ExecuteCommandAsync(); - break; - } + case PersistenceBehavior.Removed: + await db.Deleteable().Where(u => u.JobId == jobDetail.JobId).ExecuteCommandAsync(); + break; } } @@ -160,25 +150,23 @@ public class DbJobPersistence : IJobPersistence /// public async Task OnTriggerChangedAsync(PersistenceTriggerContext context) { - using (var scope = _serviceScopeFactory.CreateScope()) + using var scope = _serviceScopeFactory.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService().CopyNew(); + + var jobTrigger = context.Trigger.Adapt(); + switch (context.Behavior) { - var db = scope.ServiceProvider.GetRequiredService().CopyNew(); + case PersistenceBehavior.Appended: + await db.Insertable(jobTrigger).ExecuteCommandAsync(); + break; - var jobTrigger = context.Trigger.Adapt(); - switch (context.Behavior) - { - case PersistenceBehavior.Appended: - await db.Insertable(jobTrigger).ExecuteCommandAsync(); - break; + case PersistenceBehavior.Updated: + await db.Updateable(jobTrigger).WhereColumns(u => new { u.TriggerId, u.JobId }).IgnoreColumns(u => new { u.Id }).ExecuteCommandAsync(); + break; - case PersistenceBehavior.Updated: - await db.Updateable(jobTrigger).WhereColumns(u => new { u.TriggerId, u.JobId }).IgnoreColumns(u => new { u.Id }).ExecuteCommandAsync(); - break; - - case PersistenceBehavior.Removed: - await db.Deleteable().Where(u => u.TriggerId == jobTrigger.TriggerId && u.JobId == jobTrigger.JobId).ExecuteCommandAsync(); - break; - } + case PersistenceBehavior.Removed: + await db.Deleteable().Where(u => u.TriggerId == jobTrigger.TriggerId && u.JobId == jobTrigger.JobId).ExecuteCommandAsync(); + break; } } @@ -189,12 +177,10 @@ public class DbJobPersistence : IJobPersistence /// public async Task OnExecutionRecordAsync(PersistenceExecutionRecordContext context) { - using (var scope = _serviceScopeFactory.CreateScope()) - { - var db = scope.ServiceProvider.GetRequiredService().CopyNew(); + using var scope = _serviceScopeFactory.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService().CopyNew(); - var jobTriggerRecord = context.Timeline.Adapt(); - await db.Insertable(jobTriggerRecord).ExecuteCommandAsync(); - } + var jobTriggerRecord = context.Timeline.Adapt(); + await db.Insertable(jobTriggerRecord).ExecuteCommandAsync(); } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Job/JobClusterServer.cs b/Admin.NET/Admin.NET.Core/Service/Job/JobClusterServer.cs index fca54e02..139b2ec6 100644 --- a/Admin.NET/Admin.NET.Core/Service/Job/JobClusterServer.cs +++ b/Admin.NET/Admin.NET.Core/Service/Job/JobClusterServer.cs @@ -1,4 +1,4 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 // // 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 // diff --git a/Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs b/Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs index 4349ef44..dcf3849e 100644 --- a/Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs @@ -59,7 +59,7 @@ public class SysMenuService : IDynamicApiController, ITransient /// /// 删除登录菜单树里面的按钮 /// - private void DeleteBtnFromMenuTree(List menuList) + private static void DeleteBtnFromMenuTree(List menuList) { if (menuList == null) return; for (var i = menuList.Count - 1; i >= 0; i--) diff --git a/Admin.NET/Admin.NET.Core/Service/Role/SysRoleApiService.cs b/Admin.NET/Admin.NET.Core/Service/Role/SysRoleApiService.cs index 23d52f70..9b85f273 100644 --- a/Admin.NET/Admin.NET.Core/Service/Role/SysRoleApiService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Role/SysRoleApiService.cs @@ -27,7 +27,7 @@ public class SysRoleApiService : ITransient { await _sysRoleApiRep.DeleteAsync(u => u.RoleId == input.Id); - var roleApis = input.ApiList.Select(u => new SysRoleApi + var roleApis = input.ApiList.Where(u => !string.IsNullOrWhiteSpace(u)).Select(u => new SysRoleApi { RoleId = input.Id, Route = u diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WxOpenInput.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WxOpenInput.cs index 0ec4cede..dca1cef4 100644 --- a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WxOpenInput.cs +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WxOpenInput.cs @@ -115,4 +115,28 @@ public class AddSubscribeMessageTemplateInput /// [Required(ErrorMessage = "服务场景描述不能为空")] public string SceneDescription { get; set; } +} + +/// +/// 验证签名 +/// +public class VerifySignatureInput +{ + + /// + /// 签名 + /// + public string signature { get; set; } + /// + /// 时间戳 + /// + public string timestamp { get; set; } + /// + /// 随机数 + /// + public string nonce { get; set; } + /// + /// 随机字符串 + /// + public string echostr { get; set; } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/SysWxOpenService.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWxOpenService.cs index 9dfd2e08..1383910a 100644 --- a/Admin.NET/Admin.NET.Core/Service/Wechat/SysWxOpenService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWxOpenService.cs @@ -114,6 +114,25 @@ public class SysWxOpenService : IDynamicApiController, ITransient }; } + /// + /// 验证签名 🔖 + /// + [AllowAnonymous] + [NonUnify] + [ApiDescriptionSettings(Name = "VerifySignature"), HttpGet] + [DisplayName("验证签名")] + public string VerifySignature([FromQuery] VerifySignatureInput input) + { + + bool valid = _wechatApiClient.VerifyEventSignatureForEcho(input.timestamp, input.nonce, input.signature); + if (!valid) + { + return "fail"; + } + + return input.echostr; + } + /// /// 获取订阅消息模板列表 🔖 /// diff --git a/Admin.NET/Admin.NET.Core/Util/CommonUtil.cs b/Admin.NET/Admin.NET.Core/Util/CommonUtil.cs index 39587679..ab0910df 100644 --- a/Admin.NET/Admin.NET.Core/Util/CommonUtil.cs +++ b/Admin.NET/Admin.NET.Core/Util/CommonUtil.cs @@ -49,12 +49,12 @@ public static class CommonUtil // 代理模式:获取真正的本机地址 // X-Original-Host=原始请求 // X-Forwarded-Server=从哪里转发过来 - if (App.HttpContext.Request.Headers.ContainsKey("Origin")) // 配置成完整的路径如(结尾不要带"/"),比如 https://www.abc.com - result = $"{App.HttpContext.Request.Headers["Origin"]}"; - else if (App.HttpContext.Request.Headers.ContainsKey("X-Original")) // 配置成完整的路径如(结尾不要带"/"),比如 https://www.abc.com - result = $"{App.HttpContext.Request.Headers["X-Original"]}"; - else if (App.HttpContext.Request.Headers.ContainsKey("X-Original-Host")) - result = $"{App.HttpContext.Request.Scheme}://{App.HttpContext.Request.Headers["X-Original-Host"]}"; + if (App.HttpContext.Request.Headers.TryGetValue("Origin", out Microsoft.Extensions.Primitives.StringValues value1)) // 配置成完整的路径如(结尾不要带"/"),比如 https://www.abc.com + result = $"{value1}"; + else if (App.HttpContext.Request.Headers.TryGetValue("X-Original", out Microsoft.Extensions.Primitives.StringValues value2)) // 配置成完整的路径如(结尾不要带"/"),比如 https://www.abc.com + result = $"{value2}"; + else if (App.HttpContext.Request.Headers.TryGetValue("X-Original-Host", out Microsoft.Extensions.Primitives.StringValues value3)) + result = $"{App.HttpContext.Request.Scheme}://{value3}"; return result; } @@ -107,7 +107,7 @@ public static class CommonUtil /// public static async Task ExportExcelTemplate(string fileName = null) where T : class, new() { - IImporter importer = new ExcelImporter(); + var importer = new ExcelImporter(); var res = await importer.GenerateTemplateBytes(); return new FileContentResult(res, "application/octet-stream") { FileDownloadName = $"{(string.IsNullOrEmpty(fileName) ? typeof(T).Name : fileName)}.xlsx" }; @@ -152,9 +152,9 @@ public static class CommonUtil } var map = dict.Value.Item1; - if (map != null && map.ContainsKey(sourceVal)) + if (map != null && map.TryGetValue(sourceVal, out string value)) { - var newVal = map[sourceVal]; + var newVal = value; targeProp.SetValue(newData, newVal); } else @@ -190,7 +190,7 @@ public static class CommonUtil /// public static async Task> ImportExcelData([Required] IFormFile file) where T : class, new() { - IImporter importer = new ExcelImporter(); + var importer = new ExcelImporter(); var res = await importer.Import(file.OpenReadStream()); var message = string.Empty; if (res.HasError) @@ -203,7 +203,46 @@ public static class CommonUtil foreach (var item in drErrorInfo.FieldErrors) message += $"\r\n{item.Key}:{item.Value}(文件第{drErrorInfo.RowIndex}行)"; } - message += "字段缺失:" + string.Join(",", res.TemplateErrors.Select(m => m.RequireColumnName).ToList()); + message += "\r\n字段缺失:" + string.Join(",", res.TemplateErrors.Select(m => m.RequireColumnName).ToList()); + throw Oops.Oh("导入异常:" + message); + } + return res.Data; + } + + /// + /// 导入Excel数据并错误标记 + /// + /// + /// + /// + /// + public static async Task> ImportExcelData([Required] IFormFile file, Func, ImportResult> importResultCallback = null) where T : class, new() + { + var importer = new ExcelImporter(); + var resultStream = new MemoryStream(); + var res = await importer.Import(file.OpenReadStream(), resultStream, importResultCallback); + resultStream.Seek(0, SeekOrigin.Begin); + var userId = App.User?.FindFirst(ClaimConst.UserId)?.Value; + + App.GetRequiredService().Remove(CacheConst.KeyExcelTemp + userId); + App.GetRequiredService().Set(CacheConst.KeyExcelTemp + userId, resultStream, TimeSpan.FromMinutes(5)); + + var message = string.Empty; + if (res.HasError) + { + if (res.Exception != null) + message += $"\r\n{res.Exception.Message}"; + foreach (DataRowErrorInfo drErrorInfo in res.RowErrors) + { + int rowNum = drErrorInfo.RowIndex; + foreach (var item in drErrorInfo.FieldErrors) + message += $"\r\n{item.Key}:{item.Value}(文件第{drErrorInfo.RowIndex}行)"; + } + if (res.TemplateErrors.Count > 0) + message += "\r\n字段缺失:" + string.Join(",", res.TemplateErrors.Select(m => m.RequireColumnName).ToList()); + + if (message.Length > 200) + message = string.Concat(message.AsSpan(0, 200), "...\r\n异常过多,建议下载错误标记文件查看详细错误信息并重新导入。"); throw Oops.Oh("导入异常:" + message); } return res.Data; @@ -300,7 +339,7 @@ public static class CommonUtil else { propMappings.Add(propertyInfo.Name, new Tuple, PropertyInfo, PropertyInfo>( - null, propertyInfo, tTargetProps.ContainsKey(propertyInfo.Name) ? tTargetProps[propertyInfo.Name] : null)); + null, propertyInfo, tTargetProps.TryGetValue(propertyInfo.Name, out PropertyInfo value) ? value : null)); } } @@ -342,34 +381,34 @@ public static class CommonUtil else { propMappings.Add(propertyInfo.Name, new Tuple, PropertyInfo, PropertyInfo>( - null, sourceProps.ContainsKey(propertyInfo.Name) ? sourceProps[propertyInfo.Name] : null, propertyInfo)); + null, sourceProps.TryGetValue(propertyInfo.Name, out PropertyInfo value) ? value : null, propertyInfo)); } } return propMappings; } - /// - /// 获取属性映射 - /// - /// - /// 整理导入对象的 属性名称, 字典数据,原属性信息,目标属性信息 - private static Dictionary> GetExportDicttMap() where TTarget : new() - { - // 整理导入对象的属性名称,目标属性名,字典Code - var propMappings = new Dictionary>(); - var tTargetProps = typeof(TTarget).GetProperties(); - foreach (var propertyInfo in tTargetProps) - { - var attrs = propertyInfo.GetCustomAttribute(); - if (attrs != null && !string.IsNullOrWhiteSpace(attrs.TypeCode)) - { - propMappings.Add(propertyInfo.Name, new Tuple(attrs.TargetPropName, attrs.TypeCode)); - } - } + ///// + ///// 获取属性映射 + ///// + ///// + ///// 整理导入对象的 属性名称, 字典数据,原属性信息,目标属性信息 + //private static Dictionary> GetExportDicttMap() where TTarget : new() + //{ + // // 整理导入对象的属性名称,目标属性名,字典Code + // var propMappings = new Dictionary>(); + // var tTargetProps = typeof(TTarget).GetProperties(); + // foreach (var propertyInfo in tTargetProps) + // { + // var attrs = propertyInfo.GetCustomAttribute(); + // if (attrs != null && !string.IsNullOrWhiteSpace(attrs.TypeCode)) + // { + // propMappings.Add(propertyInfo.Name, new Tuple(attrs.TargetPropName, attrs.TypeCode)); + // } + // } - return propMappings; - } + // return propMappings; + //} /// /// 解析IP地址 diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/index.vue.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/index.vue.vm index 25e4cc6b..50484b16 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/index.vue.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/index.vue.vm @@ -154,11 +154,7 @@ @: @: {{dv('@(@column.DictTypeCode)', row.@column.LowerPropertyName)?.name}} @: - } else if(@column.EffectType == "DatePicker") { - @: - @:{{ formatDate(new Date(row.@(@column.LowerPropertyName)), 'YYYY-mm-dd HH:MM:SS') }} - @: - } + } } } @@ -296,7 +292,7 @@ const options = useVxeTable<@(@Model.ClassName)>( } else if(@column.EffectType == "EnumSelector") { @:{ field: '@column.LowerPropertyName', title: '@column.ColumnComment', minWidth: 100, showOverflow: 'tooltip', slots: { default: 'row_@column.LowerPropertyName' }, @whethersortable }, } else if(@column.EffectType == "DatePicker") { - @:{ field: '@column.LowerPropertyName', title: '@column.ColumnComment', minWidth: 100, showOverflow: 'tooltip', slots: { default: 'row_@column.LowerPropertyName' }, @whethersortable }, + @:{ field: '@column.LowerPropertyName', title: '@column.ColumnComment', minWidth: 100, showOverflow: 'tooltip', formatter: ({ cellValue }) => formatDate(new Date(cellValue), 'YYYY-mm-dd HH:MM:SS'), @whethersortable }, } else { @:{ field: '@column.LowerPropertyName', title: '@column.ColumnComment', minWidth: 100, showOverflow: 'tooltip', @whethersortable}, } @@ -342,7 +338,7 @@ const handleQuery = async (reset = false) => { }; // 重置操作 -const resetQuery = () => { +const resetQuery = async () => { state.queryParams.searchKey = undefined, @if(Model.QueryWhetherList.Count > 0) { @foreach (var column in Model.QueryWhetherList) { diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Const/DingTalkConst.cs b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Const/DingTalkConst.cs index 9df79af4..4547390e 100644 --- a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Const/DingTalkConst.cs +++ b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Const/DingTalkConst.cs @@ -36,10 +36,12 @@ public class DingTalkConst /// 主部门Id /// public const string DeptId = "sys00-mainDeptId"; + /// /// 主部门 /// public const string Dept = "sys00-mainDept"; + /// /// 职位 /// diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Entity/DingTalkUser.cs b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Entity/DingTalkUser.cs index fee3251c..fe2f0137 100644 --- a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Entity/DingTalkUser.cs +++ b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Entity/DingTalkUser.cs @@ -73,23 +73,25 @@ public class DingTalkUser : EntityBase [SugarColumn(ColumnDescription = "工号", Length = 16)] [MaxLength(16)] public string? JobNumber { get; set; } + /// /// 主部门Id /// [SugarColumn(ColumnDescription = "主部门Id", Length = 16)] [MaxLength(16)] public string? DeptId { get; set; } + /// /// 主部门 /// [SugarColumn(ColumnDescription = "主部门", Length = 16)] [MaxLength(16)] public string? Dept { get; set; } + /// /// 职位 /// [SugarColumn(ColumnDescription = "职位", Length = 16)] [MaxLength(16)] public string? Position { get; set; } - } \ No newline at end of file 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 02322eda..71365f0c 100644 --- a/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/DingTalkService.cs +++ b/Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Service/DingTalkService.cs @@ -43,7 +43,7 @@ public class DingTalkService : IDynamicApiController, IScoped /// /// /// - [DisplayName("获取在职员工列表")] + [HttpPost, DisplayName("获取在职员工列表")] public async Task> GetDingTalkCurrentEmployeesList(string access_token, [Required] GetDingTalkCurrentEmployeesListInput input) { return await _dingTalkApi.GetDingTalkCurrentEmployeesList(access_token, input); @@ -55,7 +55,7 @@ public class DingTalkService : IDynamicApiController, IScoped /// /// /// - [DisplayName("获取员工花名册字段信息")] + [HttpPost, DisplayName("获取员工花名册字段信息")] public async Task>> GetDingTalkCurrentEmployeesRosterList(string access_token, [Required] GetDingTalkCurrentEmployeesRosterListInput input) { return await _dingTalkApi.GetDingTalkCurrentEmployeesRosterList(access_token, input); diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.GoView/Entity/GoViewPro.cs b/Admin.NET/Plugins/Admin.NET.Plugin.GoView/Entity/GoViewPro.cs index 023d5c9b..0594ca67 100644 --- a/Admin.NET/Plugins/Admin.NET.Plugin.GoView/Entity/GoViewPro.cs +++ b/Admin.NET/Plugins/Admin.NET.Plugin.GoView/Entity/GoViewPro.cs @@ -1,4 +1,4 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 // // 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 // @@ -10,6 +10,7 @@ namespace Admin.NET.Plugin.GoView; /// GoView 项目表 /// [SugarTable(null, "GoView 项目表")] +[SysTable] public class GoViewPro : EntityTenant { /// diff --git a/Admin.NET/Plugins/Admin.NET.Plugin.GoView/Entity/GoViewProData.cs b/Admin.NET/Plugins/Admin.NET.Plugin.GoView/Entity/GoViewProData.cs index a792c033..ab5bd036 100644 --- a/Admin.NET/Plugins/Admin.NET.Plugin.GoView/Entity/GoViewProData.cs +++ b/Admin.NET/Plugins/Admin.NET.Plugin.GoView/Entity/GoViewProData.cs @@ -10,6 +10,7 @@ namespace Admin.NET.Plugin.GoView; /// GoView 项目数据表 /// [SugarTable(null, "GoView 项目数据表")] +[SysTable] public class GoViewProData : EntityTenant { /// diff --git a/Web/package.json b/Web/package.json index c9251964..467a1bfa 100644 --- a/Web/package.json +++ b/Web/package.json @@ -2,7 +2,7 @@ "name": "admin.net.pro", "type": "module", "version": "2.4.33", - "lastBuildTime": "2024.07.12", + "lastBuildTime": "2024.07.14", "description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架", "author": "zuohuaijun", "license": "MIT", @@ -31,7 +31,7 @@ "echarts": "^5.5.1", "echarts-gl": "^2.0.9", "echarts-wordcloud": "^2.1.0", - "element-plus": "^2.7.6", + "element-plus": "^2.7.7", "exceljs": "^4.4.0", "ezuikit": "^1.0.0", "ezuikit-js": "^8.0.5", @@ -68,8 +68,8 @@ "vue-signature-pad": "^3.0.2", "vue3-tree-org": "^4.2.2", "vuedraggable": "4.0.3", - "vxe-pc-ui": "^4.0.61", - "vxe-table": "^4.7.48", + "vxe-pc-ui": "^4.0.67", + "vxe-table": "^4.7.50", "vxe-table-plugin-element": "^4.0.4", "vxe-table-plugin-export-xlsx": "^4.0.5", "xe-utils": "^3.5.28", @@ -88,12 +88,12 @@ "@vitejs/plugin-vue-jsx": "^4.0.0", "@vue/compiler-sfc": "^3.4.31", "code-inspector-plugin": "^0.14.2", - "eslint": "^9.6.0", + "eslint": "^9.7.0", "eslint-plugin-vue": "^9.27.0", "less": "^4.2.0", - "prettier": "^3.3.2", + "prettier": "^3.3.3", "rollup-plugin-visualizer": "^5.12.0", - "sass": "^1.77.7", + "sass": "^1.77.8", "terser": "^5.31.2", "typescript": "^5.5.3", "vite": "^5.3.3", diff --git a/Web/src/views/home/widgets/components/welcome.vue b/Web/src/views/home/widgets/components/welcome.vue index fb83f5c5..7cf1a42a 100644 --- a/Web/src/views/home/widgets/components/welcome.vue +++ b/Web/src/views/home/widgets/components/welcome.vue @@ -7,7 +7,7 @@ - 欢迎使用 Admin.NET + 欢迎使用 {{ themeConfig.globalTitle }} @@ -37,6 +37,12 @@