@@ -7,6 +7,8 @@ | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="7.0.0" /> | |||
<PackageReference Include="BPA.Component.MongoClient" Version="1.0.12" /> | |||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.5" /> | |||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" /> | |||
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.2.3" /> | |||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.2.3" /> | |||
@@ -9,6 +9,9 @@ using BPA.Component.LogClient.Extensions; | |||
using BPA.Component.SDKCommon; | |||
using BPA.Component.WebApiExtensions.Extensions; | |||
using BPA.SaaS.TaskSchedule.Api.Model; | |||
using BPA.SaaS.TaskSchedule.Api.Service; | |||
using BPA.SaaS.TaskSchedule.Api.Service.QzScheduler; | |||
using BPA.SaaS.TaskSchedule.Api.Service.WarnAlarms; | |||
using Microsoft.AspNetCore.Builder; | |||
using Microsoft.AspNetCore.Diagnostics.HealthChecks; | |||
using Microsoft.AspNetCore.Http; | |||
@@ -52,10 +55,16 @@ public static class SetupBootstrap | |||
services.AddTransient(typeof(Lazy<>), typeof(BPADefaultLazy<>)); | |||
services.AddHttpContextAccessor(); | |||
services.AddControllers(options => { options.AddDefaultMvcOptions(); }); | |||
services.AddControllersWithViews(mvcOptions => { mvcOptions.AddDefaultMvcOptions(true); }).AddRazorRuntimeCompilation(); | |||
services.AddEndpointsApiExplorer(); | |||
services.AddServiceSDK(_configuration); | |||
services.AddHealthChecks(); | |||
services.SetupTaskSchedule(); | |||
services.AddHttpClient(typeof(SynRequestHttpJobScheduler).FullName); | |||
services.AddHttpClient(typeof(AnyTimeTaskScheduleExecutorService).FullName); | |||
services.AddHttpClient(typeof(WechatWebhookWarnAlarm).FullName); | |||
// services.AddCap(x => | |||
// { | |||
// var config = services.GetPreConfig(args); | |||
@@ -90,8 +99,7 @@ public static class SetupBootstrap | |||
public static ContainerBuilder SetupConfigureContainer(this ContainerBuilder builder) | |||
{ | |||
// 注入DB | |||
builder.RegisterType<TaskScheduleDbSugarClient>().InstancePerLifetimeScope(); | |||
// builder.RegisterType<TaskScheduleDbSugarClient>().InstancePerLifetimeScope(); | |||
// // 服务层 | |||
// builder.RegisterAssemblyTypes(typeof(HomeService).Assembly) | |||
// .Where(t => t.Name.EndsWith("Service")) | |||
@@ -112,7 +120,6 @@ public static class SetupBootstrap | |||
public static void PreHeatRun(this IServiceProvider serviceProvider) | |||
{ | |||
serviceProvider.GetService<TaskScheduleApiConfig>(); | |||
serviceProvider.GetService<TaskScheduleDbSugarClient>()?.DbMaintenance.GetTableInfoList(); | |||
serviceProvider.PreHeatRunRedis(); | |||
serviceProvider.PreHeatRunRabbitMQ(); | |||
serviceProvider.SetDebug(true); | |||
@@ -0,0 +1,95 @@ | |||
using BPA.Component.LogClient; | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using BPA.SaaS.TaskSchedule.Api.Service.QzScheduler; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Quartz.Logging; | |||
namespace BPA.SaaS.TaskSchedule.Api.Bootstrap | |||
{ | |||
public static class SetupTaskScheduleConfig | |||
{ | |||
/// <summary> | |||
/// 注入redis | |||
/// </summary> | |||
/// <param name="services"></param> | |||
public static void SetupTaskSchedule(this IServiceCollection services) | |||
{ | |||
//调度器相关 | |||
services.AddSingleton<JobSchedulerFactory>(); | |||
services.AddSingleton<SynRequestSchedulerManager>(); | |||
services.AddTransient<SynRequestHttpJobScheduler>(); | |||
//注释不用了 | |||
//services.AddSingleton<AnyTimeSchedulerManager>(); | |||
//services.AddTransient<AnyTimeHttpJobScheduler>(); | |||
} | |||
/// <summary> | |||
/// 预热redis | |||
/// </summary> | |||
/// <param name="serviceProvider"></param> | |||
public static void PreHeatTaskSchedule(this IServiceProvider serviceProvider) | |||
{ | |||
//LogProvider.SetCurrentLogProvider(new QuartzLogProvider()); | |||
//初始化字典 | |||
serviceProvider.GetService<ISystemDictionaryService>().Start(); | |||
//启动任务 | |||
serviceProvider.GetService<ITaskInfoService>().Start(); | |||
//启动日志任务 | |||
//serviceProvider.GetService<ITaskLogService>().Start(); | |||
} | |||
} | |||
/// <summary> | |||
/// QuartzLogProvider | |||
/// </summary> | |||
public class QuartzLogProvider : ILogProvider | |||
{ | |||
private readonly NLog.ILogger logger = BPALogManager.GetCurrentClassLogger(); | |||
public Logger GetLogger(string name) | |||
{ | |||
return (level, func, exception, parameters) => | |||
{ | |||
if (func == null) return true; | |||
switch (level) | |||
{ | |||
case LogLevel.Trace: | |||
logger.Trace(func(), parameters); | |||
break; | |||
case LogLevel.Debug: | |||
logger.Debug(func(), parameters); | |||
break; | |||
case LogLevel.Info: | |||
logger.Info(func(), parameters); | |||
break; | |||
case LogLevel.Warn: | |||
logger.Warn(func(), parameters); | |||
break; | |||
case LogLevel.Error: | |||
logger.Error(func(), parameters); | |||
break; | |||
case LogLevel.Fatal: | |||
logger.Fatal(func(), parameters); | |||
break; | |||
default: | |||
logger.Info(func(), parameters); | |||
break; | |||
} | |||
return true; | |||
}; | |||
} | |||
public IDisposable OpenMappedContext(string key, object value, bool destructure = false) | |||
{ | |||
return null; | |||
} | |||
public IDisposable OpenNestedContext(string message) | |||
{ | |||
return null; | |||
} | |||
} | |||
} |
@@ -0,0 +1,25 @@ | |||
using System.ComponentModel; | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO; | |||
public enum EnumExecutedResultType | |||
{ | |||
/// <summary>未知</summary> | |||
[Description("未知")] Unknown, | |||
/// <summary>待执行</summary> | |||
[Description("待执行")] Wait, | |||
/// <summary>请求失败</summary> | |||
[Description("请求失败")] CallFail, | |||
/// <summary>正在执行</summary> | |||
[Description("正在执行")] Executing, | |||
/// <summary>执行成功</summary> | |||
[Description("执行成功")] Success, | |||
/// <summary>执行失败</summary> | |||
[Description("执行失败")] Fail, | |||
/// <summary>暂停</summary> | |||
[Description("暂停")] Suspend, | |||
/// <summary>终止</summary> | |||
[Description("终止")] Abort, | |||
/// <summary>超时</summary> | |||
[Description("超时")] Timeout, | |||
} |
@@ -0,0 +1,11 @@ | |||
using System.ComponentModel; | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO; | |||
public enum EnumRequestMethodType | |||
{ | |||
/// <summary>POST请求</summary> | |||
[Description("POST请求")] POST, | |||
/// <summary>GET请求</summary> | |||
[Description("GET请求")] GET, | |||
} |
@@ -0,0 +1,9 @@ | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO; | |||
public enum EnumRequestURLType | |||
{ | |||
/// <summary>自定义</summary> | |||
Custom = 1, | |||
/// <summary>业务系统Api</summary> | |||
BizSysApi = 2, | |||
} |
@@ -0,0 +1,13 @@ | |||
using System.ComponentModel; | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO; | |||
public enum EnumTaskType | |||
{ | |||
/// <summary>未知</summary> | |||
[Description("未知")] Unknown, | |||
/// <summary>同步请求任务</summary> | |||
[Description("同步请求任务")] SynRequest, | |||
/// <summary>任意时间任务</summary> | |||
[Description("任意时间任务")] AnyTime, | |||
} |
@@ -0,0 +1,13 @@ | |||
using System.ComponentModel; | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO; | |||
public enum EnumTriggerType | |||
{ | |||
/// <summary>无</summary> | |||
[Description("简单模式")] Nothing, | |||
/// <summary>简单模式</summary> | |||
[Description("简单模式")] SimpleTrigger, | |||
/// <summary>Cron表达式模式</summary> | |||
[Description("Cron表达式模式")] CronTrigger, | |||
} |
@@ -0,0 +1,15 @@ | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO.SystemDictionary | |||
{ | |||
/// <summary> | |||
/// 删除 | |||
/// </summary> | |||
public class DeleteSystemDictionaryReq | |||
{ | |||
/// <summary> | |||
/// 主键Id | |||
/// </summary> | |||
public long Id { set; get; } | |||
} | |||
} |
@@ -0,0 +1,39 @@ | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO.SystemDictionary; | |||
/// <summary> | |||
/// 更新 | |||
/// </summary> | |||
public class SaveSystemDictionaryReq | |||
{ | |||
/// <summary> | |||
/// 主键Id | |||
/// </summary> | |||
public long Id { set; get; } | |||
/// <summary> | |||
/// 类型 | |||
/// </summary> | |||
public EnumSystemDictionaryType Type { set; get; } | |||
/// <summary> | |||
/// 键 | |||
/// </summary> | |||
public string Key { set; get; } | |||
/// <summary> | |||
/// 值 | |||
/// </summary> | |||
public string Value { set; get; } | |||
/// <summary> | |||
/// 值Ext | |||
/// </summary> | |||
public string ValueExt { set; get; } | |||
} | |||
public enum EnumSystemDictionaryType | |||
{ | |||
/// <summary>业务系统Api</summary> | |||
BizSysApi = 1, | |||
} |
@@ -0,0 +1,34 @@ | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO.SystemDictionary | |||
{ | |||
/// <summary> | |||
/// DTO | |||
/// </summary> | |||
public class SystemDictionaryDTO | |||
{ | |||
/// <summary> | |||
/// 主键Id | |||
/// </summary> | |||
public long Id { set; get; } | |||
/// <summary> | |||
/// 类型 | |||
/// </summary> | |||
public EnumSystemDictionaryType Type { set; get; } | |||
/// <summary> | |||
/// 键 | |||
/// </summary> | |||
public string Key { set; get; } | |||
/// <summary> | |||
/// 值 | |||
/// </summary> | |||
public string Value { set; get; } | |||
/// <summary> | |||
/// 值Ext | |||
/// </summary> | |||
public string ValueExt { set; get; } | |||
} | |||
} |
@@ -0,0 +1,13 @@ | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo | |||
{ | |||
/// <summary> | |||
/// 删除 | |||
/// </summary> | |||
public class DeleteTaskInfoReq | |||
{ | |||
/// <summary> | |||
/// 主键Id | |||
/// </summary> | |||
public long Id { set; get; } | |||
} | |||
} |
@@ -0,0 +1,29 @@ | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo | |||
{ | |||
/// <summary> | |||
/// 执行一次 | |||
/// </summary> | |||
public class DoOnceReq | |||
{ | |||
/// <summary> | |||
/// 任务ID | |||
/// </summary> | |||
public long TaskId { set; get; } | |||
/// <summary> | |||
/// 计划执行时间 | |||
/// </summary> | |||
public DateTime PlanStartTime { set; get; } | |||
/// <summary> | |||
/// 执行任务的请求参数 | |||
/// </summary> | |||
public string RequestParam { set; get; } | |||
/// <summary> | |||
/// 次数 | |||
/// </summary> | |||
public int Times { set; get; } | |||
} | |||
} |
@@ -0,0 +1,118 @@ | |||
using System.ComponentModel; | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo; | |||
/// <summary> | |||
/// 修改任务信息 | |||
/// </summary> | |||
public class SaveTaskInfoReq | |||
{ | |||
/// <summary> | |||
/// 主键 | |||
/// </summary> | |||
public long Id { set; get; } | |||
/// <summary> | |||
/// 任务名称,必须唯一 | |||
/// </summary> | |||
public string Name { set; get; } | |||
/// <summary> | |||
/// 任务标题 | |||
/// </summary> | |||
public string Title { set; get; } | |||
/// <summary> | |||
/// 任务类型 | |||
/// </summary> | |||
public EnumTaskType TaskType { set; get; } | |||
/// <summary> | |||
/// 请求地址类型 | |||
/// </summary> | |||
public EnumRequestURLType RequestURLType { set; get; } | |||
/// <summary> | |||
/// 业务系统Api名称 | |||
/// </summary> | |||
public string BizSysApiName { set; get; } | |||
/// <summary> | |||
/// 请求地址 | |||
/// </summary> | |||
public string RequestURL { set; get; } | |||
/// <summary> | |||
/// 请求方法类型 | |||
/// </summary> | |||
public EnumRequestMethodType RequestMethod { set; get; } | |||
/// <summary> | |||
/// 请求参数 | |||
/// </summary> | |||
public string RequestParam { set; get; } | |||
/// <summary> | |||
/// 任务描述 | |||
/// </summary> | |||
public string Description { set; get; } | |||
/// <summary> | |||
/// 任务开始时间 | |||
/// </summary> | |||
public DateTime StartTime { set; get; } | |||
/// <summary> | |||
/// Trigger类型,0:SimpleTrigger,1:CronTrigger | |||
/// </summary> | |||
public EnumTriggerType TriggerType { set; get; } | |||
/// <summary> | |||
/// <see cref="EnumTriggerType.SimpleTrigger"/> 重复执行次数,-1表示无限次,其他正整数表示具体重复的次数 | |||
/// </summary> | |||
public int RepeatCount { set; get; } | |||
/// <summary> | |||
/// <see cref="EnumTriggerType.SimpleTrigger"/> 重复执行间隔时间,单位:秒 | |||
/// </summary> | |||
public int RepeatInterval { set; get; } | |||
/// <summary> | |||
/// Cron表达式 | |||
/// </summary> | |||
public string CronExpression { set; get; } | |||
/// <summary> | |||
/// 任务状态 | |||
/// </summary> | |||
public EnumTaskStatusType Status { set; get; } | |||
/// <summary> | |||
/// 超时时间(秒) | |||
/// </summary> | |||
public long Timeout { set; get; } | |||
/// <summary> | |||
/// 保留日志天数,0代表不处理 | |||
/// </summary> | |||
public int KeepLogDays { set; get; } | |||
/// <summary> | |||
/// 保留日志条数,0代表不处理 | |||
/// </summary> | |||
public int KeepLogCount { set; get; } | |||
/// <summary> | |||
/// 成功标志 | |||
/// </summary> | |||
public string SuccessMark { set; get; } | |||
} | |||
public enum EnumTaskStatusType | |||
{ | |||
/// <summary>已开启</summary> | |||
[Description("已开启")] Beginning, | |||
/// <summary>暂停</summary> | |||
[Description("暂停")] Suspend, | |||
} |
@@ -0,0 +1,133 @@ | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo | |||
{ | |||
/// <summary> | |||
/// 任务信息 | |||
/// </summary> | |||
public class TaskInfoDTO | |||
{ | |||
/// <summary> | |||
/// 主键 | |||
/// </summary> | |||
public long Id { set; get; } | |||
/// <summary> | |||
/// 任务名称,必须唯一 | |||
/// </summary> | |||
public string Name { set; get; } | |||
/// <summary> | |||
/// 任务标题 | |||
/// </summary> | |||
public string Title { set; get; } | |||
/// <summary> | |||
/// 任务类型 | |||
/// </summary> | |||
public EnumTaskType TaskType { set; get; } | |||
/// <summary> | |||
/// 请求地址类型 | |||
/// </summary> | |||
public EnumRequestURLType RequestURLType { set; get; } | |||
/// <summary> | |||
/// 业务系统Api名称 | |||
/// </summary> | |||
public string BizSysApiName { set; get; } | |||
/// <summary> | |||
/// 请求地址 | |||
/// </summary> | |||
public string RequestURL { set; get; } | |||
/// <summary> | |||
/// 请求方法类型 | |||
/// </summary> | |||
public EnumRequestMethodType RequestMethod { set; get; } | |||
/// <summary> | |||
/// 请求参数 | |||
/// </summary> | |||
public string RequestParam { set; get; } | |||
/// <summary> | |||
/// 任务描述 | |||
/// </summary> | |||
public string Description { set; get; } | |||
/// <summary> | |||
/// 任务开始时间 | |||
/// </summary> | |||
public DateTime StartTime { set; get; } | |||
/// <summary> | |||
/// Trigger类型,0:SimpleTrigger,1:CronTrigger | |||
/// </summary> | |||
public EnumTriggerType TriggerType { set; get; } | |||
/// <summary> | |||
/// <see cref="EnumTriggerType.SimpleTrigger"/> 重复执行次数,-1表示无限次,其他正整数表示具体重复的次数 | |||
/// </summary> | |||
public int RepeatCount { set; get; } | |||
/// <summary> | |||
/// <see cref="EnumTriggerType.SimpleTrigger"/> 重复执行间隔时间,单位:秒 | |||
/// </summary> | |||
public int RepeatInterval { set; get; } | |||
/// <summary> | |||
/// Cron表达式 | |||
/// </summary> | |||
public string CronExpression { set; get; } | |||
/// <summary> | |||
/// 任务状态 | |||
/// </summary> | |||
public EnumTaskStatusType Status { set; get; } | |||
/// <summary> | |||
/// 创建时间 | |||
/// </summary> | |||
public DateTime CreateTime { set; get; } | |||
/// <summary> | |||
/// 更新时间 | |||
/// </summary> | |||
public DateTime UpdateTime { set; get; } | |||
/// <summary> | |||
/// 最近执行时间 | |||
/// </summary> | |||
public DateTime? LastFireTime { set; get; } | |||
/// <summary> | |||
/// 下次执行时间 | |||
/// </summary> | |||
public DateTime? NextFireTime { set; get; } | |||
/// <summary> | |||
/// 超时时间(秒) | |||
/// </summary> | |||
public long Timeout { set; get; } | |||
/// <summary> | |||
/// 保留日志天数 | |||
/// </summary> | |||
public int KeepLogDays { set; get; } | |||
/// <summary> | |||
/// 保留日志条数,0代表不处理 | |||
/// </summary> | |||
public int KeepLogCount { set; get; } | |||
/// <summary> | |||
/// 是否删除,默认为false | |||
/// </summary> | |||
public bool IsDelete { set; get; } | |||
/// <summary> | |||
/// 成功标志 | |||
/// </summary> | |||
public string SuccessMark { set; get; } | |||
} | |||
} |
@@ -0,0 +1,19 @@ | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo | |||
{ | |||
/// <summary> | |||
/// 尝试注册服务 | |||
/// </summary> | |||
public class TryRegisterTaskReq | |||
{ | |||
/// <summary> | |||
/// 业务系统 api 名称,和系统字典里面的对应 | |||
/// </summary> | |||
public string ApiName { set; get; } | |||
/// <summary> | |||
/// 任务标记 | |||
/// </summary> | |||
public List<TaskInfoTag> TaskInfoTags { set; get; } | |||
} | |||
} |
@@ -0,0 +1,97 @@ | |||
using Newtonsoft.Json; | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO | |||
{ | |||
/// <summary> | |||
/// 任务信息标记 | |||
/// </summary> | |||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] | |||
public class TaskInfoTag : Attribute | |||
{ | |||
/// <summary> | |||
/// TypeId | |||
/// </summary> | |||
[JsonIgnore] | |||
public override object TypeId => base.TypeId; | |||
/// <summary> | |||
/// 是否强制覆盖,当已经存在任务时是否强制注册,默认false | |||
/// </summary> | |||
public bool IsForceCover { set; get; } = false; | |||
/// <summary> | |||
/// 任务名称,必须唯一, 这里会自动加 apiName, 如果标记到方法上面且没有设置值,会自动根据类名和方法名生成 | |||
/// </summary> | |||
public string Name { set; get; } | |||
/// <summary> | |||
/// 任务标题 | |||
/// </summary> | |||
public string Title { set; get; } | |||
/// <summary> | |||
/// 任务类型,默认<see cref="EnumTaskType.SynRequest"/> | |||
/// </summary> | |||
public EnumTaskType TaskType { set; get; } = EnumTaskType.SynRequest; | |||
/// <summary> | |||
/// 请求地址类型,默认<see cref="EnumRequestURLType.BizSysApi"/> | |||
/// </summary> | |||
public EnumRequestURLType RequestURLType { set; get; } = EnumRequestURLType.BizSysApi; | |||
/// <summary> | |||
/// 请求地址,如果标记到方法上面且没有设置值,会自动根据类名和方法名生成 | |||
/// </summary> | |||
public string RequestURL { set; get; } | |||
/// <summary> | |||
/// 请求方法类型,默认<see cref="EnumRequestMethodType.POST"/> | |||
/// </summary> | |||
public EnumRequestMethodType RequestMethod { set; get; } = EnumRequestMethodType.POST; | |||
/// <summary> | |||
/// 任务描述 | |||
/// </summary> | |||
public string Description { set; get; } | |||
/// <summary> | |||
/// Trigger类型,0:SimpleTrigger,1:CronTrigger,默认<see cref="EnumTriggerType.CronTrigger"/> | |||
/// </summary> | |||
public EnumTriggerType TriggerType { set; get; } = EnumTriggerType.CronTrigger; | |||
/// <summary> | |||
/// <see cref="EnumTriggerType.SimpleTrigger"/> 重复执行次数,-1表示无限次,其他正整数表示具体重复的次数 | |||
/// </summary> | |||
public int RepeatCount { set; get; } | |||
/// <summary> | |||
/// <see cref="EnumTriggerType.SimpleTrigger"/> 重复执行间隔时间,单位:秒 | |||
/// </summary> | |||
public int RepeatInterval { set; get; } | |||
/// <summary> | |||
/// Cron表达式 | |||
/// </summary> | |||
public string CronExpression { set; get; } | |||
/// <summary> | |||
/// 超时时间(秒),默认60秒 | |||
/// </summary> | |||
public long Timeout { set; get; } = 60; | |||
/// <summary> | |||
/// 保留日志天数,默认10天 | |||
/// </summary> | |||
public int KeepLogDays { set; get; } = 10; | |||
/// <summary> | |||
/// 保留日志条数,0代表不处理 | |||
/// </summary> | |||
public int KeepLogCount { set; get; } | |||
/// <summary> | |||
/// 成功标志,默认 "isOK":true | |||
/// </summary> | |||
public string SuccessMark { set; get; } = "\"isOK\":true"; | |||
} | |||
} |
@@ -0,0 +1,23 @@ | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO.TaskLog | |||
{ | |||
/// <summary> | |||
/// 添加任意时间任务日志 | |||
/// </summary> | |||
public class AddAnyTimeLogReq | |||
{ | |||
/// <summary> | |||
/// 任务名称 | |||
/// </summary> | |||
public string TaskName { set; get; } | |||
/// <summary> | |||
/// 计划执行时间 | |||
/// </summary> | |||
public DateTime PlanStartTime { set; get; } | |||
/// <summary> | |||
/// 执行任务的请求参数 | |||
/// </summary> | |||
public string RequestParam { set; get; } | |||
} | |||
} |
@@ -0,0 +1,34 @@ | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO.TaskLog | |||
{ | |||
/// <summary> | |||
/// 添加任意时间任务日志 多个 | |||
/// </summary> | |||
public class AddAnyTimeLogsReq | |||
{ | |||
/// <summary> | |||
/// 任务名称 | |||
/// </summary> | |||
public string TaskName { set; get; } | |||
/// <summary> | |||
/// 多条执行日志 | |||
/// </summary> | |||
public List<AddAnyTimeLogDTO> Logs { set; get; } | |||
} | |||
/// <summary> | |||
/// 批量添加日志 | |||
/// </summary> | |||
public class AddAnyTimeLogDTO | |||
{ | |||
/// <summary> | |||
/// 计划执行时间 | |||
/// </summary> | |||
public DateTime PlanStartTime { set; get; } | |||
/// <summary> | |||
/// 执行任务的请求参数 | |||
/// </summary> | |||
public string RequestParam { set; get; } | |||
} | |||
} |
@@ -0,0 +1,35 @@ | |||
using BPA.Component.DTOCommon.BaseDTOs; | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO.TaskLog | |||
{ | |||
/// <summary> | |||
/// 分页查询 | |||
/// </summary> | |||
public class GetTaskLogPageReq : BasePageInfo | |||
{ | |||
/// <summary> | |||
/// 任务id | |||
/// </summary> | |||
public long? TaskId { set; get; } | |||
/// <summary> | |||
/// 任务类型 | |||
/// </summary> | |||
public EnumTaskType? TaskType { set; get; } | |||
/// <summary> | |||
/// 业务系统Api名称 | |||
/// </summary> | |||
public string BizSysApiName { set; get; } | |||
/// <summary> | |||
/// 任务计划开始时间 | |||
/// </summary> | |||
public DateTime? PlanStartTime { set; get; } | |||
/// <summary> | |||
/// 执行结果 | |||
/// </summary> | |||
public EnumExecutedResultType? ExecutedResult { set; get; } | |||
} | |||
} |
@@ -0,0 +1,104 @@ | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO.TaskLog | |||
{ | |||
/// <summary> | |||
/// TaskLogDTO | |||
/// </summary> | |||
public class TaskLogDTO | |||
{ | |||
/// <summary> | |||
/// ID | |||
/// </summary> | |||
public long Id { set; get; } | |||
/// <summary> | |||
/// 任务id | |||
/// </summary> | |||
public long TaskId { set; get; } | |||
/// <summary> | |||
/// 任务名称,必须唯一 | |||
/// </summary> | |||
public string Name { set; get; } | |||
/// <summary> | |||
/// 任务标题 | |||
/// </summary> | |||
public string Title { set; get; } | |||
/// <summary> | |||
/// 任务类型 | |||
/// </summary> | |||
public EnumTaskType TaskType { set; get; } | |||
/// <summary> | |||
/// 请求地址类型 | |||
/// </summary> | |||
public EnumRequestURLType RequestURLType { set; get; } | |||
/// <summary> | |||
/// 业务系统Api名称 | |||
/// </summary> | |||
public string BizSysApiName { set; get; } | |||
/// <summary> | |||
/// 请求地址 | |||
/// </summary> | |||
public string RequestURL { set; get; } | |||
/// <summary> | |||
/// 请求参数 | |||
/// </summary> | |||
public string RequestParam { set; get; } | |||
/// <summary> | |||
/// 任务计划开始时间 | |||
/// </summary> | |||
public DateTime PlanStartTime { set; get; } | |||
/// <summary> | |||
/// 开始执行时间 | |||
/// </summary> | |||
public DateTime StartTime { set; get; } | |||
/// <summary> | |||
/// 执行结束时间 | |||
/// </summary> | |||
public DateTime? EndTime { set; get; } | |||
/// <summary> | |||
/// 执行时长,单位ms | |||
/// </summary> | |||
public long ExecutionTime { set; get; } | |||
/// <summary> | |||
/// 返回参数 | |||
/// </summary> | |||
public string ResponseParam { set; get; } | |||
/// <summary> | |||
/// 执行结果 | |||
/// </summary> | |||
public EnumExecutedResultType ExecutedResult { set; get; } | |||
/// <summary> | |||
/// 备注 | |||
/// </summary> | |||
public string Remark { set; get; } | |||
/// <summary> | |||
/// 创建时间 | |||
/// </summary> | |||
public DateTime CreateTime { set; get; } | |||
/// <summary> | |||
/// 更新时间 | |||
/// </summary> | |||
public DateTime UpdateTime { set; get; } | |||
/// <summary> | |||
/// 跟踪ID | |||
/// </summary> | |||
public string TrackId { set; get; } | |||
} | |||
} |
@@ -0,0 +1,30 @@ | |||
using BPA.Component.DTOCommon.Langs; | |||
namespace BPA.SaaS.TaskSchedule.Api.DTO | |||
{ | |||
/// <summary> | |||
/// TaskScheduleLangPackge | |||
/// </summary> | |||
public class TaskScheduleLangPackge : BaseLangPackge | |||
{ | |||
/// <summary> | |||
/// 字典已存在 | |||
/// </summary> | |||
public Lang KeyExist = Lang.Build(nameof(KeyExist), "字典已存在", "key already exist"); | |||
/// <summary> | |||
/// 字典不存在 | |||
/// </summary> | |||
public Lang KeyNotExist = Lang.Build(nameof(KeyNotExist), "字典不存在", "key not exist"); | |||
/// <summary> | |||
/// 任务已存在 | |||
/// </summary> | |||
public Lang TaskExist = Lang.Build(nameof(TaskExist), "任务已存在", "task already exist"); | |||
/// <summary> | |||
/// 任务不存在 | |||
/// </summary> | |||
public Lang TaskNotExist = Lang.Build(nameof(TaskNotExist), "任务不存在", "task not exist"); | |||
} | |||
} |
@@ -1,16 +1,20 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<PropertyGroup> | |||
<TargetFramework>net6.0</TargetFramework> | |||
<Nullable>enable</Nullable> | |||
<ImplicitUsings>enable</ImplicitUsings> | |||
</PropertyGroup> | |||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> | |||
<NoWarn>1701;1702;8618</NoWarn> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageReference Include="BPA.Common.Entity" Version="1.0.6" /> | |||
<PackageReference Include="BPA.Common.Enums" Version="1.0.8" /> | |||
<PackageReference Include="BPA.Component.DbClient" Version="1.0.27" /> | |||
</ItemGroup> | |||
<PropertyGroup> | |||
<TargetFramework>net6.0</TargetFramework> | |||
<Nullable>enable</Nullable> | |||
<ImplicitUsings>enable</ImplicitUsings> | |||
</PropertyGroup> | |||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> | |||
<NoWarn>1701;1702;8618</NoWarn> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageReference Include="BPA.Common.Entity" Version="1.0.6" /> | |||
<PackageReference Include="BPA.Common.Enums" Version="1.0.8" /> | |||
<PackageReference Include="BPA.Component.DbClient" Version="1.0.27" /> | |||
<PackageReference Include="BPA.Component.MongoClient" Version="1.0.12" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\BPA.SaaS.TaskSchedule.Api.DTO\BPA.SaaS.TaskSchedule.Api.DTO.csproj" /> | |||
</ItemGroup> | |||
</Project> |
@@ -0,0 +1,39 @@ | |||
using BPA.SaaS.TaskSchedule.Api.DTO.SystemDictionary; | |||
namespace BPA.SaaS.TaskSchedule.Api.Entity; | |||
/// <summary> | |||
/// 系统字典 | |||
/// </summary> | |||
public class CrmSystemDictionary | |||
{ | |||
/// <summary> | |||
/// 主键 | |||
/// </summary> | |||
public long Id { set; get; } | |||
/// <summary> | |||
/// 类型 | |||
/// </summary> | |||
public EnumSystemDictionaryType Type { set; get; } | |||
/// <summary> | |||
/// 键 | |||
/// </summary> | |||
public string Key { set; get; } | |||
/// <summary> | |||
/// 值 | |||
/// </summary> | |||
public string Value { set; get; } | |||
/// <summary> | |||
/// 值Ext | |||
/// </summary> | |||
public string ValueExt { set; get; } | |||
/// <summary> | |||
/// 是否删除 | |||
/// </summary> | |||
public bool IsDelete { set; get; } | |||
} |
@@ -0,0 +1,126 @@ | |||
using BPA.SaaS.TaskSchedule.Api.DTO; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo; | |||
namespace BPA.SaaS.TaskSchedule.Api.Entity | |||
{ | |||
/// <summary> | |||
/// 任务信息 | |||
/// </summary> | |||
public class CrmTaskInfo | |||
{ | |||
/// <summary> | |||
/// 主键 | |||
/// </summary> | |||
public long Id { set; get; } | |||
/// <summary> | |||
/// 任务名称,必须唯一 | |||
/// </summary> | |||
public string Name { set; get; } | |||
/// <summary> | |||
/// 任务标题 | |||
/// </summary> | |||
public string Title { set; get; } | |||
/// <summary> | |||
/// 任务类型 | |||
/// </summary> | |||
public EnumTaskType TaskType { set; get; } | |||
/// <summary> | |||
/// 请求地址类型 | |||
/// </summary> | |||
public EnumRequestURLType RequestURLType { set; get; } | |||
/// <summary> | |||
/// 业务系统Api名称 | |||
/// </summary> | |||
public string BizSysApiName { set; get; } | |||
/// <summary> | |||
/// 请求地址 | |||
/// </summary> | |||
public string RequestURL { set; get; } | |||
/// <summary> | |||
/// 请求方法类型 | |||
/// </summary> | |||
public EnumRequestMethodType RequestMethod { set; get; } | |||
/// <summary> | |||
/// 请求参数 | |||
/// </summary> | |||
public string RequestParam { set; get; } | |||
/// <summary> | |||
/// 任务描述 | |||
/// </summary> | |||
public string Description { set; get; } | |||
/// <summary> | |||
/// 任务开始时间 | |||
/// </summary> | |||
public DateTime StartTime { set; get; } | |||
/// <summary> | |||
/// Trigger类型,0:SimpleTrigger,1:CronTrigger | |||
/// </summary> | |||
public EnumTriggerType TriggerType { set; get; } | |||
/// <summary> | |||
/// <see cref="EnumTriggerType.SimpleTrigger"/> 重复执行次数,-1表示无限次,其他正整数表示具体重复的次数 | |||
/// </summary> | |||
public int RepeatCount { set; get; } | |||
/// <summary> | |||
/// <see cref="EnumTriggerType.SimpleTrigger"/> 重复执行间隔时间,单位:秒 | |||
/// </summary> | |||
public int RepeatInterval { set; get; } | |||
/// <summary> | |||
/// Cron表达式 | |||
/// </summary> | |||
public string CronExpression { set; get; } | |||
/// <summary> | |||
/// 任务状态: 0:开启,1:暂停 | |||
/// </summary> | |||
public EnumTaskStatusType Status { set; get; } | |||
/// <summary> | |||
/// 是否删除,默认为false | |||
/// </summary> | |||
public bool IsDelete { set; get; } | |||
/// <summary> | |||
/// 创建时间 | |||
/// </summary> | |||
public DateTime CreateTime { set; get; } | |||
/// <summary> | |||
/// 更新时间 | |||
/// </summary> | |||
public DateTime UpdateTime { set; get; } | |||
/// <summary> | |||
/// 超时时间(秒) | |||
/// </summary> | |||
public long Timeout { set; get; } | |||
/// <summary> | |||
/// 保留日志条数,默认60条 | |||
/// </summary> | |||
public int KeepLogCount { set; get; } | |||
/// <summary> | |||
/// 保留日志天数,0代表不处理 | |||
/// </summary> | |||
public int KeepLogDays { set; get; } | |||
/// <summary> | |||
/// 成功标志 | |||
/// </summary> | |||
public string SuccessMark { set; get; } | |||
} | |||
} |
@@ -0,0 +1,106 @@ | |||
using BPA.SaaS.TaskSchedule.Api.DTO; | |||
namespace BPA.SaaS.TaskSchedule.Api.Entity | |||
{ | |||
/// <summary> | |||
/// 任务日志 | |||
/// </summary> | |||
public class CrmTaskLog | |||
{ | |||
/// <summary> | |||
/// ID | |||
/// </summary> | |||
public long Id { set; get; } | |||
/// <summary> | |||
/// 任务id | |||
/// </summary> | |||
public long TaskId { set; get; } | |||
/// <summary> | |||
/// 任务名称,必须唯一 | |||
/// </summary> | |||
public string Name { set; get; } | |||
/// <summary> | |||
/// 任务标题 | |||
/// </summary> | |||
public string Title { set; get; } | |||
/// <summary> | |||
/// 任务类型 | |||
/// </summary> | |||
public EnumTaskType TaskType { set; get; } | |||
/// <summary> | |||
/// 请求地址类型 | |||
/// </summary> | |||
public EnumRequestURLType RequestURLType { set; get; } | |||
/// <summary> | |||
/// 业务系统Api名称 | |||
/// </summary> | |||
public string BizSysApiName { set; get; } | |||
/// <summary> | |||
/// 请求地址 | |||
/// </summary> | |||
public string RequestURL { set; get; } | |||
/// <summary> | |||
/// 请求参数 | |||
/// </summary> | |||
public string RequestParam { set; get; } | |||
/// <summary> | |||
/// 任务计划开始时间 | |||
/// </summary> | |||
public DateTime PlanStartTime { set; get; } | |||
/// <summary> | |||
/// 开始执行时间 | |||
/// </summary> | |||
public DateTime StartTime { set; get; } | |||
/// <summary> | |||
/// 执行结束时间 | |||
/// </summary> | |||
public DateTime? EndTime { set; get; } | |||
/// <summary> | |||
/// 执行时长,单位ms | |||
/// </summary> | |||
public long ExecutionTime { set; get; } | |||
/// <summary> | |||
/// 返回参数 | |||
/// </summary> | |||
public string ResponseParam { set; get; } | |||
/// <summary> | |||
/// 执行结果 | |||
/// </summary> | |||
public EnumExecutedResultType ExecutedResult { set; get; } | |||
/// <summary> | |||
/// 备注 | |||
/// </summary> | |||
public string Remark { set; get; } | |||
/// <summary> | |||
/// 创建时间 | |||
/// </summary> | |||
public DateTime CreateTime { set; get; } | |||
/// <summary> | |||
/// 更新时间 | |||
/// </summary> | |||
public DateTime UpdateTime { set; get; } | |||
/// <summary> | |||
/// 跟踪ID | |||
/// </summary> | |||
public string TrackId { set; get; } | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
<?xml version="1.0"?> | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<PropertyGroup> | |||
<TargetFramework>net6.0</TargetFramework> | |||
@@ -6,9 +6,10 @@ | |||
<ImplicitUsings>enable</ImplicitUsings> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageReference Include="BPA.Component.DbClient" Version="1.0.27"/> | |||
<ProjectReference Include="..\BPA.SaaS.TaskSchedule.Api.DTO\BPA.SaaS.TaskSchedule.Api.DTO.csproj"/> | |||
<ProjectReference Include="..\BPA.SaaS.TaskSchedule.Api.Model\BPA.SaaS.TaskSchedule.Api.Model.csproj"/> | |||
<ProjectReference Include="..\BPA.SaaS.TaskSchedule.Api.Entity\BPA.SaaS.TaskSchedule.Api.Entity.csproj"/> | |||
<PackageReference Include="BPA.Component.DbClient" Version="1.0.27" /> | |||
<PackageReference Include="BPA.Component.MongoClient" Version="1.0.12" /> | |||
<ProjectReference Include="..\BPA.SaaS.TaskSchedule.Api.DTO\BPA.SaaS.TaskSchedule.Api.DTO.csproj" /> | |||
<ProjectReference Include="..\BPA.SaaS.TaskSchedule.Api.Model\BPA.SaaS.TaskSchedule.Api.Model.csproj" /> | |||
<ProjectReference Include="..\BPA.SaaS.TaskSchedule.Api.Entity\BPA.SaaS.TaskSchedule.Api.Entity.csproj" /> | |||
</ItemGroup> | |||
</Project> |
@@ -0,0 +1,9 @@ | |||
using BPA.Component.MongoClient.Repository; | |||
using BPA.SaaS.TaskSchedule.Api.Entity; | |||
namespace BPA.SaaS.TaskSchedule.Api.IRepository | |||
{ | |||
public interface ISystemDictionaryRepository: IBaseMongoDbRepository<CrmSystemDictionary> | |||
{ | |||
} | |||
} |
@@ -0,0 +1,9 @@ | |||
using BPA.Component.MongoClient.Repository; | |||
using BPA.SaaS.TaskSchedule.Api.Entity; | |||
namespace BPA.SaaS.TaskSchedule.Api.IRepository | |||
{ | |||
public interface ITaskInfoRepository : IBaseMongoDbRepository<CrmTaskInfo> | |||
{ | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
using BPA.Component.MongoClient.Repository; | |||
using BPA.SaaS.TaskSchedule.Api.Entity; | |||
namespace BPA.SaaS.TaskSchedule.Api.IRepository | |||
{ | |||
public interface ITaskLogRepository : IBaseMongoDbRepository<CrmTaskLog> | |||
{ | |||
} | |||
} |
@@ -0,0 +1,7 @@ | |||
namespace BPA.SaaS.TaskSchedule.Api.IService; | |||
public class AnyTimeTaskScheduleMsgBody | |||
{ | |||
/// <summary>任务日志ID</summary> | |||
public long TaskLogId { set; get; } | |||
} |
@@ -1,4 +1,4 @@ | |||
<?xml version="1.0"?> | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<PropertyGroup> | |||
<TargetFramework>net6.0</TargetFramework> | |||
@@ -7,5 +7,9 @@ | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\BPA.SaaS.TaskSchedule.Api.DTO\BPA.SaaS.TaskSchedule.Api.DTO.csproj" /> | |||
<ProjectReference Include="..\BPA.SaaS.TaskSchedule.Api.Entity\BPA.SaaS.TaskSchedule.Api.Entity.csproj" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Quartz" Version="3.4.0" /> | |||
</ItemGroup> | |||
</Project> |
@@ -0,0 +1,13 @@ | |||
namespace BPA.SaaS.TaskSchedule.Api.IService; | |||
/// <summary> | |||
/// 任意时间任务执行器 | |||
/// </summary> | |||
public interface IAnyTimeTaskScheduleExecutorService | |||
{ | |||
/// <summary> | |||
/// 执行 | |||
/// </summary> | |||
/// <param name="anyTimeTaskScheduleMsg"></param> | |||
void Execute(AnyTimeTaskScheduleMsgBody anyTimeTaskScheduleMsg); | |||
} |
@@ -0,0 +1,10 @@ | |||
namespace BPA.SaaS.TaskSchedule.Api.IService; | |||
public interface ISynData | |||
{ | |||
/// <summary> | |||
/// 当数据发生改变 | |||
/// </summary> | |||
/// <param name="msgBody"></param> | |||
void OnDataChange(SynDataTaskScheduleMsgBody msgBody); | |||
} |
@@ -0,0 +1,8 @@ | |||
using Quartz; | |||
namespace BPA.SaaS.TaskSchedule.Api.IService | |||
{ | |||
public interface ISynRequestSchedulerTriggerListenerService : ITriggerListener | |||
{ | |||
} | |||
} |
@@ -0,0 +1,46 @@ | |||
using BPA.Component.DTOCommon.BaseDTOs; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.SystemDictionary; | |||
using BPA.SaaS.TaskSchedule.Api.Entity; | |||
namespace BPA.SaaS.TaskSchedule.Api.IService | |||
{ | |||
/// <summary> | |||
/// 系统字典 | |||
/// </summary> | |||
public interface ISystemDictionaryService : ISynData | |||
{ | |||
/// <summary> | |||
/// 保存 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
BaseResult Save(SaveSystemDictionaryReq req); | |||
/// <summary> | |||
/// 删除 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
BaseResult Delete(DeleteSystemDictionaryReq req); | |||
/// <summary> | |||
/// 获取所有的 | |||
/// </summary> | |||
/// <returns></returns> | |||
BaseResult<List<SystemDictionaryDTO>> GetAll(); | |||
/// <summary> | |||
/// 获取缓存的字典信息 | |||
/// </summary> | |||
/// <param name="type"></param> | |||
/// <param name="key"></param> | |||
/// <returns></returns> | |||
SystemDictionaryDTO GetCache(EnumSystemDictionaryType type, string key); | |||
/// <summary> | |||
/// 启动执行一次 | |||
/// </summary> | |||
void Start(); | |||
} | |||
} |
@@ -0,0 +1,65 @@ | |||
using BPA.Component.DTOCommon.BaseDTOs; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo; | |||
namespace BPA.SaaS.TaskSchedule.Api.IService | |||
{ | |||
/// <summary> | |||
/// 任务信息服务 | |||
/// </summary> | |||
public interface ITaskInfoService: ISynData | |||
{ | |||
/// <summary> | |||
/// 保存 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
BaseResult<long> Save(SaveTaskInfoReq req); | |||
/// <summary> | |||
/// 删除 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
BaseResult Delete(DeleteTaskInfoReq req); | |||
/// <summary> | |||
/// 获取所有的任务信息 | |||
/// </summary> | |||
/// <returns></returns> | |||
BaseResult<List<TaskInfoDTO>> GetAll(); | |||
/// <summary> | |||
/// DoOnce | |||
/// </summary> | |||
/// <param name="req"></param> | |||
BaseResult DoOnce(DoOnceReq req); | |||
/// <summary> | |||
/// 获取缓存的任务信息 | |||
/// </summary> | |||
/// <param name="taskInfoId"></param> | |||
/// <returns></returns> | |||
TaskInfoDTO GetCache(long taskInfoId); | |||
/// <summary> | |||
/// 获取缓存的任务信息 | |||
/// </summary> | |||
/// <param name="taskInfoName"></param> | |||
/// <returns></returns> | |||
TaskInfoDTO GetCache(string taskInfoName); | |||
/// <summary> | |||
/// 尝试注册任务 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
BaseResult TryRegisterTask(TryRegisterTaskReq req); | |||
/// <summary> | |||
/// 启动 | |||
/// </summary> | |||
/// <param name="allTaskInfo"></param> | |||
void Start(); | |||
} | |||
} |
@@ -0,0 +1,37 @@ | |||
using BPA.Component.DTOCommon.BaseDTOs; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.TaskLog; | |||
namespace BPA.SaaS.TaskSchedule.Api.IService | |||
{ | |||
/// <summary> | |||
/// 任务日志 | |||
/// </summary> | |||
public interface ITaskLogService | |||
{ | |||
/// <summary> | |||
/// 添加任意时间任务日志 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
BaseResult AddAnyTimeLog(AddAnyTimeLogReq req); | |||
/// <summary> | |||
/// 添加任意时间任务日志 多个 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
BaseResult AddAnyTimeLogs(AddAnyTimeLogsReq req); | |||
/// <summary> | |||
/// 分页查询 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
BaseResultPage<TaskLogDTO> GetPage(GetTaskLogPageReq req); | |||
/// <summary> | |||
/// 清理日志 | |||
/// </summary> | |||
void Clear(); | |||
} | |||
} |
@@ -0,0 +1,16 @@ | |||
using BPA.SaaS.TaskSchedule.Api.Entity; | |||
namespace BPA.SaaS.TaskSchedule.Api.IService | |||
{ | |||
/// <summary> | |||
/// 警报器 | |||
/// </summary> | |||
public interface IWarnAlarm | |||
{ | |||
/// <summary> | |||
/// 响铃 | |||
/// </summary> | |||
/// <param name="taskLog"></param> | |||
Task Bell(CrmTaskLog taskLog); | |||
} | |||
} |
@@ -0,0 +1,20 @@ | |||
using BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo; | |||
using BPA.SaaS.TaskSchedule.Api.Entity; | |||
namespace BPA.SaaS.TaskSchedule.Api.IService | |||
{ | |||
/// <summary> | |||
/// 警告管理服务 | |||
/// </summary> | |||
public interface IWarnManagerService | |||
{ | |||
/// <summary> | |||
/// 警告 | |||
/// </summary> | |||
/// <param name="taskLog"></param> | |||
Task Warn(TaskInfoDTO taskInfo, CrmTaskLog taskLog); | |||
} | |||
} |
@@ -0,0 +1,13 @@ | |||
namespace BPA.SaaS.TaskSchedule.Api.IService; | |||
public class SynDataTaskScheduleMsgBody | |||
{ | |||
/// <summary>数据ID</summary> | |||
public long DataId { set; get; } | |||
/// <summary>数据类型 1=字典, 2=任务</summary> | |||
public int DataType { set; get; } | |||
/// <summary>命令 1=新增;2=修改,3=删除</summary> | |||
public int Cmd { set; get; } | |||
} |
@@ -17,11 +17,14 @@ | |||
<PackageReference Include="BPA.Component.ApolloClient" Version="1.0.9" /> | |||
<PackageReference Include="BPA.Component.DTOCommon" Version="1.0.11" /> | |||
<PackageReference Include="BPA.Component.LogClient" Version="1.0.3" /> | |||
<PackageReference Include="BPA.Component.MongoClient" Version="1.0.7" /> | |||
<PackageReference Include="BPA.Component.MongoClient" Version="1.0.12" /> | |||
<PackageReference Include="BPA.Component.RabbitMQClient" Version="1.0.7" /> | |||
<PackageReference Include="BPA.Component.RedisClient" Version="1.0.3" /> | |||
<PackageReference Include="BPA.Component.Extensions" Version="1.0.4" /> | |||
<PackageReference Include="BPA.Component.DbClient" Version="1.0.27" /> | |||
<PackageReference Include="BPA.Component.WebApiExtensions" Version="1.0.23" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\BPA.SaaS.TaskSchedule.Api.DTO\BPA.SaaS.TaskSchedule.Api.DTO.csproj" /> | |||
</ItemGroup> | |||
</Project> |
@@ -1,5 +1,9 @@ | |||
using BPA.Component.ApolloClient; | |||
using System.Net; | |||
using System.Net.NetworkInformation; | |||
using System.Net.Sockets; | |||
using BPA.Component.ApolloClient; | |||
using BPA.Component.DbClient; | |||
using BPA.Component.MongoClient; | |||
using BPA.Component.RabbitMQClient.Connection; | |||
using Com.Ctrip.Framework.Apollo; | |||
using Microsoft.Extensions.Configuration; | |||
@@ -13,34 +17,57 @@ namespace BPA.SaaS.TaskSchedule.Api.Model; | |||
public class TaskScheduleApiConfig : ApolloBPAConfig<TaskScheduleApiConfig> | |||
{ | |||
/// <summary> | |||
/// BasicMySqlDb | |||
/// RedisConfig | |||
/// </summary> | |||
[AutoWrite] | |||
public new DbConfig BasicMySqlDb { get; set; } | |||
public new List<string> RedisConfig { set; get; } | |||
/// <summary> | |||
/// RedisConfig | |||
/// RabbitMQ配置 | |||
/// </summary> | |||
[AutoWrite] | |||
public new List<string> RedisConfig { set; get; } | |||
public new RabbitMqConnectionConfig RabbitMqConfig { get; set; } | |||
/// <summary> | |||
/// 是否打印SQL | |||
/// MongoClient | |||
/// </summary> | |||
[AutoWrite] | |||
public bool IsLogSql { get; set; } = true; | |||
public MongoConfig TaskScheduleMongoConfig { get; set; } | |||
/// <summary> | |||
/// token到期时间 | |||
/// 环境名称 测试 | 生产 | |||
/// </summary> | |||
[AutoWrite] | |||
public int TokenExpireInterval { get; set; } | |||
public string EvnName { private set; get; } | |||
/// <summary> | |||
/// RabbitMQ配置 | |||
/// 报警器 | |||
/// </summary> | |||
[AutoWrite] | |||
public new RabbitMqConnectionConfig RabbitMqConfig { get; set; } | |||
public List<string> WarnAlarms { private set; get; } = new List<string>(); | |||
/// <summary> | |||
/// 跟踪ID head名称 | |||
/// </summary> | |||
public static string TraceIdHeadName = "X-TraceId"; | |||
/// <summary> | |||
/// ip s | |||
/// </summary> | |||
public string IPS | |||
{ | |||
get | |||
{ | |||
var networks = NetworkInterface.GetAllNetworkInterfaces(); | |||
var ips = networks.Select(network => network.GetIPProperties().UnicastAddresses | |||
.FirstOrDefault(p => p.Address.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(p.Address))? | |||
.Address | |||
.ToString()) | |||
.Where(ipAddress => ipAddress != null) | |||
.ToList(); | |||
return string.Join(",", ips); | |||
} | |||
} | |||
public TaskScheduleApiConfig(ApolloConfigurationManager apolloConfigurationManager, IConfiguration configuration) | |||
: base(apolloConfigurationManager, configuration) | |||
@@ -1,18 +0,0 @@ | |||
using BPA.Common.Infrastructure.BizLogs; | |||
using BPA.Component.DbClient; | |||
using Microsoft.Extensions.Logging; | |||
namespace BPA.SaaS.TaskSchedule.Api.Model; | |||
public class TaskScheduleDbSugarClient : BaseDbClient<TaskScheduleDbSugarClient> | |||
{ | |||
public TaskScheduleDbSugarClient(ILogger<TaskScheduleDbSugarClient> logger, | |||
Lazy<BizLogProducer> bizLogProducer, | |||
TaskScheduleApiConfig config) | |||
: base(BuildConnectionConfig(config.BasicMySqlDb), | |||
bizLogProducer, | |||
true, | |||
logger) | |||
{ | |||
} | |||
} |
@@ -0,0 +1,15 @@ | |||
using BPA.Component.MongoClient; | |||
using Microsoft.Extensions.Logging; | |||
namespace BPA.SaaS.TaskSchedule.Api.Model; | |||
public class TaskScheduleMongoDbClient : BaseMongoDbClient<TaskScheduleMongoDbClient> | |||
{ | |||
private readonly ILogger<TaskScheduleMongoDbClient> _logger; | |||
public TaskScheduleMongoDbClient(TaskScheduleApiConfig config, ILogger<TaskScheduleMongoDbClient> logger) | |||
: base(config.TaskScheduleMongoConfig, logger) | |||
{ | |||
_logger = logger; | |||
} | |||
} |
@@ -0,0 +1,51 @@ | |||
using BPA.SaaS.TaskSchedule.Api.DTO.SystemDictionary; | |||
namespace BPA.SaaS.TaskSchedule.Api.Model | |||
{ | |||
/// <summary> | |||
/// rediskey | |||
/// </summary> | |||
public static class TaskScheduleRedisKey | |||
{ | |||
/// <summary> | |||
/// 前缀 | |||
/// </summary> | |||
private const string Prefix = "TaskSchedule"; | |||
/// <summary> | |||
/// 数据同步起步token | |||
/// </summary> | |||
/// <returns></returns> | |||
public static string DictionaryKey(EnumSystemDictionaryType type, string key) => BuildRedisKey(nameof(DictionaryKey), type, key); | |||
/// <summary> | |||
/// 任务信息 | |||
/// </summary> | |||
/// <returns></returns> | |||
public static string TaskInfoKey(long taskInfoId) => BuildRedisKey(nameof(TaskInfoKey), taskInfoId); | |||
/// <summary> | |||
/// 任务信息 | |||
/// </summary> | |||
/// <returns></returns> | |||
public static string TaskInfoKey(string pattern) => BuildRedisKey(nameof(TaskInfoKey), pattern); | |||
/// <summary> | |||
/// 锁Trigger | |||
/// </summary> | |||
/// <param name="triggerKey"></param> | |||
/// <returns></returns> | |||
public static string LockTrigger(string triggerKey) => BuildRedisKey(nameof(LockTrigger), triggerKey); | |||
/// <summary> | |||
/// 创建rediskey | |||
/// </summary> | |||
/// <param name="keys"></param> | |||
/// <returns></returns> | |||
private static string BuildRedisKey(params object[] keys) | |||
{ | |||
if (keys == null || keys.Length == 0) throw new ArgumentNullException($"参数{keys},不能为空或者长度为0"); | |||
return $"{Prefix}:{string.Join(":", keys.Where(a => a != null))}"; | |||
} | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
<?xml version="1.0"?> | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<PropertyGroup> | |||
<TargetFramework>net6.0</TargetFramework> | |||
@@ -10,6 +10,7 @@ | |||
<PackageReference Include="BPA.Component.DbClient" Version="1.0.27" /> | |||
<PackageReference Include="BPA.Component.DTOCommon" Version="1.0.11" /> | |||
<PackageReference Include="BPA.Component.Extensions" Version="1.0.4" /> | |||
<PackageReference Include="BPA.Component.MongoClient" Version="1.0.12" /> | |||
<PackageReference Include="BPA.Component.SDKCommon" Version="1.0.3" /> | |||
<PackageReference Include="BPA.Common.Entity" Version="1.0.6" /> | |||
@@ -0,0 +1,15 @@ | |||
using BPA.SaaS.TaskSchedule.Api.Entity; | |||
using BPA.SaaS.TaskSchedule.Api.IRepository; | |||
using BPA.Component.MongoClient.Repository; | |||
using BPA.SaaS.TaskSchedule.Api.Model; | |||
namespace BPA.SaaS.TaskSchedule.Api.Repository | |||
{ | |||
public class SystemDictionaryRepository : BaseMongoDbRepository<TaskScheduleMongoDbClient, CrmSystemDictionary>, | |||
ISystemDictionaryRepository | |||
{ | |||
public SystemDictionaryRepository(TaskScheduleMongoDbClient mongoDbClient) : base(mongoDbClient) | |||
{ | |||
} | |||
} | |||
} |
@@ -0,0 +1,14 @@ | |||
using BPA.Component.MongoClient.Repository; | |||
using BPA.SaaS.TaskSchedule.Api.Entity; | |||
using BPA.SaaS.TaskSchedule.Api.IRepository; | |||
using BPA.SaaS.TaskSchedule.Api.Model; | |||
namespace BPA.SaaS.TaskSchedule.Api.Repository | |||
{ | |||
public class TaskInfoRepository : BaseMongoDbRepository<TaskScheduleMongoDbClient, CrmTaskInfo>, ITaskInfoRepository | |||
{ | |||
public TaskInfoRepository(TaskScheduleMongoDbClient mongoDbClient) : base(mongoDbClient) | |||
{ | |||
} | |||
} | |||
} |
@@ -0,0 +1,14 @@ | |||
using BPA.Component.MongoClient.Repository; | |||
using BPA.SaaS.TaskSchedule.Api.Entity; | |||
using BPA.SaaS.TaskSchedule.Api.IRepository; | |||
using BPA.SaaS.TaskSchedule.Api.Model; | |||
namespace BPA.SaaS.TaskSchedule.Api.Repository | |||
{ | |||
public class TaskLogRepository : BaseMongoDbRepository<TaskScheduleMongoDbClient, CrmTaskLog>, ITaskLogRepository | |||
{ | |||
public TaskLogRepository(TaskScheduleMongoDbClient mongoDbClient) : base(mongoDbClient) | |||
{ | |||
} | |||
} | |||
} |
@@ -0,0 +1,164 @@ | |||
using System.Text; | |||
using BPA.Component.Extensions; | |||
using BPA.SaaS.TaskSchedule.Api.DTO; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.SystemDictionary; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo; | |||
using BPA.SaaS.TaskSchedule.Api.IRepository; | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using BPA.SaaS.TaskSchedule.Api.Model; | |||
using BPA.SaaS.TaskSchedule.Api.Service.QzScheduler; | |||
using Microsoft.Extensions.Logging; | |||
using Nest; | |||
namespace BPA.SaaS.TaskSchedule.Api.Service | |||
{ | |||
/// <summary> | |||
/// any执行器 | |||
/// </summary> | |||
public class AnyTimeTaskScheduleExecutorService : BaseRequestHttpJobScheduler, IAnyTimeTaskScheduleExecutorService | |||
{ | |||
private readonly ITaskLogRepository taskLogRepository; | |||
private readonly ISystemDictionaryService systemDictionaryService; | |||
private readonly ILogger<AnyTimeTaskScheduleExecutorService> logger; | |||
private readonly ITaskInfoService taskInfoService; | |||
private readonly IHttpClientFactory httpClientFactory; | |||
private readonly IWarnManagerService warnManagerService; | |||
public AnyTimeTaskScheduleExecutorService( | |||
ITaskLogRepository taskLogRepository, | |||
ISystemDictionaryService systemDictionaryService, | |||
ILogger<AnyTimeTaskScheduleExecutorService> logger, | |||
ITaskInfoService taskInfoService, | |||
IHttpClientFactory httpClientFactory, | |||
IWarnManagerService warnManagerService) | |||
{ | |||
this.taskLogRepository = taskLogRepository; | |||
this.systemDictionaryService = systemDictionaryService; | |||
this.logger = logger; | |||
this.taskInfoService = taskInfoService; | |||
this.httpClientFactory = httpClientFactory; | |||
this.warnManagerService = warnManagerService; | |||
} | |||
/// <summary> | |||
/// 执行 | |||
/// </summary> | |||
/// <param name="msg"></param> | |||
public void Execute(AnyTimeTaskScheduleMsgBody msg) | |||
{ | |||
var taskLog = taskLogRepository.Queryable().FirstOrDefault(f => f.Id == msg.TaskLogId); | |||
if (taskLog == null) | |||
return; | |||
if (taskLog.ExecutedResult != EnumExecutedResultType.Wait) | |||
return; | |||
var taskInfo = taskInfoService.GetCache(taskLog.TaskId); | |||
if (taskInfo == null) | |||
return; | |||
taskLog.StartTime = DateTime.Now; | |||
if (taskInfo.Status == EnumTaskStatusType.Suspend) | |||
{ | |||
taskLog.ExecutedResult = EnumExecutedResultType.Suspend; | |||
taskLog.UpdateTime = DateTime.Now; | |||
taskLog.Remark = $"任务已暂停{taskLog.ExecutedResult.GetDescribe()}变为暂停,子任务不执行"; | |||
taskLogRepository.UpdateOne(taskLog, x => x.Id == taskLog.Id); | |||
return; | |||
} | |||
if (taskInfo.RequestURLType == EnumRequestURLType.BizSysApi) | |||
{ | |||
var dic = systemDictionaryService.GetCache(EnumSystemDictionaryType.BizSysApi, taskInfo.BizSysApiName); | |||
taskLog.RequestURL = $"{dic.Value}{taskInfo.RequestURL}"; | |||
#if DEBUG | |||
taskLog.RequestURL = $"{dic.ValueExt}{taskInfo.RequestURL}"; | |||
#endif | |||
} | |||
else | |||
{ | |||
taskLog.RequestURL = taskInfo.RequestURL; | |||
} | |||
taskLog.ExecutedResult = EnumExecutedResultType.Executing; | |||
taskLog.UpdateTime = DateTime.Now; | |||
taskLog.TrackId = $"{taskLog.TaskId:x}_{taskLog.Id:x}"; | |||
taskLogRepository.UpdateOne(taskLog, x => x.Id == taskLog.Id); | |||
var requestUrl = taskLog.RequestURL; | |||
try | |||
{ | |||
var client = httpClientFactory.CreateClient(typeof(AnyTimeTaskScheduleExecutorService).FullName); | |||
client.Timeout = TimeSpan.FromSeconds(taskInfo.Timeout <= 10 ? 30 : taskInfo.Timeout); | |||
client.DefaultRequestHeaders.Add(TaskScheduleApiConfig.TraceIdHeadName, taskLog.TrackId); | |||
HttpResponseMessage responseTask = null; | |||
switch (taskInfo.RequestMethod) | |||
{ | |||
case EnumRequestMethodType.GET: | |||
if (!taskLog.RequestURL.Contains("?")) | |||
requestUrl += "?"; | |||
if (taskLog.RequestParam.IsNotEmpty()) | |||
requestUrl += taskLog.RequestParam; | |||
requestUrl += $"&TaskId={taskLog.TaskId}&TaskLogId={taskLog.Id}"; | |||
//Debug.WriteLine(requestUrl); | |||
responseTask = client.GetAsync(requestUrl).GetAwaiter().GetResult(); | |||
break; | |||
case EnumRequestMethodType.POST: | |||
if (!taskLog.RequestURL.Contains("?")) | |||
requestUrl += "?"; | |||
if (taskLog.RequestParam.IsNotEmpty()) | |||
requestUrl += taskLog.RequestParam; | |||
requestUrl += $"&TaskId={taskLog.TaskId}&TaskLogId={taskLog.Id}"; | |||
//Debug.WriteLine(requestUrl); | |||
var stringContent = new StringContent(taskLog.RequestParam, Encoding.UTF8, "application/json"); | |||
responseTask = client.PostAsync(requestUrl, stringContent).GetAwaiter().GetResult(); | |||
break; | |||
default: break; | |||
} | |||
if (responseTask == null) | |||
{ | |||
taskLog.ExecutedResult = EnumExecutedResultType.CallFail; | |||
taskLog.Remark = string.Format("请求类型错误,请求类型RequestType不应该为{0}", taskInfo.RequestURLType); | |||
} | |||
else | |||
{ | |||
try | |||
{ | |||
//请求失败 | |||
if (responseTask.StatusCode == System.Net.HttpStatusCode.OK) | |||
{ | |||
taskLog.ResponseParam = responseTask.Content.ReadAsStringAsync().GetAwaiter().GetResult(); | |||
var checkSuccessMark = base.CheckSuccessMark(taskInfo, taskLog.ResponseParam); | |||
taskLog.ExecutedResult = checkSuccessMark ? EnumExecutedResultType.Success : EnumExecutedResultType.Fail; | |||
taskLog.Remark = checkSuccessMark ? "执行成功!" : $"执行失败,[{taskInfo.SuccessMark}]"; | |||
} | |||
else | |||
{ | |||
taskLog.ExecutedResult = EnumExecutedResultType.Fail; | |||
taskLog.Remark = $"StatusCode:[{(int) responseTask.StatusCode}]{responseTask.StatusCode}"; | |||
} | |||
taskLog.ResponseParam = responseTask.Content.ReadAsStringAsync().GetAwaiter().GetResult(); | |||
if (taskLog.ResponseParam.Length > 400) | |||
taskLog.ResponseParam = taskLog.ResponseParam.Substring(0, 400); | |||
} | |||
catch (Exception ex0) | |||
{ | |||
logger.LogError(ex0, "执行任务失败,{requestUrl},{taskLog}", requestUrl, taskInfo.ToJson()); | |||
} | |||
} | |||
} | |||
catch (Exception ex) | |||
{ | |||
taskLog.ExecutedResult = EnumExecutedResultType.Fail; | |||
taskLog.Remark = ex.Message; | |||
logger.LogError(ex, "执行任务失败,{requestUrl},{taskLog}", requestUrl, taskInfo.ToJson()); | |||
} | |||
taskInfo.LastFireTime = DateTime.Now; | |||
taskLog.UpdateTime = DateTime.Now; | |||
taskLog.EndTime = DateTime.Now; | |||
taskLog.ExecutionTime = (int) (taskLog.EndTime.Value - taskLog.StartTime).TotalMilliseconds; | |||
taskLogRepository.UpdateOne(taskLog, x => x.Id == taskLog.Id); | |||
warnManagerService.Warn(taskInfo, taskLog).GetAwaiter().GetResult(); | |||
return; | |||
} | |||
} | |||
} |
@@ -9,6 +9,7 @@ | |||
<PackageReference Include="BPA.Common.Infrastructure" Version="1.0.11" /> | |||
<PackageReference Include="BPA.Component.DTOCommon" Version="1.0.11" /> | |||
<PackageReference Include="BPA.Component.Extensions" Version="1.0.4" /> | |||
<PackageReference Include="BPA.Component.MongoClient" Version="1.0.12" /> | |||
<PackageReference Include="BPA.Component.SDKCommon" Version="1.0.3" /> | |||
<PackageReference Include="BPA.Component.WebApiExtensions" Version="1.0.23" /> | |||
@@ -0,0 +1,52 @@ | |||
using BPA.Component.RabbitMQClient.Base; | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Extensions.Logging; | |||
using RabbitMQ.Client.Events; | |||
namespace BPA.SaaS.TaskSchedule.Api.Service.Queue | |||
{ | |||
/// <summary> | |||
/// 任意时间 任务 消费者 | |||
/// </summary> | |||
public class AnyTimeTaskScheduleConsumer : BaseConsumer<AnyTimeTaskScheduleMsgBody, AnyTimeTaskScheduleConsumer> | |||
{ | |||
/// <summary> | |||
/// serviceProvider | |||
/// </summary> | |||
private readonly IServiceProvider serviceProvider; | |||
/// <summary> | |||
/// 构造方法 | |||
/// </summary> | |||
/// <param name="serviceProvider"></param> | |||
/// <param name="logger"></param> | |||
public AnyTimeTaskScheduleConsumer(IServiceProvider serviceProvider, ILogger<AnyTimeTaskScheduleConsumer> logger) : base(logger) | |||
{ | |||
this.serviceProvider = serviceProvider; | |||
} | |||
/// <summary> | |||
/// 消息出列 | |||
/// </summary> | |||
/// <param name="msgBody"></param> | |||
/// <param name="eventArgs"></param> | |||
/// <returns></returns> | |||
public override bool Dequeue(AnyTimeTaskScheduleMsgBody msgBody, BasicDeliverEventArgs eventArgs) | |||
{ | |||
try | |||
{ | |||
//新建作用域 | |||
var scope = serviceProvider.CreateScope(); | |||
var service = scope.ServiceProvider.GetRequiredService<IAnyTimeTaskScheduleExecutorService>(); | |||
service.Execute(msgBody); | |||
return true; | |||
} | |||
catch (Exception ex) | |||
{ | |||
Logger.LogError(ex, $"执行任意消息失败:{msgBody}"); | |||
return true; | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,29 @@ | |||
using BPA.Component.RabbitMQClient.Base; | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using Microsoft.Extensions.Logging; | |||
namespace BPA.SaaS.TaskSchedule.Api.Service.Queue | |||
{ | |||
/// <summary> | |||
/// 任意时间 任务生产者 | |||
/// </summary> | |||
public class AnyTimeTaskScheduleProducer : BaseProducer<AnyTimeTaskScheduleMsgBody, AnyTimeTaskScheduleProducer> | |||
{ | |||
public AnyTimeTaskScheduleProducer(ILogger<AnyTimeTaskScheduleProducer> logger) : base(logger) | |||
{ | |||
} | |||
/// <summary> | |||
/// 消息入列 | |||
/// </summary> | |||
/// <param name="taskLogId"></param> | |||
/// <param name="DateTime"></param> | |||
public void Enqueue(long taskLogId, DateTime planDate) | |||
{ | |||
long? expiration = null; | |||
if (planDate > DateTime.Now) | |||
expiration = ((long)(planDate - DateTime.Now).TotalSeconds) * 1000L; | |||
base.Enqueue(new AnyTimeTaskScheduleMsgBody { TaskLogId = taskLogId }, null, expiration); | |||
} | |||
} | |||
} |
@@ -0,0 +1,50 @@ | |||
using BPA.Component.RabbitMQClient.Base; | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Extensions.Logging; | |||
using RabbitMQ.Client.Events; | |||
namespace BPA.SaaS.TaskSchedule.Api.Service.Queue | |||
{ | |||
public class SynDataTaskScheduleConsumer : BaseConsumer<SynDataTaskScheduleMsgBody, SynDataTaskScheduleConsumer> | |||
{ | |||
private readonly IServiceProvider serviceProvider; | |||
public SynDataTaskScheduleConsumer(IServiceProvider serviceProvider, ILogger<SynDataTaskScheduleConsumer> logger) : base(logger) | |||
{ | |||
this.serviceProvider = serviceProvider; | |||
} | |||
/// <summary> | |||
/// 消息出列 | |||
/// </summary> | |||
/// <param name="msgBody"></param> | |||
/// <param name="eventArgs"></param> | |||
/// <returns></returns> | |||
public override bool Dequeue(SynDataTaskScheduleMsgBody msgBody, BasicDeliverEventArgs eventArgs) | |||
{ | |||
try | |||
{ | |||
//新建作用域 | |||
var scope = serviceProvider.CreateScope(); | |||
if (msgBody.DataType == 1) | |||
{ | |||
//字典 | |||
var service = scope.ServiceProvider.GetRequiredService<ISystemDictionaryService>(); | |||
service.OnDataChange(msgBody); | |||
} | |||
else if (msgBody.DataType == 2) | |||
{ | |||
//任务信息 | |||
var service = scope.ServiceProvider.GetRequiredService<ITaskInfoService>(); | |||
service.OnDataChange(msgBody); | |||
} | |||
return true; | |||
} | |||
catch (Exception ex) | |||
{ | |||
Logger.LogError(ex, $"执行任意消息失败:{msgBody}"); | |||
return true; | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,22 @@ | |||
using BPA.Component.RabbitMQClient.Base; | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using Microsoft.Extensions.Logging; | |||
namespace BPA.SaaS.TaskSchedule.Api.Service.Queue | |||
{ | |||
public class SynDataTaskScheduleProducer : BaseProducer<SynDataTaskScheduleMsgBody, SynDataTaskScheduleProducer> | |||
{ | |||
public SynDataTaskScheduleProducer(ILogger<SynDataTaskScheduleProducer> logger) : base(logger) | |||
{ | |||
} | |||
/// <summary> | |||
/// 消息入列 | |||
/// </summary> | |||
/// <param name="msgBody"></param> | |||
public void Enqueue(SynDataTaskScheduleMsgBody msgBody) | |||
{ | |||
base.Enqueue(msgBody, null, null); | |||
} | |||
} | |||
} |
@@ -0,0 +1,187 @@ | |||
using System.Diagnostics; | |||
using System.Text; | |||
using BPA.Component.Extensions; | |||
using BPA.SaaS.TaskSchedule.Api.DTO; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.SystemDictionary; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo; | |||
using BPA.SaaS.TaskSchedule.Api.Entity; | |||
using BPA.SaaS.TaskSchedule.Api.IRepository; | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using BPA.SaaS.TaskSchedule.Api.Model; | |||
using Microsoft.Extensions.Logging; | |||
using Quartz; | |||
namespace BPA.SaaS.TaskSchedule.Api.Service.QzScheduler | |||
{ | |||
/// <summary> | |||
/// AnyTimeHttpJobScheduler | |||
/// </summary> | |||
public class AnyTimeHttpJobScheduler : BaseRequestHttpJobScheduler, IJob, IDisposable | |||
{ | |||
private readonly ITaskLogRepository taskLogRepository; | |||
private readonly ISystemDictionaryService systemDictionaryService; | |||
private readonly ILogger<AnyTimeHttpJobScheduler> logger; | |||
private readonly ITaskInfoService taskInfoService; | |||
private readonly IWarnManagerService warnManagerService; | |||
public AnyTimeHttpJobScheduler( | |||
ITaskLogRepository taskLogRepository, | |||
ISystemDictionaryService systemDictionaryService, | |||
ILogger<AnyTimeHttpJobScheduler> logger, | |||
ITaskInfoService taskInfoService, IWarnManagerService warnManagerService) | |||
{ | |||
this.taskLogRepository = taskLogRepository; | |||
this.systemDictionaryService = systemDictionaryService; | |||
this.logger = logger; | |||
this.taskInfoService = taskInfoService; | |||
this.warnManagerService = warnManagerService; | |||
} | |||
/// <summary> | |||
/// Execute | |||
/// </summary> | |||
/// <param name="context"></param> | |||
/// <returns></returns> | |||
public async Task Execute(IJobExecutionContext context) | |||
{ | |||
TaskInfoDTO taskInfo = null; | |||
long taskLogId = 0; | |||
try | |||
{ | |||
var taskInfoId = (long) context.JobDetail.JobDataMap["taskInfoId"]; | |||
taskLogId = (long) context.JobDetail.JobDataMap["taskLogId"]; | |||
var planStartTime = (DateTime) context.JobDetail.JobDataMap["planStartTime"]; | |||
#if DEBUG | |||
var now = DateTime.Now; | |||
var delay = (int) (now - planStartTime).TotalMilliseconds; | |||
Debug.WriteLine($"tid:{Thread.CurrentThread.ManagedThreadId},new:{now},ptime:{planStartTime}, delay:{delay}"); | |||
#endif | |||
taskInfo = taskInfoService.GetCache(taskInfoId); | |||
await ExecuteHttpSchedulerJob(taskInfo, taskLogId); | |||
} | |||
catch (Exception ex) | |||
{ | |||
logger.LogError(ex, "执行任务失败,{taskInfo},{taskLogId}", taskInfo?.ToJson(), taskLogId); | |||
} | |||
finally | |||
{ | |||
await context.Scheduler.DeleteJob(context.JobDetail.Key); | |||
} | |||
} | |||
/// <summary> | |||
/// 执行httpJob | |||
/// </summary> | |||
/// <param name="taskInfo"></param> | |||
public async Task ExecuteHttpSchedulerJob(TaskInfoDTO taskInfo, long taskLogId) | |||
{ | |||
if (taskInfo == null) return; | |||
var taskLog = taskLogRepository.Queryable().FirstOrDefault(f => f.Id == taskLogId); | |||
if (taskLog.ExecutedResult != EnumExecutedResultType.Wait) | |||
return; | |||
taskLog.StartTime = DateTime.Now; | |||
if (taskInfo.Status == EnumTaskStatusType.Suspend) | |||
{ | |||
taskLog.ExecutedResult = EnumExecutedResultType.Suspend; | |||
taskLog.UpdateTime = DateTime.Now; | |||
taskLog.Remark = $"{taskLog.ExecutedResult.GetDescribe()}变为暂停"; | |||
taskLogRepository.UpdateOne(taskLog, x => x.Id == taskLogId); | |||
return; | |||
} | |||
if (taskInfo.RequestURLType == EnumRequestURLType.BizSysApi) | |||
{ | |||
var dic = systemDictionaryService.GetCache(EnumSystemDictionaryType.BizSysApi, taskInfo.BizSysApiName); | |||
taskLog.RequestURL = $"{dic.Value}{taskInfo.RequestURL}"; | |||
#if DEBUG | |||
taskLog.RequestURL = $"{dic.ValueExt}{taskInfo.RequestURL}"; | |||
#endif | |||
} | |||
else | |||
{ | |||
taskLog.RequestURL = taskInfo.RequestURL; | |||
} | |||
taskLog.ExecutedResult = EnumExecutedResultType.Executing; | |||
taskLog.UpdateTime = DateTime.Now; | |||
taskLog.TrackId = $"{taskLog.TaskId:x}:{taskLog.Id:x}"; | |||
taskLogRepository.UpdateOne(taskLog, x => x.Id == taskLogId); | |||
var requestUrl = taskLog.RequestURL; | |||
try | |||
{ | |||
var client = new HttpClient | |||
{ | |||
Timeout = TimeSpan.FromSeconds(taskInfo.Timeout <= 10 ? 30 : taskInfo.Timeout) | |||
}; | |||
client.DefaultRequestHeaders.Add(TaskScheduleApiConfig.TraceIdHeadName, taskLog.TrackId); | |||
HttpResponseMessage responseTask = null; | |||
switch (taskInfo.RequestMethod) | |||
{ | |||
case EnumRequestMethodType.GET: | |||
if (!taskLog.RequestURL.Contains("?")) | |||
requestUrl += "?"; | |||
if (taskLog.RequestParam.IsNotEmpty()) | |||
requestUrl += taskLog.RequestParam; | |||
//Debug.WriteLine(requestUrl); | |||
responseTask = await client.GetAsync(requestUrl); | |||
break; | |||
case EnumRequestMethodType.POST: | |||
//Debug.WriteLine(requestUrl); | |||
var stringContent = new StringContent(taskLog.RequestParam, Encoding.UTF8, "application/json"); | |||
responseTask = await client.PostAsync(requestUrl, stringContent); | |||
break; | |||
default: break; | |||
} | |||
if (responseTask == null) | |||
{ | |||
taskLog.ExecutedResult = EnumExecutedResultType.CallFail; | |||
taskLog.Remark = string.Format("请求类型错误,请求类型RequestType不应该为{0}", taskInfo.RequestURLType); | |||
} | |||
else | |||
{ | |||
try | |||
{ | |||
if (responseTask.StatusCode == System.Net.HttpStatusCode.OK) | |||
{ | |||
taskLog.ResponseParam = await responseTask.Content.ReadAsStringAsync(); | |||
var checkSuccessMark = base.CheckSuccessMark(taskInfo, taskLog.ResponseParam); | |||
taskLog.ExecutedResult = checkSuccessMark ? EnumExecutedResultType.Success : EnumExecutedResultType.Fail; | |||
taskLog.Remark = checkSuccessMark ? "执行成功!" : $"执行失败,[{taskInfo.SuccessMark}]"; | |||
} | |||
else | |||
{ | |||
//请求失败 | |||
taskLog.ExecutedResult = EnumExecutedResultType.Fail; | |||
taskLog.Remark = $"StatusCode:[{(int) responseTask.StatusCode}]{responseTask.StatusCode}"; | |||
} | |||
if (taskLog.ResponseParam != null && taskLog.ResponseParam.Length > 400) | |||
taskLog.ResponseParam = taskLog.ResponseParam.Substring(0, 400); | |||
} | |||
catch (Exception ex0) | |||
{ | |||
logger.LogError(ex0, "执行任务失败,{requestUrl},{taskLog}", requestUrl, taskInfo.ToJson()); | |||
} | |||
} | |||
} | |||
catch (Exception ex) | |||
{ | |||
taskLog.ExecutedResult = EnumExecutedResultType.Fail; | |||
taskLog.Remark = ex.Message; | |||
logger.LogError(ex, "执行任务失败,{requestUrl},{taskLog}", requestUrl, taskInfo.ToJson()); | |||
} | |||
taskInfo.LastFireTime = DateTime.Now; | |||
taskLog.UpdateTime = DateTime.Now; | |||
taskLog.EndTime = DateTime.Now; | |||
taskLog.ExecutionTime = (int) (taskLog.EndTime.Value - taskLog.StartTime).TotalMilliseconds; | |||
taskLogRepository.UpdateOne(taskLog, x => x.Id == taskLog.Id); | |||
await warnManagerService.Warn(taskInfo, taskLog); | |||
} | |||
public void Dispose() | |||
{ | |||
} | |||
} | |||
} |
@@ -0,0 +1,169 @@ | |||
using System.Collections.Concurrent; | |||
using System.Collections.Specialized; | |||
using BPA.SaaS.TaskSchedule.Api.DTO; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo; | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using BPA.SaaS.TaskSchedule.Api.Model; | |||
using Microsoft.Extensions.Logging; | |||
using Quartz; | |||
using Quartz.Impl; | |||
namespace BPA.SaaS.TaskSchedule.Api.Service.QzScheduler | |||
{ | |||
/// <summary> | |||
/// 任意时间 | |||
/// </summary> | |||
public class AnyTimeSchedulerManager | |||
{ | |||
private const int schedulerCount = 10; | |||
/// <summary> | |||
/// 调度器 | |||
/// </summary> | |||
private readonly ConcurrentDictionary<long, IScheduler> schedulers; | |||
private readonly TaskScheduleLangPackge langPackge; | |||
private readonly JobSchedulerFactory jobSchedulerFactory; | |||
private readonly Lazy<ITaskInfoService> taskInfoService; | |||
private readonly ILogger<AnyTimeSchedulerManager> logger; | |||
private readonly TaskScheduleApiConfig TaskScheduleApiConfig; | |||
public AnyTimeSchedulerManager( | |||
TaskScheduleLangPackge langPackge, | |||
JobSchedulerFactory jobSchedulerFactory, | |||
Lazy<ITaskInfoService> taskInfoService, | |||
ILogger<AnyTimeSchedulerManager> logger, | |||
TaskScheduleApiConfig TaskScheduleApiConfig) | |||
{ | |||
schedulers = new ConcurrentDictionary<long, IScheduler>(); | |||
this.langPackge = langPackge; | |||
this.jobSchedulerFactory = jobSchedulerFactory; | |||
this.taskInfoService = taskInfoService; | |||
this.logger = logger; | |||
this.TaskScheduleApiConfig = TaskScheduleApiConfig; | |||
for (var i = 0; i < schedulerCount; i++) | |||
GetScheduler(i); | |||
} | |||
/// <summary> | |||
/// GetScheduler | |||
/// </summary> | |||
/// <param name="taskLogId"></param> | |||
/// <returns></returns> | |||
private IScheduler GetScheduler(long taskLogId) | |||
{ | |||
var key = taskLogId % schedulerCount; | |||
return schedulers.GetOrAdd(key, k => | |||
{ | |||
var name = $"{nameof(AnyTimeSchedulerManager)}{k}"; | |||
var properties = new NameValueCollection | |||
{ | |||
[StdSchedulerFactory.PropertySchedulerInstanceName] = name, | |||
[StdSchedulerFactory.PropertySchedulerInstanceId] = TaskScheduleApiConfig.IPS, | |||
// [StdSchedulerFactory.PropertyJobStoreType] = typeof(MongoDbJobStore).AssemblyQualifiedName, | |||
["quartz.threadPool.type"] = "Quartz.Simpl.DefaultThreadPool", | |||
["quartz.threadPool.maxConcurrency"] = "20", | |||
// ["quartz.serializer.type"] = "binary", | |||
// I treat the database in the connection string as the one you want to connect to | |||
//[$"{StdSchedulerFactory.PropertyJobStorePrefix}.{StdSchedulerFactory.PropertyDataSourceConnectionString}"] = TaskScheduleApiConfig.MongoDBTaskScheduleUrl, | |||
// The prefix is optional | |||
//[$"{StdSchedulerFactory.PropertyJobStorePrefix}.collectionPrefix"] = $"Qz{name}" | |||
}; | |||
var factory = new StdSchedulerFactory(properties); | |||
var scheduler = factory.GetScheduler().GetAwaiter().GetResult(); | |||
scheduler.JobFactory = jobSchedulerFactory; | |||
scheduler.Start(); | |||
logger.LogInformation($"启动 {scheduler.SchedulerName} 调度器"); | |||
return scheduler; | |||
}); | |||
} | |||
/// <summary> | |||
/// 设置调度任务 | |||
/// </summary> | |||
/// <param name="jobInfo"></param> | |||
public void SetTaskScheduler(TaskInfoDTO taskInfo, long taskLogId, DateTime planStartTime) | |||
{ | |||
if (taskInfo.TaskType != EnumTaskType.AnyTime) | |||
return; | |||
var jobKey = CreateJobKey(taskLogId, taskInfo.Id); | |||
RemoveTaskScheduler(taskLogId, jobKey); | |||
if (taskInfo.Status == EnumTaskStatusType.Suspend) | |||
return; | |||
//var now = DateTime.Now; | |||
//var totalSeconds = (int)(planStartTime - now).TotalSeconds; | |||
//var intervalInSeconds = totalSeconds < 1 ? 1 : totalSeconds; | |||
var triggerKey = CreateTriggerKey(taskLogId, taskInfo.Id); | |||
var triggerBuild = TriggerBuilder.Create().WithIdentity(triggerKey); | |||
if (planStartTime < DateTime.Now) | |||
planStartTime = DateTime.Now; | |||
//秒 分 时 日 月 ? 年 中间用空格隔开 | |||
var trigger = triggerBuild.WithCronSchedule($"{planStartTime.Second} {planStartTime.Minute} {planStartTime.Hour} {planStartTime.Day} {planStartTime.Month} ? {planStartTime.Year} ").Build(); | |||
//var trigger = triggerBuild.StartAt(planStartTime).WithSimpleSchedule(x => x.WithIntervalInSeconds(1).WithRepeatCount(1)).Build(); | |||
var dataMap = new JobDataMap { { "taskInfoId", taskInfo.Id }, { "taskLogId", taskLogId }, { "planStartTime", planStartTime } }; | |||
var job = JobBuilder.Create<AnyTimeHttpJobScheduler>().SetJobData(dataMap).WithIdentity(jobKey).WithDescription(taskInfo.Description).Build(); | |||
GetScheduler(taskLogId).ScheduleJob(job, trigger).GetAwaiter().GetResult(); | |||
} | |||
/// <summary> | |||
/// 移除任务 | |||
/// </summary> | |||
/// <param name="taskInfoId"></param> | |||
public void RemoveTaskScheduler(long taskLogId, long taskInfoId) | |||
{ | |||
var key = CreateJobKey(taskLogId, taskInfoId); | |||
RemoveTaskScheduler(taskLogId, key); | |||
} | |||
/// <summary> | |||
/// 检查是否已经存在任务 | |||
/// </summary> | |||
/// <param name="taskLogId"></param> | |||
/// <param name="taskInfoId"></param> | |||
/// <returns></returns> | |||
public bool CheckExists(long taskLogId, long taskInfoId) | |||
{ | |||
var key = CreateJobKey(taskLogId, taskInfoId); | |||
return GetScheduler(taskLogId).CheckExists(key).GetAwaiter().GetResult(); | |||
} | |||
/// <summary> | |||
/// 移除任务 | |||
/// </summary> | |||
/// <param name="taskInfoId"></param> | |||
public void RemoveTaskScheduler(long taskLogId, JobKey jobKey) | |||
{ | |||
if (GetScheduler(taskLogId).CheckExists(jobKey).GetAwaiter().GetResult()) | |||
GetScheduler(taskLogId).DeleteJob(jobKey).GetAwaiter().GetResult(); | |||
} | |||
/// <summary> | |||
/// 获取下次执行时间 | |||
/// </summary> | |||
/// <param name="taskInfoId"></param> | |||
/// <returns></returns> | |||
public DateTimeOffset? GetNextFireTime(long taskLogId, long taskInfoId) | |||
{ | |||
var triggerKey = CreateTriggerKey(taskLogId, taskInfoId); | |||
var trigger = GetScheduler(taskLogId).GetTrigger(triggerKey).GetAwaiter().GetResult(); | |||
if (trigger == null) | |||
return null; | |||
return trigger.GetNextFireTimeUtc()?.ToLocalTime(); | |||
} | |||
/// <summary> | |||
/// CreateJobKey | |||
/// </summary> | |||
/// <param name="taskInfoId"></param> | |||
/// <returns></returns> | |||
private JobKey CreateJobKey(long taskLogId, long taskInfoId) => JobKey.Create(taskLogId.ToString(), taskInfoId.ToString()); | |||
/// <summary> | |||
/// CreateTriggerKey | |||
/// </summary> | |||
/// <param name="taskInfoId"></param> | |||
/// <returns></returns> | |||
private TriggerKey CreateTriggerKey(long taskLogId, long taskInfoId) => new TriggerKey(taskLogId.ToString(), taskInfoId.ToString()); | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
using BPA.Component.Extensions; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo; | |||
namespace BPA.SaaS.TaskSchedule.Api.Service.QzScheduler | |||
{ | |||
public class BaseRequestHttpJobScheduler | |||
{ | |||
/// <summary> | |||
/// 检查成功标志 | |||
/// </summary> | |||
/// <param name="taskInfo"></param> | |||
/// <param name="rpsContent"></param> | |||
/// <returns></returns> | |||
public bool CheckSuccessMark(TaskInfoDTO taskInfo,string rpsContent) | |||
{ | |||
if (taskInfo.SuccessMark.IsEmpty()) | |||
return true; | |||
if (rpsContent.IsEmpty()) | |||
return false; | |||
return rpsContent.Contains(taskInfo.SuccessMark); | |||
} | |||
} | |||
} |
@@ -0,0 +1,39 @@ | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Quartz; | |||
using Quartz.Spi; | |||
namespace BPA.SaaS.TaskSchedule.Api.Service.QzScheduler | |||
{ | |||
/// <summary> | |||
/// Job工厂 | |||
/// </summary> | |||
public class JobSchedulerFactory : IJobFactory | |||
{ | |||
private readonly IServiceProvider _serviceProvider; | |||
public JobSchedulerFactory(IServiceProvider serviceProvider) | |||
{ | |||
_serviceProvider = serviceProvider; | |||
} | |||
/// <summary> | |||
/// NewJob | |||
/// </summary> | |||
/// <param name="bundle"></param> | |||
/// <param name="scheduler"></param> | |||
/// <returns></returns> | |||
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) | |||
{ | |||
var sp = _serviceProvider.CreateScope().ServiceProvider; | |||
//Job类型 | |||
var jobType = bundle.JobDetail.JobType; | |||
//返回jobType对应类型的实例 | |||
return sp.GetService(jobType) as IJob; | |||
} | |||
public void ReturnJob(IJob job) | |||
{ | |||
var disposable = job as IDisposable; | |||
disposable?.Dispose(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,186 @@ | |||
using System.Diagnostics; | |||
using System.Text; | |||
using BPA.Component.Extensions; | |||
using BPA.SaaS.TaskSchedule.Api.DTO; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.SystemDictionary; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo; | |||
using BPA.SaaS.TaskSchedule.Api.Entity; | |||
using BPA.SaaS.TaskSchedule.Api.IRepository; | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using BPA.SaaS.TaskSchedule.Api.Model; | |||
using Microsoft.Extensions.Logging; | |||
using Quartz; | |||
namespace BPA.SaaS.TaskSchedule.Api.Service.QzScheduler | |||
{ | |||
/// <summary> | |||
/// SynHttpJobScheduler | |||
/// </summary> | |||
public class SynRequestHttpJobScheduler : BaseRequestHttpJobScheduler, IJob, IDisposable | |||
{ | |||
private readonly ITaskLogRepository taskLogRepository; | |||
private readonly ISystemDictionaryService systemDictionaryService; | |||
private readonly ILogger<SynRequestHttpJobScheduler> logger; | |||
private readonly ITaskInfoService taskInfoService; | |||
private readonly IHttpClientFactory httpClientFactory; | |||
private readonly IWarnManagerService warnManagerService; | |||
public SynRequestHttpJobScheduler( | |||
ITaskLogRepository taskLogRepository, | |||
ISystemDictionaryService systemDictionaryService, | |||
ILogger<SynRequestHttpJobScheduler> logger, | |||
ITaskInfoService taskInfoService, | |||
IHttpClientFactory httpClientFactory, IWarnManagerService warnManagerService) | |||
{ | |||
this.taskLogRepository = taskLogRepository; | |||
this.systemDictionaryService = systemDictionaryService; | |||
this.logger = logger; | |||
this.taskInfoService = taskInfoService; | |||
this.httpClientFactory = httpClientFactory; | |||
this.warnManagerService = warnManagerService; | |||
} | |||
/// <summary> | |||
/// Execute | |||
/// </summary> | |||
/// <param name="context"></param> | |||
/// <returns></returns> | |||
public async Task Execute(IJobExecutionContext context) | |||
{ | |||
TaskInfoDTO taskInfo = null; | |||
try | |||
{ | |||
var taskInfoId = (long) context.JobDetail.JobDataMap["taskInfoId"]; | |||
#if DEBUG | |||
Console.WriteLine($"{DateTime.Now} - [SynRequestHttpJobScheduler] - {taskInfoId}"); | |||
#endif | |||
taskInfo = taskInfoService.GetCache(taskInfoId); | |||
if (taskInfo == null) return; | |||
await ExecuteHttpSchedulerJob(taskInfo); | |||
} | |||
catch (Exception ex) | |||
{ | |||
logger.LogError(ex, "执行任务失败,{taskInfo}", taskInfo?.ToJson()); | |||
} | |||
} | |||
/// <summary> | |||
/// 执行httpJob | |||
/// </summary> | |||
/// <param name="taskInfo"></param> | |||
public async Task ExecuteHttpSchedulerJob(TaskInfoDTO taskInfo) | |||
{ | |||
if (taskInfo.Status == EnumTaskStatusType.Suspend) | |||
return; | |||
var taskLog = new CrmTaskLog | |||
{ | |||
BizSysApiName = taskInfo.BizSysApiName, | |||
CreateTime = DateTime.Now, | |||
EndTime = null, | |||
ExecutedResult = EnumExecutedResultType.Executing, | |||
ExecutionTime = 0, | |||
Id = BPAUniqueIdBulder.NextLong(), | |||
Name = taskInfo.Name, | |||
PlanStartTime = DateTime.Now, | |||
Remark = string.Empty, | |||
RequestParam = taskInfo.RequestParam, | |||
RequestURL = taskInfo.RequestURL, | |||
RequestURLType = taskInfo.RequestURLType, | |||
ResponseParam = string.Empty, | |||
StartTime = DateTime.Now, | |||
TaskId = taskInfo.Id, | |||
TaskType = taskInfo.TaskType, | |||
Title = taskInfo.Title, | |||
UpdateTime = DateTime.Now | |||
}; | |||
if (taskInfo.RequestURLType == EnumRequestURLType.BizSysApi) | |||
{ | |||
var dic = systemDictionaryService.GetCache(EnumSystemDictionaryType.BizSysApi, taskInfo.BizSysApiName); | |||
taskLog.RequestURL = $"{dic.Value}{taskInfo.RequestURL}"; | |||
#if DEBUG | |||
taskLog.RequestURL = $"{dic.ValueExt}{taskInfo.RequestURL}"; | |||
#endif | |||
} | |||
taskLog.TrackId = $"{taskLog.TaskId:x}_{taskLog.Id:x}"; | |||
taskLogRepository.Add(taskLog); | |||
var requestUrl = taskLog.RequestURL; | |||
try | |||
{ | |||
var client = httpClientFactory.CreateClient(typeof(AnyTimeTaskScheduleExecutorService).FullName); | |||
client.Timeout = TimeSpan.FromSeconds(taskInfo.Timeout <= 10 ? 30 : taskInfo.Timeout); | |||
client.DefaultRequestHeaders.Add(TaskScheduleApiConfig.TraceIdHeadName, taskLog.TrackId); | |||
HttpResponseMessage responseTask = null; | |||
switch (taskInfo.RequestMethod) | |||
{ | |||
case EnumRequestMethodType.GET: | |||
if (!taskLog.RequestURL.Contains("?")) | |||
requestUrl += "?"; | |||
if (taskLog.RequestParam.IsNotEmpty()) | |||
requestUrl += taskLog.RequestParam; | |||
Debug.WriteLine(requestUrl); | |||
responseTask = await client.GetAsync(requestUrl); | |||
break; | |||
case EnumRequestMethodType.POST: | |||
var stringContent = new StringContent(taskLog.RequestParam, Encoding.UTF8, "application/json"); | |||
responseTask = await client.PostAsync(requestUrl, stringContent); | |||
break; | |||
default: break; | |||
} | |||
taskLog.UpdateTime = DateTime.Now; | |||
taskLog.EndTime = DateTime.Now; | |||
taskLog.ExecutionTime = (int) (taskLog.EndTime.Value - taskLog.StartTime).TotalMilliseconds; | |||
if (responseTask == null) | |||
{ | |||
taskLog.ExecutedResult = EnumExecutedResultType.CallFail; | |||
taskLog.Remark = string.Format("请求类型错误,请求类型RequestType不应该为{0}", taskInfo.RequestURLType); | |||
} | |||
else | |||
{ | |||
try | |||
{ | |||
if (responseTask.StatusCode == System.Net.HttpStatusCode.OK) | |||
{ | |||
taskLog.ResponseParam = await responseTask.Content.ReadAsStringAsync(); | |||
var checkSuccessMark = base.CheckSuccessMark(taskInfo, taskLog.ResponseParam); | |||
taskLog.ExecutedResult = checkSuccessMark ? EnumExecutedResultType.Success : EnumExecutedResultType.Fail; | |||
taskLog.Remark = checkSuccessMark ? "执行成功!" : $"执行失败,[{taskInfo.SuccessMark}]"; | |||
} | |||
else | |||
{ | |||
//请求失败 | |||
taskLog.ExecutedResult = EnumExecutedResultType.Fail; | |||
taskLog.Remark = $"StatusCode:[{(int) responseTask.StatusCode}]{responseTask.StatusCode}"; | |||
} | |||
if (taskLog.ResponseParam != null && taskLog.ResponseParam.Length > 400) | |||
taskLog.ResponseParam = taskLog.ResponseParam.Substring(0, 400); | |||
taskLog.UpdateTime = DateTime.Now; | |||
} | |||
catch (Exception ex0) | |||
{ | |||
logger.LogError(ex0, "执行任务失败,{requestUrl},{taskLog}", requestUrl, taskInfo.ToJson()); | |||
} | |||
} | |||
} | |||
catch (Exception ex) | |||
{ | |||
taskLog.UpdateTime = DateTime.Now; | |||
taskLog.EndTime = DateTime.Now; | |||
taskLog.ExecutionTime = (int) (taskLog.EndTime.Value - taskLog.StartTime).TotalMilliseconds; | |||
taskLog.ExecutedResult = EnumExecutedResultType.Fail; | |||
taskLog.Remark = ex.Message; | |||
logger.LogError(ex, "执行任务失败,{requestUrl},{taskLog}", requestUrl, taskInfo.ToJson()); | |||
} | |||
taskInfo.LastFireTime = DateTime.Now; | |||
taskLogRepository.UpdateOne(taskLog, x => x.Id == taskLog.Id); | |||
await warnManagerService.Warn(taskInfo, taskLog); | |||
} | |||
public void Dispose() | |||
{ | |||
} | |||
} | |||
} |
@@ -0,0 +1,170 @@ | |||
using System.Collections.Specialized; | |||
using BPA.SaaS.TaskSchedule.Api.DTO; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo; | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using BPA.SaaS.TaskSchedule.Api.Model; | |||
using Microsoft.Extensions.Logging; | |||
using Quartz; | |||
using Quartz.Impl; | |||
namespace BPA.SaaS.TaskSchedule.Api.Service.QzScheduler | |||
{ | |||
/// <summary> | |||
/// 调度器 单例 | |||
/// </summary> | |||
public class SynRequestSchedulerManager | |||
{ | |||
/// <summary> | |||
/// 调度器 | |||
/// </summary> | |||
private readonly IScheduler scheduler; | |||
private readonly ILogger<SynRequestSchedulerManager> logger; | |||
private readonly TaskScheduleApiConfig TaskScheduleApiConfig; | |||
/// <summary> | |||
/// DefaultSchedulerManager | |||
/// </summary> | |||
/// <param name="jobSchedulerFactory"></param> | |||
public SynRequestSchedulerManager( | |||
JobSchedulerFactory jobSchedulerFactory, | |||
ILogger<SynRequestSchedulerManager> logger, | |||
TaskScheduleApiConfig TaskScheduleApiConfig, | |||
ISynRequestSchedulerTriggerListenerService synRequestSchedulerTriggerListenerService) | |||
{ | |||
this.logger = logger; | |||
this.TaskScheduleApiConfig = TaskScheduleApiConfig; | |||
var properties = new NameValueCollection | |||
{ | |||
[StdSchedulerFactory.PropertySchedulerInstanceName] = nameof(SynRequestSchedulerManager), | |||
[StdSchedulerFactory.PropertySchedulerInstanceId] = TaskScheduleApiConfig.IPS, | |||
["quartz.threadPool.type"] = "Quartz.Simpl.DefaultThreadPool", | |||
["quartz.threadPool.maxConcurrency"] = "20", | |||
#region MongoDbJobStore config | |||
//[StdSchedulerFactory.PropertyJobStoreType] = typeof(MongoDbJobStore).AssemblyQualifiedName, | |||
//["quartz.threadPool.maxConcurrency"] = "50", | |||
//["quartz.serializer.type"] = "binary", | |||
//// I treat the database in the connection string as the one you want to connect to | |||
//[$"{StdSchedulerFactory.PropertyJobStorePrefix}.{StdSchedulerFactory.PropertyDataSourceConnectionString}"] = TaskScheduleApiConfig.MongoDBTaskScheduleUrl, | |||
//// The prefix is optional | |||
//[$"{StdSchedulerFactory.PropertyJobStorePrefix}.collectionPrefix"] = $"Qz{nameof(SynRequestSchedulerManager)}" | |||
#endregion | |||
}; | |||
var factory = new StdSchedulerFactory(properties); | |||
scheduler = factory.GetScheduler().GetAwaiter().GetResult(); | |||
scheduler.JobFactory = jobSchedulerFactory; | |||
scheduler.Start(); | |||
scheduler.ListenerManager.AddTriggerListener(synRequestSchedulerTriggerListenerService); | |||
logger.LogInformation($"启动 {scheduler.SchedulerName} 调度器"); | |||
} | |||
/// <summary> | |||
/// 设置调度任务 | |||
/// </summary> | |||
/// <param name="jobInfo"></param> | |||
public void SetTaskScheduler(TaskInfoDTO taskInfo) | |||
{ | |||
var jobKey = CreateJobKey(taskInfo.Id); | |||
RemoveTaskScheduler(jobKey); | |||
if (taskInfo.IsDelete) | |||
return; | |||
if (taskInfo.TaskType != EnumTaskType.SynRequest) | |||
return; | |||
if (taskInfo.Status == EnumTaskStatusType.Suspend) | |||
return; | |||
ITrigger trigger = null; | |||
var triggerKey = CreateTriggerKey(taskInfo.Id); | |||
var triggerBuild = TriggerBuilder.Create().WithIdentity(triggerKey); | |||
switch (taskInfo.TriggerType) | |||
{ | |||
case EnumTriggerType.SimpleTrigger: | |||
//ICronTrigger | |||
trigger = triggerBuild.StartAt(new DateTimeOffset(taskInfo.StartTime)).WithSimpleSchedule(x => x.WithIntervalInSeconds(taskInfo.RepeatInterval).WithRepeatCount(taskInfo.RepeatCount)).Build(); | |||
//WithIntervalInMinutes | |||
break; | |||
case EnumTriggerType.CronTrigger: | |||
//ICronTrigger | |||
trigger = triggerBuild.WithCronSchedule(taskInfo.CronExpression).Build(); | |||
break; | |||
default: | |||
break; | |||
} | |||
if (trigger == null) | |||
return; | |||
var dataMap = new JobDataMap { { "taskInfoId", taskInfo.Id } }; | |||
var job = JobBuilder.Create<SynRequestHttpJobScheduler>().SetJobData(dataMap).WithIdentity(jobKey).WithDescription(taskInfo.Description).Build(); | |||
scheduler.ScheduleJob(job, trigger).GetAwaiter().GetResult(); | |||
} | |||
/// <summary> | |||
/// 移除任务 | |||
/// </summary> | |||
/// <param name="taskInfoId"></param> | |||
public void RemoveTaskScheduler(long taskInfoId) | |||
{ | |||
var key = CreateJobKey(taskInfoId); | |||
RemoveTaskScheduler(key); | |||
} | |||
/// <summary> | |||
/// 移除任务 | |||
/// </summary> | |||
/// <param name="taskInfoId"></param> | |||
public void RemoveTaskScheduler(JobKey jobKey) | |||
{ | |||
if (scheduler.CheckExists(jobKey).GetAwaiter().GetResult()) | |||
scheduler.DeleteJob(jobKey).GetAwaiter().GetResult(); | |||
} | |||
/// <summary> | |||
/// 获取下次执行时间 | |||
/// </summary> | |||
/// <param name="taskInfoId"></param> | |||
/// <returns></returns> | |||
public (DateTime? nextFireTime, DateTime? previousFireTime) GetFireTime(long taskInfoId) | |||
{ | |||
var triggerKey = CreateTriggerKey(taskInfoId); | |||
var trigger = scheduler.GetTrigger(triggerKey).GetAwaiter().GetResult(); | |||
if (trigger == null) | |||
return (null, null); | |||
var nextFireTime = trigger.GetNextFireTimeUtc()?.LocalDateTime; | |||
var previousFireTime = trigger.GetPreviousFireTimeUtc()?.LocalDateTime; | |||
return (nextFireTime, previousFireTime); | |||
} | |||
/// <summary> | |||
/// CreateJobKey | |||
/// </summary> | |||
/// <param name="taskInfoId"></param> | |||
/// <returns></returns> | |||
private JobKey CreateJobKey(long taskInfoId) => JobKey.Create(taskInfoId.ToString(), taskInfoId.ToString()); | |||
/// <summary> | |||
/// CreateTriggerKey | |||
/// </summary> | |||
/// <param name="taskInfoId"></param> | |||
/// <returns></returns> | |||
private TriggerKey CreateTriggerKey(long taskInfoId) => new TriggerKey(taskInfoId.ToString(), taskInfoId.ToString()); | |||
/// <summary> | |||
/// 校验Cron | |||
/// </summary> | |||
/// <param name="cronExpression"></param> | |||
/// <returns></returns> | |||
public (bool result, string msg) CheckCronExpression(string cronExpression) | |||
{ | |||
try | |||
{ | |||
var triggerKey = CreateTriggerKey(0); | |||
var triggerBuild = TriggerBuilder.Create().WithIdentity(triggerKey); | |||
triggerBuild.WithCronSchedule(cronExpression).Build(); | |||
return (true, "ok"); | |||
} | |||
catch (Exception ex) | |||
{ | |||
return (false, ex.Message); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,76 @@ | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using BPA.SaaS.TaskSchedule.Api.Model; | |||
using CSRedis; | |||
using Quartz; | |||
namespace BPA.SaaS.TaskSchedule.Api.Service.QzScheduler | |||
{ | |||
/// <summary> | |||
/// 触发器监听者 | |||
/// </summary> | |||
public class SynRequestSchedulerTriggerListenerService : ISynRequestSchedulerTriggerListenerService | |||
{ | |||
/// <summary> | |||
/// redisClient | |||
/// </summary> | |||
private readonly CSRedisClient redisClient; | |||
/// <summary> | |||
/// redisClient | |||
/// </summary> | |||
/// <param name="redisClient"></param> | |||
public SynRequestSchedulerTriggerListenerService(CSRedisClient redisClient) | |||
{ | |||
this.redisClient = redisClient; | |||
} | |||
public string Name => nameof(SynRequestSchedulerTriggerListenerService); | |||
public Task TriggerComplete(ITrigger trigger, IJobExecutionContext context, SchedulerInstruction triggerInstructionCode, CancellationToken cancellationToken = default) | |||
{ | |||
#if DEBUG | |||
Console.WriteLine($"[TriggerComplete]: Trigger[{trigger.Key.Name}] 被触发并且完成了 Job 的执行,此方法被调用"); | |||
#endif | |||
return Task.CompletedTask; | |||
} | |||
public Task TriggerFired(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default) | |||
{ | |||
#if DEBUG | |||
Console.WriteLine($"[TriggerFired]: Trigger[{trigger.Key.Name}] 被触发了,此时Job 上的 execute() 方法将要被执行"); | |||
#endif | |||
return Task.CompletedTask; | |||
} | |||
public Task TriggerMisfired(ITrigger trigger, CancellationToken cancellationToken = default) | |||
{ | |||
#if DEBUG | |||
Console.WriteLine($"[TriggerMisfired]: 当前Trigger[{trigger.Key.Name}]触发错过了"); | |||
#endif | |||
return Task.CompletedTask; | |||
} | |||
/// <summary> | |||
/// 决策是否可以执行 | |||
/// </summary> | |||
/// <param name="trigger"></param> | |||
/// <param name="context"></param> | |||
/// <param name="cancellationToken"></param> | |||
/// <returns></returns> | |||
public Task<bool> VetoJobExecution(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default) | |||
{ | |||
var key = TaskScheduleRedisKey.LockTrigger(trigger.Key.Name); | |||
//锁成功就了就可以触发执行了 | |||
var timeout = 2;//防止服务器有时差 | |||
if(trigger is ISimpleTrigger simpleTrigger) | |||
{ | |||
timeout = (int)simpleTrigger.RepeatInterval.TotalSeconds; | |||
if (timeout > 30) | |||
timeout = 30; | |||
} | |||
var result = !redisClient.Set(key, null, timeout, RedisExistence.Nx); | |||
Console.WriteLine($"{DateTime.Now} - [VetoJobExecution]:{key}-{result}"); | |||
return Task.FromResult(result); | |||
} | |||
} | |||
} |
@@ -0,0 +1,309 @@ | |||
using Microsoft.Extensions.Logging; | |||
using MongoDB.Driver; | |||
using MongoDB.Driver.Linq; | |||
using RWX.Component.DTOCommon; | |||
using RWX.Component.MongodbClient; | |||
using RWX.CRM.Common.EnumTaskSchedule.Enums; | |||
using RWX.CRM.TaskSchedule.DTO; | |||
using RWX.CRM.TaskSchedule.DTO.TaskLog; | |||
using RWX.CRM.TaskSchedule.Entity; | |||
using RWX.CRM.TaskSchedule.IRepository; | |||
using RWX.CRM.TaskSchedule.IService; | |||
using RWX.CRM.TaskSchedule.Model; | |||
using RWX.CRM.TaskSchedule.Service.QzScheduler; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading; | |||
namespace RWX.CRM.TaskSchedule.Service | |||
{ | |||
/// <summary> | |||
/// 任务日志 | |||
/// </summary> | |||
public class TaskLogService : ITaskLogService | |||
{ | |||
private readonly ILogger<TaskLogService> logger; | |||
private readonly TaskScheduleLangPackge langPackge; | |||
private readonly Lazy<ITaskLogRepository> taskLogRepository; | |||
private readonly Lazy<ITaskInfoService> taskInfoService; | |||
private readonly Lazy<ISystemDictionaryService> systemDictionaryService; | |||
private readonly TaskScheduleApiConfig TaskScheduleApiConfig; | |||
private readonly AnyTimeSchedulerManager anyTimeSchedulerManager; | |||
private static Thread runThread; | |||
/// <summary> | |||
/// 构造方法 | |||
/// </summary> | |||
/// <param name="langPackge"></param> | |||
/// <param name="taskInfoRepository"></param> | |||
public TaskLogService( | |||
ILogger<TaskLogService> logger, | |||
TaskScheduleLangPackge langPackge, | |||
Lazy<ITaskLogRepository> taskLogRepository, | |||
Lazy<ITaskInfoService> taskInfoService, | |||
Lazy<ISystemDictionaryService> systemDictionaryService, | |||
TaskScheduleApiConfig TaskScheduleApiConfig, | |||
AnyTimeSchedulerManager anyTimeSchedulerManager) | |||
{ | |||
this.logger = logger; | |||
this.langPackge = langPackge; | |||
this.taskLogRepository = taskLogRepository; | |||
this.taskInfoService = taskInfoService; | |||
this.systemDictionaryService = systemDictionaryService; | |||
this.TaskScheduleApiConfig = TaskScheduleApiConfig; | |||
this.anyTimeSchedulerManager = anyTimeSchedulerManager; | |||
} | |||
/// <summary> | |||
/// 添加任意时间任务日志 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
public BaseResult AddAnyTimeLog(AddAnyTimeLogReq req) | |||
{ | |||
if (req == null) | |||
return BaseResult.Build(langPackge.ParamInvalid); | |||
if (req.PlanStartTime < DateTime.Now) | |||
req.PlanStartTime = DateTime.Now; | |||
var taskInfo = taskInfoService.Value.GetCache(req.TaskName); | |||
if (taskInfo.IsNull()) | |||
return BaseResult.Build(langPackge.TaskNotExist); | |||
if (taskInfo.TaskType != EnumTaskType.AnyTime) | |||
return BaseResult.BuildFail($"[{taskInfo.Name}]{taskInfo.Title} 不是任意执行时间类型任务!"); | |||
var taskLog = new CrmTaskLog | |||
{ | |||
BizSysApiName = taskInfo.BizSysApiName, | |||
CreateTime = DateTime.Now, | |||
EndTime = null, | |||
ExecutedResult = EnumExecutedResultType.Wait, | |||
ExecutionTime = 0, | |||
Id = RwxUniqueIdBulder.NextLong(), | |||
Name = taskInfo.Name, | |||
PlanStartTime = req.PlanStartTime, | |||
Remark = string.Empty, | |||
RequestParam = req.RequestParam, | |||
RequestURL = taskInfo.RequestURL, | |||
RequestURLType = taskInfo.RequestURLType, | |||
ResponseParam = string.Empty, | |||
StartTime = DateTime.Now, | |||
TaskId = taskInfo.Id, | |||
TaskType = taskInfo.TaskType, | |||
Title = taskInfo.Title, | |||
UpdateTime = DateTime.Now | |||
}; | |||
if (taskInfo.RequestURLType == EnumRequestURLType.BizSysApi) | |||
{ | |||
var dic = systemDictionaryService.Value.GetCache(EnumSystemDictionaryType.BizSysApi, taskInfo.BizSysApiName); | |||
taskLog.RequestURL = $"{dic.Value}{taskInfo.RequestURL}"; | |||
} | |||
taskLogRepository.Value.InsertOne(taskLog); | |||
var maxDate = DateTime.Now.AddMinutes(TaskScheduleApiConfig.AntTimeTaskCollectTime); | |||
if (req.PlanStartTime < maxDate) | |||
anyTimeSchedulerManager.SetTaskScheduler(taskInfo, taskLog.Id, req.PlanStartTime); | |||
return BaseResult.BuildOK(); | |||
} | |||
/// <summary> | |||
/// 添加任意时间任务日志 多个 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
public BaseResult AddAnyTimeLogs(AddAnyTimeLogsReq req) | |||
{ | |||
if (req == null || req.Logs == null || req.Logs.Count == 0) | |||
return BaseResult.Build(langPackge.ParamInvalid); | |||
var taskInfo = taskInfoService.Value.GetCache(req.TaskName); | |||
if (taskInfo.IsNull()) | |||
return BaseResult.Build(langPackge.TaskNotExist); | |||
if (taskInfo.TaskType != EnumTaskType.AnyTime) | |||
throw new BusinessException(langPackge.Fail, $"[{taskInfo.Name}]{taskInfo.Title} 不是任意执行时间类型任务!"); | |||
var taskLogs = new List<CrmTaskLog>(); | |||
req.Logs.Each(log => | |||
{ | |||
if (log.PlanStartTime < DateTime.Now) | |||
log.PlanStartTime = DateTime.Now; | |||
var taskLog = new CrmTaskLog | |||
{ | |||
BizSysApiName = taskInfo.BizSysApiName, | |||
CreateTime = DateTime.Now, | |||
EndTime = null, | |||
ExecutedResult = EnumExecutedResultType.Wait, | |||
ExecutionTime = 0, | |||
Id = RwxUniqueIdBulder.NextLong(), | |||
Name = taskInfo.Name, | |||
PlanStartTime = log.PlanStartTime, | |||
Remark = string.Empty, | |||
RequestParam = log.RequestParam, | |||
RequestURL = taskInfo.RequestURL, | |||
RequestURLType = taskInfo.RequestURLType, | |||
ResponseParam = string.Empty, | |||
StartTime = DateTime.Now, | |||
TaskId = taskInfo.Id, | |||
TaskType = taskInfo.TaskType, | |||
Title = taskInfo.Title, | |||
UpdateTime = DateTime.Now | |||
}; | |||
if (taskInfo.RequestURLType == EnumRequestURLType.BizSysApi) | |||
{ | |||
var dic = systemDictionaryService.Value.GetCache(EnumSystemDictionaryType.BizSysApi, taskInfo.BizSysApiName); | |||
taskLog.RequestURL = $"{dic.Value}{taskInfo.RequestURL}"; | |||
} | |||
taskLogs.Add(taskLog); | |||
}); | |||
if (taskLogs.Count > 0) | |||
{ | |||
taskLogRepository.Value.InsertMany(taskLogs); | |||
var maxDate = DateTime.Now.AddMinutes(TaskScheduleApiConfig.AntTimeTaskCollectTime); | |||
taskLogs.Each(log => | |||
{ | |||
if (log.PlanStartTime < maxDate) | |||
anyTimeSchedulerManager.SetTaskScheduler(taskInfo, log.Id, log.PlanStartTime); | |||
}); | |||
} | |||
return BaseResult.BuildOK(); | |||
} | |||
/// <summary> | |||
/// 启动 | |||
/// </summary> | |||
public void Start() | |||
{ | |||
if (runThread != null) | |||
return; | |||
runThread = new Thread(() => | |||
{ | |||
while (true) | |||
{ | |||
try | |||
{ | |||
ScanWaitAnyTimeLog(); | |||
} | |||
catch (Exception ex) | |||
{ | |||
logger.LogError(ex, "加载anytime task log error"); | |||
} | |||
finally | |||
{ | |||
var sleepTime = (TaskScheduleApiConfig.AntTimeTaskCollectTime / 2) * 60 * 1000; | |||
Thread.Sleep(sleepTime); | |||
} | |||
} | |||
}); | |||
runThread.Start(); | |||
} | |||
/// <summary> | |||
/// 扫描待执行的anytime log | |||
/// </summary> | |||
public void ScanWaitAnyTimeLog(long? taskInfoId = null) | |||
{ | |||
var maxDate = DateTime.Now.AddMinutes(TaskScheduleApiConfig.AntTimeTaskCollectTime); | |||
var taskLogs = taskLogRepository.Value.Queryable() | |||
.WhereIF(taskInfoId.HasValue, x => x.TaskId == taskInfoId.Value) | |||
.Where(x => x.TaskType == EnumTaskType.AnyTime | |||
&& x.ExecutedResult == EnumExecutedResultType.Wait | |||
&& x.PlanStartTime <= maxDate) | |||
.Select(x => new { x.Id, x.TaskId, x.PlanStartTime }).ToList(); | |||
if (taskLogs.Count == 0) | |||
return; | |||
var timeout = DateTime.Now.AddMinutes(-TaskScheduleApiConfig.AnyTimeTaskLogPlanTimeout); | |||
foreach (var taskLog in taskLogs) | |||
{ | |||
if (taskLog.PlanStartTime < timeout) | |||
{ | |||
//任务超时 | |||
taskLogRepository.Value.UpdateOne(x => new CrmTaskLog | |||
{ | |||
ExecutedResult = EnumExecutedResultType.Timeout, | |||
UpdateTime = DateTime.Now, | |||
Remark = $"任务超过{TaskScheduleApiConfig.AnyTimeTaskLogPlanTimeout}分钟未执行,超时!" | |||
}, x => x.Id == taskLog.Id); | |||
continue; | |||
} | |||
//任务是否存在 | |||
if (anyTimeSchedulerManager.CheckExists(taskLog.Id, taskLog.TaskId)) | |||
continue; | |||
var taskInfo = taskInfoService.Value.GetCache(taskLog.TaskId); | |||
if (taskInfo == null) | |||
anyTimeSchedulerManager.RemoveTaskScheduler(taskLog.Id, taskLog.TaskId); | |||
else | |||
{ | |||
//添加到任务列表 | |||
anyTimeSchedulerManager.SetTaskScheduler(taskInfo, taskLog.Id, taskLog.PlanStartTime); | |||
} | |||
} | |||
} | |||
/// <summary> | |||
/// 清理日志 | |||
/// </summary> | |||
public void Clear() | |||
{ | |||
var tasks = taskInfoService.Value.GetAll().Data; | |||
foreach (var task in tasks) | |||
{ | |||
if (task.KeepLogDays > 0) | |||
{ | |||
var date = DateTime.Now.AddDays(-task.KeepLogDays).Date; | |||
taskLogRepository.Value.DeleteMany(x => x.TaskId == task.Id && x.PlanStartTime <= date && x.ExecutedResult != EnumExecutedResultType.Wait); | |||
} | |||
if (task.KeepLogCount > 0) | |||
{ | |||
var count = taskLogRepository.Value.Queryable().Count(x => x.TaskId == task.Id); | |||
if (count > task.KeepLogCount) | |||
{ | |||
var query = taskLogRepository.Value.Queryable().Where(x => x.TaskId == task.Id).OrderByDescending(x => x.Id).Skip(task.KeepLogCount - 1).Take(1).Select(x => new { x.Id }); | |||
var entity = query.Single(); | |||
taskLogRepository.Value.DeleteMany(x => x.TaskId == task.Id && x.Id < entity.Id && x.ExecutedResult != EnumExecutedResultType.Wait); | |||
} | |||
} | |||
} | |||
} | |||
/// <summary> | |||
/// 分页查询日志 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
public BaseResultPage<TaskLogDTO> GetPage(GetTaskLogPageReq req) | |||
{ | |||
req.BizSysApiName = req.BizSysApiName.ToTrim(); | |||
var query = taskLogRepository.Value.Queryable() | |||
.WhereIF(req.BizSysApiName.IsNotEmpty(), x => x.BizSysApiName == req.BizSysApiName) | |||
.WhereIF(req.ExecutedResult.HasValue, x => x.ExecutedResult == req.ExecutedResult.Value) | |||
.WhereIF(req.TaskId.HasValue, x => x.TaskId == req.TaskId.Value) | |||
.WhereIF(req.TaskType.HasValue, x => x.TaskType == req.TaskType.Value) | |||
.WhereIF(req.PlanStartTime.HasValue, x => x.PlanStartTime == req.PlanStartTime.Value) | |||
.WhereIF(req.ExecutedResult.HasValue, x => x.ExecutedResult == req.ExecutedResult.Value) | |||
.OrderByDescending(x => x.Id); | |||
var rows = query.Pagination(x => new TaskLogDTO | |||
{ | |||
BizSysApiName = x.BizSysApiName, | |||
CreateTime = x.CreateTime, | |||
EndTime = x.EndTime, | |||
ExecutedResult = x.ExecutedResult, | |||
ExecutionTime = x.ExecutionTime, | |||
Id = x.Id, | |||
Name = x.Name, | |||
PlanStartTime = x.PlanStartTime, | |||
Remark = x.Remark, | |||
RequestParam = x.RequestParam, | |||
RequestURL = x.RequestURL, | |||
RequestURLType = x.RequestURLType, | |||
ResponseParam = x.ResponseParam, | |||
StartTime = x.StartTime, | |||
TaskId = x.TaskId, | |||
TaskType = x.TaskType, | |||
Title = x.Title, | |||
UpdateTime = x.UpdateTime, | |||
TrackId=x.TrackId | |||
}, req.PageIndex, req.PageSize, out int total); | |||
return BaseResultPage<TaskLogDTO>.BuildOK(rows, total); | |||
} | |||
} | |||
} |
@@ -0,0 +1,185 @@ | |||
using BPA.Component.DTOCommon.BaseDTOs; | |||
using BPA.Component.Extensions; | |||
using BPA.SaaS.TaskSchedule.Api.DTO; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.SystemDictionary; | |||
using BPA.SaaS.TaskSchedule.Api.Entity; | |||
using BPA.SaaS.TaskSchedule.Api.IRepository; | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using BPA.SaaS.TaskSchedule.Api.Model; | |||
using BPA.SaaS.TaskSchedule.Api.Service.Queue; | |||
using Microsoft.Extensions.Caching.Memory; | |||
using Microsoft.Extensions.Options; | |||
namespace BPA.SaaS.TaskSchedule.Api.Service | |||
{ | |||
public class SystemDictionaryService : ISystemDictionaryService | |||
{ | |||
private readonly Lazy<ISystemDictionaryRepository> systemDictionaryRepository; | |||
private readonly TaskScheduleLangPackge langPackge; | |||
private readonly static MemoryCache memoryCache = new MemoryCache(Options.Create(new MemoryCacheOptions())); | |||
private readonly SynDataTaskScheduleProducer synDataTaskScheduleProducer; | |||
public SystemDictionaryService( | |||
TaskScheduleLangPackge langPackge, | |||
Lazy<ISystemDictionaryRepository> systemDictionaryRepository, | |||
SynDataTaskScheduleProducer synDataTaskScheduleProducer) | |||
{ | |||
this.langPackge = langPackge; | |||
this.systemDictionaryRepository = systemDictionaryRepository; | |||
this.synDataTaskScheduleProducer = synDataTaskScheduleProducer; | |||
} | |||
/// <summary> | |||
/// 保存 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
public BaseResult Save(SaveSystemDictionaryReq req) | |||
{ | |||
if (req.IsNull()) | |||
return BaseResult.Build(langPackge.ParamInvalid); | |||
req.Key = req.Key.ToTrim(); | |||
var cmd = 1; | |||
if (req.Id == 0) | |||
{ | |||
var has = systemDictionaryRepository.Value.Queryable() | |||
.Any(x => x.Type == req.Type && x.Key == req.Key && x.IsDelete == false); | |||
if (has) | |||
return BaseResult.Build(langPackge.KeyExist); | |||
var entity = new CrmSystemDictionary | |||
{ | |||
Id = BPAUniqueIdBulder.NextLong(), | |||
Key = req.Key, | |||
Type = req.Type, | |||
Value = req.Value.ToTrim(), | |||
ValueExt = req.ValueExt.ToTrim(), | |||
IsDelete = false | |||
}; | |||
req.Id = entity.Id; | |||
systemDictionaryRepository.Value.Add(entity); | |||
cmd = 1; | |||
} | |||
else | |||
{ | |||
var entity = systemDictionaryRepository.Value.Queryable().FirstOrDefault(x => x.Id == req.Id && x.IsDelete == false); | |||
if (entity == null) | |||
return BaseResult.Build(langPackge.KeyNotExist); | |||
var has = systemDictionaryRepository.Value.Queryable() | |||
.Any(x => x.Id != req.Id && x.Type == req.Type && x.Key == req.Key && x.IsDelete == false); | |||
if (has) | |||
return BaseResult.Build(langPackge.KeyExist); | |||
req.Value = req.Value.ToTrim(); | |||
req.ValueExt = req.ValueExt.ToTrim(); | |||
systemDictionaryRepository.Value.UpdateOne(x => new CrmSystemDictionary | |||
{ | |||
Type = req.Type, | |||
Value = req.Value, | |||
ValueExt = req.ValueExt | |||
}, x => x.Id == req.Id); | |||
cmd = 2; | |||
} | |||
//触发数据改变 | |||
synDataTaskScheduleProducer.Enqueue(new SynDataTaskScheduleMsgBody {Cmd = cmd, DataId = req.Id, DataType = 1}); | |||
Thread.Sleep(1000); | |||
return BaseResult.BuildOK(); | |||
} | |||
/// <summary> | |||
/// 删除 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
public BaseResult Delete(DeleteSystemDictionaryReq req) | |||
{ | |||
var entity = systemDictionaryRepository.Value.Queryable().First(s => s.Id == req.Id); | |||
if (entity == null || entity.IsDelete) | |||
return BaseResult.Build(langPackge.KeyNotExist); | |||
//更改为删除 | |||
systemDictionaryRepository.Value.UpdateOne(x => new CrmSystemDictionary {IsDelete = true}, x => x.Id == req.Id); | |||
//触发数据改变 | |||
synDataTaskScheduleProducer.Enqueue(new SynDataTaskScheduleMsgBody {Cmd = 3, DataId = req.Id, DataType = 1}); | |||
Thread.Sleep(1000); | |||
return BaseResult.BuildOK(); | |||
} | |||
/// <summary> | |||
/// 获取所有的 | |||
/// </summary> | |||
/// <returns></returns> | |||
public BaseResult<List<SystemDictionaryDTO>> GetAll() | |||
{ | |||
var rows = systemDictionaryRepository.Value.Queryable().Where(x => x.IsDelete == false).Select(x => new SystemDictionaryDTO | |||
{ | |||
Id = x.Id, | |||
Type = x.Type, | |||
Key = x.Key, | |||
Value = x.Value, | |||
ValueExt = x.ValueExt | |||
}).ToList(); | |||
return BaseResult<List<SystemDictionaryDTO>>.BuildOK(rows); | |||
} | |||
/// <summary> | |||
/// 获取缓存的字典信息 | |||
/// </summary> | |||
/// <param name="type"></param> | |||
/// <param name="key"></param> | |||
/// <returns></returns> | |||
public SystemDictionaryDTO GetCache(EnumSystemDictionaryType type, string key) | |||
{ | |||
var rkey = TaskScheduleRedisKey.DictionaryKey(type, key); | |||
return memoryCache.Get<SystemDictionaryDTO>(rkey); | |||
} | |||
/// <summary> | |||
/// 当数据改变 | |||
/// </summary> | |||
/// <param name="msgBody"></param> | |||
public void OnDataChange(SynDataTaskScheduleMsgBody msgBody) | |||
{ | |||
var entity = systemDictionaryRepository.Value.Queryable().FirstOrDefault(f => f.Id == msgBody.DataId); | |||
if (entity == null) return; | |||
var rkey = TaskScheduleRedisKey.DictionaryKey(entity.Type, entity.Key); | |||
if (entity.IsDelete) | |||
memoryCache.Remove(rkey); | |||
else | |||
memoryCache.Set(rkey, new SystemDictionaryDTO | |||
{ | |||
Id = entity.Id, | |||
Key = entity.Key, | |||
Type = entity.Type, | |||
Value = entity.Value, | |||
ValueExt = entity.ValueExt | |||
}); | |||
} | |||
/// <summary> | |||
/// 启动执行一次 | |||
/// </summary> | |||
public void Start() | |||
{ | |||
// systemDictionaryRepository.Value.UpdateMany(x => new CrmSystemDictionary { IsDelete = false }, x => x.Id > 0); | |||
var rows = systemDictionaryRepository.Value.Queryable().Where(x => x.IsDelete == false).Select(x => new SystemDictionaryDTO | |||
{ | |||
Id = x.Id, | |||
Type = x.Type, | |||
Key = x.Key, | |||
Value = x.Value, | |||
ValueExt = x.ValueExt | |||
}).ToList(); | |||
rows.Each(item => | |||
{ | |||
var rkey = TaskScheduleRedisKey.DictionaryKey(item.Type, item.Key); | |||
memoryCache.Set(rkey, new SystemDictionaryDTO | |||
{ | |||
Id = item.Id, | |||
Key = item.Key, | |||
Type = item.Type, | |||
Value = item.Value, | |||
ValueExt = item.ValueExt | |||
}); | |||
}); | |||
} | |||
} | |||
} |
@@ -0,0 +1,502 @@ | |||
using System.Linq.Expressions; | |||
using System.Text; | |||
using BPA.Component.DTOCommon.BaseDTOs; | |||
using BPA.Component.Extensions; | |||
using BPA.Component.MongoClient; | |||
using BPA.SaaS.TaskSchedule.Api.DTO; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.SystemDictionary; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.TaskLog; | |||
using BPA.SaaS.TaskSchedule.Api.Entity; | |||
using BPA.SaaS.TaskSchedule.Api.IRepository; | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using BPA.SaaS.TaskSchedule.Api.Model; | |||
using BPA.SaaS.TaskSchedule.Api.Service.Queue; | |||
using BPA.SaaS.TaskSchedule.Api.Service.QzScheduler; | |||
using Microsoft.Extensions.Caching.Memory; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Extensions.Options; | |||
using MongoDB.Driver; | |||
namespace BPA.SaaS.TaskSchedule.Api.Service | |||
{ | |||
/// <summary> | |||
/// 任务信息 | |||
/// </summary> | |||
public class TaskInfoService : ITaskInfoService | |||
{ | |||
private static readonly MemoryCache memoryCache = new(Options.Create(new MemoryCacheOptions())); | |||
private readonly Lazy<ITaskInfoRepository> taskInfoRepository; | |||
private readonly TaskScheduleLangPackge langPackge; | |||
private readonly Lazy<SynRequestSchedulerManager> schedulerManager; | |||
private readonly Lazy<ITaskLogRepository> taskLogRepository; | |||
private readonly Lazy<ITaskLogService> taskLogService; | |||
private readonly IServiceProvider serviceProvider; | |||
private readonly SynDataTaskScheduleProducer synDataTaskScheduleProducer; | |||
private readonly Lazy<ISystemDictionaryService> systemDictionaryService; | |||
/// <summary> | |||
/// 构造方法 | |||
/// </summary> | |||
public TaskInfoService( | |||
TaskScheduleLangPackge langPackge, | |||
Lazy<ITaskInfoRepository> taskInfoRepository, | |||
Lazy<SynRequestSchedulerManager> schedulerManager, | |||
Lazy<ITaskLogRepository> taskLogRepository, | |||
Lazy<ITaskLogService> taskLogService, | |||
IServiceProvider serviceProvider, | |||
SynDataTaskScheduleProducer synDataTaskScheduleProducer, | |||
Lazy<ISystemDictionaryService> systemDictionaryService) | |||
{ | |||
this.langPackge = langPackge; | |||
this.taskInfoRepository = taskInfoRepository; | |||
this.schedulerManager = schedulerManager; | |||
this.taskLogRepository = taskLogRepository; | |||
this.taskLogService = taskLogService; | |||
this.serviceProvider = serviceProvider; | |||
this.synDataTaskScheduleProducer = synDataTaskScheduleProducer; | |||
this.systemDictionaryService = systemDictionaryService; | |||
} | |||
/// <summary> | |||
/// 保存 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
public BaseResult<long> Save(SaveTaskInfoReq req) | |||
{ | |||
if (req.IsNull()) | |||
return BaseResult<long>.Build(langPackge.ParamInvalid); | |||
var isNew = req.Id <= 0; | |||
req.Name = req.Name.ToTrim(); | |||
req.BizSysApiName = req.BizSysApiName.ToTrim(); | |||
req.RequestURL = req.RequestURL.ToTrim(); | |||
if (req.RequestURL.IsNull()) | |||
return BaseResult<long>.Build(langPackge.ParamInvalid, "请求地址不能为空!"); | |||
if (req.RequestURLType == EnumRequestURLType.BizSysApi && req.BizSysApiName.IsNull()) | |||
return BaseResult<long>.Build(langPackge.ParamInvalid, "业务系统Api名称不能为空!"); | |||
if (req.RequestURLType == EnumRequestURLType.BizSysApi && !req.RequestURL.StartsWith("/")) | |||
return BaseResult<long>.Build(langPackge.ParamInvalid, "请求地址不能以 / 开头!"); | |||
if (req.RequestURLType == EnumRequestURLType.Custom && !req.RequestURL.ToLower().StartsWith("http")) | |||
return BaseResult<long>.Build(langPackge.ParamInvalid, "请求地址必须以http开头!"); | |||
if (req.TriggerType == EnumTriggerType.CronTrigger) | |||
{ | |||
(var checkResult, var msg) = schedulerManager.Value.CheckCronExpression(req.CronExpression); | |||
if (!checkResult) | |||
return BaseResult<long>.Build(langPackge.ParamInvalid, $"Cron表达式校验错误:{msg}!"); | |||
} | |||
//校验任务名称是否存在 | |||
var hasName = taskInfoRepository.Value.Queryable() | |||
.WhereIF(!isNew, x => x.Id != req.Id) | |||
.Any(x => x.Name == req.Name && x.IsDelete == false); | |||
if (hasName) | |||
return BaseResult<long>.Build(langPackge.TaskExist); | |||
if (req.TaskType == EnumTaskType.AnyTime) | |||
req.TriggerType = EnumTriggerType.Nothing; | |||
var statusIsChange = false; | |||
CrmTaskInfo entity = null; | |||
var cmd = 1; | |||
if (isNew) | |||
{ | |||
entity = new CrmTaskInfo | |||
{ | |||
BizSysApiName = req.BizSysApiName, | |||
CreateTime = DateTime.Now, | |||
CronExpression = req.CronExpression.ToTrim(), | |||
Description = req.Description.ToTrim(), | |||
Id = BPAUniqueIdBulder.NextLong(), | |||
IsDelete = false, | |||
Name = req.Name, | |||
RepeatCount = req.RepeatCount, | |||
RepeatInterval = req.RepeatInterval, | |||
RequestMethod = req.RequestMethod, | |||
RequestParam = req.RequestParam.ToTrim(), | |||
RequestURL = req.RequestURL, | |||
RequestURLType = req.RequestURLType, | |||
StartTime = req.StartTime, | |||
Status = req.Status, | |||
TaskType = req.TaskType, | |||
Title = req.Title.ToTrim(), | |||
TriggerType = req.TriggerType, | |||
UpdateTime = DateTime.Now, | |||
Timeout = req.Timeout, | |||
KeepLogCount = req.KeepLogCount, | |||
KeepLogDays = req.KeepLogDays, | |||
SuccessMark = req.SuccessMark.ToTrim() | |||
}; | |||
taskInfoRepository.Value.Add(entity); | |||
cmd = 1; | |||
} | |||
else | |||
{ | |||
entity = taskInfoRepository.Value.Queryable().FirstOrDefault(x => x.Id == req.Id); | |||
if (entity.IsNull()) | |||
return BaseResult<long>.Build(langPackge.TaskNotExist); | |||
//检查状态是否改变 | |||
statusIsChange = entity.Status != req.Status; | |||
entity.BizSysApiName = req.BizSysApiName; | |||
entity.CreateTime = DateTime.Now; | |||
entity.CronExpression = req.CronExpression.ToTrim(); | |||
entity.Description = req.Description.ToTrim(); | |||
entity.Name = req.Name; | |||
entity.RepeatCount = req.RepeatCount; | |||
entity.RepeatInterval = req.RepeatInterval; | |||
entity.RequestMethod = req.RequestMethod; | |||
entity.RequestParam = req.RequestParam.ToTrim(); | |||
entity.RequestURL = req.RequestURL; | |||
entity.RequestURLType = req.RequestURLType; | |||
entity.StartTime = req.StartTime; | |||
entity.Status = req.Status; | |||
entity.TaskType = req.TaskType; | |||
entity.Title = req.Title.ToTrim(); | |||
entity.TriggerType = req.TriggerType; | |||
entity.UpdateTime = DateTime.Now; | |||
entity.Timeout = req.Timeout; | |||
entity.KeepLogCount = req.KeepLogCount; | |||
entity.KeepLogDays = req.KeepLogDays; | |||
entity.SuccessMark = req.SuccessMark.ToTrim(); | |||
taskInfoRepository.Value.UpdateOne(entity, x => x.Id == entity.Id); | |||
cmd = 2; | |||
} | |||
var dto = ConverEntity2DTO(entity); | |||
if (req.TaskType == EnumTaskType.AnyTime && statusIsChange) | |||
{ | |||
UpdateResult updateResult = null; | |||
if (entity.Status == EnumTaskStatusType.Beginning) | |||
{ | |||
//改变之前暂停的为等待状态 | |||
taskLogRepository.Value.UpdateMany(x => new CrmTaskLog | |||
{ | |||
ExecutedResult = EnumExecutedResultType.Wait, | |||
UpdateTime = DateTime.Now, | |||
Remark = "暂停变为等待" | |||
}, | |||
x => x.TaskId == entity.Id && x.ExecutedResult == EnumExecutedResultType.Suspend); | |||
} | |||
else if (entity.Status == EnumTaskStatusType.Suspend) | |||
{ | |||
//改变之前等待的为暂停状态 | |||
updateResult = taskLogRepository.Value.UpdateMany(x => new CrmTaskLog | |||
{ | |||
ExecutedResult = EnumExecutedResultType.Suspend, | |||
UpdateTime = DateTime.Now, | |||
Remark = "等待变为暂停" | |||
}, | |||
x => x.TaskId == entity.Id && x.ExecutedResult == EnumExecutedResultType.Wait); | |||
} | |||
} | |||
//触发数据改变 | |||
synDataTaskScheduleProducer.Enqueue(new SynDataTaskScheduleMsgBody {Cmd = cmd, DataId = entity.Id, DataType = 2}); | |||
return BaseResult<long>.BuildOK(entity.Id); | |||
} | |||
/// <summary> | |||
/// 删除 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
public BaseResult Delete(DeleteTaskInfoReq req) | |||
{ | |||
var dto = GetCache(req.Id); | |||
if (dto.IsNull()) | |||
return BaseResult.Build(langPackge.TaskNotExist); | |||
//标记任务删除 | |||
taskInfoRepository.Value.UpdateOne(x => new CrmTaskInfo {IsDelete = true}, x => x.Id == req.Id); | |||
if (dto.TaskType == EnumTaskType.AnyTime) | |||
{ | |||
//终止 anytime 的 tasklog | |||
taskLogRepository.Value.UpdateMany(x => new CrmTaskLog | |||
{ | |||
ExecutedResult = EnumExecutedResultType.Abort, | |||
UpdateTime = DateTime.Now, | |||
Remark = "主任务删除,子任务终止任务" | |||
}, | |||
x => x.TaskId == dto.Id && (x.ExecutedResult == EnumExecutedResultType.Suspend || | |||
x.ExecutedResult == EnumExecutedResultType.Wait)); | |||
} | |||
//触发数据改变 | |||
synDataTaskScheduleProducer.Enqueue(new SynDataTaskScheduleMsgBody {Cmd = 3, DataId = req.Id, DataType = 2}); | |||
return BaseResult.BuildOK(); | |||
} | |||
/// <summary> | |||
/// 获取所有的任务信息 | |||
/// </summary> | |||
/// <returns></returns> | |||
public BaseResult<List<TaskInfoDTO>> GetAll() | |||
{ | |||
var rows = taskInfoRepository.Value.Queryable().Where(x => x.IsDelete == false).Select(entityToDTOselector).ToList(); | |||
rows.Each(row => | |||
{ | |||
if (row.TaskType == EnumTaskType.SynRequest) | |||
{ | |||
if (row.Status == EnumTaskStatusType.Beginning) | |||
{ | |||
var (nextFireTime, previousFireTime) = schedulerManager.Value.GetFireTime(row.Id); | |||
row.NextFireTime = nextFireTime; | |||
row.LastFireTime = previousFireTime; | |||
} | |||
} | |||
}); | |||
//redisClient.Keys(TaskScheduleRedisKey.TaskInfoKey("*")) | |||
// .Each(key => | |||
// { | |||
// var row = redisClient.Get<TaskInfoDTO>(key); | |||
// if (row.Status == EnumTaskStatusType.Beginning) | |||
// row.NextFireTime = schedulerManager.Value.GetNextFireTime(row.Id); | |||
// rows.Add(row); | |||
// }); | |||
return BaseResult<List<TaskInfoDTO>>.BuildOK(rows); | |||
} | |||
/// <summary> | |||
/// 实体转dto | |||
/// </summary> | |||
/// <param name="entity"></param> | |||
private TaskInfoDTO ConverEntity2DTO(CrmTaskInfo entity) | |||
{ | |||
var func = entityToDTOselector.Compile(); | |||
var taskInfo = func(entity); | |||
return taskInfo; | |||
} | |||
/// <summary> | |||
/// 缓存 | |||
/// </summary> | |||
/// <param name="taskInfo"></param> | |||
private void Cache(TaskInfoDTO taskInfo) | |||
{ | |||
var key = TaskScheduleRedisKey.TaskInfoKey(taskInfo.Id); | |||
var cacheTaskInfo = GetCache(taskInfo.Id); | |||
if (cacheTaskInfo != null) | |||
{ | |||
var oldName = cacheTaskInfo.Name; | |||
foreach (var p in taskInfo.GetType().GetProperties()) | |||
{ | |||
p.SetValue(cacheTaskInfo, p.GetValue(taskInfo)); | |||
} | |||
if (oldName != taskInfo.Name) | |||
memoryCache.Remove(oldName); | |||
taskInfo = cacheTaskInfo; | |||
} | |||
memoryCache.Set(key, taskInfo); | |||
memoryCache.Set(taskInfo.Name, taskInfo); | |||
//redisClient.Set(key, taskInfo); | |||
} | |||
/// <summary> | |||
/// 重新缓存所有的数据 | |||
/// </summary> | |||
/// <returns></returns> | |||
public List<TaskInfoDTO> ReloadCacheAll() | |||
{ | |||
var rows = taskInfoRepository.Value.Queryable().Select(entityToDTOselector).ToList(); | |||
rows.Each(row => | |||
{ | |||
if (row.IsDelete) | |||
RemoveCache(row.Id); | |||
else | |||
Cache(row); | |||
}); | |||
return rows; | |||
} | |||
/// <summary> | |||
/// 获取缓存的任务信息 | |||
/// </summary> | |||
/// <param name="taskInfoId"></param> | |||
/// <returns></returns> | |||
public TaskInfoDTO GetCache(long taskInfoId) | |||
{ | |||
var key = TaskScheduleRedisKey.TaskInfoKey(taskInfoId); | |||
return memoryCache.Get<TaskInfoDTO>(key); | |||
//return redisClient.Get<TaskInfoDTO>(key); | |||
} | |||
/// <summary> | |||
/// 获取缓存的任务信息 | |||
/// </summary> | |||
/// <param name="taskInfoName"></param> | |||
/// <returns></returns> | |||
public TaskInfoDTO GetCache(string taskInfoName) | |||
{ | |||
return memoryCache.Get<TaskInfoDTO>(taskInfoName); | |||
//return redisClient.Get<TaskInfoDTO>(key); | |||
} | |||
/// <summary> | |||
/// 删除缓存 | |||
/// </summary> | |||
/// <param name="taskInfoId"></param> | |||
public void RemoveCache(long taskInfoId) | |||
{ | |||
var key = TaskScheduleRedisKey.TaskInfoKey(taskInfoId); | |||
var cacheTaskInfo = GetCache(taskInfoId); | |||
if (cacheTaskInfo == null) return; | |||
memoryCache.Remove(key); | |||
memoryCache.Remove(cacheTaskInfo.Name); | |||
// redisClient.Del(key); | |||
} | |||
/// <summary> | |||
/// DoOnce | |||
/// </summary> | |||
/// <param name="taskInfoId"></param> | |||
public BaseResult DoOnce(DoOnceReq req) | |||
{ | |||
var taskInfo = GetCache(req.TaskId); | |||
if (taskInfo == null) | |||
return BaseResult.BuildFail("任务不存在!"); | |||
if (taskInfo.TaskType == EnumTaskType.SynRequest) | |||
{ | |||
var scheduler = serviceProvider.GetService<SynRequestHttpJobScheduler>(); | |||
scheduler.ExecuteHttpSchedulerJob(taskInfo).GetAwaiter().GetResult(); | |||
} | |||
else if (taskInfo.TaskType == EnumTaskType.AnyTime) | |||
{ | |||
//if (req.PlanStartTime < DateTime.Now) | |||
// return BaseResult.BuildFail("计划执行时间不能小于当前时间"); | |||
if (req.Times <= 0) | |||
req.Times = 1; | |||
for (var i = 0; i < req.Times; i++) | |||
{ | |||
var result = taskLogService.Value.AddAnyTimeLog(new AddAnyTimeLogReq | |||
{ | |||
PlanStartTime = req.PlanStartTime, | |||
RequestParam = req.RequestParam, | |||
TaskName = taskInfo.Name | |||
}); | |||
if (!result.IsOK) | |||
return result; | |||
} | |||
} | |||
return BaseResult.BuildOK(); | |||
} | |||
/// <summary> | |||
/// 启动 | |||
/// </summary> | |||
/// <param name="allTaskInfo"></param> | |||
public void Start() | |||
{ | |||
var rows = ReloadCacheAll(); | |||
rows.Each(row => { schedulerManager.Value.SetTaskScheduler(row); }); | |||
} | |||
/// <summary> | |||
/// 当数据发生改变 | |||
/// </summary> | |||
/// <param name="msgBody"></param> | |||
public void OnDataChange(SynDataTaskScheduleMsgBody msgBody) | |||
{ | |||
var row = taskInfoRepository.Value.Queryable<CrmTaskInfo>(null, null).Where(x => x.Id == msgBody.DataId) | |||
.Select(entityToDTOselector) | |||
.FirstOrDefault(); | |||
if (row == null) return; | |||
if (row.IsDelete) | |||
RemoveCache(row.Id); | |||
else | |||
Cache(row); | |||
schedulerManager.Value.SetTaskScheduler(row); | |||
} | |||
/// <summary> | |||
/// 尝试注册任务 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
public BaseResult TryRegisterTask(TryRegisterTaskReq req) | |||
{ | |||
if (req.TaskInfoTags == null || req.TaskInfoTags.Count == 0) | |||
return BaseResult.BuildFail($"{nameof(req.TaskInfoTags)}不能为空"); | |||
var systemDictionaryDTO = systemDictionaryService.Value.GetCache(EnumSystemDictionaryType.BizSysApi, req.ApiName); | |||
if (systemDictionaryDTO == null) | |||
return BaseResult.BuildFail($"在系统字典中无法长得{req.ApiName}"); | |||
//查询所有的任务 | |||
var rows = taskInfoRepository.Value.Queryable().Select(entityToDTOselector).ToList(); | |||
var saveDtos = new List<SaveTaskInfoReq>(); | |||
var rspMsg = new StringBuilder(); | |||
req.TaskInfoTags.Each(tag => | |||
{ | |||
if (tag.Name.IsEmpty()) | |||
{ | |||
rspMsg.Append($"Name为空:{tag.ToJson()}"); | |||
return; | |||
} | |||
var old = rows.FirstOrDefault(x => x.Name == tag.Name); | |||
if (old != null && !tag.IsForceCover) | |||
{ | |||
rspMsg.Append($"{tag.Name}:不是强制覆盖更新"); | |||
return; | |||
} | |||
var saveDto = new SaveTaskInfoReq | |||
{ | |||
BizSysApiName = req.ApiName, | |||
Name = tag.Name, | |||
CronExpression = tag.CronExpression, | |||
Description = tag.Description, | |||
Id = old == null ? 0 : old.Id, | |||
KeepLogCount = tag.KeepLogCount, | |||
KeepLogDays = tag.KeepLogDays, | |||
RepeatCount = tag.RepeatCount, | |||
RepeatInterval = tag.RepeatInterval, | |||
RequestMethod = tag.RequestMethod, | |||
RequestParam = old?.RequestParam ?? string.Empty, | |||
RequestURL = tag.RequestURL, | |||
RequestURLType = tag.RequestURLType, | |||
StartTime = old?.StartTime ?? DateTime.Now, | |||
Status = old?.Status ?? EnumTaskStatusType.Suspend, | |||
SuccessMark = tag.SuccessMark, | |||
TaskType = tag.TaskType, | |||
Timeout = tag.Timeout > 10 ? tag.Timeout : 60, | |||
Title = tag.Title, | |||
TriggerType = tag.TriggerType, | |||
}; | |||
var rsp = Save(saveDto); | |||
rspMsg.Append($"{tag.Name}:{(tag.IsForceCover ? "" : "不是")}强制覆盖更新,结果:{rsp.IsOK}:{rsp.Msg}"); | |||
}); | |||
return BaseResult.BuildOK(rspMsg.ToString()); | |||
} | |||
/// <summary> | |||
/// 实体转DTO的表达式 | |||
/// </summary> | |||
private static readonly Expression<Func<CrmTaskInfo, TaskInfoDTO>> entityToDTOselector = x => new TaskInfoDTO | |||
{ | |||
BizSysApiName = x.BizSysApiName, | |||
CreateTime = x.CreateTime, | |||
CronExpression = x.CronExpression, | |||
Description = x.Description, | |||
Id = x.Id, | |||
Name = x.Name, | |||
Title = x.Title, | |||
RepeatCount = x.RepeatCount, | |||
RepeatInterval = x.RepeatInterval, | |||
RequestMethod = x.RequestMethod, | |||
RequestParam = x.RequestParam, | |||
RequestURL = x.RequestURL, | |||
RequestURLType = x.RequestURLType, | |||
StartTime = x.StartTime, | |||
Status = x.Status, | |||
TaskType = x.TaskType, | |||
TriggerType = x.TriggerType, | |||
UpdateTime = x.UpdateTime, | |||
Timeout = x.Timeout, | |||
KeepLogCount = x.KeepLogCount, | |||
KeepLogDays = x.KeepLogDays, | |||
IsDelete = x.IsDelete, | |||
SuccessMark = x.SuccessMark | |||
}; | |||
} | |||
} |
@@ -0,0 +1,228 @@ | |||
using BPA.Component.DTOCommon.BaseDTOs; | |||
using BPA.Component.Extensions; | |||
using BPA.Component.MongoClient; | |||
using BPA.SaaS.TaskSchedule.Api.DTO; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.SystemDictionary; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.TaskLog; | |||
using BPA.SaaS.TaskSchedule.Api.Entity; | |||
using BPA.SaaS.TaskSchedule.Api.IRepository; | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using BPA.SaaS.TaskSchedule.Api.Model; | |||
using BPA.SaaS.TaskSchedule.Api.Service.Queue; | |||
using Microsoft.Extensions.Logging; | |||
namespace BPA.SaaS.TaskSchedule.Api.Service | |||
{ | |||
/// <summary> | |||
/// 任务日志 | |||
/// </summary> | |||
public class TaskLogService : ITaskLogService | |||
{ | |||
private readonly ILogger<TaskLogService> logger; | |||
private readonly TaskScheduleLangPackge langPackge; | |||
private readonly Lazy<ITaskLogRepository> taskLogRepository; | |||
private readonly Lazy<ITaskInfoService> taskInfoService; | |||
private readonly Lazy<ISystemDictionaryService> systemDictionaryService; | |||
private readonly TaskScheduleApiConfig TaskScheduleApiConfig; | |||
private readonly AnyTimeTaskScheduleProducer anyTimeTaskScheduleProducer; | |||
/// <summary> | |||
/// 构造方法 | |||
/// </summary> | |||
public TaskLogService( | |||
ILogger<TaskLogService> logger, | |||
TaskScheduleLangPackge langPackge, | |||
Lazy<ITaskLogRepository> taskLogRepository, | |||
Lazy<ITaskInfoService> taskInfoService, | |||
Lazy<ISystemDictionaryService> systemDictionaryService, | |||
TaskScheduleApiConfig TaskScheduleApiConfig, | |||
AnyTimeTaskScheduleProducer anyTimeTaskScheduleProducer) | |||
{ | |||
this.logger = logger; | |||
this.langPackge = langPackge; | |||
this.taskLogRepository = taskLogRepository; | |||
this.taskInfoService = taskInfoService; | |||
this.systemDictionaryService = systemDictionaryService; | |||
this.TaskScheduleApiConfig = TaskScheduleApiConfig; | |||
this.anyTimeTaskScheduleProducer = anyTimeTaskScheduleProducer; | |||
} | |||
/// <summary> | |||
/// 添加任意时间任务日志 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
public BaseResult AddAnyTimeLog(AddAnyTimeLogReq req) | |||
{ | |||
if (req == null) | |||
return BaseResult.Build(langPackge.ParamInvalid); | |||
if (req.PlanStartTime < DateTime.Now) | |||
req.PlanStartTime = DateTime.Now; | |||
var taskInfo = taskInfoService.Value.GetCache(req.TaskName); | |||
if (taskInfo.IsNull()) | |||
return BaseResult.Build(langPackge.TaskNotExist); | |||
if (taskInfo.TaskType != EnumTaskType.AnyTime) | |||
return BaseResult.BuildFail($"[{taskInfo.Name}]{taskInfo.Title} 不是任意执行时间类型任务!"); | |||
var taskLog = new CrmTaskLog | |||
{ | |||
BizSysApiName = taskInfo.BizSysApiName, | |||
CreateTime = DateTime.Now, | |||
EndTime = null, | |||
ExecutedResult = EnumExecutedResultType.Wait, | |||
ExecutionTime = 0, | |||
Id = BPAUniqueIdBulder.NextLong(), | |||
Name = taskInfo.Name, | |||
PlanStartTime = req.PlanStartTime, | |||
Remark = string.Empty, | |||
RequestParam = req.RequestParam, | |||
RequestURL = taskInfo.RequestURL, | |||
RequestURLType = taskInfo.RequestURLType, | |||
ResponseParam = string.Empty, | |||
StartTime = DateTime.Now, | |||
TaskId = taskInfo.Id, | |||
TaskType = taskInfo.TaskType, | |||
Title = taskInfo.Title, | |||
UpdateTime = DateTime.Now | |||
}; | |||
if (taskInfo.RequestURLType == EnumRequestURLType.BizSysApi) | |||
{ | |||
var dic = systemDictionaryService.Value.GetCache(EnumSystemDictionaryType.BizSysApi, taskInfo.BizSysApiName); | |||
taskLog.RequestURL = $"{dic.Value}{taskInfo.RequestURL}"; | |||
} | |||
taskLogRepository.Value.Add(taskLog); | |||
anyTimeTaskScheduleProducer.Enqueue(taskLog.Id, req.PlanStartTime); | |||
return BaseResult.BuildOK(); | |||
} | |||
/// <summary> | |||
/// 添加任意时间任务日志 多个 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
public BaseResult AddAnyTimeLogs(AddAnyTimeLogsReq req) | |||
{ | |||
if (req == null || req.Logs == null || req.Logs.Count == 0) | |||
return BaseResult.Build(langPackge.ParamInvalid); | |||
var taskInfo = taskInfoService.Value.GetCache(req.TaskName); | |||
if (taskInfo.IsNull()) | |||
return BaseResult.Build(langPackge.TaskNotExist); | |||
if (taskInfo.TaskType != EnumTaskType.AnyTime) | |||
throw new BusinessException(langPackge.Fail, $"[{taskInfo.Name}]{taskInfo.Title} 不是任意执行时间类型任务!"); | |||
var taskLogs = new List<CrmTaskLog>(); | |||
req.Logs.Each(log => | |||
{ | |||
if (log.PlanStartTime < DateTime.Now) | |||
log.PlanStartTime = DateTime.Now; | |||
var taskLog = new CrmTaskLog | |||
{ | |||
BizSysApiName = taskInfo.BizSysApiName, | |||
CreateTime = DateTime.Now, | |||
EndTime = null, | |||
ExecutedResult = EnumExecutedResultType.Wait, | |||
ExecutionTime = 0, | |||
Id = BPAUniqueIdBulder.NextLong(), | |||
Name = taskInfo.Name, | |||
PlanStartTime = log.PlanStartTime, | |||
Remark = string.Empty, | |||
RequestParam = log.RequestParam, | |||
RequestURL = taskInfo.RequestURL, | |||
RequestURLType = taskInfo.RequestURLType, | |||
ResponseParam = string.Empty, | |||
StartTime = DateTime.Now, | |||
TaskId = taskInfo.Id, | |||
TaskType = taskInfo.TaskType, | |||
Title = taskInfo.Title, | |||
UpdateTime = DateTime.Now | |||
}; | |||
if (taskInfo.RequestURLType == EnumRequestURLType.BizSysApi) | |||
{ | |||
var dic = systemDictionaryService.Value.GetCache(EnumSystemDictionaryType.BizSysApi, taskInfo.BizSysApiName); | |||
taskLog.RequestURL = $"{dic.Value}{taskInfo.RequestURL}"; | |||
} | |||
taskLogs.Add(taskLog); | |||
}); | |||
if (taskLogs.Count > 0) | |||
{ | |||
taskLogRepository.Value.AddList(taskLogs); | |||
taskLogs.Each(log => { anyTimeTaskScheduleProducer.Enqueue(log.Id, log.PlanStartTime); }); | |||
} | |||
return BaseResult.BuildOK(); | |||
} | |||
/// <summary> | |||
/// 清理日志 | |||
/// </summary> | |||
public void Clear() | |||
{ | |||
var tasks = taskInfoService.Value.GetAll().Data; | |||
foreach (var task in tasks) | |||
{ | |||
var date = DateTime.Now.Date; | |||
if (task.KeepLogDays > 0) | |||
{ | |||
date = date.AddDays(-task.KeepLogDays).Date; | |||
taskLogRepository.Value.DeleteMany(x => x.TaskId == task.Id && x.PlanStartTime <= date); | |||
} | |||
if (task.KeepLogCount > 0) | |||
{ | |||
var count = taskLogRepository.Value.Queryable().Count(x => x.TaskId == task.Id); | |||
if (count > task.KeepLogCount) | |||
{ | |||
var query = taskLogRepository.Value.Queryable().Where(x => x.TaskId == task.Id) | |||
.OrderByDescending(x => x.Id) | |||
.Skip(task.KeepLogCount - 1).Take(1).Select(x => new {x.Id}); | |||
var entity = query.FirstOrDefault(); | |||
if (entity != null) | |||
taskLogRepository.Value.DeleteMany(x => x.TaskId == task.Id && x.Id < entity.Id && x.PlanStartTime <= date); | |||
} | |||
} | |||
} | |||
} | |||
/// <summary> | |||
/// 分页查询日志 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
public BaseResultPage<TaskLogDTO> GetPage(GetTaskLogPageReq req) | |||
{ | |||
req.BizSysApiName = req.BizSysApiName.ToTrim(); | |||
var query = taskLogRepository.Value.Queryable() | |||
.WhereIF(req.BizSysApiName.IsNotEmpty(), x => x.BizSysApiName == req.BizSysApiName) | |||
.WhereIF(req.ExecutedResult.HasValue, x => x.ExecutedResult == req.ExecutedResult.Value) | |||
.WhereIF(req.TaskId.HasValue, x => x.TaskId == req.TaskId.Value) | |||
.WhereIF(req.TaskType.HasValue, x => x.TaskType == req.TaskType.Value) | |||
.WhereIF(req.PlanStartTime.HasValue, x => x.PlanStartTime == req.PlanStartTime.Value) | |||
.WhereIF(req.ExecutedResult.HasValue, x => x.ExecutedResult == req.ExecutedResult.Value) | |||
.OrderByDescending(x => x.Id); | |||
var rows = query.Pagination(x => new TaskLogDTO | |||
{ | |||
BizSysApiName = x.BizSysApiName, | |||
CreateTime = x.CreateTime, | |||
EndTime = x.EndTime, | |||
ExecutedResult = x.ExecutedResult, | |||
ExecutionTime = x.ExecutionTime, | |||
Id = x.Id, | |||
Name = x.Name, | |||
PlanStartTime = x.PlanStartTime, | |||
Remark = x.Remark, | |||
RequestParam = x.RequestParam, | |||
RequestURL = x.RequestURL, | |||
RequestURLType = x.RequestURLType, | |||
ResponseParam = x.ResponseParam, | |||
StartTime = x.StartTime, | |||
TaskId = x.TaskId, | |||
TaskType = x.TaskType, | |||
Title = x.Title, | |||
UpdateTime = x.UpdateTime, | |||
TrackId = x.TrackId | |||
}, req.PageIndex, req.PageSize, out int total); | |||
return BaseResultPage<TaskLogDTO>.BuildOK(rows, total); | |||
} | |||
} | |||
} |
@@ -0,0 +1,59 @@ | |||
using System.Text; | |||
using BPA.Component.Extensions; | |||
using BPA.SaaS.TaskSchedule.Api.Entity; | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using BPA.SaaS.TaskSchedule.Api.Model; | |||
using Microsoft.Extensions.Logging; | |||
namespace BPA.SaaS.TaskSchedule.Api.Service.WarnAlarms | |||
{ | |||
/// <summary> | |||
/// 企业微信 webhook 报警器 | |||
/// </summary> | |||
public class WechatWebhookWarnAlarm : IWarnAlarm | |||
{ | |||
private readonly TaskScheduleApiConfig config; | |||
private readonly IHttpClientFactory clientFactory; | |||
private readonly ILogger<WechatWebhookWarnAlarm> logger; | |||
public WechatWebhookWarnAlarm(TaskScheduleApiConfig config, IHttpClientFactory clientFactory, | |||
ILogger<WechatWebhookWarnAlarm> logger) | |||
{ | |||
this.config = config; | |||
this.clientFactory = clientFactory; | |||
this.logger = logger; | |||
} | |||
/// <summary> | |||
/// 发送预警消息 | |||
/// </summary> | |||
/// <param name="taskLog"></param> | |||
public async Task Bell(CrmTaskLog taskLog) | |||
{ | |||
// if (config.WechatWebhookUrl.IsEmpty()) | |||
// return; | |||
// var httpClient = clientFactory.CreateClient(typeof(WechatWebhookWarnAlarm).FullName); | |||
// var jsonParam = new | |||
// { | |||
// msgtype = "markdown", | |||
// markdown = new | |||
// { | |||
// content = | |||
// $"**任务调度预警[{config.EvnName}]</font>\n" + | |||
// $"**任务调度预警<font color=\"warning\">[{taskLog.ExecutedResult}]</font>,请相关同事注意**\n" + | |||
// $"> 任务名称:< font color =\"warning\">**{taskLog.Name}**</font> \n" + | |||
// $"> 任务标题:< font color =\"warning\">**{taskLog.Title}**</font> \n" + | |||
// $"> 请求地址:< font color =\"comment\">{taskLog.RequestURL}</font> \n" + | |||
// $"> 请求参数:< font color =\"comment\">{taskLog.RequestParam}</font> \n" + | |||
// $"> 执行结果:< font color =\"warning\">**{taskLog.ExecutedResult}**</font> \n" + | |||
// $"> 返回参数:< font color =\"comment\">{taskLog.ResponseParam}</font>\n" + | |||
// $"> 执行备注:< font color =\"comment\">{taskLog.Remark}</font>" | |||
// } | |||
// }; | |||
// var stringContent = new StringContent(jsonParam.ToJson(), Encoding.UTF8, "application/json"); | |||
// var rsp = await httpClient.PostAsync(config.WechatWebhookUrl, stringContent); | |||
// var result = await rsp.Content.ReadAsStringAsync(); | |||
// logger.LogInformation($"企业微信发送预警消息结果:{result}"); | |||
} | |||
} | |||
} |
@@ -0,0 +1,76 @@ | |||
using BPA.Component.Extensions; | |||
using BPA.SaaS.TaskSchedule.Api.DTO; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo; | |||
using BPA.SaaS.TaskSchedule.Api.Entity; | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using BPA.SaaS.TaskSchedule.Api.Model; | |||
using BPA.SaaS.TaskSchedule.Api.Service.WarnAlarms; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Extensions.Logging; | |||
namespace BPA.SaaS.TaskSchedule.Api.Service | |||
{ | |||
/// <summary> | |||
/// 警告管理服务 | |||
/// </summary> | |||
public class WarnManagerService : IWarnManagerService | |||
{ | |||
private readonly ILogger<WarnManagerService> _logger; | |||
private readonly TaskScheduleApiConfig _config; | |||
private readonly IServiceProvider _serviceProvider; | |||
public WarnManagerService(TaskScheduleApiConfig config, | |||
IServiceProvider serviceProvider, | |||
ILogger<WarnManagerService> logger) | |||
{ | |||
this._config = config; | |||
this._serviceProvider = serviceProvider; | |||
this._logger = logger; | |||
} | |||
/// <summary> | |||
/// 告警 | |||
/// </summary> | |||
/// <param name="taskLog"></param> | |||
public async Task Warn(TaskInfoDTO taskInfo, CrmTaskLog taskLog) | |||
{ | |||
if (taskLog == null || taskLog.ExecutedResult.IsIn(EnumExecutedResultType.Success, EnumExecutedResultType.Wait, | |||
EnumExecutedResultType.Suspend)) | |||
return; | |||
var warnAlarms = GetWarnAlarms(); | |||
foreach (var warnAlarm in warnAlarms) | |||
{ | |||
try | |||
{ | |||
await warnAlarm.Bell(taskLog); | |||
} | |||
catch (Exception ex) | |||
{ | |||
_logger.LogError(ex, $"告警失败,{warnAlarm.GetType().Name},{taskLog.ToJson()}"); | |||
} | |||
} | |||
} | |||
private List<IWarnAlarm> GetWarnAlarms() | |||
{ | |||
var warnAlarms = new List<IWarnAlarm>(); | |||
if (_config.WarnAlarms == null || _config.WarnAlarms.Count == 0) | |||
return warnAlarms; | |||
foreach (var item in _config.WarnAlarms) | |||
{ | |||
switch (item) | |||
{ | |||
case "WechatWebhookWarnAlarm": | |||
warnAlarms.Add(_serviceProvider.GetService<WechatWebhookWarnAlarm>()); | |||
break; | |||
default: | |||
break; | |||
} | |||
} | |||
return warnAlarms; | |||
} | |||
} | |||
} |
@@ -0,0 +1,172 @@ | |||
using BPA.Component.DTOCommon.BaseDTOs; | |||
using BPA.Component.DTOCommon.Langs; | |||
using BPA.Component.WebApiExtensions.Filters; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.SystemDictionary; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.TaskLog; | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using Microsoft.AspNetCore.Mvc; | |||
namespace BPA.SaaS.TaskSchedule.Api.WebApi.Controllers | |||
{ | |||
/// <summary> | |||
/// HomeController | |||
/// </summary> | |||
[Anonymous] | |||
public class HomeController : Controller | |||
{ | |||
private readonly ILogger<HomeController> _logger; | |||
/// <summary> | |||
/// taskInfoService | |||
/// </summary> | |||
private Lazy<ITaskInfoService> taskInfoService; | |||
/// <summary> | |||
/// taskLogService | |||
/// </summary> | |||
private Lazy<ITaskLogService> taskLogService; | |||
private Lazy<ISystemDictionaryService> systemDictionaryService; | |||
/// <summary> | |||
/// HomeController | |||
/// </summary> | |||
/// <param name="logger"></param> | |||
/// <param name="taskInfoService"></param> | |||
/// <param name="taskLogService"></param> | |||
/// <param name="systemDictionaryService"></param> | |||
public HomeController( | |||
ILogger<HomeController> logger, | |||
Lazy<ITaskInfoService> taskInfoService, | |||
Lazy<ITaskLogService> taskLogService, | |||
Lazy<ISystemDictionaryService> systemDictionaryService) | |||
{ | |||
_logger = logger; | |||
this.taskInfoService = taskInfoService; | |||
this.taskLogService = taskLogService; | |||
this.systemDictionaryService = systemDictionaryService; | |||
} | |||
/// <summary> | |||
/// json | |||
/// </summary> | |||
/// <param name="data"></param> | |||
/// <returns></returns> | |||
public override JsonResult Json(object data) | |||
{ | |||
if (data is BaseResult result) | |||
{ | |||
result.TrackId = Request.HttpContext.TraceIdentifier; | |||
result.Render(LangType.Zn); | |||
} | |||
return base.Json(data); | |||
} | |||
/// <summary> | |||
/// Index | |||
/// </summary> | |||
/// <returns></returns> | |||
public IActionResult Index() | |||
{ | |||
return View(); | |||
} | |||
/// <summary> | |||
/// 保存 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
[HttpPost] | |||
public IActionResult Save(SaveTaskInfoReq req) | |||
{ | |||
return Json(taskInfoService.Value.Save(req)); | |||
} | |||
/// <summary> | |||
/// 删除 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
[HttpPost] | |||
public IActionResult Delete(DeleteTaskInfoReq req) | |||
{ | |||
return Json(taskInfoService.Value.Delete(req)); | |||
} | |||
/// <summary> | |||
/// 获取所有的任务信息 | |||
/// </summary> | |||
/// <returns></returns> | |||
[HttpPost] | |||
public IActionResult GetAll() | |||
{ | |||
return Json(taskInfoService.Value.GetAll()); | |||
} | |||
/// <summary> | |||
/// 执行一次 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
[HttpPost] | |||
public IActionResult DoOnce(DoOnceReq req) | |||
{ | |||
return Json(taskInfoService.Value.DoOnce(req)); | |||
} | |||
/// <summary> | |||
/// 添加任意时间任务日志 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
[HttpPost] | |||
public IActionResult AddAnyTimeLog(AddAnyTimeLogReq req) | |||
=> Json(taskLogService.Value.AddAnyTimeLog(req)); | |||
/// <summary> | |||
/// 清理日志 | |||
/// </summary> | |||
[HttpPost] | |||
public IActionResult Clear() | |||
{ | |||
taskLogService.Value.Clear(); | |||
return Ok(); | |||
} | |||
/// <summary> | |||
/// 分页查询日志 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
[HttpPost] | |||
public IActionResult GetPage(GetTaskLogPageReq req) | |||
=> Json(taskLogService.Value.GetPage(req)); | |||
/// <summary> | |||
/// 保存 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
[HttpPost] | |||
public IActionResult SaveDic(SaveSystemDictionaryReq req) => Json(systemDictionaryService.Value.Save(req)); | |||
/// <summary> | |||
/// 删除 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
[HttpPost] | |||
public IActionResult DeleteDic(DeleteSystemDictionaryReq req) => Json(systemDictionaryService.Value.Delete(req)); | |||
/// <summary> | |||
/// 获取所有的 | |||
/// </summary> | |||
/// <returns></returns> | |||
[HttpPost] | |||
public IActionResult GetAllDic() => Json(systemDictionaryService.Value.GetAll()); | |||
} | |||
} |
@@ -0,0 +1,60 @@ | |||
using BPA.Component.DTOCommon.BaseDTOs; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo; | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using Microsoft.AspNetCore.Mvc; | |||
namespace BPA.SaaS.TaskSchedule.Api.WebApi.Controllers | |||
{ | |||
/// <summary> | |||
/// 任务信息服务 | |||
/// </summary> | |||
[Route("api/[controller]/[action]")] | |||
[ApiController] | |||
public class TaskInfoController : ControllerBase | |||
{ | |||
/// <summary> | |||
/// taskInfoService | |||
/// </summary> | |||
private Lazy<ITaskInfoService> taskInfoService; | |||
/// <summary> | |||
/// TaskInfoController | |||
/// </summary> | |||
/// <param name="taskInfoService"></param> | |||
public TaskInfoController(Lazy<ITaskInfoService> taskInfoService) => this.taskInfoService = taskInfoService; | |||
/// <summary> | |||
/// 保存 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
[HttpPost] | |||
public BaseResult<long> Save(SaveTaskInfoReq req) | |||
=> taskInfoService.Value.Save(req); | |||
/// <summary> | |||
/// 删除 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
[HttpPost] | |||
public BaseResult Delete(DeleteTaskInfoReq req) | |||
=> taskInfoService.Value.Delete(req); | |||
/// <summary> | |||
/// 获取所有的任务信息 | |||
/// </summary> | |||
/// <returns></returns> | |||
[HttpPost] | |||
public BaseResult<List<TaskInfoDTO>> GetAll() | |||
=> taskInfoService.Value.GetAll(); | |||
/// <summary> | |||
/// 尝试注册任务 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
[HttpPost] | |||
public BaseResult TryRegisterTask(TryRegisterTaskReq req) => taskInfoService.Value.TryRegisterTask(req); | |||
} | |||
} |
@@ -0,0 +1,65 @@ | |||
using BPA.Component.DTOCommon.BaseDTOs; | |||
using BPA.SaaS.TaskSchedule.Api.DTO; | |||
using BPA.SaaS.TaskSchedule.Api.DTO.TaskLog; | |||
using BPA.SaaS.TaskSchedule.Api.IService; | |||
using Microsoft.AspNetCore.Mvc; | |||
namespace BPA.SaaS.TaskSchedule.Api.WebApi.Controllers | |||
{ | |||
/// <summary> | |||
/// 任务信息服务 | |||
/// </summary> | |||
[Route("api/[controller]/[action]")] | |||
[ApiController] | |||
public class TaskLogController : ControllerBase | |||
{ | |||
/// <summary> | |||
/// taskLogService | |||
/// </summary> | |||
private Lazy<ITaskLogService> taskLogService; | |||
/// <summary> | |||
/// TaskInfoController | |||
/// </summary> | |||
/// <param name="taskLogService"></param> | |||
public TaskLogController(Lazy<ITaskLogService> taskLogService) => this.taskLogService = taskLogService; | |||
/// <summary> | |||
/// 添加任意时间任务日志 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
[HttpPost] | |||
public BaseResult AddAnyTimeLog(AddAnyTimeLogReq req) | |||
=> taskLogService.Value.AddAnyTimeLog(req); | |||
/// <summary> | |||
/// 添加任意时间任务日志 多个 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
[HttpPost] | |||
public BaseResult AddAnyTimeLogs(AddAnyTimeLogsReq req) | |||
=> taskLogService.Value.AddAnyTimeLogs(req); | |||
/// <summary> | |||
/// 清理日志 | |||
/// </summary> | |||
[HttpPost] | |||
[TaskInfoTag(Title = "清理日志", Description = "每天早上七点执行一次", CronExpression = "0 0 7 * * ? ")] | |||
public BaseResult Clear() | |||
{ | |||
taskLogService.Value.Clear(); | |||
return BaseResult.BuildOK(); | |||
} | |||
/// <summary> | |||
/// 分页查询日志 | |||
/// </summary> | |||
/// <param name="req"></param> | |||
/// <returns></returns> | |||
[HttpPost] | |||
public BaseResultPage<TaskLogDTO> GetPage(GetTaskLogPageReq req) | |||
=> taskLogService.Value.GetPage(req); | |||
} | |||
} |
@@ -1,7 +1,6 @@ | |||
using Autofac; | |||
using Autofac.Extensions.DependencyInjection; | |||
using BPA.Component.WebApiExtensions.Extensions; | |||
using BPA.Component.WebApiExtensions.Middlewares; | |||
using BPA.SaaS.TaskSchedule.Api.Bootstrap; | |||
using Microsoft.AspNetCore.Mvc; | |||
@@ -27,12 +26,11 @@ if (app.Environment.IsDevelopment()) | |||
// Use module | |||
app.UseRouting(); | |||
app.UseHttpsRedirection(); | |||
app.UseAuthorization(); | |||
app.UseBpaHealthChecks(); | |||
app.MapControllers(); | |||
app.UseGlobalErrorHandler(); | |||
app.UseMiddleware<BizLogQueryMiddleware>(); | |||
app.UseStaticFiles(); | |||
app.UseEndpoints(routeBuilder => routeBuilder.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}")); | |||
// PreRun | |||
((IApplicationBuilder) app).ApplicationServices.PreHeatRun(); | |||
@@ -5,8 +5,8 @@ | |||
"commandName": "Project", | |||
"dotnetRunMessages": true, | |||
"launchBrowser": true, | |||
"launchUrl": "swagger", | |||
"applicationUrl": "http://localhost:5047", | |||
"launchUrl": "Home/Index", | |||
"applicationUrl": "http://localhost:5077", | |||
"environmentVariables": { | |||
"ASPNETCORE_ENVIRONMENT": "Development", | |||
"APOLLO_META_SERVER_URL": "http://10.2.1.21:28080", | |||
@@ -0,0 +1,544 @@ | |||
| |||
@{ | |||
ViewBag.Title = "_Cron"; | |||
} | |||
<style> | |||
.cronTable input { | |||
width: 50px; | |||
} | |||
</style> | |||
<div id="CronWindow" class="easyui-window" title="Cron" style="height:550px;width:600px;" modal="true" | |||
closed="true" footer='#CronWindowFt'> | |||
<div class="easyui-layout" fit="true"> | |||
<div data-options="region:'center',border:false"> | |||
<div class="easyui-tabs" data-options="fit:true,border:true"> | |||
<div title="秒"> | |||
<div class="line"> | |||
<input type="radio" checked="checked" name="second" onclick="everyTime(this)"> | |||
每秒 允许的通配符[, - * /] | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="second" onclick="cycle(this)"> | |||
周期从 | |||
<input class="numberspinner" style="width: 60px;" data-options="min:1,max:58" value="1" | |||
id="secondStart_0"> | |||
- | |||
<input class="numberspinner" style="width: 60px;" data-options="min:2,max:59" value="2" | |||
id="secondEnd_0"> | |||
秒 | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="second" onclick="startOn(this)"> | |||
从 | |||
<input class="numberspinner" style="width: 60px;" data-options="min:0,max:59" value="0" | |||
id="secondStart_1"> | |||
秒开始,每 | |||
<input class="numberspinner" style="width: 60px;" data-options="min:1,max:59" value="1" | |||
id="secondEnd_1"> | |||
秒执行一次 | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="second" id="sencond_appoint"> | |||
指定 | |||
</div> | |||
<div class="imp secondList"> | |||
<input type="checkbox" value="0">00 | |||
<input type="checkbox" value="1">01 | |||
<input type="checkbox" value="2">02 | |||
<input type="checkbox" value="3">03 | |||
<input type="checkbox" value="4">04 | |||
<input type="checkbox" value="5">05 | |||
<input type="checkbox" value="6">06 | |||
<input type="checkbox" value="7">07 | |||
<input type="checkbox" value="8">08 | |||
<input type="checkbox" value="9">09 | |||
</div> | |||
<div class="imp secondList"> | |||
<input type="checkbox" value="10">10 | |||
<input type="checkbox" value="11">11 | |||
<input type="checkbox" value="12">12 | |||
<input type="checkbox" value="13">13 | |||
<input type="checkbox" value="14">14 | |||
<input type="checkbox" value="15">15 | |||
<input type="checkbox" value="16">16 | |||
<input type="checkbox" value="17">17 | |||
<input type="checkbox" value="18">18 | |||
<input type="checkbox" value="19">19 | |||
</div> | |||
<div class="imp secondList"> | |||
<input type="checkbox" value="20">20 | |||
<input type="checkbox" value="21">21 | |||
<input type="checkbox" value="22">22 | |||
<input type="checkbox" value="23">23 | |||
<input type="checkbox" value="24">24 | |||
<input type="checkbox" value="25">25 | |||
<input type="checkbox" value="26">26 | |||
<input type="checkbox" value="27">27 | |||
<input type="checkbox" value="28">28 | |||
<input type="checkbox" value="29">29 | |||
</div> | |||
<div class="imp secondList"> | |||
<input type="checkbox" value="30">30 | |||
<input type="checkbox" value="31">31 | |||
<input type="checkbox" value="32">32 | |||
<input type="checkbox" value="33">33 | |||
<input type="checkbox" value="34">34 | |||
<input type="checkbox" value="35">35 | |||
<input type="checkbox" value="36">36 | |||
<input type="checkbox" value="37">37 | |||
<input type="checkbox" value="38">38 | |||
<input type="checkbox" value="39">39 | |||
</div> | |||
<div class="imp secondList"> | |||
<input type="checkbox" value="40">40 | |||
<input type="checkbox" value="41">41 | |||
<input type="checkbox" value="42">42 | |||
<input type="checkbox" value="43">43 | |||
<input type="checkbox" value="44">44 | |||
<input type="checkbox" value="45">45 | |||
<input type="checkbox" value="46">46 | |||
<input type="checkbox" value="47">47 | |||
<input type="checkbox" value="48">48 | |||
<input type="checkbox" value="49">49 | |||
</div> | |||
<div class="imp secondList"> | |||
<input type="checkbox" value="50">50 | |||
<input type="checkbox" value="51">51 | |||
<input type="checkbox" value="52">52 | |||
<input type="checkbox" value="53">53 | |||
<input type="checkbox" value="54">54 | |||
<input type="checkbox" value="55">55 | |||
<input type="checkbox" value="56">56 | |||
<input type="checkbox" value="57">57 | |||
<input type="checkbox" value="58">58 | |||
<input type="checkbox" value="59">59 | |||
</div> | |||
</div> | |||
<div title="分钟"> | |||
<div class="line"> | |||
<input type="radio" checked="checked" name="min" onclick="everyTime(this)"> | |||
分钟 允许的通配符[, - * /] | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="min" onclick="cycle(this)"> | |||
周期从 | |||
<input class="numberspinner" style="width: 60px;" data-options="min:1,max:58" value="1" | |||
id="minStart_0"> | |||
- | |||
<input class="numberspinner" style="width: 60px;" data-options="min:2,max:59" value="2" | |||
id="minEnd_0"> | |||
分钟 | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="min" onclick="startOn(this)"> | |||
从 | |||
<input class="numberspinner" style="width: 60px;" data-options="min:0,max:59" value="0" | |||
id="minStart_1"> | |||
分钟开始,每 | |||
<input class="numberspinner" style="width: 60px;" data-options="min:1,max:59" value="1" | |||
id="minEnd_1"> | |||
分钟执行一次 | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="min" id="min_appoint"> | |||
指定 | |||
</div> | |||
<div class="imp minList"> | |||
<input type="checkbox" value="0">00 | |||
<input type="checkbox" value="1">01 | |||
<input type="checkbox" value="2">02 | |||
<input type="checkbox" value="3">03 | |||
<input type="checkbox" value="4">04 | |||
<input type="checkbox" value="5">05 | |||
<input type="checkbox" value="6">06 | |||
<input type="checkbox" value="7">07 | |||
<input type="checkbox" value="8">08 | |||
<input type="checkbox" value="9">09 | |||
</div> | |||
<div class="imp minList"> | |||
<input type="checkbox" value="10">10 | |||
<input type="checkbox" value="11">11 | |||
<input type="checkbox" value="12">12 | |||
<input type="checkbox" value="13">13 | |||
<input type="checkbox" value="14">14 | |||
<input type="checkbox" value="15">15 | |||
<input type="checkbox" value="16">16 | |||
<input type="checkbox" value="17">17 | |||
<input type="checkbox" value="18">18 | |||
<input type="checkbox" value="19">19 | |||
</div> | |||
<div class="imp minList"> | |||
<input type="checkbox" value="20">20 | |||
<input type="checkbox" value="21">21 | |||
<input type="checkbox" value="22">22 | |||
<input type="checkbox" value="23">23 | |||
<input type="checkbox" value="24">24 | |||
<input type="checkbox" value="25">25 | |||
<input type="checkbox" value="26">26 | |||
<input type="checkbox" value="27">27 | |||
<input type="checkbox" value="28">28 | |||
<input type="checkbox" value="29">29 | |||
</div> | |||
<div class="imp minList"> | |||
<input type="checkbox" value="30">30 | |||
<input type="checkbox" value="31">31 | |||
<input type="checkbox" value="32">32 | |||
<input type="checkbox" value="33">33 | |||
<input type="checkbox" value="34">34 | |||
<input type="checkbox" value="35">35 | |||
<input type="checkbox" value="36">36 | |||
<input type="checkbox" value="37">37 | |||
<input type="checkbox" value="38">38 | |||
<input type="checkbox" value="39">39 | |||
</div> | |||
<div class="imp minList"> | |||
<input type="checkbox" value="40">40 | |||
<input type="checkbox" value="41">41 | |||
<input type="checkbox" value="42">42 | |||
<input type="checkbox" value="43">43 | |||
<input type="checkbox" value="44">44 | |||
<input type="checkbox" value="45">45 | |||
<input type="checkbox" value="46">46 | |||
<input type="checkbox" value="47">47 | |||
<input type="checkbox" value="48">48 | |||
<input type="checkbox" value="49">49 | |||
</div> | |||
<div class="imp minList"> | |||
<input type="checkbox" value="50">50 | |||
<input type="checkbox" value="51">51 | |||
<input type="checkbox" value="52">52 | |||
<input type="checkbox" value="53">53 | |||
<input type="checkbox" value="54">54 | |||
<input type="checkbox" value="55">55 | |||
<input type="checkbox" value="56">56 | |||
<input type="checkbox" value="57">57 | |||
<input type="checkbox" value="58">58 | |||
<input type="checkbox" value="59">59 | |||
</div> | |||
</div> | |||
<div title="小时"> | |||
<div class="line"> | |||
<input type="radio" checked="checked" name="hour" onclick="everyTime(this)"> | |||
小时 允许的通配符[, - * /] | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="hour" onclick="cycle(this)"> | |||
周期从 | |||
<input class="numberspinner" style="width: 60px;" data-options="min:0,max:23" value="0" | |||
id="hourStart_0"> | |||
- | |||
<input class="numberspinner" style="width: 60px;" data-options="min:2,max:23" value="2" | |||
id="hourEnd_1"> | |||
小时 | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="hour" onclick="startOn(this)"> | |||
从 | |||
<input class="numberspinner" style="width: 60px;" data-options="min:0,max:23" value="0" | |||
id="hourStart_1"> | |||
小时开始,每 | |||
<input class="numberspinner" style="width: 60px;" data-options="min:1,max:23" value="1" | |||
id="hourEnd_1"> | |||
小时执行一次 | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="hour" id="hour_appoint"> | |||
指定 | |||
</div> | |||
<div class="imp hourList"> | |||
AM: | |||
<input type="checkbox" value="0">00 | |||
<input type="checkbox" value="1">01 | |||
<input type="checkbox" value="2">02 | |||
<input type="checkbox" value="3">03 | |||
<input type="checkbox" value="4">04 | |||
<input type="checkbox" value="5">05 | |||
<input type="checkbox" value="6">06 | |||
<input type="checkbox" value="7">07 | |||
<input type="checkbox" value="8">08 | |||
<input type="checkbox" value="9">09 | |||
<input type="checkbox" value="10">10 | |||
<input type="checkbox" value="11">11 | |||
</div> | |||
<div class="imp hourList"> | |||
PM: | |||
<input type="checkbox" value="12">12 | |||
<input type="checkbox" value="13">13 | |||
<input type="checkbox" value="14">14 | |||
<input type="checkbox" value="15">15 | |||
<input type="checkbox" value="16">16 | |||
<input type="checkbox" value="17">17 | |||
<input type="checkbox" value="18">18 | |||
<input type="checkbox" value="19">19 | |||
<input type="checkbox" value="20">20 | |||
<input type="checkbox" value="21">21 | |||
<input type="checkbox" value="22">22 | |||
<input type="checkbox" value="23">23 | |||
</div> | |||
</div> | |||
<div title="日"> | |||
<div class="line"> | |||
<input type="radio" checked="checked" name="day" onclick="everyTime(this)"> | |||
日 允许的通配符[, - * / L W] | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="day" onclick="unAppoint(this)"> | |||
不指定 | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="day" onclick="cycle(this)"> | |||
周期从 | |||
<input class="numberspinner" style="width: 60px;" data-options="min:1,max:31" value="1" | |||
id="dayStart_0"> | |||
- | |||
<input class="numberspinner" style="width: 60px;" data-options="min:2,max:31" value="2" | |||
id="dayEnd_0"> | |||
日 | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="day" onclick="startOn(this)"> | |||
从 | |||
<input class="numberspinner" style="width: 60px;" data-options="min:1,max:31" value="1" | |||
id="dayStart_1"> | |||
日开始,每 | |||
<input class="numberspinner" style="width: 60px;" data-options="min:1,max:31" value="1" | |||
id="dayEnd_1"> | |||
天执行一次 | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="day" onclick="workDay(this)"> | |||
每月 | |||
<input class="numberspinner" style="width: 60px;" data-options="min:1,max:31" value="1" | |||
id="dayStart_2"> | |||
号最近的那个工作日 | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="day" onclick="lastDay(this)"> | |||
本月最后一天 | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="day" id="day_appoint"> | |||
指定 | |||
</div> | |||
<div class="imp dayList"> | |||
<input type="checkbox" value="1">1 | |||
<input type="checkbox" value="2">2 | |||
<input type="checkbox" value="3">3 | |||
<input type="checkbox" value="4">4 | |||
<input type="checkbox" value="5">5 | |||
<input type="checkbox" value="6">6 | |||
<input type="checkbox" value="7">7 | |||
<input type="checkbox" value="8">8 | |||
<input type="checkbox" value="9">9 | |||
<input type="checkbox" value="10">10 | |||
<input type="checkbox" value="11">11 | |||
<input type="checkbox" value="12">12 | |||
<input type="checkbox" value="13">13 | |||
<input type="checkbox" value="14">14 | |||
<input type="checkbox" value="15">15 | |||
<input type="checkbox" value="16">16 | |||
</div> | |||
<div class="imp dayList"> | |||
<input type="checkbox" value="17">17 | |||
<input type="checkbox" value="18">18 | |||
<input type="checkbox" value="19">19 | |||
<input type="checkbox" value="20">20 | |||
<input type="checkbox" value="21">21 | |||
<input type="checkbox" value="22">22 | |||
<input type="checkbox" value="23">23 | |||
<input type="checkbox" value="24">24 | |||
<input type="checkbox" value="25">25 | |||
<input type="checkbox" value="26">26 | |||
<input type="checkbox" value="27">27 | |||
<input type="checkbox" value="28">28 | |||
<input type="checkbox" value="29">29 | |||
<input type="checkbox" value="30">30 | |||
<input type="checkbox" value="31">31 | |||
</div> | |||
</div> | |||
<div title="月"> | |||
<div class="line"> | |||
<input type="radio" checked="checked" name="mouth" onclick="everyTime(this)"> | |||
月 允许的通配符[, - * /] | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="mouth" onclick="unAppoint(this)"> | |||
不指定 | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="mouth" onclick="cycle(this)"> | |||
周期从 | |||
<input class="numberspinner" style="width: 60px;" data-options="min:1,max:12" value="1" | |||
id="mouthStart_0"> | |||
- | |||
<input class="numberspinner" style="width: 60px;" data-options="min:2,max:12" value="2" | |||
id="mouthEnd_0"> | |||
月 | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="mouth" onclick="startOn(this)"> | |||
从 | |||
<input class="numberspinner" style="width: 60px;" data-options="min:1,max:12" value="1" | |||
id="mouthStart_1"> | |||
日开始,每 | |||
<input class="numberspinner" style="width: 60px;" data-options="min:1,max:12" value="1" | |||
id="mouthEnd_1"> | |||
月执行一次 | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="mouth" id="mouth_appoint"> | |||
指定 | |||
</div> | |||
<div class="imp mouthList"> | |||
<input type="checkbox" value="1">1 | |||
<input type="checkbox" value="2">2 | |||
<input type="checkbox" value="3">3 | |||
<input type="checkbox" value="4">4 | |||
<input type="checkbox" value="5">5 | |||
<input type="checkbox" value="6">6 | |||
<input type="checkbox" value="7">7 | |||
<input type="checkbox" value="8">8 | |||
<input type="checkbox" value="9">9 | |||
<input type="checkbox" value="10">10 | |||
<input type="checkbox" value="11">11 | |||
<input type="checkbox" value="12">12 | |||
</div> | |||
</div> | |||
<div title="周"> | |||
<div class="line"> | |||
<input type="radio" checked="checked" name="week" onclick="everyTime(this)"> | |||
周 允许的通配符[, - * / L #] | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="week" onclick="unAppoint(this)"> | |||
不指定 | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="week" onclick="startOn(this)"> | |||
周期 从星期<input class="numberspinner" style="width: 60px;" data-options="min:1,max:7" | |||
id="weekStart_0" value="1"> | |||
- | |||
<input class="numberspinner" style="width: 60px;" data-options="min:2,max:7" value="2" | |||
id="weekEnd_0"> | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="week" onclick="weekOfDay(this)"> | |||
第<input class="numberspinner" style="width: 60px;" data-options="min:1,max:4" value="1" | |||
id="weekStart_1"> | |||
周 的星期<input class="numberspinner" style="width: 60px;" data-options="min:1,max:7" | |||
id="weekEnd_1" value="1"> | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="week" onclick="lastWeek(this)"> | |||
本月最后一个星期<input class="numberspinner" style="width: 60px;" data-options="min:1,max:7" | |||
id="weekStart_2" value="1"> | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="week" id="week_appoint"> | |||
指定 | |||
</div> | |||
<div class="imp weekList"> | |||
<input type="checkbox" value="1">1 | |||
<input type="checkbox" value="2">2 | |||
<input type="checkbox" value="3">3 | |||
<input type="checkbox" value="4">4 | |||
<input type="checkbox" value="5">5 | |||
<input type="checkbox" value="6">6 | |||
<input type="checkbox" value="7">7 | |||
</div> | |||
</div> | |||
<div title="年"> | |||
<div class="line"> | |||
<input type="radio" checked="checked" name="year" onclick="unAppoint(this)"> | |||
不指定 允许的通配符[, - * /] 非必填 | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="year" onclick="everyTime(this)"> | |||
每年 | |||
</div> | |||
<div class="line"> | |||
<input type="radio" name="year" onclick="cycle(this)">周期 从 | |||
<input class="numberspinner" style="width: 90px;" data-options="min:2013,max:3000" | |||
id="yearStart_0" value="2013"> | |||
- | |||
<input class="numberspinner" style="width: 90px;" data-options="min:2014,max:3000" | |||
id="yearEnd_0" value="2014"> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div data-options="region:'south',border:false" style="height:250px"> | |||
<fieldset style="border-radius: 3px; height: 220px; "> | |||
<legend>表达式</legend> | |||
<table class="cronTable" style="height: 100px;"> | |||
<tbody> | |||
<tr> | |||
<td width="100px;"></td> | |||
<td align="center"> | |||
秒 | |||
</td> | |||
<td align="center"> | |||
分钟 | |||
</td> | |||
<td align="center"> | |||
小时 | |||
</td> | |||
<td align="center"> | |||
日 | |||
</td> | |||
<td align="center"> | |||
月<br /> | |||
</td> | |||
<td align="center"> | |||
星期 | |||
</td> | |||
<td align="center"> | |||
年 | |||
</td> | |||
</tr> | |||
<tr> | |||
<td> | |||
表达式字段: | |||
</td> | |||
<td> | |||
<input type="text" name="v_second" class="col" value="*" readonly="readonly" /> | |||
</td> | |||
<td> | |||
<input type="text" name="v_min" class="col" value="*" readonly="readonly" /> | |||
</td> | |||
<td> | |||
<input type="text" name="v_hour" class="col" value="*" readonly="readonly" /> | |||
</td> | |||
<td> | |||
<input type="text" name="v_day" class="col" value="*" readonly="readonly" /> | |||
</td> | |||
<td> | |||
<input type="text" name="v_mouth" class="col" value="*" readonly="readonly" /> | |||
</td> | |||
<td> | |||
<input type="text" name="v_week" class="col" value="?" readonly="readonly" /> | |||
</td> | |||
<td> | |||
<input type="text" name="v_year" class="col" readonly="readonly" /> | |||
</td> | |||
</tr> | |||
<tr> | |||
<td>Cron 表达式:</td> | |||
<td colspan="6"> | |||
<input type="text" name="cron" style="width: 100%;" value="* * * * * ?" id="cron" /> | |||
</td> | |||
<td><input type="button" value="反解析到UI " id="btnFan" onclick="btnFan()" /></td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
</fieldset> | |||
</div> | |||
</div> | |||
</div> | |||
<div id="CronWindowFt" style="text-align:right;padding:5px 0 0;"> | |||
<a id="CronWindowFtOk" class="easyui-linkbutton" data-options="iconCls:'icon-ok'" href="javascript:void(0)" style="width:80px">确定</a> | |||
<a id="CronWindowFtCancel" class="easyui-linkbutton" data-options="iconCls:'icon-cancel'" href="javascript:void(0)" style="width:80px">取消</a> | |||
</div> | |||
s |
@@ -0,0 +1,25 @@ | |||
@* @model ErrorViewModel *@ | |||
@{ | |||
ViewData["Title"] = "Error"; | |||
} | |||
<h1 class="text-danger">Error.</h1> | |||
<h2 class="text-danger">An error occurred while processing your request.</h2> | |||
@if (Model.ShowRequestId) | |||
{ | |||
<p> | |||
<strong>Request ID:</strong> <code>@Model.RequestId</code> | |||
</p> | |||
} | |||
<h3>Development Mode</h3> | |||
<p> | |||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred. | |||
</p> | |||
<p> | |||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong> | |||
It can result in displaying sensitive information from exceptions to end users. | |||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong> | |||
and restarting the app. | |||
</p> |
@@ -0,0 +1,22 @@ | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | |||
<meta charset="utf-8" /> | |||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||
<title>@ViewBag.Title</title> | |||
<link href="/jquery-easyui/themes/default/easyui.css" rel="stylesheet" asp-append-version="true" /> | |||
<link href="/jquery-easyui/themes/icon.css" rel="stylesheet" asp-append-version="true" /> | |||
<link href="/jquery-easyui/themes/color.css" rel="stylesheet" asp-append-version="true" /> | |||
</head> | |||
<body> | |||
@RenderBody() | |||
<script src="/jquery-1.8.2.min.js" asp-append-version="true" ></script> | |||
<script src="/jquery-easyui/jquery.easyui.min.js" asp-append-version="true" ></script> | |||
<script src="/jquery-easyui/easyui-lang-zh_CN.js" asp-append-version="true" ></script> | |||
<script src="/jquery.extend.js" charset="gbk" asp-append-version="true" ></script> | |||
<script src="/jquery.form.js" asp-append-version="true" ></script> | |||
<script src="/cron.js" asp-append-version="true" ></script> | |||
@RenderSection("scripts", required: false) | |||
</body> | |||
</html> |
@@ -0,0 +1,2 @@ | |||
@using BPA.SaaS.TaskSchedule.Api | |||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
@@ -0,0 +1,3 @@ | |||
@{ | |||
Layout = "_Layout"; | |||
} |
@@ -0,0 +1,516 @@ | |||
/** | |||
* 每周期 | |||
*/ | |||
function everyTime(dom) { | |||
var item = $("input[name=v_" + dom.name + "]"); | |||
item.val("*"); | |||
item.change(); | |||
} | |||
/** | |||
* 不指定 | |||
*/ | |||
function unAppoint(dom) { | |||
var name = dom.name; | |||
var val = "?"; | |||
if (name == "year") | |||
val = ""; | |||
var item = $("input[name=v_" + name + "]"); | |||
item.val(val); | |||
item.change(); | |||
} | |||
function appoint(dom) { | |||
} | |||
/** | |||
* 周期 | |||
*/ | |||
function cycle(dom) { | |||
var name = dom.name; | |||
var ns = $(dom).parent().find(".numberspinner"); | |||
var start = ns.eq(0).numberspinner("getValue"); | |||
var end = ns.eq(1).numberspinner("getValue"); | |||
var item = $("input[name=v_" + name + "]"); | |||
item.val(start + "-" + end); | |||
item.change(); | |||
} | |||
/** | |||
* 从开始 | |||
*/ | |||
function startOn(dom) { | |||
var name = dom.name; | |||
var ns = $(dom).parent().find(".numberspinner"); | |||
var start = ns.eq(0).numberspinner("getValue"); | |||
var end = ns.eq(1).numberspinner("getValue"); | |||
var item = $("input[name=v_" + name + "]"); | |||
item.val(start + "/" + end); | |||
item.change(); | |||
} | |||
function lastDay(dom) { | |||
var item = $("input[name=v_" + dom.name + "]"); | |||
item.val("L"); | |||
item.change(); | |||
} | |||
function weekOfDay(dom) { | |||
var name = dom.name; | |||
var ns = $(dom).parent().find(".numberspinner"); | |||
var start = ns.eq(0).numberspinner("getValue"); | |||
var end = ns.eq(1).numberspinner("getValue"); | |||
var item = $("input[name=v_" + name + "]"); | |||
item.val(start + "#" + end); | |||
item.change(); | |||
} | |||
function lastWeek(dom) { | |||
var item = $("input[name=v_" + dom.name + "]"); | |||
var ns = $(dom).parent().find(".numberspinner"); | |||
var start = ns.eq(0).numberspinner("getValue"); | |||
item.val(start + "L"); | |||
item.change(); | |||
} | |||
function workDay(dom) { | |||
var name = dom.name; | |||
var ns = $(dom).parent().find(".numberspinner"); | |||
var start = ns.eq(0).numberspinner("getValue"); | |||
var item = $("input[name=v_" + name + "]"); | |||
item.val(start + "W"); | |||
item.change(); | |||
} | |||
$(function () { | |||
$(".numberspinner").numberspinner({ | |||
onChange: function () { | |||
$(this).closest("div.line").children().eq(0).click(); | |||
} | |||
}); | |||
var vals = $("input[name^='v_']"); | |||
var cron = $("#cron"); | |||
vals.change(function () { | |||
var item = []; | |||
vals.each(function () { | |||
item.push(this.value); | |||
}); | |||
//修复表达式错误BUG,如果后一项不为* 那么前一项肯定不为为*,要不然就成了每秒执行了 | |||
//获取当前选中tab | |||
var currentIndex = 0; | |||
$(".tabs>li").each(function (i, item) { | |||
if ($(item).hasClass("tabs-selected")) { | |||
currentIndex = i; | |||
return false; | |||
} | |||
}); | |||
//当前选中项之前的如果为*,则都设置成0 | |||
for (var i = currentIndex; i >= 1; i--) { | |||
if (item[i] != "*" && item[i - 1] == "*") { | |||
if (i < 3) | |||
item[i - 1] = "0"; | |||
} | |||
} | |||
//当前选中项之后的如果不为*则都设置成* | |||
if (item[currentIndex] == "*") { | |||
for (var i = currentIndex + 1; i < item.length; i++) { | |||
if (i == 5) { | |||
item[i] = "?"; | |||
} else { | |||
item[i] = "*"; | |||
} | |||
} | |||
} | |||
cron.val(item.join(" ")).change(); | |||
}); | |||
cron.change(function () { | |||
btnFan(); | |||
//设置最近五次运行时间 | |||
return; | |||
$.ajax({ | |||
type: 'get', | |||
url: "CalcRunTime.ashx", | |||
dataType: "json", | |||
data: { "CronExpression": $("#cron").val() }, | |||
success: function (data) { | |||
if (data && data.length == 5) { | |||
var strHTML = "<ul>"; | |||
for (var i = 0; i < data.length; i++) { | |||
strHTML += "<li>" + data[i] + "</li>"; | |||
} | |||
strHTML += "</ul>" | |||
$("#runTime").html(strHTML); | |||
} else { | |||
$("#runTime").html(""); | |||
} | |||
} | |||
}); | |||
}); | |||
var secondList = $(".secondList").children(); | |||
$("#sencond_appoint").click(function () { | |||
if (this.checked) { | |||
if ($(secondList).filter(":checked").length == 0) { | |||
$(secondList.eq(0)).attr("checked", true); | |||
} | |||
secondList.eq(0).change(); | |||
} | |||
}); | |||
secondList.change(function () { | |||
var sencond_appoint = $("#sencond_appoint").prop("checked"); | |||
if (sencond_appoint) { | |||
var vals = []; | |||
secondList.each(function () { | |||
if (this.checked) { | |||
vals.push(this.value); | |||
} | |||
}); | |||
var val = "?"; | |||
if (vals.length > 0 && vals.length < 59) { | |||
val = vals.join(","); | |||
} else if (vals.length == 59) { | |||
val = "*"; | |||
} | |||
var item = $("input[name=v_second]"); | |||
item.val(val); | |||
item.change(); | |||
} | |||
}); | |||
var minList = $(".minList").children(); | |||
$("#min_appoint").click(function () { | |||
if (this.checked) { | |||
if ($(minList).filter(":checked").length == 0) { | |||
$(minList.eq(0)).attr("checked", true); | |||
} | |||
minList.eq(0).change(); | |||
} | |||
}); | |||
minList.change(function () { | |||
var min_appoint = $("#min_appoint").prop("checked"); | |||
if (min_appoint) { | |||
var vals = []; | |||
minList.each(function () { | |||
if (this.checked) { | |||
vals.push(this.value); | |||
} | |||
}); | |||
var val = "?"; | |||
if (vals.length > 0 && vals.length < 59) { | |||
val = vals.join(","); | |||
} else if (vals.length == 59) { | |||
val = "*"; | |||
} | |||
var item = $("input[name=v_min]"); | |||
item.val(val); | |||
item.change(); | |||
} | |||
}); | |||
var hourList = $(".hourList").children(); | |||
$("#hour_appoint").click(function () { | |||
if (this.checked) { | |||
if ($(hourList).filter(":checked").length == 0) { | |||
$(hourList.eq(0)).attr("checked", true); | |||
} | |||
hourList.eq(0).change(); | |||
} | |||
}); | |||
hourList.change(function () { | |||
var hour_appoint = $("#hour_appoint").prop("checked"); | |||
if (hour_appoint) { | |||
var vals = []; | |||
hourList.each(function () { | |||
if (this.checked) { | |||
vals.push(this.value); | |||
} | |||
}); | |||
var val = "?"; | |||
if (vals.length > 0 && vals.length < 24) { | |||
val = vals.join(","); | |||
} else if (vals.length == 24) { | |||
val = "*"; | |||
} | |||
var item = $("input[name=v_hour]"); | |||
item.val(val); | |||
item.change(); | |||
} | |||
}); | |||
var dayList = $(".dayList").children(); | |||
$("#day_appoint").click(function () { | |||
if (this.checked) { | |||
if ($(dayList).filter(":checked").length == 0) { | |||
$(dayList.eq(0)).attr("checked", true); | |||
} | |||
dayList.eq(0).change(); | |||
} | |||
}); | |||
dayList.change(function () { | |||
var day_appoint = $("#day_appoint").prop("checked"); | |||
if (day_appoint) { | |||
var vals = []; | |||
dayList.each(function () { | |||
if (this.checked) { | |||
vals.push(this.value); | |||
} | |||
}); | |||
var val = "?"; | |||
if (vals.length > 0 && vals.length < 31) { | |||
val = vals.join(","); | |||
} else if (vals.length == 31) { | |||
val = "*"; | |||
} | |||
var item = $("input[name=v_day]"); | |||
item.val(val); | |||
item.change(); | |||
} | |||
}); | |||
var mouthList = $(".mouthList").children(); | |||
$("#mouth_appoint").click(function () { | |||
if (this.checked) { | |||
if ($(mouthList).filter(":checked").length == 0) { | |||
$(mouthList.eq(0)).attr("checked", true); | |||
} | |||
mouthList.eq(0).change(); | |||
} | |||
}); | |||
mouthList.change(function () { | |||
var mouth_appoint = $("#mouth_appoint").prop("checked"); | |||
if (mouth_appoint) { | |||
var vals = []; | |||
mouthList.each(function () { | |||
if (this.checked) { | |||
vals.push(this.value); | |||
} | |||
}); | |||
var val = "?"; | |||
if (vals.length > 0 && vals.length < 12) { | |||
val = vals.join(","); | |||
} else if (vals.length == 12) { | |||
val = "*"; | |||
} | |||
var item = $("input[name=v_mouth]"); | |||
item.val(val); | |||
item.change(); | |||
} | |||
}); | |||
var weekList = $(".weekList").children(); | |||
$("#week_appoint").click(function () { | |||
if (this.checked) { | |||
if ($(weekList).filter(":checked").length == 0) { | |||
$(weekList.eq(0)).attr("checked", true); | |||
} | |||
weekList.eq(0).change(); | |||
} | |||
}); | |||
weekList.change(function () { | |||
var week_appoint = $("#week_appoint").prop("checked"); | |||
if (week_appoint) { | |||
var vals = []; | |||
weekList.each(function () { | |||
if (this.checked) { | |||
vals.push(this.value); | |||
} | |||
}); | |||
var val = "?"; | |||
if (vals.length > 0 && vals.length < 7) { | |||
val = vals.join(","); | |||
} else if (vals.length == 7) { | |||
val = "*"; | |||
} | |||
var item = $("input[name=v_week]"); | |||
item.val(val); | |||
item.change(); | |||
} | |||
}); | |||
}); | |||
/*killIe*/ | |||
var cpro_id = "u1331261"; | |||
function btnFan() { | |||
//获取参数中表达式的值 | |||
var txt = $("#cron").val(); | |||
if (txt) { | |||
var regs = txt.split(' '); | |||
$("input[name=v_second]").val(regs[0]); | |||
$("input[name=v_min]").val(regs[1]); | |||
$("input[name=v_hour]").val(regs[2]); | |||
$("input[name=v_day]").val(regs[3]); | |||
$("input[name=v_mouth]").val(regs[4]); | |||
$("input[name=v_week]").val(regs[5]); | |||
initObj(regs[0], "second"); | |||
initObj(regs[1], "min"); | |||
initObj(regs[2], "hour"); | |||
initDay(regs[3]); | |||
initMonth(regs[4]); | |||
initWeek(regs[5]); | |||
if (regs.length > 6) { | |||
$("input[name=v_year]").val(regs[6]); | |||
initYear(regs[6]); | |||
} | |||
} | |||
} | |||
function initObj(strVal, strid) { | |||
var ary = null; | |||
var objRadio = $("input[name='" + strid + "'"); | |||
if (strVal == "*") { | |||
objRadio.eq(0).attr("checked", "checked"); | |||
} else if (strVal.split('-').length > 1) { | |||
ary = strVal.split('-'); | |||
objRadio.eq(1).attr("checked", "checked"); | |||
$("#" + strid + "Start_0").numberspinner('setValue', ary[0]); | |||
$("#" + strid + "End_0").numberspinner('setValue', ary[1]); | |||
} else if (strVal.split('/').length > 1) { | |||
ary = strVal.split('/'); | |||
objRadio.eq(2).attr("checked", "checked"); | |||
$("#" + strid + "Start_1").numberspinner('setValue', ary[0]); | |||
$("#" + strid + "End_1").numberspinner('setValue', ary[1]); | |||
} else { | |||
objRadio.eq(3).attr("checked", "checked"); | |||
if (strVal != "?") { | |||
ary = strVal.split(","); | |||
for (var i = 0; i < ary.length; i++) { | |||
$("." + strid + "List input[value='" + ary[i] + "']").attr("checked", "checked"); | |||
} | |||
} | |||
} | |||
} | |||
function initDay(strVal) { | |||
var ary = null; | |||
var objRadio = $("input[name='day'"); | |||
if (strVal == "*") { | |||
objRadio.eq(0).attr("checked", "checked"); | |||
} else if (strVal == "?") { | |||
objRadio.eq(1).attr("checked", "checked"); | |||
} else if (strVal.split('-').length > 1) { | |||
ary = strVal.split('-'); | |||
objRadio.eq(2).attr("checked", "checked"); | |||
$("#dayStart_0").numberspinner('setValue', ary[0]); | |||
$("#dayEnd_0").numberspinner('setValue', ary[1]); | |||
} else if (strVal.split('/').length > 1) { | |||
ary = strVal.split('/'); | |||
objRadio.eq(3).attr("checked", "checked"); | |||
$("#dayStart_1").numberspinner('setValue', ary[0]); | |||
$("#dayEnd_1").numberspinner('setValue', ary[1]); | |||
} else if (strVal.split('W').length > 1) { | |||
ary = strVal.split('W'); | |||
objRadio.eq(4).attr("checked", "checked"); | |||
$("#dayStart_2").numberspinner('setValue', ary[0]); | |||
} else if (strVal == "L") { | |||
objRadio.eq(5).attr("checked", "checked"); | |||
} else { | |||
objRadio.eq(6).attr("checked", "checked"); | |||
ary = strVal.split(","); | |||
for (var i = 0; i < ary.length; i++) { | |||
$(".dayList input[value='" + ary[i] + "']").attr("checked", "checked"); | |||
} | |||
} | |||
} | |||
function initMonth(strVal) { | |||
var ary = null; | |||
var objRadio = $("input[name='mouth'"); | |||
if (strVal == "*") { | |||
objRadio.eq(0).attr("checked", "checked"); | |||
} else if (strVal == "?") { | |||
objRadio.eq(1).attr("checked", "checked"); | |||
} else if (strVal.split('-').length > 1) { | |||
ary = strVal.split('-'); | |||
objRadio.eq(2).attr("checked", "checked"); | |||
$("#mouthStart_0").numberspinner('setValue', ary[0]); | |||
$("#mouthEnd_0").numberspinner('setValue', ary[1]); | |||
} else if (strVal.split('/').length > 1) { | |||
ary = strVal.split('/'); | |||
objRadio.eq(3).attr("checked", "checked"); | |||
$("#mouthStart_1").numberspinner('setValue', ary[0]); | |||
$("#mouthEnd_1").numberspinner('setValue', ary[1]); | |||
} else { | |||
objRadio.eq(4).attr("checked", "checked"); | |||
ary = strVal.split(","); | |||
for (var i = 0; i < ary.length; i++) { | |||
$(".mouthList input[value='" + ary[i] + "']").attr("checked", "checked"); | |||
} | |||
} | |||
} | |||
function initWeek(strVal) { | |||
var ary = null; | |||
var objRadio = $("input[name='week'"); | |||
if (strVal == "*") { | |||
objRadio.eq(0).attr("checked", "checked"); | |||
} else if (strVal == "?") { | |||
objRadio.eq(1).attr("checked", "checked"); | |||
} else if (strVal.split('/').length > 1) { | |||
ary = strVal.split('/'); | |||
objRadio.eq(2).attr("checked", "checked"); | |||
$("#weekStart_0").numberspinner('setValue', ary[0]); | |||
$("#weekEnd_0").numberspinner('setValue', ary[1]); | |||
} else if (strVal.split('-').length > 1) { | |||
ary = strVal.split('-'); | |||
objRadio.eq(3).attr("checked", "checked"); | |||
$("#weekStart_1").numberspinner('setValue', ary[0]); | |||
$("#weekEnd_1").numberspinner('setValue', ary[1]); | |||
} else if (strVal.split('L').length > 1) { | |||
ary = strVal.split('L'); | |||
objRadio.eq(4).attr("checked", "checked"); | |||
$("#weekStart_2").numberspinner('setValue', ary[0]); | |||
} else { | |||
objRadio.eq(5).attr("checked", "checked"); | |||
ary = strVal.split(","); | |||
for (var i = 0; i < ary.length; i++) { | |||
$(".weekList input[value='" + ary[i] + "']").attr("checked", "checked"); | |||
} | |||
} | |||
} | |||
function initYear(strVal) { | |||
var ary = null; | |||
var objRadio = $("input[name='year'"); | |||
if (strVal == "*") { | |||
objRadio.eq(1).attr("checked", "checked"); | |||
} else if (strVal.split('-').length > 1) { | |||
ary = strVal.split('-'); | |||
objRadio.eq(2).attr("checked", "checked"); | |||
$("#yearStart_0").numberspinner('setValue', ary[0]); | |||
$("#yearEnd_0").numberspinner('setValue', ary[1]); | |||
} | |||
} | |||
var cornCallBack; | |||
var cronWindow = $("#CronWindow"); | |||
$("#CronWindowFtCancel").click(function () { | |||
cronWindow.window("close"); | |||
}); | |||
$("#CronWindowFtOk").click(function () { | |||
cronWindow.window("close"); | |||
cornCallBack($("#cron").val()); | |||
}); | |||
function openCorn(cron, callBack) { | |||
cronWindow.window("open"); | |||
cornCallBack = callBack; | |||
$("#cron").val(cron); | |||
btnFan(); | |||
} | |||
@@ -0,0 +1,480 @@ | |||
var detailview = $.extend({}, $.fn.datagrid.defaults.view, { | |||
render: function(target, container, frozen){ | |||
var state = $.data(target, 'datagrid'); | |||
var opts = state.options; | |||
if (frozen){ | |||
if (!(opts.rownumbers || (opts.frozenColumns && opts.frozenColumns.length))){ | |||
return; | |||
} | |||
} | |||
var rows = state.data.rows; | |||
var fields = $(target).datagrid('getColumnFields', frozen); | |||
var table = []; | |||
table.push('<table class="datagrid-btable" cellspacing="0" cellpadding="0" border="0"><tbody>'); | |||
for(var i=0; i<rows.length; i++) { | |||
// get the class and style attributes for this row | |||
var css = opts.rowStyler ? opts.rowStyler.call(target, i, rows[i]) : ''; | |||
var classValue = ''; | |||
var styleValue = ''; | |||
if (typeof css == 'string'){ | |||
styleValue = css; | |||
} else if (css){ | |||
classValue = css['class'] || ''; | |||
styleValue = css['style'] || ''; | |||
} | |||
var cls = 'class="datagrid-row ' + (i % 2 && opts.striped ? 'datagrid-row-alt ' : ' ') + classValue + '"'; | |||
var style = styleValue ? 'style="' + styleValue + '"' : ''; | |||
var rowId = state.rowIdPrefix + '-' + (frozen?1:2) + '-' + i; | |||
table.push('<tr id="' + rowId + '" datagrid-row-index="' + i + '" ' + cls + ' ' + style + '>'); | |||
table.push(this.renderRow.call(this, target, fields, frozen, i, rows[i])); | |||
table.push('</tr>'); | |||
table.push('<tr style="display:none;">'); | |||
if (frozen){ | |||
table.push('<td colspan=' + (fields.length+2) + ' style="border-right:0">'); | |||
} else { | |||
table.push('<td colspan=' + (fields.length) + '>'); | |||
} | |||
table.push('<div class="datagrid-row-detail">'); | |||
if (frozen){ | |||
table.push(' '); | |||
} else { | |||
table.push(opts.detailFormatter.call(target, i, rows[i])); | |||
} | |||
table.push('</div>'); | |||
table.push('</td>'); | |||
table.push('</tr>'); | |||
} | |||
table.push('</tbody></table>'); | |||
$(container).html(table.join('')); | |||
}, | |||
renderRow: function(target, fields, frozen, rowIndex, rowData){ | |||
var opts = $.data(target, 'datagrid').options; | |||
var cc = []; | |||
if (frozen && opts.rownumbers){ | |||
var rownumber = rowIndex + 1; | |||
if (opts.pagination){ | |||
rownumber += (opts.pageNumber-1)*opts.pageSize; | |||
} | |||
cc.push('<td class="datagrid-td-rownumber"><div class="datagrid-cell-rownumber">'+rownumber+'</div></td>'); | |||
} | |||
for(var i=0; i<fields.length; i++){ | |||
var field = fields[i]; | |||
var col = $(target).datagrid('getColumnOption', field); | |||
if (col){ | |||
var value = rowData[field]; // the field value | |||
var css = col.styler ? (col.styler(value, rowData, rowIndex)||'') : ''; | |||
var classValue = ''; | |||
var styleValue = ''; | |||
if (typeof css == 'string'){ | |||
styleValue = css; | |||
} else if (cc){ | |||
classValue = css['class'] || ''; | |||
styleValue = css['style'] || ''; | |||
} | |||
var cls = classValue ? 'class="' + classValue + '"' : ''; | |||
var style = col.hidden ? 'style="display:none;' + styleValue + '"' : (styleValue ? 'style="' + styleValue + '"' : ''); | |||
cc.push('<td field="' + field + '" ' + cls + ' ' + style + '>'); | |||
if (col.checkbox){ | |||
style = ''; | |||
} else if (col.expander){ | |||
style = "text-align:center;height:16px;"; | |||
} else { | |||
style = styleValue; | |||
if (col.align){style += ';text-align:' + col.align + ';'} | |||
if (!opts.nowrap){ | |||
style += ';white-space:normal;height:auto;'; | |||
} else if (opts.autoRowHeight){ | |||
style += ';height:auto;'; | |||
} | |||
} | |||
cc.push('<div style="' + style + '" '); | |||
if (col.checkbox){ | |||
cc.push('class="datagrid-cell-check '); | |||
} else { | |||
cc.push('class="datagrid-cell ' + col.cellClass); | |||
} | |||
cc.push('">'); | |||
if (col.checkbox){ | |||
cc.push('<input type="checkbox" name="' + field + '" value="' + (value!=undefined ? value : '') + '">'); | |||
} else if (col.expander) { | |||
//cc.push('<div style="text-align:center;width:16px;height:16px;">'); | |||
cc.push('<span class="datagrid-row-expander datagrid-row-expand" style="display:inline-block;width:16px;height:16px;cursor:pointer;" />'); | |||
//cc.push('</div>'); | |||
} else if (col.formatter){ | |||
cc.push(col.formatter(value, rowData, rowIndex)); | |||
} else { | |||
cc.push(value); | |||
} | |||
cc.push('</div>'); | |||
cc.push('</td>'); | |||
} | |||
} | |||
return cc.join(''); | |||
}, | |||
insertRow: function(target, index, row){ | |||
var opts = $.data(target, 'datagrid').options; | |||
var dc = $.data(target, 'datagrid').dc; | |||
var panel = $(target).datagrid('getPanel'); | |||
var view1 = dc.view1; | |||
var view2 = dc.view2; | |||
var isAppend = false; | |||
var rowLength = $(target).datagrid('getRows').length; | |||
if (rowLength == 0){ | |||
$(target).datagrid('loadData',{total:1,rows:[row]}); | |||
return; | |||
} | |||
if (index == undefined || index == null || index >= rowLength) { | |||
index = rowLength; | |||
isAppend = true; | |||
this.canUpdateDetail = false; | |||
} | |||
$.fn.datagrid.defaults.view.insertRow.call(this, target, index, row); | |||
_insert(true); | |||
_insert(false); | |||
this.canUpdateDetail = true; | |||
function _insert(frozen){ | |||
var v = frozen ? view1 : view2; | |||
var tr = v.find('tr[datagrid-row-index='+index+']'); | |||
if (isAppend){ | |||
var newDetail = tr.next().clone(); | |||
tr.insertAfter(tr.next()); | |||
} else { | |||
var newDetail = tr.next().next().clone(); | |||
} | |||
newDetail.insertAfter(tr); | |||
newDetail.hide(); | |||
if (!frozen){ | |||
newDetail.find('div.datagrid-row-detail').html(opts.detailFormatter.call(target, index, row)); | |||
} | |||
} | |||
}, | |||
deleteRow: function(target, index){ | |||
var opts = $.data(target, 'datagrid').options; | |||
var dc = $.data(target, 'datagrid').dc; | |||
var tr = opts.finder.getTr(target, index); | |||
tr.next().remove(); | |||
$.fn.datagrid.defaults.view.deleteRow.call(this, target, index); | |||
dc.body2.triggerHandler('scroll'); | |||
}, | |||
updateRow: function(target, rowIndex, row){ | |||
var dc = $.data(target, 'datagrid').dc; | |||
var opts = $.data(target, 'datagrid').options; | |||
var cls = $(target).datagrid('getExpander', rowIndex).attr('class'); | |||
$.fn.datagrid.defaults.view.updateRow.call(this, target, rowIndex, row); | |||
$(target).datagrid('getExpander', rowIndex).attr('class',cls); | |||
// update the detail content | |||
if (this.canUpdateDetail){ | |||
var row = $(target).datagrid('getRows')[rowIndex]; | |||
var detail = $(target).datagrid('getRowDetail', rowIndex); | |||
detail.html(opts.detailFormatter.call(target, rowIndex, row)); | |||
} | |||
}, | |||
bindEvents: function(target){ | |||
var state = $.data(target, 'datagrid'); | |||
if (state.ss.bindDetailEvents){return;} | |||
state.ss.bindDetailEvents = true; | |||
var dc = state.dc; | |||
var opts = state.options; | |||
var body = dc.body1.add(dc.body2); | |||
var clickHandler = ($.data(body[0],'events')||$._data(body[0],'events')).click[0].handler; | |||
body.unbind('click').bind('click', function(e){ | |||
var tt = $(e.target); | |||
var tr = tt.closest('tr.datagrid-row'); | |||
if (!tr.length){return} | |||
if (tt.hasClass('datagrid-row-expander')){ | |||
var rowIndex = parseInt(tr.attr('datagrid-row-index')); | |||
if (tt.hasClass('datagrid-row-expand')){ | |||
$(target).datagrid('expandRow', rowIndex); | |||
} else { | |||
$(target).datagrid('collapseRow', rowIndex); | |||
} | |||
$(target).datagrid('fixRowHeight'); | |||
} else { | |||
clickHandler(e); | |||
} | |||
e.stopPropagation(); | |||
}); | |||
}, | |||
onBeforeRender: function(target){ | |||
var state = $.data(target, 'datagrid'); | |||
var opts = state.options; | |||
var dc = state.dc; | |||
var t = $(target); | |||
var hasExpander = false; | |||
var fields = t.datagrid('getColumnFields',true).concat(t.datagrid('getColumnFields')); | |||
for(var i=0; i<fields.length; i++){ | |||
var col = t.datagrid('getColumnOption', fields[i]); | |||
if (col.expander){ | |||
hasExpander = true; | |||
break; | |||
} | |||
} | |||
if (!hasExpander){ | |||
if (opts.frozenColumns && opts.frozenColumns.length){ | |||
opts.frozenColumns[0].splice(0,0,{field:'_expander',expander:true,width:24,resizable:false,fixed:true}); | |||
} else { | |||
opts.frozenColumns = [[{field:'_expander',expander:true,width:24,resizable:false,fixed:true}]]; | |||
} | |||
var t = dc.view1.children('div.datagrid-header').find('table'); | |||
var td = $('<td rowspan="'+opts.frozenColumns.length+'"><div class="datagrid-header-expander" style="width:24px;"></div></td>'); | |||
if ($('tr',t).length == 0){ | |||
td.wrap('<tr></tr>').parent().appendTo($('tbody',t)); | |||
} else if (opts.rownumbers){ | |||
td.insertAfter(t.find('td:has(div.datagrid-header-rownumber)')); | |||
} else { | |||
td.prependTo(t.find('tr:first')); | |||
} | |||
} | |||
// if (!state.bindDetailEvents){ | |||
// state.bindDetailEvents = true; | |||
// var that = this; | |||
// setTimeout(function(){ | |||
// that.bindEvents(target); | |||
// },0); | |||
// } | |||
}, | |||
onAfterRender: function(target){ | |||
var that = this; | |||
var state = $.data(target, 'datagrid'); | |||
var dc = state.dc; | |||
var opts = state.options; | |||
var panel = $(target).datagrid('getPanel'); | |||
$.fn.datagrid.defaults.view.onAfterRender.call(this, target); | |||
if (!state.onResizeColumn){ | |||
state.onResizeColumn = opts.onResizeColumn; | |||
} | |||
if (!state.onResize){ | |||
state.onResize = opts.onResize; | |||
} | |||
function resizeDetails(){ | |||
var ht = dc.header2.find('table'); | |||
var fr = ht.find('tr.datagrid-filter-row').hide(); | |||
var ww = ht.width()-1; | |||
var details = dc.body2.find('>table.datagrid-btable>tbody>tr>td>div.datagrid-row-detail:visible')._outerWidth(ww); | |||
// var details = dc.body2.find('div.datagrid-row-detail:visible')._outerWidth(ww); | |||
details.find('.easyui-fluid').trigger('_resize'); | |||
fr.show(); | |||
} | |||
opts.onResizeColumn = function(field, width){ | |||
if (!opts.fitColumns){ | |||
resizeDetails(); | |||
} | |||
var rowCount = $(target).datagrid('getRows').length; | |||
for(var i=0; i<rowCount; i++){ | |||
$(target).datagrid('fixDetailRowHeight', i); | |||
} | |||
// call the old event code | |||
state.onResizeColumn.call(target, field, width); | |||
}; | |||
opts.onResize = function(width, height){ | |||
if (opts.fitColumns){ | |||
resizeDetails(); | |||
} | |||
state.onResize.call(panel, width, height); | |||
}; | |||
this.canUpdateDetail = true; // define if to update the detail content when 'updateRow' method is called; | |||
var footer = dc.footer1.add(dc.footer2); | |||
footer.find('span.datagrid-row-expander').css('visibility', 'hidden'); | |||
$(target).datagrid('resize'); | |||
this.bindEvents(target); | |||
var detail = dc.body1.add(dc.body2).find('div.datagrid-row-detail'); | |||
detail.unbind().bind('mouseover mouseout click dblclick contextmenu scroll', function(e){ | |||
e.stopPropagation(); | |||
}); | |||
} | |||
}); | |||
$.extend($.fn.datagrid.methods, { | |||
fixDetailRowHeight: function(jq, index){ | |||
return jq.each(function(){ | |||
var opts = $.data(this, 'datagrid').options; | |||
if (!(opts.rownumbers || (opts.frozenColumns && opts.frozenColumns.length))){ | |||
return; | |||
} | |||
var dc = $.data(this, 'datagrid').dc; | |||
var tr1 = opts.finder.getTr(this, index, 'body', 1).next(); | |||
var tr2 = opts.finder.getTr(this, index, 'body', 2).next(); | |||
// fix the detail row height | |||
if (tr2.is(':visible')){ | |||
tr1.css('height', ''); | |||
tr2.css('height', ''); | |||
var height = Math.max(tr1.height(), tr2.height()); | |||
tr1.css('height', height); | |||
tr2.css('height', height); | |||
} | |||
dc.body2.triggerHandler('scroll'); | |||
}); | |||
}, | |||
getExpander: function(jq, index){ // get row expander object | |||
var opts = $.data(jq[0], 'datagrid').options; | |||
return opts.finder.getTr(jq[0], index).find('span.datagrid-row-expander'); | |||
}, | |||
// get row detail container | |||
getRowDetail: function(jq, index){ | |||
var opts = $.data(jq[0], 'datagrid').options; | |||
var tr = opts.finder.getTr(jq[0], index, 'body', 2); | |||
// return tr.next().find('div.datagrid-row-detail'); | |||
return tr.next().find('>td>div.datagrid-row-detail'); | |||
}, | |||
expandRow: function(jq, index){ | |||
return jq.each(function(){ | |||
var opts = $(this).datagrid('options'); | |||
var dc = $.data(this, 'datagrid').dc; | |||
var expander = $(this).datagrid('getExpander', index); | |||
if (expander.hasClass('datagrid-row-expand')){ | |||
expander.removeClass('datagrid-row-expand').addClass('datagrid-row-collapse'); | |||
var tr1 = opts.finder.getTr(this, index, 'body', 1).next(); | |||
var tr2 = opts.finder.getTr(this, index, 'body', 2).next(); | |||
tr1.show(); | |||
tr2.show(); | |||
$(this).datagrid('fixDetailRowHeight', index); | |||
if (opts.onExpandRow){ | |||
var row = $(this).datagrid('getRows')[index]; | |||
opts.onExpandRow.call(this, index, row); | |||
} | |||
} | |||
}); | |||
}, | |||
collapseRow: function(jq, index){ | |||
return jq.each(function(){ | |||
var opts = $(this).datagrid('options'); | |||
var dc = $.data(this, 'datagrid').dc; | |||
var expander = $(this).datagrid('getExpander', index); | |||
if (expander.hasClass('datagrid-row-collapse')){ | |||
expander.removeClass('datagrid-row-collapse').addClass('datagrid-row-expand'); | |||
var tr1 = opts.finder.getTr(this, index, 'body', 1).next(); | |||
var tr2 = opts.finder.getTr(this, index, 'body', 2).next(); | |||
tr1.hide(); | |||
tr2.hide(); | |||
dc.body2.triggerHandler('scroll'); | |||
if (opts.onCollapseRow){ | |||
var row = $(this).datagrid('getRows')[index]; | |||
opts.onCollapseRow.call(this, index, row); | |||
} | |||
} | |||
}); | |||
} | |||
}); | |||
$.extend($.fn.datagrid.methods, { | |||
subgrid: function(jq, conf){ | |||
return jq.each(function(){ | |||
createGrid(this, conf); | |||
function createGrid(target, conf, prow){ | |||
var queryParams = $.extend({}, conf.options.queryParams||{}); | |||
queryParams[conf.options.foreignField] = prow ? prow[conf.options.foreignField] : undefined; | |||
$(target).datagrid($.extend({}, conf.options, { | |||
subgrid: conf.subgrid, | |||
view: (conf.subgrid ? detailview : undefined), | |||
queryParams: queryParams, | |||
detailFormatter: function(index, row){ | |||
return '<div><table class="datagrid-subgrid"></table></div>'; | |||
}, | |||
onExpandRow: function(index, row){ | |||
var opts = $(this).datagrid('options'); | |||
var rd = $(this).datagrid('getRowDetail', index); | |||
var dg = getSubGrid(rd); | |||
if (!dg.data('datagrid')){ | |||
createGrid(dg[0], opts.subgrid, row); | |||
} | |||
rd.find('.easyui-fluid').trigger('_resize'); | |||
setHeight(this, index); | |||
if (conf.options.onExpandRow){ | |||
conf.options.onExpandRow.call(this, index, row); | |||
} | |||
}, | |||
onCollapseRow: function(index, row){ | |||
setHeight(this, index); | |||
if (conf.options.onCollapseRow){ | |||
conf.options.onCollapseRow.call(this, index, row); | |||
} | |||
}, | |||
onResize: function(){ | |||
var dg = $(this).children('div.datagrid-view').children('table') | |||
setParentHeight(this); | |||
}, | |||
onResizeColumn: function(field, width){ | |||
setParentHeight(this); | |||
if (conf.options.onResizeColumn){ | |||
conf.options.onResizeColumn.call(this, field, width); | |||
} | |||
}, | |||
onLoadSuccess: function(data){ | |||
setParentHeight(this); | |||
if (conf.options.onLoadSuccess){ | |||
conf.options.onLoadSuccess.call(this, data); | |||
} | |||
} | |||
})); | |||
} | |||
function getSubGrid(rowDetail){ | |||
var div = $(rowDetail).children('div'); | |||
if (div.children('div.datagrid').length){ | |||
return div.find('>div.datagrid>div.panel-body>div.datagrid-view>table.datagrid-subgrid'); | |||
} else { | |||
return div.find('>table.datagrid-subgrid'); | |||
} | |||
} | |||
function setParentHeight(target){ | |||
var tr = $(target).closest('div.datagrid-row-detail').closest('tr').prev(); | |||
if (tr.length){ | |||
var index = parseInt(tr.attr('datagrid-row-index')); | |||
var dg = tr.closest('div.datagrid-view').children('table'); | |||
setHeight(dg[0], index); | |||
} | |||
} | |||
function setHeight(target, index){ | |||
$(target).datagrid('fixDetailRowHeight', index); | |||
$(target).datagrid('fixRowHeight', index); | |||
var tr = $(target).closest('div.datagrid-row-detail').closest('tr').prev(); | |||
if (tr.length){ | |||
var index = parseInt(tr.attr('datagrid-row-index')); | |||
var dg = tr.closest('div.datagrid-view').children('table'); | |||
setHeight(dg[0], index); | |||
} | |||
} | |||
}); | |||
} | |||
}); |
@@ -0,0 +1,66 @@ | |||
if ($.fn.pagination){ | |||
$.fn.pagination.defaults.beforePageText = '第'; | |||
$.fn.pagination.defaults.afterPageText = '共{pages}页'; | |||
$.fn.pagination.defaults.displayMsg = '显示{from}到{to},共{total}记录'; | |||
} | |||
if ($.fn.datagrid){ | |||
$.fn.datagrid.defaults.loadMsg = '正在处理,请稍待。。。'; | |||
} | |||
if ($.fn.treegrid && $.fn.datagrid){ | |||
$.fn.treegrid.defaults.loadMsg = $.fn.datagrid.defaults.loadMsg; | |||
} | |||
if ($.messager){ | |||
$.messager.defaults.ok = '确定'; | |||
$.messager.defaults.cancel = '取消'; | |||
} | |||
$.map(['validatebox','textbox','filebox','searchbox', | |||
'combo','combobox','combogrid','combotree', | |||
'datebox','datetimebox','numberbox', | |||
'spinner','numberspinner','timespinner','datetimespinner'], function(plugin){ | |||
if ($.fn[plugin]){ | |||
$.fn[plugin].defaults.missingMessage = '该输入项为必输项'; | |||
} | |||
}); | |||
if ($.fn.validatebox){ | |||
$.fn.validatebox.defaults.rules.email.message = '请输入有效的电子邮件地址'; | |||
$.fn.validatebox.defaults.rules.url.message = '请输入有效的URL地址'; | |||
$.fn.validatebox.defaults.rules.length.message = '输入内容长度必须介于{0}和{1}之间'; | |||
$.fn.validatebox.defaults.rules.remote.message = '请修正该字段'; | |||
} | |||
if ($.fn.calendar){ | |||
$.fn.calendar.defaults.weeks = ['日','一','二','三','四','五','六']; | |||
$.fn.calendar.defaults.months = ['一月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月']; | |||
} | |||
if ($.fn.datebox){ | |||
$.fn.datebox.defaults.currentText = '今天'; | |||
$.fn.datebox.defaults.closeText = '关闭'; | |||
$.fn.datebox.defaults.okText = '确定'; | |||
$.fn.datebox.defaults.formatter = function(date){ | |||
var y = date.getFullYear(); | |||
var m = date.getMonth()+1; | |||
var d = date.getDate(); | |||
return y+'-'+(m<10?('0'+m):m)+'-'+(d<10?('0'+d):d); | |||
}; | |||
$.fn.datebox.defaults.parser = function(s){ | |||
if (!s) return new Date(); | |||
var ss = s.split('-'); | |||
var y = parseInt(ss[0],10); | |||
var m = parseInt(ss[1],10); | |||
var d = parseInt(ss[2],10); | |||
if (!isNaN(y) && !isNaN(m) && !isNaN(d)){ | |||
return new Date(y,m-1,d); | |||
} else { | |||
return new Date(); | |||
} | |||
}; | |||
} | |||
if ($.fn.datetimebox && $.fn.datebox){ | |||
$.extend($.fn.datetimebox.defaults,{ | |||
currentText: $.fn.datebox.defaults.currentText, | |||
closeText: $.fn.datebox.defaults.closeText, | |||
okText: $.fn.datebox.defaults.okText | |||
}); | |||
} | |||
if ($.fn.datetimespinner){ | |||
$.fn.datetimespinner.defaults.selections = [[0,4],[5,7],[8,10],[11,13],[14,16],[17,19]] | |||
} |
@@ -0,0 +1,176 @@ | |||
$.extend($.fn.validatebox.defaults.rules, { | |||
fileExtension: { | |||
validator: function (value, param) { | |||
if (value == undefined || value == null || param == undefined || param == null || param.length == 0) return false; | |||
return value.toLowerCase().lastIndexOf(param[0].toLowerCase()) > 0; | |||
}, | |||
message: '请选择{0}格式文件!' | |||
} | |||
}); | |||
//datagrid扩展方法,导出数据 | |||
$.extend($.fn.datagrid.methods, { | |||
export: function (jq, ops) { | |||
if (jq.length == 0) return; | |||
var defaults = { | |||
sheetName: "sheet1" | |||
}; | |||
var options = $.extend(defaults, ops); | |||
var cols = new Array(); | |||
var frozenColumns = this.options(jq).frozenColumns; | |||
if (frozenColumns.length != 0) { | |||
var columns = frozenColumns[frozenColumns.length-1]; | |||
for (var rIndex = 0; rIndex < columns.length; rIndex++) { | |||
var column = columns[rIndex]; | |||
var col = "{'" + column.field + "':'" + column.title + "'}"; | |||
cols.push(eval('(' + col + ')')); | |||
} | |||
} | |||
var ocolumns = this.options(jq).columns; | |||
if (frozenColumns.length != 0) { | |||
var columns = ocolumns[ocolumns.length - 1]; | |||
for (var rIndex = 0; rIndex < columns.length; rIndex++) { | |||
var column = columns[rIndex]; | |||
var col = "{'" + column.field + "':'" + column.title + "'}"; | |||
cols.push(eval('(' + col + ')')); | |||
} | |||
} | |||
var rows = this.getData(jq).rows; | |||
var tableHtml = $.json2Table({ columns: cols, rows: rows }); | |||
var excelFile = "<html xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:x='urn:schemas-microsoft-com:office:excel' xmlns='http://www.w3.org/TR/REC-html40'>"; | |||
excelFile += "<head>"; | |||
excelFile += "<!--[if gte mso 9]>"; | |||
excelFile += "<xml>"; | |||
excelFile += "<x:ExcelWorkbook>"; | |||
excelFile += "<x:ExcelWorksheets>"; | |||
excelFile += "<x:ExcelWorksheet>"; | |||
excelFile += "<x:Name>"; | |||
excelFile += options.sheetName; | |||
excelFile += "</x:Name>"; | |||
excelFile += "<x:WorksheetOptions>"; | |||
excelFile += "<x:DisplayGridlines/>"; | |||
excelFile += "</x:WorksheetOptions>"; | |||
excelFile += "</x:ExcelWorksheet>"; | |||
excelFile += "</x:ExcelWorksheets>"; | |||
excelFile += "</x:ExcelWorkbook>"; | |||
excelFile += "</xml>"; | |||
excelFile += "<![endif]-->"; | |||
excelFile += "<style type=\"text/css\">td{mso-number-format:\"\\@\";border:1px;border-style:solid;}</style>" | |||
excelFile += "</head>"; | |||
excelFile += "<body>"; | |||
excelFile += tableHtml; | |||
excelFile += "</body>"; | |||
excelFile += "</html>"; | |||
var base64data = "base64," + $.base64.encode(excelFile); | |||
window.open('data:application/vnd.ms-excel;filename=exportData.doc;' + base64data); | |||
}, | |||
//ops= { title: "明细", index:0, width: 450, height: 400, modal: false, row: null }; | |||
showPropertyGrid: function (jq, ops) { | |||
if (jq.length == 0 || ops == null) return; | |||
var defaults = { title: "明细", index: 1, width: 450, height: 400, modal: false, row: null }; | |||
var options = $.extend(defaults, ops); | |||
var row = options.row == null ? $(jq).datagrid("getSelected") : options.row | |||
if (row == null) return; | |||
var cols = new Array(); | |||
var frozenColumns = this.options(jq).frozenColumns; | |||
if (frozenColumns.length != 0) { | |||
var fcolumns = this.options(jq).frozenColumns[frozenColumns.length - 1]; | |||
for (var rIndex = 0; rIndex < fcolumns.length; rIndex++) { | |||
var column = fcolumns[rIndex]; | |||
var col = { name: column.title, value: row[column.field] } | |||
cols.push(col); | |||
} | |||
} | |||
var columns = this.options(jq).columns; | |||
if (columns.length != 0) { | |||
var ccolumns = this.options(jq).columns[columns.length - 1]; | |||
for (var rIndex = 0; rIndex < ccolumns.length; rIndex++) { | |||
var column = ccolumns[rIndex]; | |||
var col = { name: column.title, value: row[column.field] } | |||
cols.push(col); | |||
} | |||
} | |||
var id = "t_w_" + options.index; | |||
if ($("#" + id).length == 0) | |||
$("body").append("<div id='" + id + "'><div id='" + id + "1'></div></div>"); | |||
id = "#" + id; | |||
$(id + "1").propertygrid({ | |||
showGroup: false, | |||
fit: true, | |||
fitColumns: true, | |||
scrollbarSize: 0, | |||
columns: [ | |||
[ | |||
{ field: 'name', title: '属性', width: 100, sortable: true }, | |||
{ field: 'value', title: '值', width: 100, resizable: false } | |||
]], | |||
data: cols | |||
}); | |||
$(id).window({ | |||
title: options.title, | |||
modal: options.modal, | |||
width: options.width, | |||
height: options.height, | |||
minimizable: false, | |||
collapsible: false, | |||
top: (options.index * 5), | |||
left: document.body.clientWidth - options.width, | |||
closable: true, | |||
onClose: function () { | |||
$(id).window("destroy"); | |||
} | |||
}); | |||
} | |||
}); | |||
//form的扩展方法获取json数据 | |||
$.extend($.fn.form.methods, { | |||
//获取json格式数据 | |||
getData: function (jq) { | |||
var temp = $(jq).serializeArray(); | |||
var json = {}; | |||
for (var i in temp) { | |||
var row = temp[i]; | |||
json[row.name] = row.value; | |||
} | |||
return json; | |||
} | |||
}); | |||
$.extend($.fn.textbox.methods, { | |||
addClearBtn: function (jq, iconCls) { | |||
return jq.each(function () { | |||
var t = $(this); | |||
var opts = t.textbox('options'); | |||
opts.icons = opts.icons || []; | |||
opts.icons.unshift({ | |||
iconCls: iconCls, | |||
handler: function (e) { | |||
$(e.data.target).textbox('clear').textbox('textbox').focus(); | |||
$(this).css('visibility', 'hidden'); | |||
} | |||
}); | |||
t.textbox(); | |||
if (!t.textbox('getText')) { | |||
t.textbox('getIcon', 0).css('visibility', 'hidden'); | |||
} | |||
t.textbox('textbox').bind('keyup', function () { | |||
var icon = t.textbox('getIcon', 0); | |||
if ($(this).val()) { | |||
icon.css('visibility', 'visible'); | |||
} else { | |||
icon.css('visibility', 'hidden'); | |||
} | |||
}); | |||
}); | |||
} | |||
}); | |||
$.extend($.fn.datagrid.defaults, { | |||
rowHeight: 25, | |||
onBeforeFetch: function (page) { }, | |||
onFetch: function (page, rows) { } | |||
}); | |||