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