Ver código fonte

新增調度系統

dev_1.0.1
stevelee 2 anos atrás
pai
commit
8608353da7
100 arquivos alterados com 23940 adições e 59 exclusões
  1. +2
    -0
      src/BPA.SaaS.TaskSchedule.Api.Bootstrap/BPA.SaaS.TaskSchedule.Api.Bootstrap.csproj
  2. +10
    -3
      src/BPA.SaaS.TaskSchedule.Api.Bootstrap/SetupBootstrap.cs
  3. +95
    -0
      src/BPA.SaaS.TaskSchedule.Api.Bootstrap/SetupTaskScheduleConfig.cs
  4. +25
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/EnumExecutedResultType.cs
  5. +11
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/EnumRequestMethodType.cs
  6. +9
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/EnumRequestURLType.cs
  7. +13
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/EnumTaskType.cs
  8. +13
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/EnumTriggerType.cs
  9. +15
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/SystemDictionary/DeleteSystemDictionaryReq.cs
  10. +39
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/SystemDictionary/SaveSystemDictionaryReq.cs
  11. +34
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/SystemDictionary/SystemDictionaryDTO.cs
  12. +13
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/TaskInfo/DeleteTaskInfoReq.cs
  13. +29
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/TaskInfo/DoOnceReq.cs
  14. +118
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/TaskInfo/SaveTaskInfoReq.cs
  15. +133
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/TaskInfo/TaskInfoDTO.cs
  16. +19
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/TaskInfo/TryRegisterTaskReq.cs
  17. +97
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/TaskInfoTag.cs
  18. +23
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/TaskLog/AddAnyTimeLogReq.cs
  19. +34
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/TaskLog/AddAnyTimeLogsReq.cs
  20. +35
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/TaskLog/GetTaskLogPageReq.cs
  21. +104
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/TaskLog/TaskLogDTO.cs
  22. +30
    -0
      src/BPA.SaaS.TaskSchedule.Api.DTO/TaskScheduleLangPackge.cs
  23. +17
    -13
      src/BPA.SaaS.TaskSchedule.Api.Entity/BPA.SaaS.TaskSchedule.Api.Entity.csproj
  24. +39
    -0
      src/BPA.SaaS.TaskSchedule.Api.Entity/CrmSystemDictionary.cs
  25. +126
    -0
      src/BPA.SaaS.TaskSchedule.Api.Entity/CrmTaskInfo.cs
  26. +106
    -0
      src/BPA.SaaS.TaskSchedule.Api.Entity/CrmTaskLog.cs
  27. +6
    -5
      src/BPA.SaaS.TaskSchedule.Api.IRepository/BPA.SaaS.TaskSchedule.Api.IRepository.csproj
  28. +9
    -0
      src/BPA.SaaS.TaskSchedule.Api.IRepository/ISystemDictionaryRepository.cs
  29. +9
    -0
      src/BPA.SaaS.TaskSchedule.Api.IRepository/ITaskInfoRepository.cs
  30. +10
    -0
      src/BPA.SaaS.TaskSchedule.Api.IRepository/ITaskLogRepository.cs
  31. +7
    -0
      src/BPA.SaaS.TaskSchedule.Api.IService/AnyTimeTaskScheduleMsgBody.cs
  32. +5
    -1
      src/BPA.SaaS.TaskSchedule.Api.IService/BPA.SaaS.TaskSchedule.Api.IService.csproj
  33. +13
    -0
      src/BPA.SaaS.TaskSchedule.Api.IService/IAnyTimeTaskScheduleExecutorService.cs
  34. +10
    -0
      src/BPA.SaaS.TaskSchedule.Api.IService/ISynData.cs
  35. +8
    -0
      src/BPA.SaaS.TaskSchedule.Api.IService/ISynRequestSchedulerTriggerListenerService.cs
  36. +46
    -0
      src/BPA.SaaS.TaskSchedule.Api.IService/ISystemDictionaryService.cs
  37. +65
    -0
      src/BPA.SaaS.TaskSchedule.Api.IService/ITaskInfoService.cs
  38. +37
    -0
      src/BPA.SaaS.TaskSchedule.Api.IService/ITaskLogService.cs
  39. +16
    -0
      src/BPA.SaaS.TaskSchedule.Api.IService/IWarnAlarm.cs
  40. +20
    -0
      src/BPA.SaaS.TaskSchedule.Api.IService/IWarnManagerService.cs
  41. +13
    -0
      src/BPA.SaaS.TaskSchedule.Api.IService/SynDataTaskScheduleMsgBody.cs
  42. +4
    -1
      src/BPA.SaaS.TaskSchedule.Api.Model/BPA.SaaS.TaskSchedule.Api.Model.csproj
  43. +38
    -11
      src/BPA.SaaS.TaskSchedule.Api.Model/TaskScheduleApiConfig.cs
  44. +0
    -18
      src/BPA.SaaS.TaskSchedule.Api.Model/TaskScheduleDbSugarClient.cs
  45. +15
    -0
      src/BPA.SaaS.TaskSchedule.Api.Model/TaskScheduleMongoDbClient.cs
  46. +51
    -0
      src/BPA.SaaS.TaskSchedule.Api.Model/TaskScheduleRedisKey.cs
  47. +2
    -1
      src/BPA.SaaS.TaskSchedule.Api.Repository/BPA.SaaS.TaskSchedule.Api.Repository.csproj
  48. +15
    -0
      src/BPA.SaaS.TaskSchedule.Api.Repository/SystemDictionaryRepository.cs
  49. +14
    -0
      src/BPA.SaaS.TaskSchedule.Api.Repository/TaskInfoRepository.cs
  50. +14
    -0
      src/BPA.SaaS.TaskSchedule.Api.Repository/TaskLogRepository.cs
  51. +164
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/AnyTimeTaskScheduleExecutorService.cs
  52. +1
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/BPA.SaaS.TaskSchedule.Api.Service.csproj
  53. +52
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/Queue/AnyTimeTaskScheduleConsumer.cs
  54. +29
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/Queue/AnyTimeTaskScheduleProducer.cs
  55. +50
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/Queue/SynDataTaskScheduleConsumer.cs
  56. +22
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/Queue/SynDataTaskScheduleProducer.cs
  57. +187
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/QzScheduler/AnyTimeHttpJobScheduler.cs
  58. +169
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/QzScheduler/AnyTimeSchedulerManager.cs
  59. +24
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/QzScheduler/BaseRequestHttpJobScheduler.cs
  60. +39
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/QzScheduler/JobSchedulerFactory.cs
  61. +186
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/QzScheduler/SynRequestHttpJobScheduler.cs
  62. +170
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/QzScheduler/SynRequestSchedulerManager.cs
  63. +76
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/QzScheduler/SynRequestSchedulerTriggerListenerService.cs
  64. +309
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/QzScheduler/TaskLogServiceBak.txt
  65. +185
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/SystemDictionaryService.cs
  66. +502
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/TaskInfoService.cs
  67. +228
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/TaskLogService.cs
  68. +59
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/WarnAlarms/WechatWebhookWarnAlarm.cs
  69. +76
    -0
      src/BPA.SaaS.TaskSchedule.Api.Service/WarnManagerService.cs
  70. +172
    -0
      src/BPA.SaaS.TaskSchedule.Api.WebApi/Controllers/HomeController.cs
  71. +60
    -0
      src/BPA.SaaS.TaskSchedule.Api.WebApi/Controllers/TaskInfoController.cs
  72. +65
    -0
      src/BPA.SaaS.TaskSchedule.Api.WebApi/Controllers/TaskLogController.cs
  73. +2
    -4
      src/BPA.SaaS.TaskSchedule.Api.WebApi/Program.cs
  74. +2
    -2
      src/BPA.SaaS.TaskSchedule.Api.WebApi/Properties/launchSettings.json
  75. +997
    -0
      src/BPA.SaaS.TaskSchedule.Api.WebApi/Views/Home/Index.cshtml
  76. +544
    -0
      src/BPA.SaaS.TaskSchedule.Api.WebApi/Views/Home/_Cron.cshtml
  77. +25
    -0
      src/BPA.SaaS.TaskSchedule.Api.WebApi/Views/Shared/Error.cshtml
  78. +22
    -0
      src/BPA.SaaS.TaskSchedule.Api.WebApi/Views/Shared/_Layout.cshtml
  79. +2
    -0
      src/BPA.SaaS.TaskSchedule.Api.WebApi/Views/_ViewImports.cshtml
  80. +3
    -0
      src/BPA.SaaS.TaskSchedule.Api.WebApi/Views/_ViewStart.cshtml
  81. +516
    -0
      src/BPA.SaaS.TaskSchedule.Api.WebApi/wwwroot/cron.js
  82. BIN
     
  83. +19
    -0
      src/BPA.SaaS.TaskSchedule.Api.WebApi/wwwroot/jquery-1.8.2.min.js
  84. +480
    -0
      src/BPA.SaaS.TaskSchedule.Api.WebApi/wwwroot/jquery-easyui/datagrid-detailview.js
  85. +66
    -0
      src/BPA.SaaS.TaskSchedule.Api.WebApi/wwwroot/jquery-easyui/easyui-lang-zh_CN.js
  86. +176
    -0
      src/BPA.SaaS.TaskSchedule.Api.WebApi/wwwroot/jquery-easyui/jquery.easyui.min.extend.js
  87. +13911
    -0
      src/BPA.SaaS.TaskSchedule.Api.WebApi/wwwroot/jquery-easyui/jquery.easyui.min.js
  88. +2734
    -0
      src/BPA.SaaS.TaskSchedule.Api.WebApi/wwwroot/jquery-easyui/themes/black/easyui.css
  89. BIN
     
  90. BIN
     
  91. BIN
     
  92. BIN
     
  93. BIN
     
  94. BIN
     
  95. BIN
     
  96. BIN
     
  97. BIN
     
  98. BIN
     
  99. BIN
     
  100. BIN
     

