diff --git a/AntJob.Agent/AntJob.Agent.csproj b/AntJob.Agent/AntJob.Agent.csproj
index ac5d7d7..7b0f4ca 100644
--- a/AntJob.Agent/AntJob.Agent.csproj
+++ b/AntJob.Agent/AntJob.Agent.csproj
@@ -7,7 +7,7 @@
调度中心下发C#或Sql给蚂蚁代理执行
新生命开发团队
版权所有(C) 新生命开发团队 2022
- 3.3
+ 3.4
$([System.DateTime]::Now.ToString(`yyyy.MMdd`))
$(VersionPrefix).$(VersionSuffix)
$(Version)
diff --git a/AntJob.Data/AntJob.Data.csproj b/AntJob.Data/AntJob.Data.csproj
index b74c561..eab036f 100644
--- a/AntJob.Data/AntJob.Data.csproj
+++ b/AntJob.Data/AntJob.Data.csproj
@@ -4,8 +4,8 @@
蚂蚁数据
蚂蚁调度系统数据库结构
新生命开发团队
- 版权所有(C) 新生命开发团队 2020
- 2.0
+ 版权所有(C) 新生命开发团队 2024
+ 3.4
$([System.DateTime]::Now.ToString(`yyyy.MMdd`))
$(VersionPrefix).$(VersionSuffix)
$(Version)
diff --git a/AntJob.Extensions/AntJob.Extensions.csproj b/AntJob.Extensions/AntJob.Extensions.csproj
index a0e7392..4bcf78e 100644
--- a/AntJob.Extensions/AntJob.Extensions.csproj
+++ b/AntJob.Extensions/AntJob.Extensions.csproj
@@ -5,8 +5,8 @@
蚂蚁数据调度SDK
分布式任务调度系统,纯NET打造的重量级大数据实时计算平台,万亿级调度经验积累。
新生命开发团队
- ©2002-2023 NewLife
- 3.3
+ ©2002-2024 NewLife
+ 3.4
$([System.DateTime]::Now.ToString(`yyyy.MMdd`))
$(VersionPrefix).$(VersionSuffix)
$(Version)
diff --git a/AntJob.Server/AntJob.Server.csproj b/AntJob.Server/AntJob.Server.csproj
index 475576b..6da9ada 100644
--- a/AntJob.Server/AntJob.Server.csproj
+++ b/AntJob.Server/AntJob.Server.csproj
@@ -7,7 +7,7 @@
分布式任务调度系统,纯NET打造的重量级大数据实时计算平台,万亿级调度经验积累
新生命开发团队
版权所有(C) 新生命开发团队 2023
- 3.3
+ 3.4
$([System.DateTime]::Now.ToString(`yyyy.MMdd`))
$(VersionPrefix).$(VersionSuffix)
$(Version)
diff --git a/AntJob.Server/AntService.cs b/AntJob.Server/AntService.cs
index 5bba367..5db006a 100644
--- a/AntJob.Server/AntService.cs
+++ b/AntJob.Server/AntService.cs
@@ -58,7 +58,8 @@ class AntService : IApi, IActionFilter
{
_App = app;
- var online = GetOnline(app, _Net);
+ var ip = _Net.Remote.Host;
+ var online = _appService.GetOnline(app, ip);
online.UpdateTime = TimerX.Now;
online.SaveAsync();
}
@@ -81,7 +82,9 @@ class AntService : IApi, IActionFilter
else
XTrace.WriteException(ex);
- WriteHistory(null, filterContext.ActionName, false, ex.GetMessage());
+ _Net = Session as INetSession;
+ var ip = _Net.Remote.Host;
+ _appService.WriteHistory(_App, filterContext.ActionName, false, ex.GetMessage(), ip);
}
}
}
@@ -136,39 +139,8 @@ class AntService : IApi, IActionFilter
var job = model.Job?.Trim();
if (job.IsNullOrEmpty()) return new TaskModel[0];
- return _jobService.Acquire(_App, model);
- }
-
- private void CheckErrorTask(App app, Job jb, Int32 count, List list)
- {
- // 每分钟检查一下错误任务和中断任务
- var nextKey = $"_NextAcquireOld_{jb.ID}";
- var now = TimerX.Now;
- var ext = Session as IExtend;
- var next = (DateTime)(ext[nextKey] ?? DateTime.MinValue);
- if (next < now)
- {
- //var ps = ControllerContext.Current.Parameters;
- //var server = ps["server"] + "";
- //var pid = ps["pid"].ToInt();
- var online = GetOnline(app, _Net);
- var ip = _Net.Remote.Host;
-
- next = now.AddSeconds(60);
- list.AddRange(jb.AcquireOld(online.Server, ip, online.ProcessId, count, _cacheProvider.Cache));
-
- if (list.Count > 0)
- {
- // 既然有数据,待会还来
- next = now;
-
- var n1 = list.Count(e => e.Status == JobStatus.错误 || e.Status == JobStatus.取消);
- var n2 = list.Count(e => e.Status == JobStatus.就绪 || e.Status == JobStatus.抽取中 || e.Status == JobStatus.处理中);
- _log.Info("作业[{0}/{1}]准备处理[{2}]个错误和[{3}]超时任务 [{4}]", app, jb.Name, n1, n2, list.Join(",", e => e.ID + ""));
- }
- else
- ext[nextKey] = next;
- }
+ var ip = _Net.Remote.Host;
+ return _jobService.Acquire(_App, model, ip);
}
/// 生产消息
@@ -191,7 +163,8 @@ class AntService : IApi, IActionFilter
{
if (task == null || task.ID == 0) throw new InvalidOperationException("无效操作 TaskID=" + task?.ID);
- return _jobService.Report(_App, task);
+ var ip = _Net.Remote.Host;
+ return _jobService.Report(_App, task, ip);
}
#endregion
}
\ No newline at end of file
diff --git a/AntJob.Server/Services/AppService.cs b/AntJob.Server/Services/AppService.cs
index c806860..f04e4b5 100644
--- a/AntJob.Server/Services/AppService.cs
+++ b/AntJob.Server/Services/AppService.cs
@@ -9,6 +9,11 @@ using NewLife.Log;
using NewLife.Net;
using NewLife.Security;
using AntJob.Models;
+using NewLife.Data;
+using NewLife.Remoting;
+using NewLife.Web;
+using System.Reflection;
+using System.Xml.Linq;
namespace AntJob.Server.Services;
@@ -64,14 +69,11 @@ public class AppService
app.Save();
// 应用上线
- var online = CreateOnline(app, _Net, model.Machine, model.ProcessId);
+ var online = CreateOnline(app, ip, model.Machine, model.ProcessId);
online.Version = model.Version;
online.CompileTime = model.Compile;
online.Save();
- //// 记录当前用户
- //Session["App"] = app;
-
WriteHistory(app, autoReg ? "注册" : "登录", true, $"[{model.User}/{model.Pass}]在[{model.Machine}@{model.ProcessId}]登录[{app}]成功");
var rs = new LoginResponse { Name = app.Name, DisplayName = app.DisplayName };
@@ -133,49 +135,34 @@ public class AppService
return olts.Select(e => e.ToModel()).ToArray();
}
- AppOnline CreateOnline(App app, INetSession ns, String machine, Int32 pid)
+ AppOnline CreateOnline(App app, String ip, String machine, Int32 pid)
{
- var ip = ns.Remote.Host;
-
- var online = GetOnline(app, ns);
+ var online = GetOnline(app, ip);
online.Client = $"{(ip.IsNullOrEmpty() ? machine : ip)}@{pid}";
online.Name = machine;
online.ProcessId = pid;
online.UpdateIP = ip;
//online.Version = version;
- online.Server = Local + "";
+ online.Server = Environment.MachineName;
//online.Save();
- // 真正的用户
- Session["AppOnline"] = online;
-
- // 下线
- ns.OnDisposed += (s, e) =>
- {
- online.Delete();
- WriteHistory(online.App, "下线", true, $"[{online.Name}]登录于{online.CreateTime},最后活跃于{online.UpdateTime}");
- };
-
return online;
}
- public AppOnline GetOnline(App app, INetSession ns)
+ public AppOnline GetOnline(App app, String ip)
{
- if (Session["AppOnline"] is AppOnline online) return online;
-
- var ip = ns.Remote.Host;
- var ins = ns.Remote.EndPoint + "";
- online = AppOnline.FindByInstance(ins) ?? new AppOnline { CreateIP = ip };
+ var ins = $"{app.Name}@{ip}";
+ var online = AppOnline.FindByInstance(ins) ?? new AppOnline { CreateIP = ip };
online.AppID = app.ID;
online.Instance = ins;
return online;
}
- public void UpdateOnline(App app, JobTask ji, INetSession ns)
+ public void UpdateOnline(App app, JobTask ji, String ip)
{
- var online = GetOnline(app, ns);
+ var online = GetOnline(app, ip);
online.Total += ji.Total;
online.Success += ji.Success;
online.Error += ji.Error;
@@ -187,6 +174,77 @@ public class AppService
#endregion
#region 写历史
- public void WriteHistory(App app, String action, Boolean success, String remark) => AppHistory.Create(app, action, success, remark, Local + "", _Net.Remote?.Host);
+ public void WriteHistory(App app, String action, Boolean success, String remark, String ip = null) =>
+ AppHistory.Create(app, action, success, remark, Environment.MachineName, ip);
+ #endregion
+
+ #region 辅助
+ public TokenModel IssueToken(String name, AntJobSetting set)
+ {
+ // 颁发令牌
+ var ss = set.TokenSecret.Split(':');
+ var jwt = new JwtBuilder
+ {
+ Issuer = Assembly.GetEntryAssembly().GetName().Name,
+ Subject = name,
+ Id = Rand.NextString(8),
+ Expire = DateTime.Now.AddSeconds(set.TokenExpire),
+
+ Algorithm = ss[0],
+ Secret = ss[1],
+ };
+
+ return new TokenModel
+ {
+ AccessToken = jwt.Encode(null),
+ TokenType = jwt.Type ?? "JWT",
+ ExpireIn = set.TokenExpire,
+ RefreshToken = jwt.Encode(null),
+ };
+ }
+
+ public (App, Exception) DecodeToken(String token, String tokenSecret)
+ {
+ if (token.IsNullOrEmpty()) throw new ArgumentNullException(nameof(token));
+ //if (token.IsNullOrEmpty()) throw new ApiException(401, $"节点未登录[ip={UserHost}]");
+
+ // 解码令牌
+ var ss = tokenSecret.Split(':');
+ var jwt = new JwtBuilder
+ {
+ Algorithm = ss[0],
+ Secret = ss[1],
+ };
+
+ var rs = jwt.TryDecode(token, out var message);
+ var app = App.FindByName(jwt.Subject);
+
+ Exception ex = null;
+ if (!rs || app == null)
+ {
+ if (app != null)
+ ex = new ApiException(403, $"[{app.Name}/{app.DisplayName}]非法访问 {message}");
+ else
+ ex = new ApiException(403, $"[{jwt.Subject}]非法访问 {message}");
+ }
+
+ return (app, ex);
+ }
+
+ public TokenModel ValidAndIssueToken(String deviceCode, String token, AntJobSetting set)
+ {
+ if (token.IsNullOrEmpty()) return null;
+ //var set = Setting.Current;
+
+ // 令牌有效期检查,10分钟内过期者,重新颁发令牌
+ var ss = set.TokenSecret.Split(':');
+ var jwt = new JwtBuilder
+ {
+ Algorithm = ss[0],
+ Secret = ss[1],
+ };
+ var rs = jwt.TryDecode(token, out var message);
+ return !rs || jwt == null ? null : DateTime.Now.AddMinutes(10) > jwt.Expire ? IssueToken(deviceCode, set) : null;
+ }
#endregion
}
diff --git a/AntJob.Server/Services/JobService.cs b/AntJob.Server/Services/JobService.cs
index 756cd24..7c87641 100644
--- a/AntJob.Server/Services/JobService.cs
+++ b/AntJob.Server/Services/JobService.cs
@@ -1,7 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using AntJob.Data;
+using AntJob.Data;
using AntJob.Data.Entity;
using AntJob.Models;
using NewLife;
@@ -87,7 +84,7 @@ public class JobService
/// 申请作业任务
/// 模型
///
- public ITask[] Acquire(App app, AcquireModel model)
+ public ITask[] Acquire(App app, AcquireModel model, String ip)
{
var job = model.Job?.Trim();
if (job.IsNullOrEmpty()) return new TaskModel[0];
@@ -111,12 +108,12 @@ public class JobService
if (jb == null) throw new XException($"应用[{app.ID}/{app.Name}]下未找到作业[{job}]");
if (jb.Step == 0 || jb.Start.Year <= 2000) throw new XException("作业[{0}/{1}]未设置开始时间或步进", jb.ID, jb.Name);
- var online = _appService.GetOnline(app, _Net);
+ var online = _appService.GetOnline(app, ip);
var list = new List();
// 每分钟检查一下错误任务和中断任务
- CheckErrorTask(app, jb, model.Count, list);
+ CheckErrorTask(app, jb, model.Count, list, ip);
// 错误项不够时,增加切片
if (list.Count < model.Count)
@@ -125,7 +122,6 @@ public class JobService
var server = online.Name;
var pid = online.ProcessId;
//var topic = ps["topic"] + "";
- var ip = _Net.Remote.Host;
switch (jb.Mode)
{
@@ -157,20 +153,15 @@ public class JobService
return list.Select(e => e.ToModel()).ToArray();
}
- private void CheckErrorTask(App app, Job jb, Int32 count, List list)
+ private void CheckErrorTask(App app, Job jb, Int32 count, List list, String ip)
{
// 每分钟检查一下错误任务和中断任务
- var nextKey = $"_NextAcquireOld_{jb.ID}";
+ var nextKey = $"antjob:NextAcquireOld_{jb.ID}";
var now = TimerX.Now;
- var ext = Session as IExtend;
- var next = (DateTime)(ext[nextKey] ?? DateTime.MinValue);
+ var next = _cacheProvider.Cache.Get(nextKey);
if (next < now)
{
- //var ps = ControllerContext.Current.Parameters;
- //var server = ps["server"] + "";
- //var pid = ps["pid"].ToInt();
- var online = _appService.GetOnline(app, _Net);
- var ip = _Net.Remote.Host;
+ var online = _appService.GetOnline(app, ip);
next = now.AddSeconds(60);
list.AddRange(jb.AcquireOld(online.Server, ip, online.ProcessId, count, _cacheProvider.Cache));
@@ -185,7 +176,7 @@ public class JobService
_log.Info("作业[{0}/{1}]准备处理[{2}]个错误和[{3}]超时任务 [{4}]", app, jb.Name, n1, n2, list.Join(",", e => e.ID + ""));
}
else
- ext[nextKey] = next;
+ _cacheProvider.Cache.Set(nextKey, next);
}
}
@@ -276,7 +267,7 @@ public class JobService
/// 报告状态(进度、成功、错误)
///
///
- public Boolean Report(App app, TaskResult task)
+ public Boolean Report(App app, TaskResult task, String ip)
{
if (task == null || task.ID == 0) throw new InvalidOperationException("无效操作 TaskID=" + task?.ID);
@@ -308,7 +299,7 @@ public class JobService
SetJobFinish(job, jt);
// 记录状态
- _appService.UpdateOnline(app, jt, _Net);
+ _appService.UpdateOnline(app, jt, ip);
}
if (task.Status == JobStatus.错误)
{
diff --git a/AntJob.Server/Setting.cs b/AntJob.Server/Setting.cs
index 7fbed39..c0fb289 100644
--- a/AntJob.Server/Setting.cs
+++ b/AntJob.Server/Setting.cs
@@ -16,6 +16,18 @@ public class AntJobSetting : Config
[Description("端口")]
public Int32 Port { get; set; } = 9999;
+ /// 令牌密钥。用于生成JWT令牌的算法和密钥,如HS256:ABCD1234
+ [Description("令牌密钥。用于生成JWT令牌的算法和密钥,如HS256:ABCD1234")]
+ public String TokenSecret { get; set; }
+
+ /// 令牌有效期。默认2*3600秒
+ [Description("令牌有效期。默认2*3600秒")]
+ public Int32 TokenExpire { get; set; } = 2 * 3600;
+
+ /// 会话超时。默认600秒
+ [Description("会话超时。默认600秒")]
+ public Int32 SessionTimeout { get; set; } = 600;
+
/// 自动注册。任意应用登录时自动注册,省去人工配置应用账号的麻烦,默认true
[Description("自动注册。任意应用登录时自动注册,省去人工配置应用账号的麻烦,默认true")]
public Boolean AutoRegistry { get; set; } = true;
diff --git a/AntJob.Web/AntJob.Web.csproj b/AntJob.Web/AntJob.Web.csproj
index 15c964e..601ca66 100644
--- a/AntJob.Web/AntJob.Web.csproj
+++ b/AntJob.Web/AntJob.Web.csproj
@@ -6,7 +6,7 @@
分布式任务调度系统,纯NET打造的重量级大数据实时计算平台,万亿级调度经验积累
新生命开发团队
版权所有(C) 新生命开发团队 2023
- 3.3
+ 3.4
$([System.DateTime]::Now.ToString(`yyyy.MMdd`))
$(VersionPrefix).$(VersionSuffix)
$(Version)
@@ -52,7 +52,6 @@
-
\ No newline at end of file
diff --git a/AntJob.Web/Common/ApiFilterAttribute.cs b/AntJob.Web/Common/ApiFilterAttribute.cs
new file mode 100644
index 0000000..c7f8381
--- /dev/null
+++ b/AntJob.Web/Common/ApiFilterAttribute.cs
@@ -0,0 +1,81 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using NewLife;
+using NewLife.Log;
+
+namespace AntJob.Web.Common;
+
+/// 统一Api过滤处理
+///
+/// 1,解析访问令牌
+/// 2,包装响应结果为标准Json格式
+/// 3,拦截异常,包装为标准Json格式
+///
+public sealed class ApiFilterAttribute : ActionFilterAttribute
+{
+ /// 从请求头中获取令牌
+ ///
+ ///
+ public static String GetToken(HttpContext httpContext)
+ {
+ var request = httpContext.Request;
+ var token = request.Query["Token"] + "";
+ if (token.IsNullOrEmpty()) token = (request.Headers["Authorization"] + "").TrimStart("Bearer ");
+ if (token.IsNullOrEmpty()) token = request.Headers["X-Token"] + "";
+ if (token.IsNullOrEmpty()) token = request.Cookies["Token"] + "";
+
+ return token;
+ }
+
+ /// 执行前,验证模型
+ ///
+ public override void OnActionExecuting(ActionExecutingContext context)
+ {
+ //if (!context.ModelState.IsValid)
+ // throw new ApplicationException(context.ModelState.Values.First(p => p.Errors.Count > 0).Errors[0].ErrorMessage);
+
+ // 访问令牌
+ var token = GetToken(context.HttpContext);
+ context.HttpContext.Items["Token"] = token;
+ if (!context.ActionArguments.ContainsKey("token")) context.ActionArguments.Add("token", token);
+
+ base.OnActionExecuting(context);
+ }
+
+ /// 执行后,包装结果和异常
+ ///
+ public override void OnActionExecuted(ActionExecutedContext context)
+ {
+ if (context.HttpContext.WebSockets.IsWebSocketRequest) return;
+
+ if (context.Result != null)
+ if (context.Result is ObjectResult obj)
+ context.Result = new JsonResult(new { code = obj.StatusCode ?? 0, data = obj.Value });
+ else if (context.Result is EmptyResult)
+ {
+ DefaultTracer.Instance?.NewSpan("apiFilter-EmptyResult");
+ context.Result = new JsonResult(new { code = 0, data = new { } });
+ }
+ else if (context.Exception != null && !context.ExceptionHandled)
+ {
+ var ex = context.Exception.GetTrue();
+ if (ex is NewLife.Remoting.ApiException aex)
+ context.Result = new JsonResult(new { code = aex.Code, data = aex.Message });
+ else
+ {
+ context.Result = new JsonResult(new { code = 500, data = ex.Message });
+
+ // 埋点拦截业务异常
+ var action = context.HttpContext.Request.Path + "";
+ if (context.ActionDescriptor is ControllerActionDescriptor act) action = $"/{act.ControllerName}/{act.ActionName}";
+
+ DefaultTracer.Instance?.NewError(action, ex);
+ }
+
+ context.ExceptionHandled = true;
+ }
+
+ base.OnActionExecuted(context);
+ }
+}
\ No newline at end of file
diff --git a/AntJob.Web/Controllers/AntJobController.cs b/AntJob.Web/Controllers/AntJobController.cs
new file mode 100644
index 0000000..4b62292
--- /dev/null
+++ b/AntJob.Web/Controllers/AntJobController.cs
@@ -0,0 +1,167 @@
+using System.Reflection;
+using AntJob.Data;
+using AntJob.Data.Entity;
+using AntJob.Models;
+using AntJob.Server;
+using AntJob.Server.Services;
+using AntJob.Web.Common;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using NewLife;
+using NewLife.Caching;
+using NewLife.Cube;
+using NewLife.Log;
+using NewLife.Remoting;
+using NewLife.Serialization;
+using IActionFilter = Microsoft.AspNetCore.Mvc.Filters.IActionFilter;
+
+namespace AntJob.Web.Controllers;
+
+public class AntJobController : ControllerBase, IActionFilter
+{
+ /// 令牌
+ public String Token { get; private set; }
+
+ /// 用户主机
+ public String UserHost => HttpContext.GetUserHost();
+
+ private App _App;
+ private IDictionary _args;
+ private AntJobSetting _setting;
+ private readonly AppService _appService;
+ private readonly JobService _jobService;
+ private readonly ICacheProvider _cacheProvider;
+
+ #region 构造
+ public AntJobController(AppService appService, JobService jobService, AntJobSetting setting)
+ {
+ _appService = appService;
+ _jobService = jobService;
+ _setting = setting;
+ }
+
+ void IActionFilter.OnActionExecuting(ActionExecutingContext context)
+ {
+ _args = context.ActionArguments;
+
+ var token = Token = ApiFilterAttribute.GetToken(context.HttpContext);
+
+ try
+ {
+ if (context.ActionDescriptor is ControllerActionDescriptor act && !act.MethodInfo.IsDefined(typeof(AllowAnonymousAttribute)))
+ {
+ var rs = !token.IsNullOrEmpty() && OnAuthorize(token);
+ if (!rs) throw new ApiException(403, "认证失败");
+ }
+ }
+ catch (Exception ex)
+ {
+ var traceId = DefaultSpan.Current?.TraceId;
+ context.Result = ex is ApiException aex
+ ? new JsonResult(new { code = aex.Code, data = aex.Message, traceId })
+ : new JsonResult(new { code = 500, data = ex.Message, traceId });
+
+ WriteError(ex, context);
+ }
+ }
+
+ void IActionFilter.OnActionExecuted(ActionExecutedContext context)
+ {
+ if (context.Exception != null) WriteError(context.Exception, context);
+ }
+
+ protected Boolean OnAuthorize(String token)
+ {
+ var (app, ex) = _appService.DecodeToken(token, _setting.TokenSecret);
+ _App = app;
+ if (ex != null) throw ex;
+
+ return app != null;
+ }
+
+ private void WriteError(Exception ex, ActionContext context)
+ {
+ // 拦截全局异常,写日志
+ var action = context.HttpContext.Request.Path + "";
+ if (context.ActionDescriptor is ControllerActionDescriptor act) action = $"{act.ControllerName}/{act.ActionName}";
+
+ _appService.WriteHistory(_App, action, false, ex?.GetTrue() + Environment.NewLine + _args?.ToJson(true), UserHost);
+ }
+ #endregion
+
+ #region 登录
+ /// 应用登录
+ /// 模型
+ ///
+ [AllowAnonymous]
+ [HttpPost(nameof(Login))]
+ public LoginResponse Login(LoginModel model)
+ {
+ if (model.User.IsNullOrEmpty()) throw new ArgumentNullException(nameof(model.User));
+
+ var (app, rs) = _appService.Login(model, UserHost);
+
+ return rs;
+ }
+
+ /// 获取当前应用的所有在线实例
+ ///
+ [HttpGet(nameof(GetPeers))]
+ public PeerModel[] GetPeers() => _appService.GetPeers(_App);
+ #endregion
+
+ #region 业务
+ /// 获取指定名称的作业
+ ///
+ [HttpGet(nameof(GetJobs))]
+ public IJob[] GetJobs() => _jobService.GetJobs(_App);
+
+ /// 批量添加作业
+ ///
+ ///
+ [HttpPost(nameof(AddJobs))]
+ public String[] AddJobs(JobModel[] jobs)
+ {
+ if (jobs == null || jobs.Length == 0) return new String[0];
+
+ return _jobService.AddJobs(_App, jobs);
+ }
+
+ /// 申请作业任务
+ /// 模型
+ ///
+ [HttpPost(nameof(Acquire))]
+ public ITask[] Acquire(AcquireModel model)
+ {
+ var job = model.Job?.Trim();
+ if (job.IsNullOrEmpty()) return new TaskModel[0];
+
+ return _jobService.Acquire(_App, model, UserHost);
+ }
+
+ /// 生产消息
+ /// 模型
+ ///
+ [HttpPost(nameof(Produce))]
+ public Int32 Produce(ProduceModel model)
+ {
+ var messages = model?.Messages?.Where(e => !e.IsNullOrEmpty()).Distinct().ToArray();
+ if (messages == null || messages.Length == 0) return 0;
+
+ return _jobService.Produce(_App, model);
+ }
+
+ /// 报告状态(进度、成功、错误)
+ ///
+ ///
+ [HttpPost(nameof(Report))]
+ public Boolean Report(TaskResult task)
+ {
+ if (task == null || task.ID == 0) throw new InvalidOperationException("无效操作 TaskID=" + task?.ID);
+
+ return _jobService.Report(_App, task, UserHost);
+ }
+ #endregion
+}
diff --git a/AntJob/AntJob.csproj b/AntJob/AntJob.csproj
index e703e48..465ead5 100644
--- a/AntJob/AntJob.csproj
+++ b/AntJob/AntJob.csproj
@@ -5,8 +5,8 @@
蚂蚁调度SDK
分布式任务调度系统,纯NET打造的重量级大数据实时计算平台,万亿级调度经验积累。
新生命开发团队
- ©2002-2023 NewLife
- 3.3
+ ©2002-2024 NewLife
+ 3.4
$([System.DateTime]::Now.ToString(`yyyy.MMdd`))
$(VersionPrefix).$(VersionSuffix)
$(Version)