ソースを参照

新增調度系統

dev_1.0.1
stevelee 2年前
コミット
8608353da7
100個のファイルの変更23940行の追加59行の削除
  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. バイナリ
     
  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. バイナリ
     
  90. バイナリ
     
  91. バイナリ
     
  92. バイナリ
     
  93. バイナリ
     
  94. バイナリ
     
  95. バイナリ
     
  96. バイナリ
     
  97. バイナリ
     
  98. バイナリ
     
  99. バイナリ
     
  100. バイナリ
     

+ 2
- 0
src/BPA.SaaS.TaskSchedule.Api.Bootstrap/BPA.SaaS.TaskSchedule.Api.Bootstrap.csproj ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 544
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/Views/Home/_Cron.cshtml ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

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

+ 516
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/wwwroot/cron.js ファイルの表示

@@ -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
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 480
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/wwwroot/jquery-easyui/datagrid-detailview.js ファイルの表示

@@ -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 ファイルの表示

@@ -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 ファイルの表示

@@ -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
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 2734
- 0
src/BPA.SaaS.TaskSchedule.Api.WebApi/wwwroot/jquery-easyui/themes/black/easyui.css
ファイル差分が大きすぎるため省略します
ファイルの表示


バイナリ
ファイルの表示


バイナリ
ファイルの表示


バイナリ
ファイルの表示


バイナリ
ファイルの表示


バイナリ
ファイルの表示


バイナリ
ファイルの表示


バイナリ
ファイルの表示


バイナリ
ファイルの表示


バイナリ
ファイルの表示


バイナリ
ファイルの表示


バイナリ
ファイルの表示


バイナリ
ファイルの表示


変更されたファイルが多すぎるため、一部のファイルは表示されません

読み込み中…
キャンセル
保存