+ 2
- 0
src/BPA.SaaS.TaskSchedule.Api.Bootstrap/BPA.SaaS.TaskSchedule.Api.Bootstrap.csproj Ver arquivo

@@ -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" />


+ 10
- 3
src/BPA.SaaS.TaskSchedule.Api.Bootstrap/SetupBootstrap.cs Ver arquivo

@@ -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);


+ 95
- 0
src/BPA.SaaS.TaskSchedule.Api.Bootstrap/SetupTaskScheduleConfig.cs Ver arquivo

@@ -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;
}
}
}

+ 25
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/EnumExecutedResultType.cs Ver arquivo

@@ -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,
}

+ 11
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/EnumRequestMethodType.cs Ver arquivo

@@ -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,
}

+ 9
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/EnumRequestURLType.cs Ver arquivo

@@ -0,0 +1,9 @@
namespace BPA.SaaS.TaskSchedule.Api.DTO;

public enum EnumRequestURLType
{
/// <summary>自定义</summary>
Custom = 1,
/// <summary>业务系统Api</summary>
BizSysApi = 2,
}

+ 13
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/EnumTaskType.cs Ver arquivo

@@ -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,
}

+ 13
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/EnumTriggerType.cs Ver arquivo

@@ -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,
}

+ 15
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/SystemDictionary/DeleteSystemDictionaryReq.cs Ver arquivo

