From 8293ec0297875ebc9ad75cce9465bd587929c0bf Mon Sep 17 00:00:00 2001 From: nsnail Date: Fri, 2 Feb 2024 14:05:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20=E8=AE=A1=E5=88=92=E4=BD=9C?= =?UTF-8?q?=E4=B8=9A=20(#87)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/res/Enums.ln | 2 + assets/res/Statements.ln | 8 + assets/seed-data/Sys_Job.json | 13 + assets/seed-data/Sys_Menu.json | 21 +- build/code.quality.props | 4 +- dotnet-tools.json | 2 +- scripts/find.unused.ln.csx | 15 ++ .../NetAdmin.BizServer.Host/Startup.cs | 2 + .../NetAdmin.BizServer.Tests.csproj | 2 +- .../DataValidation/ApiIdAttribute.cs | 24 ++ .../DataValidation/CronAttribute.cs | 18 ++ .../DataValidation/UserIdAttribute.cs | 24 ++ .../NetAdmin.Domain/DataAbstraction.cs | 11 + .../NetAdmin.Domain/DbMaps/Sys/Sys_Job.cs | 107 ++++++++ .../DbMaps/Sys/Sys_JobRecord.cs | 82 +++++++ .../Dto/Sys/Job/CreateJobReq.cs | 65 +++++ .../Dto/Sys/Job/QueryJobReq.cs | 14 ++ .../Dto/Sys/Job/QueryJobRsp.cs | 80 ++++++ .../Dto/Sys/Job/UpdateJobReq.cs | 13 + .../Dto/Sys/JobRecord/CreateJobRecordReq.cs | 8 + .../Dto/Sys/JobRecord/QueryJobRecordReq.cs | 14 ++ .../Dto/Sys/JobRecord/QueryJobRecordRsp.cs | 14 ++ .../Dto/Sys/JobRecord/UpdateJobRecordReq.cs | 6 + .../NetAdmin.Domain/Enums/HttpMethods.cs | 69 ++++++ .../NetAdmin.Domain/Enums/Sys/JobStatues.cs | 22 ++ .../NetAdmin.Infrastructure/Constant/Chars.cs | 8 +- .../NetAdmin.Infrastructure/GlobalStatic.cs | 37 +++ .../NetAdmin.Infrastructure.csproj | 3 +- .../Modules/Sys/IJobModule.cs | 20 ++ .../Modules/Sys/IJobRecordModule.cs | 14 ++ .../Services/Sys/ApiService.cs | 23 +- .../Services/Sys/CacheService.cs | 1 + .../Services/Sys/CaptchaService.cs | 1 + .../Services/Sys/ConfigService.cs | 8 + .../Sys/Dependency/IJobRecordService.cs | 9 + .../Services/Sys/Dependency/IJobService.cs | 21 ++ .../Services/Sys/Dependency/IUserService.cs | 5 + .../Services/Sys/DeptService.cs | 8 + .../Services/Sys/DevService.cs | 3 + .../Services/Sys/DicCatalogService.cs | 8 + .../Services/Sys/DicContentService.cs | 8 + .../Services/Sys/DicService.cs | 14 ++ .../Services/Sys/JobRecordService.cs | 112 +++++++++ .../Services/Sys/JobService.cs | 175 +++++++++++++ .../Services/Sys/MenuService.cs | 8 + .../Services/Sys/RequestLogService.cs | 8 + .../Services/Sys/RoleService.cs | 8 + .../Services/Sys/SiteMsgDeptService.cs | 8 + .../Services/Sys/SiteMsgFlagService.cs | 8 + .../Services/Sys/SiteMsgRoleService.cs | 8 + .../Services/Sys/SiteMsgService.cs | 11 + .../Services/Sys/SiteMsgUserService.cs | 8 + .../Services/Sys/UserProfileService.cs | 8 + .../Services/Sys/UserService.cs | 30 +++ .../Services/Sys/VerifyCodeService.cs | 10 + .../Services/Tpl/ExampleService.cs | 8 + .../Sys/Dependency/IJobCache.cs | 10 + .../Sys/Dependency/IJobRecordCache.cs | 10 + .../Sys/JobCache.cs | 66 +++++ .../Sys/JobRecordCache.cs | 60 +++++ .../Controllers/Sys/JobController.cs | 92 +++++++ .../Extensions/ServiceCollectionExtensions.cs | 21 ++ .../Jobs/ScheduledJob.cs | 141 +++++++++++ src/frontend/admin/src/api/sys/job.js | 106 ++++++++ .../admin/src/assets/icons/ScheduledJob.vue | 10 + src/frontend/admin/src/assets/icons/index.js | 3 +- src/frontend/admin/src/config/iconSelect.js | 2 +- src/frontend/admin/src/style/app.scss | 1 + .../admin/src/views/sys/job/index.vue | 229 ++++++++++++++++++ src/frontend/admin/src/views/sys/job/save.vue | 193 +++++++++++++++ 70 files changed, 2171 insertions(+), 14 deletions(-) create mode 100644 assets/seed-data/Sys_Job.json create mode 100644 scripts/find.unused.ln.csx create mode 100644 src/backend/NetAdmin.Domain/Attributes/DataValidation/ApiIdAttribute.cs create mode 100644 src/backend/NetAdmin.Domain/Attributes/DataValidation/CronAttribute.cs create mode 100644 src/backend/NetAdmin.Domain/Attributes/DataValidation/UserIdAttribute.cs create mode 100644 src/backend/NetAdmin.Domain/DbMaps/Sys/Sys_Job.cs create mode 100644 src/backend/NetAdmin.Domain/DbMaps/Sys/Sys_JobRecord.cs create mode 100644 src/backend/NetAdmin.Domain/Dto/Sys/Job/CreateJobReq.cs create mode 100644 src/backend/NetAdmin.Domain/Dto/Sys/Job/QueryJobReq.cs create mode 100644 src/backend/NetAdmin.Domain/Dto/Sys/Job/QueryJobRsp.cs create mode 100644 src/backend/NetAdmin.Domain/Dto/Sys/Job/UpdateJobReq.cs create mode 100644 src/backend/NetAdmin.Domain/Dto/Sys/JobRecord/CreateJobRecordReq.cs create mode 100644 src/backend/NetAdmin.Domain/Dto/Sys/JobRecord/QueryJobRecordReq.cs create mode 100644 src/backend/NetAdmin.Domain/Dto/Sys/JobRecord/QueryJobRecordRsp.cs create mode 100644 src/backend/NetAdmin.Domain/Dto/Sys/JobRecord/UpdateJobRecordReq.cs create mode 100644 src/backend/NetAdmin.Domain/Enums/HttpMethods.cs create mode 100644 src/backend/NetAdmin.Domain/Enums/Sys/JobStatues.cs create mode 100644 src/backend/NetAdmin.SysComponent.Application/Modules/Sys/IJobModule.cs create mode 100644 src/backend/NetAdmin.SysComponent.Application/Modules/Sys/IJobRecordModule.cs create mode 100644 src/backend/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IJobRecordService.cs create mode 100644 src/backend/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IJobService.cs create mode 100644 src/backend/NetAdmin.SysComponent.Application/Services/Sys/JobRecordService.cs create mode 100644 src/backend/NetAdmin.SysComponent.Application/Services/Sys/JobService.cs create mode 100644 src/backend/NetAdmin.SysComponent.Cache/Sys/Dependency/IJobCache.cs create mode 100644 src/backend/NetAdmin.SysComponent.Cache/Sys/Dependency/IJobRecordCache.cs create mode 100644 src/backend/NetAdmin.SysComponent.Cache/Sys/JobCache.cs create mode 100644 src/backend/NetAdmin.SysComponent.Cache/Sys/JobRecordCache.cs create mode 100644 src/backend/NetAdmin.SysComponent.Host/Controllers/Sys/JobController.cs create mode 100644 src/backend/NetAdmin.SysComponent.Host/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/backend/NetAdmin.SysComponent.Host/Jobs/ScheduledJob.cs create mode 100644 src/frontend/admin/src/api/sys/job.js create mode 100644 src/frontend/admin/src/assets/icons/ScheduledJob.vue create mode 100644 src/frontend/admin/src/views/sys/job/index.vue create mode 100644 src/frontend/admin/src/views/sys/job/save.vue diff --git a/assets/res/Enums.ln b/assets/res/Enums.ln index e60b78e5..4d729f2b 100644 --- a/assets/res/Enums.ln +++ b/assets/res/Enums.ln @@ -65,6 +65,7 @@ 硕士 离异 私信 +空闲 等于 等待发送 系统模块 @@ -79,6 +80,7 @@ 调试 跟踪 身份证 +运行 重设密码 链接 错误 diff --git a/assets/res/Statements.ln b/assets/res/Statements.ln index c9da7e97..655633cd 100644 --- a/assets/res/Statements.ln +++ b/assets/res/Statements.ln @@ -7,6 +7,7 @@ XML注释文件不存在 事务已提交 人机校验请求不能为空 人机验证未通过 +作业名称不能为空 允许的文件大小 允许的文件格式 区号电话号码分机号 @@ -23,6 +24,7 @@ XML注释文件不存在 开始事务 手机号码不正确 手机号码不能为空 +接口编码不存在 支付宝账号 数据库同步开始 数据库服务器时钟偏移 @@ -39,7 +41,10 @@ XML注释文件不存在 旧手机号码不正确 旧手机号码验证码不正确 时间戳缺失或误差过大 +时间表达式 +时间计划不能为空 未指定部门 +未获取到待执行任务 模块名称不能为空 模块说明不能为空 消息主题不能为空 @@ -52,6 +57,7 @@ XML注释文件不存在 用户名长度4位以上 用户头像不能为空 用户档案不能为空 +用户编号不存在 目标设备不能为空 短信验证请求不能为空 站内信不存在 @@ -68,7 +74,9 @@ XML注释文件不存在 该角色下存在用户 该部门下存在子部门 该部门下存在用户 +请求地址不能为空 请求对象不能为空 +请求方法不正确 请联系管理员激活账号 读取用户令牌出错 账号不能为空 diff --git a/assets/seed-data/Sys_Job.json b/assets/seed-data/Sys_Job.json new file mode 100644 index 00000000..d8f51b06 --- /dev/null +++ b/assets/seed-data/Sys_Job.json @@ -0,0 +1,13 @@ +[ + { + "Enabled": true, + "ExecutionCron": "* * * * *", + "HttpMethod": 3, + "JobName": "HTTP 请求测试", + "NextExecTime": "2020/9/13 12:26:40", + "NextTimeId": 1600000000, + "RequestUrl": "https://httpbin.org/ip", + "Status": 1, + "UserId": 370942943322181, + } +] \ No newline at end of file diff --git a/assets/seed-data/Sys_Menu.json b/assets/seed-data/Sys_Menu.json index aee25b21..3111e3ff 100644 --- a/assets/seed-data/Sys_Menu.json +++ b/assets/seed-data/Sys_Menu.json @@ -85,6 +85,17 @@ "Title": "系统设置", "Type": 1 }, + { + "Component": "sys/job", + "Icon": "sc-icon-ScheduledJob", + "Id": 510067557638158, + "Name": "sys-job", + "ParentId": 485278637670422, + "Path": "/sys/job", + "Sort": 99, + "Title": "计划作业", + "Type": 1 + }, { "Component": "sys/dic", "Icon": "sc-icon-dic", @@ -92,7 +103,7 @@ "Name": "sys-dic", "ParentId": 485278637670422, "Path": "/sys/dic", - "Sort": 99, + "Sort": 98, "Title": "字典管理", "Type": 1 }, @@ -103,7 +114,7 @@ "Name": "sys-msg", "ParentId": 485278637670422, "Path": "/sys/msg", - "Sort": 98, + "Sort": 97, "Title": "消息管理", "Type": 1, }, @@ -114,7 +125,7 @@ "Name": "sys-api", "ParentId": 485278637670422, "Path": "/sys/api", - "Sort": 97, + "Sort": 96, "Title": "接口管理", "Type": 1 }, @@ -125,7 +136,7 @@ "Name": "sys-cache", "ParentId": 485278637670422, "Path": "/sys/cache", - "Sort": 96, + "Sort": 95, "Title": "缓存管理", "Type": 1 }, @@ -136,7 +147,7 @@ "Name": "sys-about", "ParentId": 485278637670422, "Path": "/sys/about", - "Sort": 95, + "Sort": 94, "Title": "版本信息", "Type": 1, }, diff --git a/build/code.quality.props b/build/code.quality.props index 8858cb21..87bbca52 100644 --- a/build/code.quality.props +++ b/build/code.quality.props @@ -19,11 +19,11 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/dotnet-tools.json b/dotnet-tools.json index 856df9f8..05d31eed 100644 --- a/dotnet-tools.json +++ b/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "jetbrains.resharper.globaltools": { - "version": "2023.3.2", + "version": "2023.3.3", "commands": [ "jb" ] diff --git a/scripts/find.unused.ln.csx b/scripts/find.unused.ln.csx new file mode 100644 index 00000000..6bb02084 --- /dev/null +++ b/scripts/find.unused.ln.csx @@ -0,0 +1,15 @@ +using System.Text.RegularExpressions; + +Console.WriteLine(string.Join(Environment.NewLine + , Regex + .Matches(File.ReadAllText(@"../assets/res/Ln.resx") + , "data name=\"(.*?)\"") + .Select(x => x.Groups[1].Value) + .Where(x => !Directory + .GetFiles(@"../src/backend/", "*.cs" + , new EnumerationOptions { + RecurseSubdirectories = true + }) + .Select(File.ReadAllText) + .Any(y => y.Contains(x))))); +Console.ReadKey(); \ No newline at end of file diff --git a/src/backend/NetAdmin.BizServer.Host/Startup.cs b/src/backend/NetAdmin.BizServer.Host/Startup.cs index 083fc2d5..0d48aeb9 100644 --- a/src/backend/NetAdmin.BizServer.Host/Startup.cs +++ b/src/backend/NetAdmin.BizServer.Host/Startup.cs @@ -2,6 +2,7 @@ using NetAdmin.BizServer.Host; using NetAdmin.BizServer.Host.Extensions; using NetAdmin.Host.Extensions; using NetAdmin.Host.Middlewares; +using NetAdmin.SysComponent.Host.Extensions; using Spectre.Console.Cli; using ValidationResult = Spectre.Console.ValidationResult; #if !DEBUG @@ -62,6 +63,7 @@ namespace NetAdmin.BizServer.Host .AddCorsAccessor() // 添加支持跨域访问 .AddContextUser() // 添加上下文用户 .AddRedisCache() // 添加 Redis 缓存 + .AddSchedules() // 添加计划任务 // IMvcBuilder .AddControllers() // 添加控制器 diff --git a/src/backend/NetAdmin.BizServer.Tests/NetAdmin.BizServer.Tests.csproj b/src/backend/NetAdmin.BizServer.Tests/NetAdmin.BizServer.Tests.csproj index 76726a6d..03105dbd 100644 --- a/src/backend/NetAdmin.BizServer.Tests/NetAdmin.BizServer.Tests.csproj +++ b/src/backend/NetAdmin.BizServer.Tests/NetAdmin.BizServer.Tests.csproj @@ -4,6 +4,6 @@ - + \ No newline at end of file diff --git a/src/backend/NetAdmin.Domain/Attributes/DataValidation/ApiIdAttribute.cs b/src/backend/NetAdmin.Domain/Attributes/DataValidation/ApiIdAttribute.cs new file mode 100644 index 00000000..3ad4d917 --- /dev/null +++ b/src/backend/NetAdmin.Domain/Attributes/DataValidation/ApiIdAttribute.cs @@ -0,0 +1,24 @@ +using NetAdmin.Domain.Dto.Dependency; +using NetAdmin.Domain.Dto.Sys.Api; + +namespace NetAdmin.Domain.Attributes.DataValidation; + +/// +/// 接口编码验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class ApiIdAttribute : ValidationAttribute +{ + /// + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + var service = App.GetService(App.EffectiveTypes.Single( + x => x.FullName == "NetAdmin.SysComponent.Cache.Sys.Dependency.IApiCache")); + + var req = new QueryReq { Filter = new QueryApiReq { Id = value as string } }; + + var method = service.GetType().GetMethod("ExistAsync"); + var exist = ((Task)method!.Invoke(service, [req]))!.ConfigureAwait(false).GetAwaiter().GetResult(); + return !exist ? new ValidationResult(Ln.接口编码不存在) : ValidationResult.Success; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin.Domain/Attributes/DataValidation/CronAttribute.cs b/src/backend/NetAdmin.Domain/Attributes/DataValidation/CronAttribute.cs new file mode 100644 index 00000000..e59b9a9b --- /dev/null +++ b/src/backend/NetAdmin.Domain/Attributes/DataValidation/CronAttribute.cs @@ -0,0 +1,18 @@ +namespace NetAdmin.Domain.Attributes.DataValidation; + +/// +/// 时间表达式验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class CronAttribute : RegexAttribute +{ + /// + /// Initializes a new instance of the class. + /// + public CronAttribute() // + : base(Chars.RGX_CRON) + { + ErrorMessageResourceName = nameof(Ln.时间表达式); + ErrorMessageResourceType = typeof(Ln); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin.Domain/Attributes/DataValidation/UserIdAttribute.cs b/src/backend/NetAdmin.Domain/Attributes/DataValidation/UserIdAttribute.cs new file mode 100644 index 00000000..1e59fc1b --- /dev/null +++ b/src/backend/NetAdmin.Domain/Attributes/DataValidation/UserIdAttribute.cs @@ -0,0 +1,24 @@ +using NetAdmin.Domain.Dto.Dependency; +using NetAdmin.Domain.Dto.Sys.User; + +namespace NetAdmin.Domain.Attributes.DataValidation; + +/// +/// 用户编号验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class UserIdAttribute : ValidationAttribute +{ + /// + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + var service = App.GetService(App.EffectiveTypes.Single( + x => x.FullName == "NetAdmin.SysComponent.Cache.Sys.Dependency.IUserCache")); + + var req = new QueryReq { Filter = new QueryUserReq { Id = (long)value! } }; + + var method = service.GetType().GetMethod("ExistAsync"); + var exist = ((Task)method!.Invoke(service, [req]))!.ConfigureAwait(false).GetAwaiter().GetResult(); + return !exist ? new ValidationResult(Ln.用户编号不存在) : ValidationResult.Success; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin.Domain/DataAbstraction.cs b/src/backend/NetAdmin.Domain/DataAbstraction.cs index 498be2a0..efd33e13 100644 --- a/src/backend/NetAdmin.Domain/DataAbstraction.cs +++ b/src/backend/NetAdmin.Domain/DataAbstraction.cs @@ -5,6 +5,17 @@ namespace NetAdmin.Domain; /// public abstract record DataAbstraction { + /// + /// 如果数据校验失败,抛出异常 + /// + /// NetAdminInvalidInputException + public void ThrowIfInvalid() + { + if (!this.TryValidate().IsValid) { + throw new NetAdminInvalidInputException(Ln.无效输入); + } + } + /// public override string ToString() { diff --git a/src/backend/NetAdmin.Domain/DbMaps/Sys/Sys_Job.cs b/src/backend/NetAdmin.Domain/DbMaps/Sys/Sys_Job.cs new file mode 100644 index 00000000..e1c77b46 --- /dev/null +++ b/src/backend/NetAdmin.Domain/DbMaps/Sys/Sys_Job.cs @@ -0,0 +1,107 @@ +using NetAdmin.Domain.DbMaps.Dependency; +using NetAdmin.Domain.DbMaps.Dependency.Fields; +using NetAdmin.Domain.Enums.Sys; +using HttpMethods = NetAdmin.Domain.Enums.HttpMethods; + +namespace NetAdmin.Domain.DbMaps.Sys; + +/// +/// 计划作业表 +/// +[Table(Name = Chars.FLG_TABLE_NAME_PREFIX + nameof(Sys_Job))] +public record Sys_Job : VersionEntity, IFieldEnabled, IFieldSummary +{ + /// + [JsonIgnore] + [Column] + public virtual bool Enabled { get; init; } + + /// + /// 执行时间计划 + /// + [JsonIgnore] + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31)] + public virtual string ExecutionCron { get; init; } + + /// + /// 请求方法 + /// + [JsonIgnore] + [Column] + public virtual HttpMethods HttpMethod { get; init; } + + /// + /// 作业名称 + /// + [JsonIgnore] + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)] + public virtual string JobName { get; init; } + + /// + /// 上次执行时间 + /// + [JsonIgnore] + [Column] + public virtual DateTime? LastExecTime { get; init; } + + /// + /// 上次执行状态 + /// + [JsonIgnore] + [Column] + public virtual HttpStatusCode? LastStatusCode { get; init; } + + /// + /// 下次执行时间 + /// + [JsonIgnore] + [Column] + public virtual DateTime? NextExecTime { get; init; } + + /// + /// 下次执行时间编号 + /// + [JsonIgnore] + [Column] + public virtual long? NextTimeId { get; init; } + + /// + /// 请求体 + /// + [JsonIgnore] + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + public virtual string RequestBody { get; init; } + + /// + /// 请求头 + /// + [JsonIgnore] + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + public virtual string RequestHeader { get; init; } + + /// + /// 请求的网络地址 + /// + [JsonIgnore] + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + public virtual string RequestUrl { get; init; } + + /// + /// 作业状态 + /// + [JsonIgnore] + [Column] + public virtual JobStatues Status { get; init; } + + /// + [JsonIgnore] + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + public virtual string Summary { get; init; } + + /// + /// 执行用户编号 + /// + [JsonIgnore] + [Column] + public virtual long UserId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin.Domain/DbMaps/Sys/Sys_JobRecord.cs b/src/backend/NetAdmin.Domain/DbMaps/Sys/Sys_JobRecord.cs new file mode 100644 index 00000000..14d75874 --- /dev/null +++ b/src/backend/NetAdmin.Domain/DbMaps/Sys/Sys_JobRecord.cs @@ -0,0 +1,82 @@ +using NetAdmin.Domain.DbMaps.Dependency; +using HttpMethods = NetAdmin.Domain.Enums.HttpMethods; + +namespace NetAdmin.Domain.DbMaps.Sys; + +/// +/// 计划作业执行记录表 +/// +[Table(Name = Chars.FLG_TABLE_NAME_PREFIX + nameof(Sys_JobRecord))] +[Index($"idx_{{tablename}}_{nameof(JobId)}_{nameof(TimeId)}", $"{nameof(JobId)},{nameof(TimeId)}", true)] +public record Sys_JobRecord : LiteImmutableEntity +{ + /// + /// 执行耗时(毫秒) + /// + [Column] + [JsonIgnore] + public virtual long Duration { get; init; } + + /// + /// 请求方法 + /// + [JsonIgnore] + [Column] + public virtual HttpMethods HttpMethod { get; init; } + + /// + /// HTTP 状态码 + /// + [Column] + [JsonIgnore] + public virtual HttpStatusCode HttpStatusCode { get; init; } + + /// + /// 作业编号 + /// + [Column] + [JsonIgnore] + public long JobId { get; init; } + + /// + /// 请求体 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + [JsonIgnore] + public virtual string RequestBody { get; init; } + + /// + /// 请求头 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + [JsonIgnore] + public virtual string RequestHeader { get; init; } + + /// + /// 请求的网络地址 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127)] + [JsonIgnore] + public virtual string RequestUrl { get; init; } + + /// + /// 响应体 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + [JsonIgnore] + public virtual string ResponseBody { get; init; } + + /// + /// 响应头 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + [JsonIgnore] + public virtual string ResponseHeader { get; init; } + + /// + /// 执行时间编号 + /// + [Column] + [JsonIgnore] + public long TimeId { get; set; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin.Domain/Dto/Sys/Job/CreateJobReq.cs b/src/backend/NetAdmin.Domain/Dto/Sys/Job/CreateJobReq.cs new file mode 100644 index 00000000..56696b08 --- /dev/null +++ b/src/backend/NetAdmin.Domain/Dto/Sys/Job/CreateJobReq.cs @@ -0,0 +1,65 @@ +using NetAdmin.Domain.Attributes.DataValidation; +using NetAdmin.Domain.DbMaps.Dependency.Fields; +using NetAdmin.Domain.DbMaps.Sys; +using NetAdmin.Domain.Enums.Sys; +using HttpMethods = NetAdmin.Domain.Enums.HttpMethods; + +namespace NetAdmin.Domain.Dto.Sys.Job; + +/// +/// 请求:创建计划作业 +/// +public record CreateJobReq : Sys_Job +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } = true; + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Cron] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.时间计划不能为空))] + public override string ExecutionCron { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [EnumDataType(typeof(HttpMethods), ErrorMessageResourceType = typeof(Ln) + , ErrorMessageResourceName = nameof(Ln.请求方法不正确))] + public override HttpMethods HttpMethod { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.作业名称不能为空))] + public override string JobName { get; init; } + + /// + public override long? NextTimeId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RequestBody { get; init; } + + /// + /// 请求头 + /// + public Dictionary RequestHeaders { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.请求地址不能为空))] + [Url(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.网络地址不正确))] + public override string RequestUrl { get; init; } + + /// + public override JobStatues Status { get; init; } = JobStatues.Idle; + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Summary { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [Range(1, long.MaxValue, ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.用户编号不存在))] + [UserId] + public override long UserId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin.Domain/Dto/Sys/Job/QueryJobReq.cs b/src/backend/NetAdmin.Domain/Dto/Sys/Job/QueryJobReq.cs new file mode 100644 index 00000000..2d2147a6 --- /dev/null +++ b/src/backend/NetAdmin.Domain/Dto/Sys/Job/QueryJobReq.cs @@ -0,0 +1,14 @@ +using NetAdmin.Domain.DbMaps.Dependency.Fields; +using NetAdmin.Domain.DbMaps.Sys; + +namespace NetAdmin.Domain.Dto.Sys.Job; + +/// +/// 请求:查询计划作业 +/// +public sealed record QueryJobReq : Sys_Job +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin.Domain/Dto/Sys/Job/QueryJobRsp.cs b/src/backend/NetAdmin.Domain/Dto/Sys/Job/QueryJobRsp.cs new file mode 100644 index 00000000..a311be3d --- /dev/null +++ b/src/backend/NetAdmin.Domain/Dto/Sys/Job/QueryJobRsp.cs @@ -0,0 +1,80 @@ +using NetAdmin.Domain.DbMaps.Dependency.Fields; +using NetAdmin.Domain.DbMaps.Sys; +using NetAdmin.Domain.Enums.Sys; +using HttpMethods = NetAdmin.Domain.Enums.HttpMethods; + +namespace NetAdmin.Domain.Dto.Sys.Job; + +/// +/// 响应:查询计划作业 +/// +public sealed record QueryJobRsp : Sys_Job +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override DateTime CreatedTime { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string ExecutionCron { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override HttpMethods HttpMethod { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string JobName { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override DateTime? LastExecTime { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override HttpStatusCode? LastStatusCode { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override DateTime? NextExecTime { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override long? NextTimeId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RequestBody { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RequestHeader { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RequestUrl { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override JobStatues Status { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Summary { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long UserId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin.Domain/Dto/Sys/Job/UpdateJobReq.cs b/src/backend/NetAdmin.Domain/Dto/Sys/Job/UpdateJobReq.cs new file mode 100644 index 00000000..e8b4b02f --- /dev/null +++ b/src/backend/NetAdmin.Domain/Dto/Sys/Job/UpdateJobReq.cs @@ -0,0 +1,13 @@ +using NetAdmin.Domain.DbMaps.Dependency.Fields; + +namespace NetAdmin.Domain.Dto.Sys.Job; + +/// +/// 请求:更新计划作业 +/// +public sealed record UpdateJobReq : CreateJobReq +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin.Domain/Dto/Sys/JobRecord/CreateJobRecordReq.cs b/src/backend/NetAdmin.Domain/Dto/Sys/JobRecord/CreateJobRecordReq.cs new file mode 100644 index 00000000..c9e6d53d --- /dev/null +++ b/src/backend/NetAdmin.Domain/Dto/Sys/JobRecord/CreateJobRecordReq.cs @@ -0,0 +1,8 @@ +using NetAdmin.Domain.DbMaps.Sys; + +namespace NetAdmin.Domain.Dto.Sys.JobRecord; + +/// +/// 请求:创建计划作业执行记录 +/// +public record CreateJobRecordReq : Sys_JobRecord; \ No newline at end of file diff --git a/src/backend/NetAdmin.Domain/Dto/Sys/JobRecord/QueryJobRecordReq.cs b/src/backend/NetAdmin.Domain/Dto/Sys/JobRecord/QueryJobRecordReq.cs new file mode 100644 index 00000000..46ac8059 --- /dev/null +++ b/src/backend/NetAdmin.Domain/Dto/Sys/JobRecord/QueryJobRecordReq.cs @@ -0,0 +1,14 @@ +using NetAdmin.Domain.DbMaps.Dependency.Fields; +using NetAdmin.Domain.DbMaps.Sys; + +namespace NetAdmin.Domain.Dto.Sys.JobRecord; + +/// +/// 请求:查询计划作业执行记录 +/// +public sealed record QueryJobRecordReq : Sys_JobRecord +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin.Domain/Dto/Sys/JobRecord/QueryJobRecordRsp.cs b/src/backend/NetAdmin.Domain/Dto/Sys/JobRecord/QueryJobRecordRsp.cs new file mode 100644 index 00000000..ac36085a --- /dev/null +++ b/src/backend/NetAdmin.Domain/Dto/Sys/JobRecord/QueryJobRecordRsp.cs @@ -0,0 +1,14 @@ +using NetAdmin.Domain.DbMaps.Dependency.Fields; +using NetAdmin.Domain.DbMaps.Sys; + +namespace NetAdmin.Domain.Dto.Sys.JobRecord; + +/// +/// 响应:查询计划作业执行记录 +/// +public sealed record QueryJobRecordRsp : Sys_JobRecord +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin.Domain/Dto/Sys/JobRecord/UpdateJobRecordReq.cs b/src/backend/NetAdmin.Domain/Dto/Sys/JobRecord/UpdateJobRecordReq.cs new file mode 100644 index 00000000..b869fa1c --- /dev/null +++ b/src/backend/NetAdmin.Domain/Dto/Sys/JobRecord/UpdateJobRecordReq.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.Domain.Dto.Sys.JobRecord; + +/// +/// 请求:更新计划作业执行记录 +/// +public sealed record UpdateJobRecordReq : CreateJobRecordReq { } \ No newline at end of file diff --git a/src/backend/NetAdmin.Domain/Enums/HttpMethods.cs b/src/backend/NetAdmin.Domain/Enums/HttpMethods.cs new file mode 100644 index 00000000..972bc766 --- /dev/null +++ b/src/backend/NetAdmin.Domain/Enums/HttpMethods.cs @@ -0,0 +1,69 @@ +namespace NetAdmin.Domain.Enums; + +/// +/// HTTP 请求方法 +/// +[Export] +public enum HttpMethods +{ + /// + /// Connect + /// + Connect = 1 + + , + + /// + /// Delete + /// + Delete = 2 + + , + + /// + /// Get + /// + Get = 3 + + , + + /// + /// Head + /// + Head = 4 + + , + + /// + /// Options + /// + Options = 5 + + , + + /// + /// Patch + /// + Patch = 6 + + , + + /// + /// Post + /// + Post = 7 + + , + + /// + /// Put + /// + Put = 8 + + , + + /// + /// Trace + /// + Trace = 9 +} \ No newline at end of file diff --git a/src/backend/NetAdmin.Domain/Enums/Sys/JobStatues.cs b/src/backend/NetAdmin.Domain/Enums/Sys/JobStatues.cs new file mode 100644 index 00000000..b4afbd5a --- /dev/null +++ b/src/backend/NetAdmin.Domain/Enums/Sys/JobStatues.cs @@ -0,0 +1,22 @@ +namespace NetAdmin.Domain.Enums.Sys; + +/// +/// 计划作业状态 +/// +[Export] +public enum JobStatues +{ + /// + /// 空闲 + /// + [ResourceDescription(nameof(Ln.空闲))] + Idle = 1 + + , + + /// + /// 运行 + /// + [ResourceDescription(nameof(Ln.运行))] + Running = 2 +} \ No newline at end of file diff --git a/src/backend/NetAdmin.Infrastructure/Constant/Chars.cs b/src/backend/NetAdmin.Infrastructure/Constant/Chars.cs index f6e55e41..a01425e9 100644 --- a/src/backend/NetAdmin.Infrastructure/Constant/Chars.cs +++ b/src/backend/NetAdmin.Infrastructure/Constant/Chars.cs @@ -11,7 +11,9 @@ namespace NetAdmin.Infrastructure.Constant; public static class Chars { public const string FLG_ACCESS_TOKEN = "ACCESS-TOKEN"; + public const string FLG_ACCESS_TOKEN_HEADER_KEY = "Authorization"; public const string FLG_APPLICATION_JSON = "application/json"; + public const string FLG_AUTH_SCHEMA = "Bearer"; public const string FLG_CONSUL_REG_HOSTNAME = "CONSUL_REG_HOSTNAME"; public const string FLG_CONSUL_REG_PORT = "CONSUL_REG_PORT"; public const string FLG_CONTEXT_MEMBER_INFO = nameof(FLG_CONTEXT_MEMBER_INFO); @@ -66,13 +68,17 @@ public static class Chars public const string FLG_VISIBLE_ASCIIS = """!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"""; - public const string FLG_X_ACCESS_TOKEN = "X-ACCESS-TOKEN"; + public const string FLG_X_ACCESS_TOKEN = "X-ACCESS-TOKEN"; + public const string FLG_X_ACCESS_TOKEN_HEADER_KEY = "X-Authorization"; public const string RGX_CERTIFICATE = "^[a-zA-Z0-9-_]+$"; public const string RGX_CHINESE_NAME = """^(?:赵|钱|孙|李|周|吴|郑|王|冯|陈|褚|卫|蒋|沈|韩|杨|朱|秦|尤|许|何|吕|施|张|孔|曹|严|华|金|魏|陶|姜|戚|谢|邹|喻|柏|水|窦|章|云|苏|潘|葛|奚|范|彭|郎|鲁|韦|昌|马|苗|凤|花|方|俞|任|袁|柳|酆|鲍|史|唐|费|廉|岑|薛|雷|贺|倪|汤|滕|殷|罗|毕|郝|邬|安|常|乐|于|时|傅|皮|卞|齐|康|伍|余|元|卜|顾|孟|平|黄|和|穆|萧|尹|姚|邵|湛|汪|祁|毛|禹|狄|米|贝|明|臧|计|伏|成|戴|谈|宋|茅|庞|熊|纪|舒|屈|项|祝|董|梁|杜|阮|蓝|闵|席|季|麻|强|贾|路|娄|危|江|童|颜|郭|梅|盛|林|刁|钟|徐|邱|骆|高|夏|蔡|田|樊|胡|凌|霍|虞|万|支|柯|昝|管|卢|莫|经|房|裘|缪|干|解|应|宗|丁|宣|贲|邓|郁|单|杭|洪|包|诸|左|石|崔|吉|钮|龚|程|嵇|邢|滑|裴|陆|荣|翁|荀|羊|於|惠|甄|曲|家|封|芮|羿|储|靳|汲|邴|糜|松|井|段|富|巫|乌|焦|巴|弓|牧|隗|山|谷|车|侯|宓|蓬|全|郗|班|仰|秋|仲|伊|宫|宁|仇|栾|暴|甘|钭|厉|戎|祖|武|符|刘|景|詹|束|龙|叶|幸|司|韶|郜|黎|蓟|薄|印|宿|白|怀|蒲|邰|从|鄂|索|咸|籍|赖|卓|蔺|屠|蒙|池|乔|阴|胥|能|苍|双|闻|莘|党|翟|谭|贡|劳|逄|姬|申|扶|堵|冉|宰|郦|雍|郤|璩|桑|桂|濮|牛|寿|通|边|扈|燕|冀|郏|浦|尚|农|温|别|庄|晏|柴|瞿|阎|充|慕|连|茹|习|宦|艾|鱼|容|向|古|易|慎|戈|廖|庾|终|暨|居|衡|步|都|耿|满|弘|匡|国|文|寇|广|禄|阙|东|欧|殳|沃|利|蔚|越|夔|隆|师|巩|厍|聂|晁|勾|敖|融|冷|訾|辛|阚|那|简|饶|空|曾|毋|沙|乜|养|鞠|须|丰|巢|关|蒯|相|查|後|荆|红|游|竺|权|逯|盖|益|桓|公|万俟|司马|上官|欧阳|夏侯|诸葛|闻人|东方|赫连|皇甫|尉迟|公羊|澹台|公冶|宗政|濮阳|淳于|单于|太叔|申屠|公孙|仲孙|轩辕|令狐|钟离|宇文|长孙|慕容|鲜于|闾丘|司徒|司空|亓官|司寇|仉|督|子车|颛孙|端木|巫马|公西|漆雕|乐正|壤驷|公良|拓跋|夹谷|宰父|谷梁|晋|楚|闫|法|汝|鄢|涂|钦|段干|百里|东郭|南门|呼延|归|海|羊舌|微生|岳|帅|缑|亢|况|后|有|琴|梁丘|左丘|东门|西门|商|牟|佘|佴|伯|赏|南宫|墨|哈|谯|笪|年|爱|阳|佟|第五|言|福)[\u4e00-\u9fa5]{1,3}$"""; + public const string RGX_CRON + = """^(?:[0-5]?[0-9]|[0-9]|[1-5]?[0-9](?:,[0-5]?[0-9])?|\*|\*/[0-5]?[0-9])\s+(?:[01]?[0-9]|2[0-3]|\*)\s+(?:[0-2]?[0-9]|3[01]|\*)\s+(?:0?[0-9]|1[0-1]|\*)\s+(?:[0-6]|\*)$"""; + public const string RGX_EMAIL = """^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$"""; diff --git a/src/backend/NetAdmin.Infrastructure/GlobalStatic.cs b/src/backend/NetAdmin.Infrastructure/GlobalStatic.cs index 9445184c..360c80eb 100644 --- a/src/backend/NetAdmin.Infrastructure/GlobalStatic.cs +++ b/src/backend/NetAdmin.Infrastructure/GlobalStatic.cs @@ -1,3 +1,5 @@ +using DataType = FreeSql.DataType; + namespace NetAdmin.Infrastructure; /// @@ -27,6 +29,41 @@ public static class GlobalStatic /// public static string SecretKey => "{6C4922D3-499A-46db-BFC4-0B51A9C4395F}"; + /// + /// SQL 随机排序语法 + /// + /// NotImplementedException + public static string SqlRandomSorting => + App.GetOptions().DbType switch { + DataType.MySql => "RAND()" + , DataType.SqlServer => "NEWID()" + , DataType.PostgreSQL => "RANDOM()" + , DataType.Oracle => "DBMS_RANDOM.value" + , DataType.Sqlite => "RANDOM()" + , DataType.OdbcOracle => throw new NotImplementedException() + , DataType.OdbcSqlServer => throw new NotImplementedException() + , DataType.OdbcMySql => throw new NotImplementedException() + , DataType.OdbcPostgreSQL => throw new NotImplementedException() + , DataType.Odbc => throw new NotImplementedException() + , DataType.OdbcDameng => throw new NotImplementedException() + , DataType.MsAccess => throw new NotImplementedException() + , DataType.Dameng => throw new NotImplementedException() + , DataType.OdbcKingbaseES => throw new NotImplementedException() + , DataType.ShenTong => throw new NotImplementedException() + , DataType.KingbaseES => throw new NotImplementedException() + , DataType.Firebird => throw new NotImplementedException() + , DataType.Custom => throw new NotImplementedException() + , DataType.ClickHouse => throw new NotImplementedException() + , DataType.GBase => throw new NotImplementedException() + , DataType.QuestDb => throw new NotImplementedException() + , DataType.Xugu => throw new NotImplementedException() + , DataType.CustomOracle => throw new NotImplementedException() + , DataType.CustomSqlServer => throw new NotImplementedException() + , DataType.CustomMySql => throw new NotImplementedException() + , DataType.CustomPostgreSQL => throw new NotImplementedException() + , _ => throw new NotImplementedException() + }; + /// /// Json序列化选项 /// diff --git a/src/backend/NetAdmin.Infrastructure/NetAdmin.Infrastructure.csproj b/src/backend/NetAdmin.Infrastructure/NetAdmin.Infrastructure.csproj index d2513e28..e7a3c59a 100644 --- a/src/backend/NetAdmin.Infrastructure/NetAdmin.Infrastructure.csproj +++ b/src/backend/NetAdmin.Infrastructure/NetAdmin.Infrastructure.csproj @@ -6,16 +6,17 @@ + + - diff --git a/src/backend/NetAdmin.SysComponent.Application/Modules/Sys/IJobModule.cs b/src/backend/NetAdmin.SysComponent.Application/Modules/Sys/IJobModule.cs new file mode 100644 index 00000000..c77dc76d --- /dev/null +++ b/src/backend/NetAdmin.SysComponent.Application/Modules/Sys/IJobModule.cs @@ -0,0 +1,20 @@ +using NetAdmin.Application.Modules; +using NetAdmin.Domain.Dto.Dependency; +using NetAdmin.Domain.Dto.Sys.Job; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 计划作业模块 +/// +public interface IJobModule : ICrudModule +{ + /// + /// 启用/禁用作业 + /// + Task SetEnabledAsync(UpdateJobReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin.SysComponent.Application/Modules/Sys/IJobRecordModule.cs b/src/backend/NetAdmin.SysComponent.Application/Modules/Sys/IJobRecordModule.cs new file mode 100644 index 00000000..0b001721 --- /dev/null +++ b/src/backend/NetAdmin.SysComponent.Application/Modules/Sys/IJobRecordModule.cs @@ -0,0 +1,14 @@ +using NetAdmin.Application.Modules; +using NetAdmin.Domain.Dto.Dependency; +using NetAdmin.Domain.Dto.Sys.JobRecord; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 计划作业执行记录模块 +/// +public interface IJobRecordModule : ICrudModule; \ No newline at end of file diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/ApiService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/ApiService.cs index f6431943..3a5f2efb 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/ApiService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/ApiService.cs @@ -17,42 +17,49 @@ public sealed class ApiService( /// public Task BulkDeleteAsync(BulkReq req) { + req.ThrowIfInvalid(); throw new NotImplementedException(); } /// public Task CreateAsync(CreateApiReq req) { + req.ThrowIfInvalid(); throw new NotImplementedException(); } /// public Task DeleteAsync(DelReq req) { + req.ThrowIfInvalid(); throw new NotImplementedException(); } /// public Task ExistAsync(QueryReq req) { - throw new NotImplementedException(); + req.ThrowIfInvalid(); + return QueryInternal(req).AnyAsync(); } /// public Task GetAsync(QueryApiReq req) { + req.ThrowIfInvalid(); throw new NotImplementedException(); } /// public Task> PagedQueryAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); throw new NotImplementedException(); } /// public async Task> QueryAsync(QueryReq req) { + req.ThrowIfInvalid(); var ret = await Rpo.Select.WhereDynamicFilter(req.DynamicFilter) .WhereDynamic(req.Filter) .ToTreeListAsync() @@ -108,6 +115,7 @@ public sealed class ApiService( /// public Task UpdateAsync(NopReq req) { + req.ThrowIfInvalid(); throw new NotImplementedException(); } @@ -129,4 +137,17 @@ public sealed class ApiService( ?.HttpMethods.First() }); } + + private ISelect QueryInternal(QueryReq req) + { + var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter) + .WhereDynamic(req.Filter) + .OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + + if (!req.Prop?.Equals(nameof(req.Filter.CreatedTime), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.CreatedTime); + } + + return ret; + } } \ No newline at end of file diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/CacheService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/CacheService.cs index 2afe0203..4a562a9a 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/CacheService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/CacheService.cs @@ -23,6 +23,7 @@ public sealed class CacheService(IConnectionMultiplexer connectionMultiplexer) / /// public async Task> GetAllEntriesAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); var database = connectionMultiplexer.GetDatabase((int?)req.Filter?.DbIndex ?? 0); var redisResults = (RedisResult[])await database .ExecuteAsync("scan", (req.Page - 1) * req.PageSize, "count" diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/CaptchaService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/CaptchaService.cs index ae00aa19..0b0d83a1 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/CaptchaService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/CaptchaService.cs @@ -37,6 +37,7 @@ public sealed class CaptchaService : ServiceBase, ICaptchaServi /// public Task VerifyCaptchaAsync(VerifyCaptchaReq req) { + req.ThrowIfInvalid(); if (req.SawOffsetX == null) { return Task.FromResult(false); } diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/ConfigService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/ConfigService.cs index 6fef44aa..55c59d98 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/ConfigService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/ConfigService.cs @@ -15,6 +15,7 @@ public sealed class ConfigService(DefaultRepository rpo) // /// public async Task BulkDeleteAsync(BulkReq req) { + req.ThrowIfInvalid(); var sum = 0; foreach (var item in req.Items) { sum += await DeleteAsync(item).ConfigureAwait(false); @@ -26,6 +27,7 @@ public sealed class ConfigService(DefaultRepository rpo) // /// public async Task CreateAsync(CreateConfigReq req) { + req.ThrowIfInvalid(); var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); return ret.Adapt(); } @@ -33,18 +35,21 @@ public sealed class ConfigService(DefaultRepository rpo) // /// public Task DeleteAsync(DelReq req) { + req.ThrowIfInvalid(); return Rpo.DeleteAsync(a => a.Id == req.Id); } /// public Task ExistAsync(QueryReq req) { + req.ThrowIfInvalid(); return QueryInternal(req).AnyAsync(); } /// public async Task GetAsync(QueryConfigReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(new QueryReq { Filter = req }).ToOneAsync().ConfigureAwait(false); return ret.Adapt(); } @@ -61,6 +66,7 @@ public sealed class ConfigService(DefaultRepository rpo) // /// public async Task> PagedQueryAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); var list = await QueryInternal(req) .Page(req.Page, req.PageSize) .Count(out var total) @@ -74,6 +80,7 @@ public sealed class ConfigService(DefaultRepository rpo) // /// public async Task> QueryAsync(QueryReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(req).Take(req.Count).ToListAsync().ConfigureAwait(false); return ret.Adapt>(); } @@ -81,6 +88,7 @@ public sealed class ConfigService(DefaultRepository rpo) // /// public async Task UpdateAsync(UpdateConfigReq req) { + req.ThrowIfInvalid(); if (Rpo.Orm.Ado.DataType == DataType.Sqlite) { return await UpdateForSqliteAsync(req).ConfigureAwait(false) as QueryConfigRsp; } diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IJobRecordService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IJobRecordService.cs new file mode 100644 index 00000000..e69f4e7d --- /dev/null +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IJobRecordService.cs @@ -0,0 +1,9 @@ +using NetAdmin.Application.Services; +using NetAdmin.SysComponent.Application.Modules.Sys; + +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 计划作业执行记录服务 +/// +public interface IJobRecordService : IService, IJobRecordModule; \ No newline at end of file diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IJobService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IJobService.cs new file mode 100644 index 00000000..eb954cae --- /dev/null +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IJobService.cs @@ -0,0 +1,21 @@ +using NetAdmin.Application.Services; +using NetAdmin.Domain.Dto.Sys.Job; +using NetAdmin.SysComponent.Application.Modules.Sys; + +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 计划作业服务 +/// +public interface IJobService : IService, IJobModule +{ + /// + /// 完成计划作业 + /// + Task FinishJobAsync(UpdateJobReq req); + + /// + /// 获取下一个要执行的计划作业 + /// + Task GetNextJobAsync(); +} \ No newline at end of file diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IUserService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IUserService.cs index 23cf0b5a..4cdd6129 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IUserService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IUserService.cs @@ -14,6 +14,11 @@ public interface IUserService : IService, IUserModule /// Task GetForUpdateAsync(QueryUserReq req); + /// + /// 用户编号登录 + /// + Task LoginByUserIdAsync(long userId); + /// /// 单体更新 /// diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DeptService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DeptService.cs index fce1025b..dc9dad39 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DeptService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DeptService.cs @@ -14,6 +14,7 @@ public sealed class DeptService(DefaultRepository rpo) // /// public async Task BulkDeleteAsync(BulkReq req) { + req.ThrowIfInvalid(); var sum = 0; foreach (var item in req.Items) { sum += await DeleteAsync(item).ConfigureAwait(false); @@ -26,6 +27,7 @@ public sealed class DeptService(DefaultRepository rpo) // /// Parent_department_does_not_exist public async Task CreateAsync(CreateDeptReq req) { + req.ThrowIfInvalid(); if (req.ParentId != 0 && !await Rpo.Select.AnyAsync(a => a.Id == req.ParentId).ConfigureAwait(false)) { throw new NetAdminInvalidOperationException(Ln.父节点不存在); } @@ -40,6 +42,7 @@ public sealed class DeptService(DefaultRepository rpo) // /// 该部门下存在子部门 public async Task DeleteAsync(DelReq req) { + req.ThrowIfInvalid(); if (await Rpo.Orm.Select().AnyAsync(a => a.DeptId == req.Id).ConfigureAwait(false)) { throw new NetAdminInvalidOperationException(Ln.该部门下存在用户); } @@ -56,12 +59,14 @@ public sealed class DeptService(DefaultRepository rpo) // /// public Task ExistAsync(QueryReq req) { + req.ThrowIfInvalid(); return QueryInternal(req).AnyAsync(); } /// public async Task GetAsync(QueryDeptReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(new QueryReq { Filter = req }).ToOneAsync().ConfigureAwait(false); return ret.Adapt(); } @@ -69,12 +74,14 @@ public sealed class DeptService(DefaultRepository rpo) // /// public Task> PagedQueryAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); throw new NotImplementedException(); } /// public async Task> QueryAsync(QueryReq req) { + req.ThrowIfInvalid(); return (await QueryInternal(req).ToTreeListAsync().ConfigureAwait(false)).Adapt>(); } @@ -82,6 +89,7 @@ public sealed class DeptService(DefaultRepository rpo) // /// NetAdminUnexpectedException public async Task UpdateAsync(UpdateDeptReq req) { + req.ThrowIfInvalid(); return await Rpo.UpdateDiy.SetSource(req).ExecuteAffrowsAsync().ConfigureAwait(false) <= 0 ? throw new NetAdminUnexpectedException() : (await QueryInternal(new QueryReq { Filter = new QueryDeptReq { Id = req.Id } }, true) diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DevService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DevService.cs index 415cb2a7..4ac459b9 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DevService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DevService.cs @@ -22,6 +22,8 @@ public sealed class DevService(IApiService apiService) : ServiceBase /// public async Task GenerateCsCodeAsync(GenerateCsCodeReq req) { + req.ThrowIfInvalid(); + // 模块类型(Sys、Biz、等) var moduleType = Enum.GetName(req.Type)!; @@ -111,6 +113,7 @@ public sealed class DevService(IApiService apiService) : ServiceBase /// public async Task GenerateIconCodeAsync(GenerateIconCodeReq req) { + req.ThrowIfInvalid(); var tplSvg = await File.ReadAllTextAsync( Path.Combine(_clientProjectPath, "src", "assets", "icons", "tpl", "Svg.vue")) .ConfigureAwait(false); diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DicCatalogService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DicCatalogService.cs index d4403e6f..0e8b7624 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DicCatalogService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DicCatalogService.cs @@ -14,6 +14,7 @@ public sealed class DicCatalogService(DefaultRepository rpo) // /// public async Task BulkDeleteAsync(BulkReq req) { + req.ThrowIfInvalid(); var sum = 0; foreach (var item in req.Items) { sum += await DeleteAsync(item).ConfigureAwait(false); @@ -26,6 +27,7 @@ public sealed class DicCatalogService(DefaultRepository rpo) // /// The_parent_node_does_not_exist public async Task CreateAsync(CreateDicCatalogReq req) { + req.ThrowIfInvalid(); if (req.ParentId != 0 && !await Rpo.Where(a => a.Id == req.ParentId).ForUpdate().AnyAsync().ConfigureAwait(false)) { throw new NetAdminInvalidOperationException(Ln.父节点不存在); @@ -38,6 +40,7 @@ public sealed class DicCatalogService(DefaultRepository rpo) // /// public async Task DeleteAsync(DelReq req) { + req.ThrowIfInvalid(); var ret = await Rpo.DeleteCascadeByDatabaseAsync(a => a.Id == req.Id).ConfigureAwait(false); return ret.Count; } @@ -45,12 +48,14 @@ public sealed class DicCatalogService(DefaultRepository rpo) // /// public Task ExistAsync(QueryReq req) { + req.ThrowIfInvalid(); return QueryInternal(req).AnyAsync(); } /// public async Task GetAsync(QueryDicCatalogReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(new QueryReq { Filter = req }) .ToOneAsync() .ConfigureAwait(false); @@ -60,6 +65,7 @@ public sealed class DicCatalogService(DefaultRepository rpo) // /// public async Task> PagedQueryAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); var list = await QueryInternal(req) .Page(req.Page, req.PageSize) .Count(out var total) @@ -73,6 +79,7 @@ public sealed class DicCatalogService(DefaultRepository rpo) // /// public async Task> QueryAsync(QueryReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(req).ToTreeListAsync().ConfigureAwait(false); return ret.Adapt>(); } @@ -82,6 +89,7 @@ public sealed class DicCatalogService(DefaultRepository rpo) // /// NetAdminUnexpectedException public async Task UpdateAsync(UpdateDicCatalogReq req) { + req.ThrowIfInvalid(); if (req.ParentId != 0 && !await Rpo.Where(a => a.Id == req.ParentId).ForUpdate().AnyAsync().ConfigureAwait(false)) { throw new NetAdminInvalidOperationException(Ln.父节点不存在); diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DicContentService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DicContentService.cs index 3cda6f73..ce33f04d 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DicContentService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DicContentService.cs @@ -14,6 +14,7 @@ public sealed class DicContentService(DefaultRepository rpo) // /// public async Task BulkDeleteAsync(BulkReq req) { + req.ThrowIfInvalid(); var sum = 0; foreach (var item in req.Items) { sum += await DeleteAsync(item).ConfigureAwait(false); @@ -26,6 +27,7 @@ public sealed class DicContentService(DefaultRepository rpo) // /// Dictionary_directory_does_not_exist public async Task CreateAsync(CreateDicContentReq req) { + req.ThrowIfInvalid(); if (!await Rpo.Orm.Select() .Where(a => a.Id == req.CatalogId) .ForUpdate() @@ -41,18 +43,21 @@ public sealed class DicContentService(DefaultRepository rpo) // /// public Task DeleteAsync(DelReq req) { + req.ThrowIfInvalid(); return Rpo.DeleteAsync(a => a.Id == req.Id); } /// public Task ExistAsync(QueryReq req) { + req.ThrowIfInvalid(); return QueryInternal(req).AnyAsync(); } /// public async Task GetAsync(QueryDicContentReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(new QueryReq { Filter = req }) .ToOneAsync() .ConfigureAwait(false); @@ -62,6 +67,7 @@ public sealed class DicContentService(DefaultRepository rpo) // /// public async Task> PagedQueryAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); var list = await QueryInternal(req) .Page(req.Page, req.PageSize) .Count(out var total) @@ -75,6 +81,7 @@ public sealed class DicContentService(DefaultRepository rpo) // /// public async Task> QueryAsync(QueryReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(req).Take(req.Count).ToListAsync().ConfigureAwait(false); return ret.Adapt>(); } @@ -84,6 +91,7 @@ public sealed class DicContentService(DefaultRepository rpo) // /// NetAdminUnexpectedException public async Task UpdateAsync(UpdateDicContentReq req) { + req.ThrowIfInvalid(); if (!await Rpo.Orm.Select() .Where(a => a.Id == req.CatalogId) .ForUpdate() diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DicService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DicService.cs index 0bf9aa4b..6281e2bd 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DicService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/DicService.cs @@ -13,84 +13,98 @@ public sealed class DicService(IDicCatalogService catalogService, IDicContentSer /// public Task BulkDeleteCatalogAsync(BulkReq req) { + req.ThrowIfInvalid(); return catalogService.BulkDeleteAsync(req); } /// public Task BulkDeleteContentAsync(BulkReq req) { + req.ThrowIfInvalid(); return contentService.BulkDeleteAsync(req); } /// public Task CreateCatalogAsync(CreateDicCatalogReq req) { + req.ThrowIfInvalid(); return catalogService.CreateAsync(req); } /// public Task CreateContentAsync(CreateDicContentReq req) { + req.ThrowIfInvalid(); return contentService.CreateAsync(req); } /// public Task DeleteCatalogAsync(DelReq req) { + req.ThrowIfInvalid(); return catalogService.DeleteAsync(req); } /// public Task DeleteContentAsync(DelReq req) { + req.ThrowIfInvalid(); return contentService.DeleteAsync(req); } /// public Task GetCatalogAsync(QueryDicCatalogReq req) { + req.ThrowIfInvalid(); return catalogService.GetAsync(req); } /// public Task GetContentAsync(QueryDicContentReq req) { + req.ThrowIfInvalid(); return contentService.GetAsync(req); } /// public Task> PagedQueryCatalogAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); return catalogService.PagedQueryAsync(req); } /// public Task> PagedQueryContentAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); return contentService.PagedQueryAsync(req); } /// public Task> QueryCatalogAsync(QueryReq req) { + req.ThrowIfInvalid(); return catalogService.QueryAsync(req); } /// public Task> QueryContentAsync(QueryReq req) { + req.ThrowIfInvalid(); return contentService.QueryAsync(req); } /// public Task UpdateCatalogAsync(UpdateDicCatalogReq req) { + req.ThrowIfInvalid(); return catalogService.UpdateAsync(req); } /// public Task UpdateContentAsync(UpdateDicContentReq req) { + req.ThrowIfInvalid(); return contentService.UpdateAsync(req); } } \ No newline at end of file diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/JobRecordService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/JobRecordService.cs new file mode 100644 index 00000000..07f6386f --- /dev/null +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/JobRecordService.cs @@ -0,0 +1,112 @@ +using NetAdmin.Application.Repositories; +using NetAdmin.Application.Services; +using NetAdmin.Domain.DbMaps.Sys; +using NetAdmin.Domain.Dto.Dependency; +using NetAdmin.Domain.Dto.Sys.JobRecord; +using NetAdmin.SysComponent.Application.Services.Sys.Dependency; +using DataType = FreeSql.DataType; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class JobRecordService(DefaultRepository rpo) // + : RepositoryService(rpo), IJobRecordService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var sum = 0; + foreach (var item in req.Items) { + sum += await DeleteAsync(item).ConfigureAwait(false); + } + + return sum; + } + + /// + public async Task CreateAsync(CreateJobRecordReq req) + { + req.ThrowIfInvalid(); + var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + return Rpo.DeleteAsync(a => a.Id == req.Id); + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req).AnyAsync(); + } + + /// + public async Task GetAsync(QueryJobRecordReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req }) + .ToOneAsync() + .ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var list = await QueryInternal(req) + .Page(req.Page, req.PageSize) + .Count(out var total) + .ToListAsync() + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total + , list.Adapt>()); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req).Take(req.Count).ToListAsync().ConfigureAwait(false); + return ret.Adapt>(); + } + + /// + public async Task UpdateAsync(UpdateJobRecordReq req) + { + req.ThrowIfInvalid(); + if (Rpo.Orm.Ado.DataType == DataType.Sqlite) { + return await UpdateForSqliteAsync(req).ConfigureAwait(false) as QueryJobRecordRsp; + } + + var ret = await Rpo.UpdateDiy.SetSource(req).ExecuteUpdatedAsync().ConfigureAwait(false); + return ret.FirstOrDefault()?.Adapt(); + } + + /// + protected override async Task UpdateForSqliteAsync(Sys_JobRecord req) + { + return await Rpo.UpdateDiy.SetSource(req).ExecuteAffrowsAsync().ConfigureAwait(false) <= 0 + ? null + : await GetAsync(new QueryJobRecordReq { Id = req.Id }).ConfigureAwait(false); + } + + private ISelect QueryInternal(QueryReq req) + { + var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter) + .WhereDynamic(req.Filter) + .OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + if (!req.Prop?.Equals(nameof(req.Filter.Id), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.Id); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/JobService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/JobService.cs new file mode 100644 index 00000000..62289897 --- /dev/null +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/JobService.cs @@ -0,0 +1,175 @@ +using Cronos; +using NetAdmin.Application.Repositories; +using NetAdmin.Application.Services; +using NetAdmin.Domain.DbMaps.Sys; +using NetAdmin.Domain.Dto.Dependency; +using NetAdmin.Domain.Dto.Sys.Job; +using NetAdmin.Domain.Enums.Sys; +using NetAdmin.SysComponent.Application.Services.Sys.Dependency; +using DataType = FreeSql.DataType; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class JobService(DefaultRepository rpo) // + : RepositoryService(rpo), IJobService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var sum = 0; + foreach (var item in req.Items) { + sum += await DeleteAsync(item).ConfigureAwait(false); + } + + return sum; + } + + /// + public async Task CreateAsync(CreateJobReq req) + { + req.ThrowIfInvalid(); + var nextExecTime = CronExpression.Parse(req.ExecutionCron).GetNextOccurrence(DateTime.UtcNow, TimeZoneInfo.Utc); + var ret = await Rpo.InsertAsync(req with { + NextExecTime = nextExecTime + , NextTimeId = nextExecTime?.TimeUnixUtc() + , RequestHeader = req.RequestHeaders?.Json() + }) + .ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + var ret = await Rpo.DeleteCascadeByDatabaseAsync(a => a.Id == req.Id).ConfigureAwait(false); + return ret.Count; + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req).AnyAsync(); + } + + /// + public async Task FinishJobAsync(UpdateJobReq req) + { + var nextExecTime = CronExpression.Parse(req.ExecutionCron).GetNextOccurrence(DateTime.UtcNow, TimeZoneInfo.Utc); + _ = await UpdateAsync(req with { + Status = JobStatues.Idle + , NextExecTime = nextExecTime + , NextTimeId = nextExecTime?.TimeUnixUtc() + }) + .ConfigureAwait(false); + } + + /// + public async Task GetAsync(QueryJobReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req }).ToOneAsync().ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task GetNextJobAsync() + { + var df = new DynamicFilterInfo { + Filters = [ + new DynamicFilterInfo { + Field = nameof(QueryJobReq.NextExecTime) + , Value = DateTime.UtcNow + , Operator = DynamicFilterOperators.LessThan + } + , new DynamicFilterInfo { + Field = nameof(QueryJobReq.Status) + , Value = JobStatues.Idle + , Operator = DynamicFilterOperators.Eq + } + , new DynamicFilterInfo { + Field = nameof(QueryJobReq.Enabled) + , Value = true + , Operator = DynamicFilterOperators.Eq + } + ] + }; + var job = await QueryInternal(new QueryReq { DynamicFilter = df, Count = 1 }, true) + .Where(a => !Rpo.Orm.Select() + .As("b") + .Where(b => b.JobId == a.Id && b.TimeId == a.NextTimeId) + .Any()) + .ToOneAsync() + .ConfigureAwait(false); + return job == null + ? null + : await UpdateAsync(job.Adapt() with { + Status = JobStatues.Running + , LastExecTime = DateTime.UtcNow + }) + .ConfigureAwait(false); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var list = await QueryInternal(req) + .Page(req.Page, req.PageSize) + .Count(out var total) + .ToListAsync() + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req).Take(req.Count).ToListAsync().ConfigureAwait(false); + return ret.Adapt>(); + } + + /// + public Task SetEnabledAsync(UpdateJobReq req) + { + req.ThrowIfInvalid(); + return Rpo.UpdateDiy.Set(a => a.Enabled == req.Enabled).Where(a => a.Id == req.Id).ExecuteAffrowsAsync(); + } + + /// + public async Task UpdateAsync(UpdateJobReq req) + { + req.ThrowIfInvalid(); + if (Rpo.Orm.Ado.DataType == DataType.Sqlite) { + return (await UpdateForSqliteAsync(req).ConfigureAwait(false)).Adapt(); + } + + _ = await Rpo.UpdateAsync(req).ConfigureAwait(false); + return req.Adapt(); + } + + /// + protected override async Task UpdateForSqliteAsync(Sys_Job req) + { + _ = await Rpo.UpdateAsync(req).ConfigureAwait(false); + return req; + } + + private ISelect QueryInternal(QueryReq req, bool orderByRandom = false) + { + var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter) + .WhereDynamic(req.Filter) + .WhereIf( // + req.Keywords?.Length > 0 + , a => a.Id == req.Keywords.Int64Try(0) || a.JobName.Contains(req.Keywords)) + .OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + return !orderByRandom && (!req.Prop?.Equals(nameof(req.Filter.Id), StringComparison.OrdinalIgnoreCase) ?? true) + ? ret.OrderByDescending(a => a.Id) + : ret.OrderByRandom(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/MenuService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/MenuService.cs index d9fabbe7..41f45a81 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/MenuService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/MenuService.cs @@ -14,6 +14,7 @@ public sealed class MenuService(DefaultRepository rpo, IUserService us /// public async Task BulkDeleteAsync(BulkReq req) { + req.ThrowIfInvalid(); var sum = 0; foreach (var item in req.Items) { sum += await DeleteAsync(item).ConfigureAwait(false); @@ -25,6 +26,7 @@ public sealed class MenuService(DefaultRepository rpo, IUserService us /// public async Task CreateAsync(CreateMenuReq req) { + req.ThrowIfInvalid(); var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); return ret.Adapt(); } @@ -32,18 +34,21 @@ public sealed class MenuService(DefaultRepository rpo, IUserService us /// public Task DeleteAsync(DelReq req) { + req.ThrowIfInvalid(); return Rpo.DeleteAsync(a => a.Id == req.Id); } /// public Task ExistAsync(QueryReq req) { + req.ThrowIfInvalid(); return QueryInternal(req).AnyAsync(); } /// public async Task GetAsync(QueryMenuReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(new QueryReq { Filter = req }).ToOneAsync().ConfigureAwait(false); return ret.Adapt(); } @@ -51,12 +56,14 @@ public sealed class MenuService(DefaultRepository rpo, IUserService us /// public Task> PagedQueryAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); throw new NotImplementedException(); } /// public async Task> QueryAsync(QueryReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(req).ToTreeListAsync().ConfigureAwait(false); return ret.Adapt>(); } @@ -65,6 +72,7 @@ public sealed class MenuService(DefaultRepository rpo, IUserService us /// NetAdminUnexpectedException public async Task UpdateAsync(UpdateMenuReq req) { + req.ThrowIfInvalid(); if (await Rpo.UpdateDiy.SetSource(req).ExecuteAffrowsAsync().ConfigureAwait(false) <= 0) { throw new NetAdminUnexpectedException(); } diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/RequestLogService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/RequestLogService.cs index 66fd449d..e8a047a1 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/RequestLogService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/RequestLogService.cs @@ -14,6 +14,7 @@ public sealed class RequestLogService(DefaultRepository rpo) // /// public async Task BulkDeleteAsync(BulkReq req) { + req.ThrowIfInvalid(); var sum = 0; foreach (var item in req.Items) { sum += await DeleteAsync(item).ConfigureAwait(false); @@ -25,6 +26,7 @@ public sealed class RequestLogService(DefaultRepository rpo) // /// public async Task CreateAsync(CreateRequestLogReq req) { + req.ThrowIfInvalid(); var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); return ret.Adapt(); } @@ -32,18 +34,21 @@ public sealed class RequestLogService(DefaultRepository rpo) // /// public Task DeleteAsync(DelReq req) { + req.ThrowIfInvalid(); throw new NotImplementedException(); } /// public Task ExistAsync(QueryReq req) { + req.ThrowIfInvalid(); return QueryInternal(req).AnyAsync(); } /// public async Task GetAsync(QueryRequestLogReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(new QueryReq { Filter = req }) .ToOneAsync() .ConfigureAwait(false); @@ -53,6 +58,7 @@ public sealed class RequestLogService(DefaultRepository rpo) // /// public async Task> PagedQueryAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); var list = await QueryInternal(req) .Page(req.Page, req.PageSize) .Count(out var total) @@ -78,6 +84,7 @@ public sealed class RequestLogService(DefaultRepository rpo) // /// public async Task> QueryAsync(QueryReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(req).Take(req.Count).ToListAsync().ConfigureAwait(false); return ret.Adapt>(); } @@ -85,6 +92,7 @@ public sealed class RequestLogService(DefaultRepository rpo) // /// public Task UpdateAsync(NopReq req) { + req.ThrowIfInvalid(); throw new NotImplementedException(); } diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/RoleService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/RoleService.cs index 1f8bde46..769587ff 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/RoleService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/RoleService.cs @@ -14,6 +14,7 @@ public sealed class RoleService(DefaultRepository rpo) // /// public async Task BulkDeleteAsync(BulkReq req) { + req.ThrowIfInvalid(); var sum = 0; foreach (var item in req.Items) { sum += await DeleteAsync(item).ConfigureAwait(false); @@ -25,6 +26,7 @@ public sealed class RoleService(DefaultRepository rpo) // /// public async Task CreateAsync(CreateRoleReq req) { + req.ThrowIfInvalid(); var entity = req.Adapt(); var ret = await Rpo.InsertAsync(entity).ConfigureAwait(false); @@ -40,6 +42,7 @@ public sealed class RoleService(DefaultRepository rpo) // /// Users_exist_under_this_role_and_deletion_is_not_allowed public async Task DeleteAsync(DelReq req) { + req.ThrowIfInvalid(); return await Rpo.Orm.Select().ForUpdate().AnyAsync(a => a.RoleId == req.Id).ConfigureAwait(false) ? throw new NetAdminInvalidOperationException(Ln.该角色下存在用户) : await Rpo.DeleteAsync(a => a.Id == req.Id).ConfigureAwait(false); @@ -48,12 +51,14 @@ public sealed class RoleService(DefaultRepository rpo) // /// public Task ExistAsync(QueryReq req) { + req.ThrowIfInvalid(); return QueryInternal(req).AnyAsync(); } /// public async Task GetAsync(QueryRoleReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(new QueryReq { Filter = req }).ToOneAsync().ConfigureAwait(false); return ret.Adapt(); } @@ -61,6 +66,7 @@ public sealed class RoleService(DefaultRepository rpo) // /// public async Task> PagedQueryAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); var list = await QueryInternal(req) .Page(req.Page, req.PageSize) .Count(out var total) @@ -73,6 +79,7 @@ public sealed class RoleService(DefaultRepository rpo) // /// public async Task> QueryAsync(QueryReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(req).ToListAsync().ConfigureAwait(false); return ret.Adapt>(); } @@ -80,6 +87,7 @@ public sealed class RoleService(DefaultRepository rpo) // /// public async Task UpdateAsync(UpdateRoleReq req) { + req.ThrowIfInvalid(); var entity = req.Adapt(); _ = await Rpo.UpdateAsync(entity).ConfigureAwait(false); await Rpo.SaveManyAsync(entity, nameof(entity.Depts)).ConfigureAwait(false); diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgDeptService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgDeptService.cs index 267e2137..2f93ccc4 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgDeptService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgDeptService.cs @@ -15,6 +15,7 @@ public sealed class SiteMsgDeptService(DefaultRepository rpo) / /// public async Task BulkDeleteAsync(BulkReq req) { + req.ThrowIfInvalid(); var sum = 0; foreach (var item in req.Items) { sum += await DeleteAsync(item).ConfigureAwait(false); @@ -26,6 +27,7 @@ public sealed class SiteMsgDeptService(DefaultRepository rpo) / /// public async Task CreateAsync(CreateSiteMsgDeptReq req) { + req.ThrowIfInvalid(); var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); return ret.Adapt(); } @@ -33,18 +35,21 @@ public sealed class SiteMsgDeptService(DefaultRepository rpo) / /// public Task DeleteAsync(DelReq req) { + req.ThrowIfInvalid(); return Rpo.DeleteAsync(a => a.Id == req.Id); } /// public Task ExistAsync(QueryReq req) { + req.ThrowIfInvalid(); return QueryInternal(req).AnyAsync(); } /// public async Task GetAsync(QuerySiteMsgDeptReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(new QueryReq { Filter = req }) .ToOneAsync() .ConfigureAwait(false); @@ -54,6 +59,7 @@ public sealed class SiteMsgDeptService(DefaultRepository rpo) / /// public async Task> PagedQueryAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); var list = await QueryInternal(req) .Page(req.Page, req.PageSize) .Count(out var total) @@ -67,6 +73,7 @@ public sealed class SiteMsgDeptService(DefaultRepository rpo) / /// public async Task> QueryAsync(QueryReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(req).Take(req.Count).ToListAsync().ConfigureAwait(false); return ret.Adapt>(); } @@ -74,6 +81,7 @@ public sealed class SiteMsgDeptService(DefaultRepository rpo) / /// public async Task UpdateAsync(UpdateSiteMsgDeptReq req) { + req.ThrowIfInvalid(); if (Rpo.Orm.Ado.DataType == DataType.Sqlite) { return await UpdateForSqliteAsync(req).ConfigureAwait(false) as QuerySiteMsgDeptRsp; } diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgFlagService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgFlagService.cs index c20710bc..34a40023 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgFlagService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgFlagService.cs @@ -15,6 +15,7 @@ public sealed class SiteMsgFlagService(DefaultRepository rpo) / /// public async Task BulkDeleteAsync(BulkReq req) { + req.ThrowIfInvalid(); var sum = 0; foreach (var item in req.Items) { sum += await DeleteAsync(item).ConfigureAwait(false); @@ -26,6 +27,7 @@ public sealed class SiteMsgFlagService(DefaultRepository rpo) / /// public async Task CreateAsync(CreateSiteMsgFlagReq req) { + req.ThrowIfInvalid(); var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); return ret.Adapt(); } @@ -33,18 +35,21 @@ public sealed class SiteMsgFlagService(DefaultRepository rpo) / /// public Task DeleteAsync(DelReq req) { + req.ThrowIfInvalid(); return Rpo.DeleteAsync(a => a.Id == req.Id); } /// public Task ExistAsync(QueryReq req) { + req.ThrowIfInvalid(); return QueryInternal(req).AnyAsync(); } /// public async Task GetAsync(QuerySiteMsgFlagReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(new QueryReq { Filter = req }) .ToOneAsync() .ConfigureAwait(false); @@ -54,6 +59,7 @@ public sealed class SiteMsgFlagService(DefaultRepository rpo) / /// public async Task> PagedQueryAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); var list = await QueryInternal(req) .Page(req.Page, req.PageSize) .Count(out var total) @@ -67,6 +73,7 @@ public sealed class SiteMsgFlagService(DefaultRepository rpo) / /// public async Task> QueryAsync(QueryReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(req).Take(req.Count).ToListAsync().ConfigureAwait(false); return ret.Adapt>(); } @@ -74,6 +81,7 @@ public sealed class SiteMsgFlagService(DefaultRepository rpo) / /// public async Task UpdateAsync(UpdateSiteMsgFlagReq req) { + req.ThrowIfInvalid(); if (Rpo.Orm.Ado.DataType == DataType.Sqlite) { return await UpdateForSqliteAsync(req).ConfigureAwait(false) as QuerySiteMsgFlagRsp; } diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgRoleService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgRoleService.cs index 775683d3..df5e883a 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgRoleService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgRoleService.cs @@ -15,6 +15,7 @@ public sealed class SiteMsgRoleService(DefaultRepository rpo) / /// public async Task BulkDeleteAsync(BulkReq req) { + req.ThrowIfInvalid(); var sum = 0; foreach (var item in req.Items) { sum += await DeleteAsync(item).ConfigureAwait(false); @@ -26,6 +27,7 @@ public sealed class SiteMsgRoleService(DefaultRepository rpo) / /// public async Task CreateAsync(CreateSiteMsgRoleReq req) { + req.ThrowIfInvalid(); var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); return ret.Adapt(); } @@ -33,18 +35,21 @@ public sealed class SiteMsgRoleService(DefaultRepository rpo) / /// public Task DeleteAsync(DelReq req) { + req.ThrowIfInvalid(); return Rpo.DeleteAsync(a => a.Id == req.Id); } /// public Task ExistAsync(QueryReq req) { + req.ThrowIfInvalid(); return QueryInternal(req).AnyAsync(); } /// public async Task GetAsync(QuerySiteMsgRoleReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(new QueryReq { Filter = req }) .ToOneAsync() .ConfigureAwait(false); @@ -54,6 +59,7 @@ public sealed class SiteMsgRoleService(DefaultRepository rpo) / /// public async Task> PagedQueryAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); var list = await QueryInternal(req) .Page(req.Page, req.PageSize) .Count(out var total) @@ -67,6 +73,7 @@ public sealed class SiteMsgRoleService(DefaultRepository rpo) / /// public async Task> QueryAsync(QueryReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(req).Take(req.Count).ToListAsync().ConfigureAwait(false); return ret.Adapt>(); } @@ -74,6 +81,7 @@ public sealed class SiteMsgRoleService(DefaultRepository rpo) / /// public async Task UpdateAsync(UpdateSiteMsgRoleReq req) { + req.ThrowIfInvalid(); if (Rpo.Orm.Ado.DataType == DataType.Sqlite) { return await UpdateForSqliteAsync(req).ConfigureAwait(false) as QuerySiteMsgRoleRsp; } diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgService.cs index 8c221172..65cd5d1a 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgService.cs @@ -21,6 +21,7 @@ public sealed class SiteMsgService( /// public async Task BulkDeleteAsync(BulkReq req) { + req.ThrowIfInvalid(); var sum = 0; foreach (var item in req.Items) { sum += await DeleteAsync(item).ConfigureAwait(false); @@ -32,6 +33,7 @@ public sealed class SiteMsgService( /// public async Task CreateAsync(CreateSiteMsgReq req) { + req.ThrowIfInvalid(); await CreateUpdateCheckAsync(req).ConfigureAwait(false); // 主表 @@ -56,6 +58,7 @@ public sealed class SiteMsgService( /// public async Task DeleteAsync(DelReq req) { + req.ThrowIfInvalid(); var ret = await Rpo.DeleteCascadeByDatabaseAsync(a => a.Id == req.Id).ConfigureAwait(false); return ret.Count; } @@ -63,12 +66,14 @@ public sealed class SiteMsgService( /// public Task ExistAsync(QueryReq req) { + req.ThrowIfInvalid(); return QueryInternal(req).AnyAsync(); } /// public async Task GetAsync(QuerySiteMsgReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(new QueryReq { Filter = req }) .IncludeMany(a => a.Roles) .IncludeMany(a => a.Users) @@ -81,6 +86,7 @@ public sealed class SiteMsgService( /// public async Task GetMineAsync(QuerySiteMsgReq req) { + req.ThrowIfInvalid(); var ret = await PagedQueryMineAsync( new PagedQueryReq { DynamicFilter @@ -97,6 +103,7 @@ public sealed class SiteMsgService( /// public async Task> PagedQueryAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); var list = await QueryInternal(req) .Page(req.Page, req.PageSize) .Count(out var total) @@ -118,12 +125,14 @@ public sealed class SiteMsgService( /// public Task> PagedQueryMineAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); return PagedQueryMineAsync(req, false); } /// public async Task> QueryAsync(QueryReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(req).Take(req.Count).ToListAsync().ConfigureAwait(false); return ret.Adapt>(); } @@ -131,6 +140,7 @@ public sealed class SiteMsgService( /// public async Task SetSiteMsgStatusAsync(UpdateSiteMsgFlagReq req) { + req.ThrowIfInvalid(); if (!await ExistAsync(new QueryReq { Filter = new QuerySiteMsgReq { Id = req.SiteMsgId } }) .ConfigureAwait(false)) { throw new NetAdminInvalidOperationException(Ln.站内信不存在); @@ -160,6 +170,7 @@ public sealed class SiteMsgService( /// public async Task UpdateAsync(UpdateSiteMsgReq req) { + req.ThrowIfInvalid(); await CreateUpdateCheckAsync(req).ConfigureAwait(false); // 主表 diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgUserService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgUserService.cs index b9caf13b..7cf99d35 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgUserService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgUserService.cs @@ -15,6 +15,7 @@ public sealed class SiteMsgUserService(DefaultRepository rpo) / /// public async Task BulkDeleteAsync(BulkReq req) { + req.ThrowIfInvalid(); var sum = 0; foreach (var item in req.Items) { sum += await DeleteAsync(item).ConfigureAwait(false); @@ -26,6 +27,7 @@ public sealed class SiteMsgUserService(DefaultRepository rpo) / /// public async Task CreateAsync(CreateSiteMsgUserReq req) { + req.ThrowIfInvalid(); var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); return ret.Adapt(); } @@ -33,18 +35,21 @@ public sealed class SiteMsgUserService(DefaultRepository rpo) / /// public Task DeleteAsync(DelReq req) { + req.ThrowIfInvalid(); return Rpo.DeleteAsync(a => a.Id == req.Id); } /// public Task ExistAsync(QueryReq req) { + req.ThrowIfInvalid(); return QueryInternal(req).AnyAsync(); } /// public async Task GetAsync(QuerySiteMsgUserReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(new QueryReq { Filter = req }) .ToOneAsync() .ConfigureAwait(false); @@ -54,6 +59,7 @@ public sealed class SiteMsgUserService(DefaultRepository rpo) / /// public async Task> PagedQueryAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); var list = await QueryInternal(req) .Page(req.Page, req.PageSize) .Count(out var total) @@ -67,6 +73,7 @@ public sealed class SiteMsgUserService(DefaultRepository rpo) / /// public async Task> QueryAsync(QueryReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(req).Take(req.Count).ToListAsync().ConfigureAwait(false); return ret.Adapt>(); } @@ -74,6 +81,7 @@ public sealed class SiteMsgUserService(DefaultRepository rpo) / /// public async Task UpdateAsync(UpdateSiteMsgUserReq req) { + req.ThrowIfInvalid(); if (Rpo.Orm.Ado.DataType == DataType.Sqlite) { return await UpdateForSqliteAsync(req).ConfigureAwait(false) as QuerySiteMsgUserRsp; } diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/UserProfileService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/UserProfileService.cs index a43b3574..5388ad64 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/UserProfileService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/UserProfileService.cs @@ -16,6 +16,7 @@ public sealed class UserProfileService(DefaultRepository rpo) / /// public async Task BulkDeleteAsync(BulkReq req) { + req.ThrowIfInvalid(); var sum = 0; foreach (var item in req.Items) { sum += await DeleteAsync(item).ConfigureAwait(false); @@ -27,6 +28,7 @@ public sealed class UserProfileService(DefaultRepository rpo) / /// public async Task CreateAsync(CreateUserProfileReq req) { + req.ThrowIfInvalid(); var entity = req.Adapt(); var ret = await Rpo.InsertAsync(entity).ConfigureAwait(false); return ret.Adapt(); @@ -35,18 +37,21 @@ public sealed class UserProfileService(DefaultRepository rpo) / /// public Task DeleteAsync(DelReq req) { + req.ThrowIfInvalid(); return Rpo.DeleteAsync(a => a.Id == req.Id); } /// public Task ExistAsync(QueryReq req) { + req.ThrowIfInvalid(); return QueryInternal(req).AnyAsync(); } /// public async Task GetAsync(QueryUserProfileReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(new QueryReq { Filter = req }) .ToOneAsync() .ConfigureAwait(false); @@ -56,6 +61,7 @@ public sealed class UserProfileService(DefaultRepository rpo) / /// public async Task> PagedQueryAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); var list = await QueryInternal(req) .Page(req.Page, req.PageSize) .Count(out var total) @@ -82,6 +88,7 @@ public sealed class UserProfileService(DefaultRepository rpo) / /// public async Task> QueryAsync(QueryReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(req) .Take(req.Count) .ToListAsync((a, b, c, d, e) => new { @@ -115,6 +122,7 @@ public sealed class UserProfileService(DefaultRepository rpo) / /// public async Task UpdateAsync(UpdateUserProfileReq req) { + req.ThrowIfInvalid(); var entity = req.Adapt(); if (Rpo.Orm.Ado.DataType == DataType.Sqlite) { return await UpdateForSqliteAsync(entity).ConfigureAwait(false) as QueryUserProfileRsp; diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/UserService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/UserService.cs index dd34b7f9..4b61374c 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/UserService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/UserService.cs @@ -38,6 +38,7 @@ public sealed class UserService( /// public async Task BulkDeleteAsync(BulkReq req) { + req.ThrowIfInvalid(); var sum = 0; foreach (var item in req.Items) { sum += await DeleteAsync(item).ConfigureAwait(false); @@ -49,12 +50,14 @@ public sealed class UserService( /// public async Task CheckMobileAvailableAsync(CheckMobileAvailableReq req) { + req.ThrowIfInvalid(); return !await Rpo.Select.Where(a => a.Mobile == req.Mobile && a.Id != req.Id).AnyAsync().ConfigureAwait(false); } /// public async Task CheckUserNameAvailableAsync(CheckUserNameAvailableReq req) { + req.ThrowIfInvalid(); return !await Rpo.Select.Where(a => a.UserName == req.UserName && a.Id != req.Id) .AnyAsync() .ConfigureAwait(false); @@ -63,6 +66,7 @@ public sealed class UserService( /// public async Task CreateAsync(CreateUserReq req) { + req.ThrowIfInvalid(); await CreateUpdateCheckAsync(req).ConfigureAwait(false); // 主表 @@ -82,6 +86,7 @@ public sealed class UserService( /// public async Task DeleteAsync(DelReq req) { + req.ThrowIfInvalid(); var effect = 0; // 删除主表 @@ -101,12 +106,14 @@ public sealed class UserService( /// public async Task ExistAsync(QueryReq req) { + req.ThrowIfInvalid(); return await (await QueryInternalAsync(req).ConfigureAwait(false)).AnyAsync().ConfigureAwait(false); } /// public async Task GetAsync(QueryUserReq req) { + req.ThrowIfInvalid(); var ret = await (await QueryInternalAsync(new QueryReq { Filter = req }).ConfigureAwait(false)) .ToOneAsync() .ConfigureAwait(false); @@ -116,6 +123,8 @@ public sealed class UserService( /// public async Task GetForUpdateAsync(QueryUserReq req) { + req.ThrowIfInvalid(); + // ReSharper disable once MethodHasAsyncOverload #pragma warning disable VSTHRD103 return (await QueryInternal(new QueryReq { Filter = req }) @@ -129,6 +138,7 @@ public sealed class UserService( /// 用户名或密码错误 public async Task LoginByPwdAsync(LoginByPwdReq req) { + req.ThrowIfInvalid(); var pwd = req.Password.Pwd().Guid(); Sys_User dbUser; @@ -155,6 +165,7 @@ public sealed class UserService( /// 用户不存在 public async Task LoginBySmsAsync(LoginBySmsReq req) { + req.ThrowIfInvalid(); if (!await verifyCodeService.VerifyAsync(req.Adapt()).ConfigureAwait(false)) { throw new NetAdminInvalidOperationException(Ln.验证码不正确); } @@ -163,9 +174,18 @@ public sealed class UserService( return dbUser == null ? throw new NetAdminInvalidOperationException(Ln.用户不存在) : LoginInternal(dbUser); } + /// + public async Task LoginByUserIdAsync(long userId) + { + var dbUser = await Rpo.Where(a => a.Id == userId).ToOneAsync().ConfigureAwait(false); + + return LoginInternal(dbUser); + } + /// public async Task> PagedQueryAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); var list = await (await QueryInternalAsync(req).ConfigureAwait(false)).Page(req.Page, req.PageSize) .Count(out var total) .ToListAsync(_selectUserFields) @@ -176,6 +196,7 @@ public sealed class UserService( /// public async Task> QueryAsync(QueryReq req) { + req.ThrowIfInvalid(); var list = await (await QueryInternalAsync(req).ConfigureAwait(false)).Take(req.Count) .ToListAsync(_selectUserFields) .ConfigureAwait(false); @@ -185,6 +206,7 @@ public sealed class UserService( /// public Task> QueryProfileAsync(QueryReq req) { + req.ThrowIfInvalid(); return userProfileService.QueryAsync(req); } @@ -192,6 +214,7 @@ public sealed class UserService( /// 验证码不正确 public async Task RegisterAsync(RegisterUserReq req) { + req.ThrowIfInvalid(); if (!await verifyCodeService.VerifyAsync(req.VerifySmsCodeReq).ConfigureAwait(false)) { throw new NetAdminInvalidOperationException(Ln.验证码不正确); } @@ -205,6 +228,7 @@ public sealed class UserService( /// 用户不存在 public async Task ResetPasswordAsync(ResetPasswordReq req) { + req.ThrowIfInvalid(); return !await verifyCodeService.VerifyAsync(req.VerifySmsCodeReq).ConfigureAwait(false) ? throw new NetAdminInvalidOperationException(Ln.验证码不正确) : (uint)await Rpo.UpdateDiy @@ -221,6 +245,7 @@ public sealed class UserService( /// public async Task SetAvatarAsync(SetAvatarReq req) { + req.ThrowIfInvalid(); if (await Rpo.UpdateDiy .SetSource(req with { Id = UserToken.Id @@ -244,6 +269,7 @@ public sealed class UserService( /// public async Task SetEmailAsync(SetEmailReq req) { + req.ThrowIfInvalid(); var user = Rpo.Where(a => a.Id == UserToken.Id).ToOne(a => new { a.Mobile, a.Version, a.Email }); // 如果已绑定手机号、需要手机安全验证 @@ -277,6 +303,7 @@ public sealed class UserService( /// public async Task SetMobileAsync(SetMobileReq req) { + req.ThrowIfInvalid(); var user = await Rpo.Where(a => a.Id == UserToken.Id) .ToOneAsync(a => new { a.Version, a.Mobile }) .ConfigureAwait(false); @@ -321,6 +348,7 @@ public sealed class UserService( /// public async Task SetPasswordAsync(SetPasswordReq req) { + req.ThrowIfInvalid(); var version = await Rpo.Where(a => a.Id == UserToken.Id && a.Password == req.OldPassword.Pwd().Guid()) .ToOneAsync(a => new long?(a.Version)) .ConfigureAwait(false); @@ -343,6 +371,7 @@ public sealed class UserService( /// public async Task UpdateAsync(UpdateUserReq req) { + req.ThrowIfInvalid(); await CreateUpdateCheckAsync(req).ConfigureAwait(false); // 主表 @@ -374,6 +403,7 @@ public sealed class UserService( /// public Task UpdateSingleAsync(UpdateUserReq req) { + req.ThrowIfInvalid(); return Rpo.UpdateAsync(req); } diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/VerifyCodeService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/VerifyCodeService.cs index 0cd67834..943c5729 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Sys/VerifyCodeService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Sys/VerifyCodeService.cs @@ -19,6 +19,7 @@ public sealed class VerifyCodeService(DefaultRepository rpo, IEv /// public async Task BulkDeleteAsync(BulkReq req) { + req.ThrowIfInvalid(); var sum = 0; foreach (var item in req.Items) { sum += await DeleteAsync(item).ConfigureAwait(false); @@ -30,6 +31,7 @@ public sealed class VerifyCodeService(DefaultRepository rpo, IEv /// public async Task CreateAsync(CreateVerifyCodeReq req) { + req.ThrowIfInvalid(); var entity = await Rpo.InsertAsync(req).ConfigureAwait(false); var ret = entity.Adapt(); @@ -43,18 +45,21 @@ public sealed class VerifyCodeService(DefaultRepository rpo, IEv /// public Task DeleteAsync(DelReq req) { + req.ThrowIfInvalid(); return Rpo.DeleteAsync(a => a.Id == req.Id); } /// public Task ExistAsync(QueryReq req) { + req.ThrowIfInvalid(); return QueryInternal(req).AnyAsync(); } /// public async Task GetAsync(QueryVerifyCodeReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(new QueryReq { Filter = req }) .ToOneAsync() .ConfigureAwait(false); @@ -64,6 +69,7 @@ public sealed class VerifyCodeService(DefaultRepository rpo, IEv /// public async Task> PagedQueryAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); var list = await QueryInternal(req) .Page(req.Page, req.PageSize) .Count(out var total) @@ -77,6 +83,7 @@ public sealed class VerifyCodeService(DefaultRepository rpo, IEv /// public async Task> QueryAsync(QueryReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(req).Take(req.Count).ToListAsync().ConfigureAwait(false); return ret.Adapt>(); } @@ -84,6 +91,7 @@ public sealed class VerifyCodeService(DefaultRepository rpo, IEv /// public async Task SendVerifyCodeAsync(SendVerifyCodeReq req) { + req.ThrowIfInvalid(); var lastSent = await GetLastSentAsync(req.DestDevice).ConfigureAwait(false); QueryVerifyCodeRsp ret; @@ -110,6 +118,7 @@ public sealed class VerifyCodeService(DefaultRepository rpo, IEv /// public async Task UpdateAsync(UpdateVerifyCodeReq req) { + req.ThrowIfInvalid(); if (Rpo.Orm.Ado.DataType == DataType.Sqlite) { return await UpdateForSqliteAsync(req).ConfigureAwait(false) as QueryVerifyCodeRsp; } @@ -121,6 +130,7 @@ public sealed class VerifyCodeService(DefaultRepository rpo, IEv /// public async Task VerifyAsync(VerifyCodeReq req) { + req.ThrowIfInvalid(); #if DEBUG if (req.Code == "8888") { return true; diff --git a/src/backend/NetAdmin.SysComponent.Application/Services/Tpl/ExampleService.cs b/src/backend/NetAdmin.SysComponent.Application/Services/Tpl/ExampleService.cs index fd57cc16..f2f511d6 100644 --- a/src/backend/NetAdmin.SysComponent.Application/Services/Tpl/ExampleService.cs +++ b/src/backend/NetAdmin.SysComponent.Application/Services/Tpl/ExampleService.cs @@ -15,6 +15,7 @@ public sealed class ExampleService(DefaultRepository rpo) // /// public async Task BulkDeleteAsync(BulkReq req) { + req.ThrowIfInvalid(); var sum = 0; foreach (var item in req.Items) { sum += await DeleteAsync(item).ConfigureAwait(false); @@ -26,6 +27,7 @@ public sealed class ExampleService(DefaultRepository rpo) // /// public async Task CreateAsync(CreateExampleReq req) { + req.ThrowIfInvalid(); var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); return ret.Adapt(); } @@ -33,18 +35,21 @@ public sealed class ExampleService(DefaultRepository rpo) // /// public Task DeleteAsync(DelReq req) { + req.ThrowIfInvalid(); return Rpo.DeleteAsync(a => a.Id == req.Id); } /// public Task ExistAsync(QueryReq req) { + req.ThrowIfInvalid(); return QueryInternal(req).AnyAsync(); } /// public async Task GetAsync(QueryExampleReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(new QueryReq { Filter = req }) .ToOneAsync() .ConfigureAwait(false); @@ -54,6 +59,7 @@ public sealed class ExampleService(DefaultRepository rpo) // /// public async Task> PagedQueryAsync(PagedQueryReq req) { + req.ThrowIfInvalid(); var list = await QueryInternal(req) .Page(req.Page, req.PageSize) .Count(out var total) @@ -67,6 +73,7 @@ public sealed class ExampleService(DefaultRepository rpo) // /// public async Task> QueryAsync(QueryReq req) { + req.ThrowIfInvalid(); var ret = await QueryInternal(req).Take(req.Count).ToListAsync().ConfigureAwait(false); return ret.Adapt>(); } @@ -74,6 +81,7 @@ public sealed class ExampleService(DefaultRepository rpo) // /// public async Task UpdateAsync(UpdateExampleReq req) { + req.ThrowIfInvalid(); if (Rpo.Orm.Ado.DataType == DataType.Sqlite) { return await UpdateForSqliteAsync(req).ConfigureAwait(false) as QueryExampleRsp; } diff --git a/src/backend/NetAdmin.SysComponent.Cache/Sys/Dependency/IJobCache.cs b/src/backend/NetAdmin.SysComponent.Cache/Sys/Dependency/IJobCache.cs new file mode 100644 index 00000000..a8a8bb1a --- /dev/null +++ b/src/backend/NetAdmin.SysComponent.Cache/Sys/Dependency/IJobCache.cs @@ -0,0 +1,10 @@ +using NetAdmin.Cache; +using NetAdmin.SysComponent.Application.Modules.Sys; +using NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 计划作业缓存 +/// +public interface IJobCache : ICache, IJobModule; \ No newline at end of file diff --git a/src/backend/NetAdmin.SysComponent.Cache/Sys/Dependency/IJobRecordCache.cs b/src/backend/NetAdmin.SysComponent.Cache/Sys/Dependency/IJobRecordCache.cs new file mode 100644 index 00000000..e8551afe --- /dev/null +++ b/src/backend/NetAdmin.SysComponent.Cache/Sys/Dependency/IJobRecordCache.cs @@ -0,0 +1,10 @@ +using NetAdmin.Cache; +using NetAdmin.SysComponent.Application.Modules.Sys; +using NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 计划作业执行记录缓存 +/// +public interface IJobRecordCache : ICache, IJobRecordModule; \ No newline at end of file diff --git a/src/backend/NetAdmin.SysComponent.Cache/Sys/JobCache.cs b/src/backend/NetAdmin.SysComponent.Cache/Sys/JobCache.cs new file mode 100644 index 00000000..e18999f6 --- /dev/null +++ b/src/backend/NetAdmin.SysComponent.Cache/Sys/JobCache.cs @@ -0,0 +1,66 @@ +using NetAdmin.Cache; +using NetAdmin.Domain.Dto.Dependency; +using NetAdmin.Domain.Dto.Sys.Job; +using NetAdmin.SysComponent.Application.Services.Sys.Dependency; +using NetAdmin.SysComponent.Cache.Sys.Dependency; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class JobCache(IDistributedCache cache, IJobService service) + : DistributedCache(cache, service), IScoped, IJobCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CreateAsync(CreateJobReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task GetAsync(QueryJobReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } + + /// + public Task SetEnabledAsync(UpdateJobReq req) + { + return Service.SetEnabledAsync(req); + } + + /// + public Task UpdateAsync(UpdateJobReq req) + { + return Service.UpdateAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin.SysComponent.Cache/Sys/JobRecordCache.cs b/src/backend/NetAdmin.SysComponent.Cache/Sys/JobRecordCache.cs new file mode 100644 index 00000000..e3cbfc1e --- /dev/null +++ b/src/backend/NetAdmin.SysComponent.Cache/Sys/JobRecordCache.cs @@ -0,0 +1,60 @@ +using NetAdmin.Cache; +using NetAdmin.Domain.Dto.Dependency; +using NetAdmin.Domain.Dto.Sys.JobRecord; +using NetAdmin.SysComponent.Application.Services.Sys.Dependency; +using NetAdmin.SysComponent.Cache.Sys.Dependency; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class JobRecordCache(IDistributedCache cache, IJobRecordService service) + : DistributedCache(cache, service), IScoped, IJobRecordCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CreateAsync(CreateJobRecordReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task GetAsync(QueryJobRecordReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } + + /// + public Task UpdateAsync(UpdateJobRecordReq req) + { + return Service.UpdateAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin.SysComponent.Host/Controllers/Sys/JobController.cs b/src/backend/NetAdmin.SysComponent.Host/Controllers/Sys/JobController.cs new file mode 100644 index 00000000..b08a6fd3 --- /dev/null +++ b/src/backend/NetAdmin.SysComponent.Host/Controllers/Sys/JobController.cs @@ -0,0 +1,92 @@ +using NetAdmin.Domain.Dto.Dependency; +using NetAdmin.Domain.Dto.Sys.Job; +using NetAdmin.Host.Attributes; +using NetAdmin.Host.Controllers; +using NetAdmin.SysComponent.Application.Modules.Sys; +using NetAdmin.SysComponent.Application.Services.Sys.Dependency; +using NetAdmin.SysComponent.Cache.Sys.Dependency; + +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 计划作业服务 +/// +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +public sealed class JobController(IJobCache cache) : ControllerBase(cache), IJobModule +{ + /// + /// 批量删除计划作业 + /// + [Transaction] + public Task BulkDeleteAsync(BulkReq req) + { + return Cache.BulkDeleteAsync(req); + } + + /// + /// 创建计划作业 + /// + [Transaction] + public Task CreateAsync(CreateJobReq req) + { + return Cache.CreateAsync(req); + } + + /// + /// 删除计划作业 + /// + [Transaction] + public Task DeleteAsync(DelReq req) + { + return Cache.DeleteAsync(req); + } + + /// + /// 计划作业是否存在 + /// + public Task ExistAsync(QueryReq req) + { + return Cache.ExistAsync(req); + } + + /// + /// 获取单个计划作业 + /// + public Task GetAsync(QueryJobReq req) + { + return Cache.GetAsync(req); + } + + /// + /// 分页查询计划作业 + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Cache.PagedQueryAsync(req); + } + + /// + /// 查询计划作业 + /// + public Task> QueryAsync(QueryReq req) + { + return Cache.QueryAsync(req); + } + + /// + /// 启用/禁用作业 + /// + public Task SetEnabledAsync(UpdateJobReq req) + { + return Cache.SetEnabledAsync(req); + } + + /// + /// 更新计划作业 + /// + [Transaction] + public Task UpdateAsync(UpdateJobReq req) + { + return Cache.UpdateAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin.SysComponent.Host/Extensions/ServiceCollectionExtensions.cs b/src/backend/NetAdmin.SysComponent.Host/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..0c792a78 --- /dev/null +++ b/src/backend/NetAdmin.SysComponent.Host/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,21 @@ +using Furion.Schedule; +using NetAdmin.SysComponent.Host.Jobs; + +namespace NetAdmin.SysComponent.Host.Extensions; + +/// +/// ServiceCollection 扩展方法 +/// +[SuppressSniffer] +public static class ServiceCollectionExtensions +{ + /// + /// 添加定时任务 + /// + public static IServiceCollection AddSchedules(this IServiceCollection me) + { + return me.AddSchedule( // + builder => builder // + .AddJob(false, Triggers.PeriodSeconds(5).SetRunOnStart(true))); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin.SysComponent.Host/Jobs/ScheduledJob.cs b/src/backend/NetAdmin.SysComponent.Host/Jobs/ScheduledJob.cs new file mode 100644 index 00000000..3bb303c6 --- /dev/null +++ b/src/backend/NetAdmin.SysComponent.Host/Jobs/ScheduledJob.cs @@ -0,0 +1,141 @@ +using FreeSql.Internal; +using Furion.RemoteRequest; +using Furion.RemoteRequest.Extensions; +using Furion.Schedule; +using NetAdmin.Domain.DbMaps.Sys; +using NetAdmin.Domain.Dto.Sys.Job; +using NetAdmin.Domain.Dto.Sys.JobRecord; +using NetAdmin.Host.BackgroundRunning; +using NetAdmin.Host.Extensions; +using NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +namespace NetAdmin.SysComponent.Host.Jobs; + +/// +/// 计划作业 +/// +public sealed class ScheduledJob : WorkBase, IJob +{ + private static string _accessToken; + private static string _refreshToken; + private readonly IJobRecordService _jobRecordService; + private readonly IJobService _jobService; + private readonly ILogger _logger; + private readonly IUserService _userService; + private string _requestHeader; + + /// + /// Initializes a new instance of the class. + /// + public ScheduledJob() + { + _jobRecordService = ServiceProvider.GetService(); + _jobService = ServiceProvider.GetService(); + _logger = ServiceProvider.GetService>(); + _userService = ServiceProvider.GetService(); + } + + /// + /// 具体处理逻辑 + /// + /// 作业执行前上下文 + /// 取消任务 Token + /// 加锁失败异常 + public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken) + { + await WorkflowAsync(stoppingToken).ConfigureAwait(false); + } + + /// + /// 通用工作流 + /// + /// NotImplementedException + /// ArgumentOutOfRangeException + protected override async ValueTask WorkflowAsync(CancellationToken cancelToken) + { + QueryJobRsp job = null; + try { + job = await _jobService.GetNextJobAsync().ConfigureAwait(false); + } + catch (DbUpdateVersionException) { + // ignore + } + + if (job == null) { + _logger.Info(Ln.未获取到待执行任务); + return; + } + + var request = BuildRequest(job); + var sw = new Stopwatch(); + sw.Start(); + var rsp = await request.SendAsync(cancelToken).ConfigureAwait(false); + if (rsp.StatusCode == HttpStatusCode.Unauthorized) { + var loginRsp = await _userService.LoginByUserIdAsync(job.UserId).ConfigureAwait(false); + #pragma warning disable S2696 + _accessToken = loginRsp.AccessToken; + _refreshToken = loginRsp.RefreshToken; + #pragma warning restore S2696 + request = BuildRequest(job); + rsp = await request.SendAsync(cancelToken).ConfigureAwait(false); + } + + sw.Stop(); + await UowManager.AtomicOperateAsync(async () => { + var rspBody = await rsp.Content.ReadAsStringAsync(cancelToken).ConfigureAwait(false); + var jobRecord = new CreateJobRecordReq // + { + Duration = sw.ElapsedMilliseconds + , HttpMethod = job.HttpMethod + , HttpStatusCode = rsp.StatusCode + , JobId = job.Id + , RequestBody = job.RequestBody + , RequestHeader = _requestHeader + , RequestUrl = job.RequestUrl + , ResponseBody = rspBody + , ResponseHeader = rsp.Headers.Json() + , TimeId = job.NextTimeId!.Value + }; + _ = await _jobRecordService.CreateAsync(jobRecord).ConfigureAwait(false); + await _jobService + .FinishJobAsync(job.Adapt() with { LastStatusCode = rsp.StatusCode }) + .ConfigureAwait(false); + }) + .ConfigureAwait(false); + } + + private HttpRequestPart BuildRequest(Sys_Job job) + { + var ret = job.RequestUrl.SetHttpMethod(new HttpMethod(job.HttpMethod.ToString())); + var headers = new Dictionary(); + + if (!_accessToken.NullOrEmpty()) { + headers.Add(Chars.FLG_ACCESS_TOKEN_HEADER_KEY, $"{Chars.FLG_AUTH_SCHEMA} {_accessToken}"); + } + + if (!_refreshToken.NullOrEmpty()) { + headers.Add(Chars.FLG_X_ACCESS_TOKEN_HEADER_KEY, $"{Chars.FLG_AUTH_SCHEMA} {_refreshToken}"); + } + + if (!job.RequestHeader.NullOrEmpty()) { + ret = ret.SetHeaders(headers.Union(job.RequestHeader.Object>()) + .ToDictionary(x => x.Key, x => x.Value)); + } + + if (!job.RequestBody.NullOrEmpty()) { + ret = ret.SetBody(job.RequestBody); + } + + return ret.OnResponsing(GetRequestHeader).OnException(GetRequestHeader); + } + + private void GetRequestHeader(HttpClient _, HttpResponseMessage rsp, string __) + { + _requestHeader = rsp!.RequestMessage!.Headers.Json(); + } + + private void GetRequestHeader(HttpClient _, HttpResponseMessage rsp) + { + GetRequestHeader(_, rsp, null); + } +} \ No newline at end of file diff --git a/src/frontend/admin/src/api/sys/job.js b/src/frontend/admin/src/api/sys/job.js new file mode 100644 index 00000000..9a3a5c93 --- /dev/null +++ b/src/frontend/admin/src/api/sys/job.js @@ -0,0 +1,106 @@ +/** + * 计划作业服务 + * @module @/api/sys/job + */ +import config from '@/config' +import http from '@/utils/request' +export default { + /** + * 批量删除计划作业 + */ + bulkDelete: { + url: `${config.API_URL}/api/sys/job/bulk.delete`, + name: `批量删除计划作业`, + post: async function (data = {}, config = {}) { + return await http.post(this.url, data, config) + }, + }, + + /** + * 创建计划作业 + */ + create: { + url: `${config.API_URL}/api/sys/job/create`, + name: `创建计划作业`, + post: async function (data = {}, config = {}) { + return await http.post(this.url, data, config) + }, + }, + + /** + * 删除计划作业 + */ + delete: { + url: `${config.API_URL}/api/sys/job/delete`, + name: `删除计划作业`, + post: async function (data = {}, config = {}) { + return await http.post(this.url, data, config) + }, + }, + + /** + * 计划作业是否存在 + */ + exist: { + url: `${config.API_URL}/api/sys/job/exist`, + name: `计划作业是否存在`, + post: async function (data = {}, config = {}) { + return await http.post(this.url, data, config) + }, + }, + + /** + * 获取单个计划作业 + */ + get: { + url: `${config.API_URL}/api/sys/job/get`, + name: `获取单个计划作业`, + post: async function (data = {}, config = {}) { + return await http.post(this.url, data, config) + }, + }, + + /** + * 分页查询计划作业 + */ + pagedQuery: { + url: `${config.API_URL}/api/sys/job/paged.query`, + name: `分页查询计划作业`, + post: async function (data = {}, config = {}) { + return await http.post(this.url, data, config) + }, + }, + + /** + * 查询计划作业 + */ + query: { + url: `${config.API_URL}/api/sys/job/query`, + name: `查询计划作业`, + post: async function (data = {}, config = {}) { + return await http.post(this.url, data, config) + }, + }, + + /** + * 启用/禁用作业 + */ + setEnabled: { + url: `${config.API_URL}/api/sys/job/set.enabled`, + name: `启用/禁用作业`, + post: async function (data = {}, config = {}) { + return await http.post(this.url, data, config) + }, + }, + + /** + * 更新计划作业 + */ + update: { + url: `${config.API_URL}/api/sys/job/update`, + name: `更新计划作业`, + post: async function (data = {}, config = {}) { + return await http.post(this.url, data, config) + }, + }, +} \ No newline at end of file diff --git a/src/frontend/admin/src/assets/icons/ScheduledJob.vue b/src/frontend/admin/src/assets/icons/ScheduledJob.vue new file mode 100644 index 00000000..0e2483c5 --- /dev/null +++ b/src/frontend/admin/src/assets/icons/ScheduledJob.vue @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/src/frontend/admin/src/assets/icons/index.js b/src/frontend/admin/src/assets/icons/index.js index d2d4bb37..13a500a9 100644 --- a/src/frontend/admin/src/assets/icons/index.js +++ b/src/frontend/admin/src/assets/icons/index.js @@ -52,4 +52,5 @@ export { default as LoginLog } from './LoginLog.vue' export { default as ExLog } from './ExLog.vue' export { default as Key } from './Key.vue' export { default as OpenDoor } from './OpenDoor.vue' -export { default as Alert } from './Alert.vue' \ No newline at end of file +export { default as Alert } from './Alert.vue' +export { default as ScheduledJob } from './ScheduledJob.vue' \ No newline at end of file diff --git a/src/frontend/admin/src/config/iconSelect.js b/src/frontend/admin/src/config/iconSelect.js index 759e0d3f..0931ae35 100644 --- a/src/frontend/admin/src/config/iconSelect.js +++ b/src/frontend/admin/src/config/iconSelect.js @@ -1 +1 @@ -export default{"icons":[{"icons":["el-icon-add-location","el-icon-aim","el-icon-alarm-clock","el-icon-apple","el-icon-arrow-down","el-icon-arrow-down-bold","el-icon-arrow-left","el-icon-arrow-left-bold","el-icon-arrow-right","el-icon-arrow-right-bold","el-icon-arrow-up","el-icon-arrow-up-bold","el-icon-avatar","el-icon-back","el-icon-baseball","el-icon-basketball","el-icon-bell","el-icon-bell-filled","el-icon-bicycle","el-icon-bottom","el-icon-bottom-left","el-icon-bottom-right","el-icon-bowl","el-icon-box","el-icon-briefcase","el-icon-brush","el-icon-brush-filled","el-icon-burger","el-icon-calendar","el-icon-camera","el-icon-camera-filled","el-icon-caret-bottom","el-icon-caret-left","el-icon-caret-right","el-icon-caret-top","el-icon-cellphone","el-icon-chat-dot-round","el-icon-chat-dot-square","el-icon-chat-line-round","el-icon-chat-line-square","el-icon-chat-round","el-icon-chat-square","el-icon-check","el-icon-checked","el-icon-cherry","el-icon-chicken","el-icon-circle-check","el-icon-circle-check-filled","el-icon-circle-close","el-icon-circle-close-filled","el-icon-circle-plus","el-icon-circle-plus-filled","el-icon-clock","el-icon-close","el-icon-close-bold","el-icon-cloudy","el-icon-coffee","el-icon-coffee-cup","el-icon-coin","el-icon-cold-drink","el-icon-collection","el-icon-collection-tag","el-icon-comment","el-icon-compass","el-icon-connection","el-icon-coordinate","el-icon-copy-document","el-icon-cpu","el-icon-credit-card","el-icon-crop","el-icon-d-arrow-left","el-icon-d-arrow-right","el-icon-d-caret","el-icon-data-analysis","el-icon-data-board","el-icon-data-line","el-icon-delete","el-icon-delete-filled","el-icon-delete-location","el-icon-dessert","el-icon-discount","el-icon-dish","el-icon-dish-dot","el-icon-document","el-icon-document-add","el-icon-document-checked","el-icon-document-copy","el-icon-document-delete","el-icon-document-remove","el-icon-download","el-icon-drizzling","el-icon-edit","el-icon-edit-pen","el-icon-eleme","el-icon-eleme-filled","el-icon-element-plus","el-icon-expand","el-icon-failed","el-icon-female","el-icon-files","el-icon-film","el-icon-filter","el-icon-finished","el-icon-first-aid-kit","el-icon-flag","el-icon-fold","el-icon-folder","el-icon-folder-add","el-icon-folder-checked","el-icon-folder-delete","el-icon-folder-opened","el-icon-folder-remove","el-icon-food","el-icon-football","el-icon-fork-spoon","el-icon-fries","el-icon-full-screen","el-icon-goblet","el-icon-goblet-full","el-icon-goblet-square","el-icon-goblet-square-full","el-icon-goods","el-icon-goods-filled","el-icon-grape","el-icon-grid","el-icon-guide","el-icon-headset","el-icon-help","el-icon-help-filled","el-icon-hide","el-icon-histogram","el-icon-home-filled","el-icon-hot-water","el-icon-house","el-icon-ice-cream","el-icon-ice-cream-round","el-icon-ice-cream-square","el-icon-ice-drink","el-icon-ice-tea","el-icon-info-filled","el-icon-iphone","el-icon-key","el-icon-knife-fork","el-icon-lightning","el-icon-link","el-icon-list","el-icon-loading","el-icon-location","el-icon-location-filled","el-icon-location-information","el-icon-lock","el-icon-lollipop","el-icon-magic-stick","el-icon-magnet","el-icon-male","el-icon-management","el-icon-map-location","el-icon-medal","el-icon-menu","el-icon-message","el-icon-message-box","el-icon-mic","el-icon-microphone","el-icon-milk-tea","el-icon-minus","el-icon-money","el-icon-monitor","el-icon-moon","el-icon-moon-night","el-icon-more","el-icon-more-filled","el-icon-mostly-cloudy","el-icon-mouse","el-icon-mug","el-icon-mute","el-icon-mute-notification","el-icon-no-smoking","el-icon-notebook","el-icon-notification","el-icon-odometer","el-icon-office-building","el-icon-open","el-icon-operation","el-icon-opportunity","el-icon-orange","el-icon-paperclip","el-icon-partly-cloudy","el-icon-pear","el-icon-phone","el-icon-phone-filled","el-icon-picture","el-icon-picture-filled","el-icon-picture-rounded","el-icon-pie-chart","el-icon-place","el-icon-platform","el-icon-plus","el-icon-pointer","el-icon-position","el-icon-postcard","el-icon-pouring","el-icon-present","el-icon-price-tag","el-icon-printer","el-icon-promotion","el-icon-question-filled","el-icon-rank","el-icon-reading","el-icon-reading-lamp","el-icon-refresh","el-icon-refresh-left","el-icon-refresh-right","el-icon-refrigerator","el-icon-remove","el-icon-remove-filled","el-icon-right","el-icon-scale-to-original","el-icon-school","el-icon-scissor","el-icon-search","el-icon-select","el-icon-sell","el-icon-semi-select","el-icon-service","el-icon-set-up","el-icon-setting","el-icon-share","el-icon-ship","el-icon-shop","el-icon-shopping-bag","el-icon-shopping-cart","el-icon-shopping-cart-full","el-icon-smoking","el-icon-soccer","el-icon-sold-out","el-icon-sort","el-icon-sort-down","el-icon-sort-up","el-icon-stamp","el-icon-star","el-icon-star-filled","el-icon-stopwatch","el-icon-success-filled","el-icon-sugar","el-icon-suitcase","el-icon-sunny","el-icon-sunrise","el-icon-sunset","el-icon-switch","el-icon-switch-button","el-icon-takeaway-box","el-icon-ticket","el-icon-tickets","el-icon-timer","el-icon-toilet-paper","el-icon-tools","el-icon-top","el-icon-top-left","el-icon-top-right","el-icon-trend-charts","el-icon-trophy","el-icon-turn-off","el-icon-umbrella","el-icon-unlock","el-icon-upload","el-icon-upload-filled","el-icon-user","el-icon-user-filled","el-icon-van","el-icon-video-camera","el-icon-video-camera-filled","el-icon-video-pause","el-icon-video-play","el-icon-view","el-icon-wallet","el-icon-wallet-filled","el-icon-warning","el-icon-warning-filled","el-icon-watch","el-icon-watermelon","el-icon-wind-power","el-icon-zoom-in","el-icon-zoom-out"],"name":"默认"},{"icons":["sc-icon-Vue","sc-icon-Code","sc-icon-Wechat","sc-icon-BugFill","sc-icon-BugLine","sc-icon-FileWord","sc-icon-FileExcel","sc-icon-FilePpt","sc-icon-Organization","sc-icon-Upload","sc-icon-Download","sc-icon-Role","sc-icon-Dept","sc-icon-Js","sc-icon-Memory","sc-icon-Dashboard","sc-icon-Api","sc-icon-Code2","sc-icon-Csharp","sc-icon-Dic","sc-icon-Position","sc-icon-Tpl","sc-icon-Demo","sc-icon-Link","sc-icon-Unlink","sc-icon-Send","sc-icon-Smscode","sc-icon-Meter","sc-icon-Grafana","sc-icon-Elastic","sc-icon-Kibana","sc-icon-Kafka","sc-icon-Resource","sc-icon-Robot","sc-icon-Device","sc-icon-Business","sc-icon-App","sc-icon-App2","sc-icon-Sync","sc-icon-Drone","sc-icon-Gitea","sc-icon-Docker","sc-icon-Task","sc-icon-ProductCategory","sc-icon-Product","sc-icon-Error","sc-icon-Warning","sc-icon-Stats","sc-icon-Log","sc-icon-OperLog","sc-icon-LoginLog","sc-icon-ExLog","sc-icon-Key","sc-icon-OpenDoor","sc-icon-Alert"],"name":"扩展"}]} \ No newline at end of file +export default{"icons":[{"icons":["el-icon-add-location","el-icon-aim","el-icon-alarm-clock","el-icon-apple","el-icon-arrow-down","el-icon-arrow-down-bold","el-icon-arrow-left","el-icon-arrow-left-bold","el-icon-arrow-right","el-icon-arrow-right-bold","el-icon-arrow-up","el-icon-arrow-up-bold","el-icon-avatar","el-icon-back","el-icon-baseball","el-icon-basketball","el-icon-bell","el-icon-bell-filled","el-icon-bicycle","el-icon-bottom","el-icon-bottom-left","el-icon-bottom-right","el-icon-bowl","el-icon-box","el-icon-briefcase","el-icon-brush","el-icon-brush-filled","el-icon-burger","el-icon-calendar","el-icon-camera","el-icon-camera-filled","el-icon-caret-bottom","el-icon-caret-left","el-icon-caret-right","el-icon-caret-top","el-icon-cellphone","el-icon-chat-dot-round","el-icon-chat-dot-square","el-icon-chat-line-round","el-icon-chat-line-square","el-icon-chat-round","el-icon-chat-square","el-icon-check","el-icon-checked","el-icon-cherry","el-icon-chicken","el-icon-circle-check","el-icon-circle-check-filled","el-icon-circle-close","el-icon-circle-close-filled","el-icon-circle-plus","el-icon-circle-plus-filled","el-icon-clock","el-icon-close","el-icon-close-bold","el-icon-cloudy","el-icon-coffee","el-icon-coffee-cup","el-icon-coin","el-icon-cold-drink","el-icon-collection","el-icon-collection-tag","el-icon-comment","el-icon-compass","el-icon-connection","el-icon-coordinate","el-icon-copy-document","el-icon-cpu","el-icon-credit-card","el-icon-crop","el-icon-d-arrow-left","el-icon-d-arrow-right","el-icon-d-caret","el-icon-data-analysis","el-icon-data-board","el-icon-data-line","el-icon-delete","el-icon-delete-filled","el-icon-delete-location","el-icon-dessert","el-icon-discount","el-icon-dish","el-icon-dish-dot","el-icon-document","el-icon-document-add","el-icon-document-checked","el-icon-document-copy","el-icon-document-delete","el-icon-document-remove","el-icon-download","el-icon-drizzling","el-icon-edit","el-icon-edit-pen","el-icon-eleme","el-icon-eleme-filled","el-icon-element-plus","el-icon-expand","el-icon-failed","el-icon-female","el-icon-files","el-icon-film","el-icon-filter","el-icon-finished","el-icon-first-aid-kit","el-icon-flag","el-icon-fold","el-icon-folder","el-icon-folder-add","el-icon-folder-checked","el-icon-folder-delete","el-icon-folder-opened","el-icon-folder-remove","el-icon-food","el-icon-football","el-icon-fork-spoon","el-icon-fries","el-icon-full-screen","el-icon-goblet","el-icon-goblet-full","el-icon-goblet-square","el-icon-goblet-square-full","el-icon-goods","el-icon-goods-filled","el-icon-grape","el-icon-grid","el-icon-guide","el-icon-headset","el-icon-help","el-icon-help-filled","el-icon-hide","el-icon-histogram","el-icon-home-filled","el-icon-hot-water","el-icon-house","el-icon-ice-cream","el-icon-ice-cream-round","el-icon-ice-cream-square","el-icon-ice-drink","el-icon-ice-tea","el-icon-info-filled","el-icon-iphone","el-icon-key","el-icon-knife-fork","el-icon-lightning","el-icon-link","el-icon-list","el-icon-loading","el-icon-location","el-icon-location-filled","el-icon-location-information","el-icon-lock","el-icon-lollipop","el-icon-magic-stick","el-icon-magnet","el-icon-male","el-icon-management","el-icon-map-location","el-icon-medal","el-icon-menu","el-icon-message","el-icon-message-box","el-icon-mic","el-icon-microphone","el-icon-milk-tea","el-icon-minus","el-icon-money","el-icon-monitor","el-icon-moon","el-icon-moon-night","el-icon-more","el-icon-more-filled","el-icon-mostly-cloudy","el-icon-mouse","el-icon-mug","el-icon-mute","el-icon-mute-notification","el-icon-no-smoking","el-icon-notebook","el-icon-notification","el-icon-odometer","el-icon-office-building","el-icon-open","el-icon-operation","el-icon-opportunity","el-icon-orange","el-icon-paperclip","el-icon-partly-cloudy","el-icon-pear","el-icon-phone","el-icon-phone-filled","el-icon-picture","el-icon-picture-filled","el-icon-picture-rounded","el-icon-pie-chart","el-icon-place","el-icon-platform","el-icon-plus","el-icon-pointer","el-icon-position","el-icon-postcard","el-icon-pouring","el-icon-present","el-icon-price-tag","el-icon-printer","el-icon-promotion","el-icon-question-filled","el-icon-rank","el-icon-reading","el-icon-reading-lamp","el-icon-refresh","el-icon-refresh-left","el-icon-refresh-right","el-icon-refrigerator","el-icon-remove","el-icon-remove-filled","el-icon-right","el-icon-scale-to-original","el-icon-school","el-icon-scissor","el-icon-search","el-icon-select","el-icon-sell","el-icon-semi-select","el-icon-service","el-icon-set-up","el-icon-setting","el-icon-share","el-icon-ship","el-icon-shop","el-icon-shopping-bag","el-icon-shopping-cart","el-icon-shopping-cart-full","el-icon-smoking","el-icon-soccer","el-icon-sold-out","el-icon-sort","el-icon-sort-down","el-icon-sort-up","el-icon-stamp","el-icon-star","el-icon-star-filled","el-icon-stopwatch","el-icon-success-filled","el-icon-sugar","el-icon-suitcase","el-icon-sunny","el-icon-sunrise","el-icon-sunset","el-icon-switch","el-icon-switch-button","el-icon-takeaway-box","el-icon-ticket","el-icon-tickets","el-icon-timer","el-icon-toilet-paper","el-icon-tools","el-icon-top","el-icon-top-left","el-icon-top-right","el-icon-trend-charts","el-icon-trophy","el-icon-turn-off","el-icon-umbrella","el-icon-unlock","el-icon-upload","el-icon-upload-filled","el-icon-user","el-icon-user-filled","el-icon-van","el-icon-video-camera","el-icon-video-camera-filled","el-icon-video-pause","el-icon-video-play","el-icon-view","el-icon-wallet","el-icon-wallet-filled","el-icon-warning","el-icon-warning-filled","el-icon-watch","el-icon-watermelon","el-icon-wind-power","el-icon-zoom-in","el-icon-zoom-out"],"name":"默认"},{"icons":["sc-icon-Vue","sc-icon-Code","sc-icon-Wechat","sc-icon-BugFill","sc-icon-BugLine","sc-icon-FileWord","sc-icon-FileExcel","sc-icon-FilePpt","sc-icon-Organization","sc-icon-Upload","sc-icon-Download","sc-icon-Role","sc-icon-Dept","sc-icon-Js","sc-icon-Memory","sc-icon-Dashboard","sc-icon-Api","sc-icon-Code2","sc-icon-Csharp","sc-icon-Dic","sc-icon-Position","sc-icon-Tpl","sc-icon-Demo","sc-icon-Link","sc-icon-Unlink","sc-icon-Send","sc-icon-Smscode","sc-icon-Meter","sc-icon-Grafana","sc-icon-Elastic","sc-icon-Kibana","sc-icon-Kafka","sc-icon-Resource","sc-icon-Robot","sc-icon-Device","sc-icon-Business","sc-icon-App","sc-icon-App2","sc-icon-Sync","sc-icon-Drone","sc-icon-Gitea","sc-icon-Docker","sc-icon-Task","sc-icon-ProductCategory","sc-icon-Product","sc-icon-Error","sc-icon-Warning","sc-icon-Stats","sc-icon-Log","sc-icon-OperLog","sc-icon-LoginLog","sc-icon-ExLog","sc-icon-Key","sc-icon-OpenDoor","sc-icon-Alert","sc-icon-ScheduledJob"],"name":"扩展"}]} \ No newline at end of file diff --git a/src/frontend/admin/src/style/app.scss b/src/frontend/admin/src/style/app.scss index 7b3ed06b..883e3122 100644 --- a/src/frontend/admin/src/style/app.scss +++ b/src/frontend/admin/src/style/app.scss @@ -6,6 +6,7 @@ html { height: 100%; background-color: #f6f8f9; font-size: 13px; + font-family: monospace; } a { diff --git a/src/frontend/admin/src/views/sys/job/index.vue b/src/frontend/admin/src/views/sys/job/index.vue new file mode 100644 index 00000000..e33f5419 --- /dev/null +++ b/src/frontend/admin/src/views/sys/job/index.vue @@ -0,0 +1,229 @@ + + + + + \ No newline at end of file diff --git a/src/frontend/admin/src/views/sys/job/save.vue b/src/frontend/admin/src/views/sys/job/save.vue new file mode 100644 index 00000000..6eac60e2 --- /dev/null +++ b/src/frontend/admin/src/views/sys/job/save.vue @@ -0,0 +1,193 @@ + + + \ No newline at end of file