@@ -0,0 +1,15 @@
namespace BPA.SaaS.TaskSchedule.Api.DTO.SystemDictionary
{
/// <summary>
/// 删除
/// </summary>
public class DeleteSystemDictionaryReq
{

/// <summary>
/// 主键Id
/// </summary>
public long Id { set; get; }
}
}

+ 39
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/SystemDictionary/SaveSystemDictionaryReq.cs Ver arquivo

@@ -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,
}

+ 34
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/SystemDictionary/SystemDictionaryDTO.cs Ver arquivo

@@ -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; }
}
}

+ 13
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/TaskInfo/DeleteTaskInfoReq.cs Ver arquivo

@@ -0,0 +1,13 @@
namespace BPA.SaaS.TaskSchedule.Api.DTO.TaskInfo
{
/// <summary>
/// 删除
/// </summary>
public class DeleteTaskInfoReq
{
/// <summary>
/// 主键Id
/// </summary>
public long Id { set; get; }
}
}

+ 29
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/TaskInfo/DoOnceReq.cs Ver arquivo

@@ -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; }
}
}

+ 118
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/TaskInfo/SaveTaskInfoReq.cs Ver arquivo

@@ -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,
}

+ 133
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/TaskInfo/TaskInfoDTO.cs Ver arquivo

@@ -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; }
}
}

+ 19
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/TaskInfo/TryRegisterTaskReq.cs Ver arquivo

@@ -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; }
}
}

+ 97
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/TaskInfoTag.cs Ver arquivo

@@ -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";
}
}

+ 23
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/TaskLog/AddAnyTimeLogReq.cs Ver arquivo

@@ -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; }
}
}

+ 34
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/TaskLog/AddAnyTimeLogsReq.cs Ver arquivo

@@ -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; }
}
}

+ 35
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/TaskLog/GetTaskLogPageReq.cs Ver arquivo

@@ -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; }
}
}

+ 104
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/TaskLog/TaskLogDTO.cs Ver arquivo

@@ -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; }
}
}

+ 30
- 0
src/BPA.SaaS.TaskSchedule.Api.DTO/TaskScheduleLangPackge.cs Ver arquivo

@@ -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");
}
}

+ 17
- 13
src/BPA.SaaS.TaskSchedule.Api.Entity/BPA.SaaS.TaskSchedule.Api.Entity.csproj Ver arquivo

@@ -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>

+ 39
- 0
src/BPA.SaaS.TaskSchedule.Api.Entity/CrmSystemDictionary.cs Ver arquivo

@@ -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; }
}

+ 126
- 0
src/BPA.SaaS.TaskSchedule.Api.Entity/CrmTaskInfo.cs Ver arquivo

@@ -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; }
}
}

+ 106
- 0
src/BPA.SaaS.TaskSchedule.Api.Entity/CrmTaskLog.cs Ver arquivo

@@ -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; }
}
}

+ 6
- 5
src/BPA.SaaS.TaskSchedule.Api.IRepository/BPA.SaaS.TaskSchedule.Api.IRepository.csproj Ver arquivo

@@ -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>

+ 9
- 0
src/BPA.SaaS.TaskSchedule.Api.IRepository/ISystemDictionaryRepository.cs Ver arquivo

@@ -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>
{
}
}

+ 9
- 0
src/BPA.SaaS.TaskSchedule.Api.IRepository/ITaskInfoRepository.cs Ver arquivo

@@ -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>
{
}
}

+ 10
- 0
src/BPA.SaaS.TaskSchedule.Api.IRepository/ITaskLogRepository.cs Ver arquivo

@@ -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>
{
}
}

+ 7
- 0
src/BPA.SaaS.TaskSchedule.Api.IService/AnyTimeTaskScheduleMsgBody.cs Ver arquivo

@@ -0,0 +1,7 @@
namespace BPA.SaaS.TaskSchedule.Api.IService;

public class AnyTimeTaskScheduleMsgBody
{
/// <summary>任务日志ID</summary>
public long TaskLogId { set; get; }
}

+ 5
- 1
src/BPA.SaaS.TaskSchedule.Api.IService/BPA.SaaS.TaskSchedule.Api.IService.csproj Ver arquivo

@@ -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>

+ 13
- 0
src/BPA.SaaS.TaskSchedule.Api.IService/IAnyTimeTaskScheduleExecutorService.cs Ver arquivo

@@ -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);
}

+ 10
- 0
src/BPA.SaaS.TaskSchedule.Api.IService/ISynData.cs Ver arquivo

@@ -0,0 +1,10 @@
namespace BPA.SaaS.TaskSchedule.Api.IService;

public interface ISynData
{
/// <summary>
/// 当数据发生改变
/// </summary>
/// <param name="msgBody"></param>
void OnDataChange(SynDataTaskScheduleMsgBody msgBody);
}

+ 8
- 0
src/BPA.SaaS.TaskSchedule.Api.IService/ISynRequestSchedulerTriggerListenerService.cs Ver arquivo

@@ -0,0 +1,8 @@
using Quartz;

namespace BPA.SaaS.TaskSchedule.Api.IService
{
public interface ISynRequestSchedulerTriggerListenerService : ITriggerListener
{
}
}

+ 46
- 0
src/BPA.SaaS.TaskSchedule.Api.IService/ISystemDictionaryService.cs Ver arquivo

@@ -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();

}
}

+ 65
- 0
src/BPA.SaaS.TaskSchedule.Api.IService/ITaskInfoService.cs Ver arquivo

@@ -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();
}
}

+ 37
- 0
src/BPA.SaaS.TaskSchedule.Api.IService/ITaskLogService.cs Ver arquivo

@@ -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();
}
}

+ 16
- 0
src/BPA.SaaS.TaskSchedule.Api.IService/IWarnAlarm.cs Ver arquivo

@@ -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);
}
}

+ 20
- 0
src/BPA.SaaS.TaskSchedule.Api.IService/IWarnManagerService.cs Ver arquivo

@@ -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);

}
}

+ 13
- 0
src/BPA.SaaS.TaskSchedule.Api.IService/SynDataTaskScheduleMsgBody.cs Ver arquivo

@@ -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; }
}

+ 4
- 1
src/BPA.SaaS.TaskSchedule.Api.Model/BPA.SaaS.TaskSchedule.Api.Model.csproj Ver arquivo

@@ -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>

+ 38
- 11
src/BPA.SaaS.TaskSchedule.Api.Model/TaskScheduleApiConfig.cs Ver arquivo

@@ -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)


+ 0
- 18
src/BPA.SaaS.TaskSchedule.Api.Model/TaskScheduleDbSugarClient.cs Ver arquivo

@@ -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)
{
}
}

+ 15
- 0
src/BPA.SaaS.TaskSchedule.Api.Model/TaskScheduleMongoDbClient.cs Ver arquivo

@@ -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;
}
}

+ 51
- 0
src/BPA.SaaS.TaskSchedule.Api.Model/TaskScheduleRedisKey.cs Ver arquivo

@@ -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))}";
}
}
}

+ 2
- 1
src/BPA.SaaS.TaskSchedule.Api.Repository/BPA.SaaS.TaskSchedule.Api.Repository.csproj Ver arquivo

@@ -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" />


+ 15
- 0
src/BPA.SaaS.TaskSchedule.Api.Repository/SystemDictionaryRepository.cs Ver arquivo

@@ -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)
{
}
}
}

+ 14
- 0
src/BPA.SaaS.TaskSchedule.Api.Repository/TaskInfoRepository.cs Ver arquivo

@@ -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)
{
}
}
}

+ 14
- 0
src/BPA.SaaS.TaskSchedule.Api.Repository/TaskLogRepository.cs Ver arquivo

@@ -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)
{
}
}
}

+ 164
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/AnyTimeTaskScheduleExecutorService.cs Ver arquivo

@@ -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;
}
}
}

+ 1
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/BPA.SaaS.TaskSchedule.Api.Service.csproj Ver arquivo

@@ -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" />


+ 52
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/Queue/AnyTimeTaskScheduleConsumer.cs Ver arquivo

@@ -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;
}
}
}
}

+ 29
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/Queue/AnyTimeTaskScheduleProducer.cs Ver arquivo

@@ -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);
}
}
}

+ 50
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/Queue/SynDataTaskScheduleConsumer.cs Ver arquivo

@@ -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;
}
}
}
}

+ 22
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/Queue/SynDataTaskScheduleProducer.cs Ver arquivo

@@ -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);
}
}
}

+ 187
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/QzScheduler/AnyTimeHttpJobScheduler.cs Ver arquivo

@@ -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()
{
}
}
}

+ 169
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/QzScheduler/AnyTimeSchedulerManager.cs Ver arquivo

@@ -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());


}
}

+ 24
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/QzScheduler/BaseRequestHttpJobScheduler.cs Ver arquivo

@@ -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);
}
}
}

+ 39
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/QzScheduler/JobSchedulerFactory.cs Ver arquivo

@@ -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();
}
}
}

+ 186
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/QzScheduler/SynRequestHttpJobScheduler.cs Ver arquivo

@@ -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()
{
}
}
}

+ 170
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/QzScheduler/SynRequestSchedulerManager.cs Ver arquivo

@@ -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);
}
}
}
}

+ 76
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/QzScheduler/SynRequestSchedulerTriggerListenerService.cs Ver arquivo

@@ -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);
}
}
}

+ 309
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/QzScheduler/TaskLogServiceBak.txt Ver arquivo

@@ -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);
}

}
}

+ 185
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/SystemDictionaryService.cs Ver arquivo

@@ -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
});
});
}
}
}

+ 502
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/TaskInfoService.cs Ver arquivo

@@ -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
};
}
}

+ 228
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/TaskLogService.cs Ver arquivo

@@ -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);
}
}
}

+ 59
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/WarnAlarms/WechatWebhookWarnAlarm.cs Ver arquivo

@@ -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}");
}
}
}

+ 76
- 0
src/BPA.SaaS.TaskSchedule.Api.Service/WarnManagerService.cs Ver arquivo

@@ -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;
}
}
}

+ 172
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/Controllers/HomeController.cs Ver arquivo

@@ -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());
}
}

+ 60
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/Controllers/TaskInfoController.cs Ver arquivo

@@ -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);
}
}

+ 65
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/Controllers/TaskLogController.cs Ver arquivo

@@ -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);
}
}

+ 2
- 4
src/BPA.SaaS.TaskSchedule.Api.WebApi/Program.cs Ver arquivo

@@ -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();


+ 2
- 2
src/BPA.SaaS.TaskSchedule.Api.WebApi/Properties/launchSettings.json Ver arquivo

@@ -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",


+ 997
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/Views/Home/Index.cshtml
Diferenças do arquivo suprimidas por serem muito extensas
Ver arquivo


+ 544
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/Views/Home/_Cron.cshtml Ver arquivo

@@ -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

+ 25
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/Views/Shared/Error.cshtml Ver arquivo

@@ -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>

+ 22
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/Views/Shared/_Layout.cshtml Ver arquivo

@@ -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>

+ 2
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/Views/_ViewImports.cshtml Ver arquivo

@@ -0,0 +1,2 @@
@using BPA.SaaS.TaskSchedule.Api
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

+ 3
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/Views/_ViewStart.cshtml Ver arquivo

@@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

+ 516
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/wwwroot/cron.js Ver arquivo

@@ -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();
}



+ 19
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/wwwroot/jquery-1.8.2.min.js
Diferenças do arquivo suprimidas por serem muito extensas
Ver arquivo


+ 480
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/wwwroot/jquery-easyui/datagrid-detailview.js Ver arquivo

@@ -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('&nbsp;');
} 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);
}
}
});
}
});

+ 66
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/wwwroot/jquery-easyui/easyui-lang-zh_CN.js Ver arquivo

@@ -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]]
}

+ 176
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/wwwroot/jquery-easyui/jquery.easyui.min.extend.js Ver arquivo

@@ -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) { }
});


+ 13911
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/wwwroot/jquery-easyui/jquery.easyui.min.js
Diferenças do arquivo suprimidas por serem muito extensas
Ver arquivo


+ 2734
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/wwwroot/jquery-easyui/themes/black/easyui.css
Diferenças do arquivo suprimidas por serem muito extensas
Ver arquivo














Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff

Carregando…
Cancelar
Salvar