添加项目文件。

This commit is contained in:
2023-03-13 15:00:34 +08:00
parent 42bf06ca3e
commit 1d73df3235
1205 changed files with 185078 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
using JNPF.DependencyInjection;
using JNPF.Systems.Entitys.Permission;
using SqlSugar;
namespace JNPF.TaskScheduler.Listener;
/// <summary>
/// 定时任务demo.
/// </summary>
public class SpareTimeDemo : ISpareTimeWorker
{
/// <summary>
/// 3秒后出勤统计.
/// </summary>
/// <param name="timer">参数</param>
/// <param name="count">次数</param>
[SpareTime("* * * * * ?", "执行Sql", ExecuteType = SpareTimeExecuteTypes.Serial)]
public void ExecSql(SpareTimer timer, long count)
{
// 创建作用域
Scoped.Create((factory, scope) =>
{
// 数据库操作
var sqlSugarRepository = App.GetService<ISqlSugarRepository<UserEntity>>(scope.ServiceProvider);
sqlSugarRepository.DeleteById("226890444955452677");
});
}
/// <summary>
/// 3秒后出勤统计.
/// </summary>
/// <param name="timer">参数</param>
/// <param name="count">次数</param>
[SpareTime("0 0/1 * * * ?", "执行Sql1", ExecuteType = SpareTimeExecuteTypes.Serial)]
public void ExecSql1(SpareTimer timer, long count)
{
// 创建作用域
Scoped.Create((factory, scope) =>
{
var start = DateTime.Now;
Console.WriteLine(start.ToString("yyyy-MM-dd HH:mm:ss") + ":任务开始-----------");
// 数据库操作
var sqlSugarRepository = App.GetService<ISqlSugarRepository<UserEntity>>(scope.ServiceProvider);
sqlSugarRepository.DeleteById("226890444955452677");
var end = DateTime.Now;
Console.WriteLine(end.ToString("yyyy-MM-dd HH:mm:ss") + ":任务结束-----------");
Console.WriteLine($"SQL执行了{count} 次,耗时:{(end - start).TotalMilliseconds}ms");
});
}
}

View File

@@ -0,0 +1,107 @@
using JNPF.Common.Configuration;
using JNPF.Common.Extension;
using JNPF.Common.Security;
using JNPF.DependencyInjection;
using JNPF.EventBus;
using JNPF.EventHandler;
using JNPF.Systems.Entitys.System;
using Microsoft.Extensions.DependencyInjection;
using SqlSugar;
namespace JNPF.TaskScheduler.Listener;
/// <summary>
/// 定时任务监听器.
/// </summary>
public class SpareTimeListener : ISpareTimeListener, ISingleton
{
public SqlSugarScope _sqlSugarClient;
private readonly IEventPublisher _eventPublisher;
/// <summary>
/// 构造函数.
/// </summary>
public SpareTimeListener(
ISqlSugarClient context,
IEventPublisher eventPublisher)
{
_eventPublisher = eventPublisher;
_sqlSugarClient = (SqlSugarScope)context;
}
/// <summary>
/// 监听所有任务.
/// </summary>
/// <param name="executer"></param>
/// <returns></returns>
public async Task OnListener(SpareTimerExecuter executer)
{
switch (executer.Status)
{
// 执行开始通知
case 0:
// Console.WriteLine($"{executer.Timer.WorkerName} 任务开始通知");
break;
// 任务执行之前通知
case 1:
// Console.WriteLine($"{executer.Timer.WorkerName} 执行之前通知");
break;
// 执行成功通知
case 2:
// 任务执行失败通知
case 3:
await RecoreTaskLog(executer);
break;
// 任务执行停止通知
case -1:
// Console.WriteLine($"{executer.Timer.WorkerName} 执行停止通知");
break;
// 任务执行取消通知
case -2:
// Console.WriteLine($"{executer.Timer.WorkerName} 执行取消通知");
break;
}
}
/// <summary>
/// 记录日志.
/// </summary>
/// <param name="executer"></param>
/// <returns></returns>
private async Task RecoreTaskLog(SpareTimerExecuter executer)
{
if (executer.Timer.Description.IsNotEmptyOrNull())
{
var connectionConfig = executer.Timer.Description.ToObject<ConnectionConfigOptions>();
if (KeyVariable.MultiTenancy)
{
if (connectionConfig.ConfigId == null) return;
_sqlSugarClient.AddConnection(JNPFTenantExtensions.GetConfig(connectionConfig));
_sqlSugarClient.ChangeDatabase(connectionConfig.ConfigId);
}
var taskEntity = await _sqlSugarClient.Queryable<TimeTaskEntity>().FirstAsync(x => x.Id == executer.Timer.WorkerName);
var nextRunTime = ((DateTimeOffset)SpareTime.GetCronNextOccurrence(taskEntity.ExecuteCycleJson)).DateTime;
await _eventPublisher.PublishAsync(new TaskEventSource("Task:UpdateTask", connectionConfig, new TimeTaskEntity()
{
Id = taskEntity.Id,
NextRunTime = nextRunTime,
}));
await _eventPublisher.PublishAsync(new TaskLogEventSource("Log:CreateTaskLog", connectionConfig, new TimeTaskLogEntity()
{
Id = SnowflakeIdHelper.NextId(),
TaskId = executer.Timer.WorkerName,
RunTime = DateTime.Now.AddSeconds(10),
RunResult = executer.Status == 2 ? 0 : 1,
Description = executer.Status == 2 ? "执行成功" : "执行失败,失败原因:" + executer.Timer.Exception.ToJsonString()
}));
}
}
}

View File

@@ -0,0 +1,417 @@
using System.Reflection;
using JNPF.Common.Configuration;
using JNPF.Common.Const;
using JNPF.Common.Core.Manager;
using JNPF.Common.Enums;
using JNPF.Common.Extension;
using JNPF.Common.Filter;
using JNPF.Common.Manager;
using JNPF.Common.Security;
using JNPF.DataEncryption;
using JNPF.DependencyInjection;
using JNPF.DynamicApiController;
using JNPF.FriendlyException;
using JNPF.LinqBuilder;
using JNPF.Systems.Interfaces.System;
using JNPF.TaskScheduler.Entitys.Dto.TaskScheduler;
using JNPF.TaskScheduler.Entitys.Entity;
using JNPF.TaskScheduler.Entitys.Enum;
using JNPF.TaskScheduler.Entitys.Model;
using JNPF.TaskScheduler.Interfaces.TaskScheduler;
using Mapster;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
namespace JNPF.TaskScheduler;
/// <summary>
/// 定时任务
/// 版 本V3.4.1
/// 版 权拓通智联科技有限公司http://www.tuotong-tech.com
/// 日 期2021-06-01.
/// </summary>
[ApiDescriptionSettings(Tag = "TaskScheduler", Name = "scheduletask", Order = 220)]
[Route("api/[controller]")]
public class TimeTaskService : ITimeTaskService, IDynamicApiController, ITransient
{
private readonly ISqlSugarRepository<TimeTaskEntity> _repository;
private readonly IDataInterfaceService _dataInterfaceService;
private readonly IUserManager _userManager;
private readonly ICacheManager _cacheManager;
/// <summary>
/// 初始化一个<see cref="TimeTaskService"/>类型的新实例.
/// </summary>
public TimeTaskService(
ISqlSugarRepository<TimeTaskEntity> repository,
IUserManager userManager,
IDataInterfaceService dataInterfaceService,
ICacheManager cacheManager)
{
_repository = repository;
_userManager = userManager;
_dataInterfaceService = dataInterfaceService;
_cacheManager = cacheManager;
}
#region Get
/// <summary>
/// 列表.
/// </summary>
/// <param name="input">请求参数</param>
/// <returns></returns>
[HttpGet("")]
public async Task<dynamic> GetList([FromQuery] PageInputBase input)
{
var queryWhere = LinqExpression.And<TimeTaskEntity>().And(x => x.DeleteMark == null);
if (!string.IsNullOrEmpty(input.keyword))
queryWhere = queryWhere.And(m => m.FullName.Contains(input.keyword) || m.EnCode.Contains(input.keyword));
var list = await _repository.AsQueryable().Where(queryWhere).OrderBy(x => x.CreatorTime, OrderByType.Desc)
.OrderByIF(!string.IsNullOrEmpty(input.keyword), t => t.LastModifyTime, OrderByType.Desc).ToPagedListAsync(input.currentPage, input.pageSize);
var pageList = new SqlSugarPagedList<TimeTaskListOutput>()
{
list = list.list.Adapt<List<TimeTaskListOutput>>(),
pagination = list.pagination
};
return PageResult<TimeTaskListOutput>.SqlSugarPageResult(pageList);
}
/// <summary>
/// 列表(执行记录).
/// </summary>
/// <param name="input">请求参数.</param>
/// <param name="id">任务Id.</param>
/// <returns></returns>
[HttpGet("{id}/TaskLog")]
public async Task<dynamic> GetTaskLogList([FromQuery] TaskLogInput input, string id)
{
var whereLambda = LinqExpression.And<TimeTaskLogEntity>().And(x => x.TaskId == id);
if (input.runResult.IsNotEmptyOrNull())
whereLambda = whereLambda.And(x => x.RunResult == input.runResult);
if (input.endTime != null && input.startTime != null)
{
var start = Convert.ToDateTime(string.Format("{0:yyyy-MM-dd 00:00:00}", input.startTime?.TimeStampToDateTime()));
var end = Convert.ToDateTime(string.Format("{0:yyyy-MM-dd 23:59:59}", input.endTime?.TimeStampToDateTime()));
whereLambda = whereLambda.And(x => SqlFunc.Between(x.RunTime, start, end));
}
var list = await _repository.AsSugarClient().Queryable<TimeTaskLogEntity>().Where(whereLambda).OrderBy(x => x.RunTime, OrderByType.Desc).ToPagedListAsync(input.currentPage, input.pageSize);
var pageList = new SqlSugarPagedList<TimeTaskTaskLogListOutput>()
{
list = list.list.Adapt<List<TimeTaskTaskLogListOutput>>(),
pagination = list.pagination
};
return PageResult<TimeTaskTaskLogListOutput>.SqlSugarPageResult(pageList);
}
/// <summary>
/// 信息.
/// </summary>
/// <param name="id">主键值.</param>
/// <returns></returns>
[HttpGet("Info/{id}")]
public async Task<dynamic> GetInfo_Api(string id)
{
return (await GetInfo(id)).Adapt<TimeTaskInfoOutput>();
}
/// <summary>
/// 本地方法.
/// </summary>
/// <returns></returns>
[HttpGet("TaskMethods")]
public async Task<dynamic> GetTaskMethodSelector()
{
return await GetTaskMethods();
}
#endregion
#region Post
/// <summary>
/// 新建.
/// </summary>
/// <param name="input">实体对象</param>
/// <returns></returns>
[HttpPost("")]
public async Task Create([FromBody] TimeTaskCrInput input)
{
if (await _repository.IsAnyAsync(x => (x.EnCode == input.enCode || x.FullName == input.fullName) && x.DeleteMark == null))
throw Oops.Oh(ErrorCode.COM1004);
var comtentModel = input.executeContent.ToObject<ContentModel>();
comtentModel.TenantId = _userManager.TenantId;
comtentModel.TenantDbName = _userManager.TenantDbName;
comtentModel.ConnectionConfig = _userManager.ConnectionConfig;
comtentModel.Token = _userManager.ToKen;
var entity = input.Adapt<TimeTaskEntity>();
entity.ExecuteContent = comtentModel.ToJsonString();
entity.ExecuteCycleJson = comtentModel.cron;
var result = await _repository.AsInsertable(entity).IgnoreColumns(ignoreNullColumn: true).CallEntityMethod(m => m.Create()).ExecuteReturnEntityAsync();
_ = result ?? throw Oops.Oh(ErrorCode.COM1000);
// 添加到任务调度里
AddTimerJob(result);
}
/// <summary>
/// 更新.
/// </summary>
/// <param name="id">主键值</param>
/// <param name="input">实体对象</param>
/// <returns></returns>
[HttpPut("{id}")]
public async Task Update(string id, [FromBody] TimeTaskUpInput input)
{
if (await _repository.IsAnyAsync(x => x.Id != id && (x.EnCode == input.enCode || x.FullName == input.fullName) && x.DeleteMark == null))
throw Oops.Oh(ErrorCode.COM1004);
var entityOld = await GetInfo(id);
// 先从调度器里取消
SpareTime.Cancel(id);
var entityNew = input.Adapt<TimeTaskEntity>();
entityNew.RunCount = entityOld.RunCount;
var comtentModel = input.executeContent.ToObject<ContentModel>();
comtentModel.TenantId = _userManager.TenantId;
comtentModel.TenantDbName = _userManager.TenantDbName;
comtentModel.ConnectionConfig = _userManager.ConnectionConfig;
comtentModel.Token = _userManager.ToKen;
entityNew.ExecuteContent = comtentModel.ToJsonString();
entityNew.ExecuteCycleJson = comtentModel.cron;
var isOk = await _repository.AsUpdateable(entityNew).IgnoreColumns(ignoreAllNullColumns: true).CallEntityMethod(m => m.LastModify()).ExecuteCommandHasChangeAsync();
if (!isOk)
throw Oops.Oh(ErrorCode.COM1001);
// 再添加到任务调度里
if (entityNew.EnabledMark == 1)
{
AddTimerJob(entityNew);
}
}
/// <summary>
/// 删除.
/// </summary>
/// <param name="id">主键值.</param>
/// <returns></returns>
[HttpDelete("{id}")]
public async Task Delete(string id)
{
var entity = await GetInfo(id);
if (entity == null)
throw Oops.Oh(ErrorCode.COM1005);
var isOk = await _repository.AsUpdateable(entity).CallEntityMethod(m => m.Delete()).UpdateColumns(it => new { it.DeleteMark, it.DeleteTime, it.DeleteUserId }).ExecuteCommandHasChangeAsync();
if (!isOk)
throw Oops.Oh(ErrorCode.COM1002);
// 从调度器里取消
SpareTime.Cancel(entity.Id);
}
/// <summary>
/// 停止.
/// </summary>
/// <param name="id">主键值.</param>
/// <returns></returns>
[HttpPut("{id}/Actions/Stop")]
public async Task Stop(string id)
{
var isOk = await _repository.AsUpdateable().SetColumns(it => new TimeTaskEntity()
{
EnabledMark = SqlFunc.IIF(it.EnabledMark == 1, 0, 1),
LastModifyUserId = _userManager.UserId,
LastModifyTime = SqlFunc.GetDate()
}).Where(it => it.Id.Equals(id)).ExecuteCommandHasChangeAsync();
if (!isOk)
throw Oops.Oh(ErrorCode.COM1003);
SpareTime.Stop(id);
}
/// <summary>
/// 启动.
/// </summary>
/// <param name="id">主键值.</param>
/// <returns></returns>
[HttpPut("{id}/Actions/Enable")]
public async Task Enable(string id)
{
var entity = await GetInfo(id);
var isOk = await _repository.AsUpdateable().SetColumns(it => new TimeTaskEntity()
{
EnabledMark = SqlFunc.IIF(it.EnabledMark == 1, 0, 1),
LastModifyUserId = _userManager.UserId,
LastModifyTime = SqlFunc.GetDate()
}).Where(it => it.Id.Equals(id)).ExecuteCommandHasChangeAsync();
if (!isOk)
throw Oops.Oh(ErrorCode.COM1003);
var comtentModel = entity.ExecuteContent.ToObject<ContentModel>();
comtentModel.Token = _userManager.ToKen;
entity.ExecuteContent = comtentModel.ToJsonString();
var timer = SpareTime.GetWorkers().ToList().Find(u => u.WorkerName == id);
if (timer == null)
{
AddTimerJob(entity);
}
else
{
// 如果 StartNow 为 flase , 执行 AddTimerJob 并不会启动任务
SpareTime.Start(entity.Id);
}
}
#endregion
#region PublicMethod
/// <summary>
/// 启动自启动任务.
/// </summary>
[NonAction]
public void StartTimerJob()
{
// 非多租户模式启动自启任务
if (!KeyVariable.MultiTenancy)
{
_repository.AsQueryable().Where(x => x.DeleteMark == null && x.EnabledMark == 1).ToList().ForEach(AddTimerJob);
}
}
/// <summary>
/// 根据类型执行任务.
/// </summary>
/// <param name="entity">任务实体.</param>
/// <returns></returns>
[NonAction]
public async Task<string> PerformJob(TimeTaskEntity entity)
{
try
{
var model = entity.ExecuteContent.ToObject<ContentModel>();
if (!string.IsNullOrEmpty(model.Token))
{
var claims = JWTEncryption.ReadJwtToken(model.Token.Replace("Bearer ", string.Empty).Replace("bearer ", string.Empty))?.Claims;
var connectionConfig = claims.FirstOrDefault(e => e.Type == ClaimConst.CONNECTIONCONFIG)?.Value.ToObject<ConnectionConfigOptions>();
var parameters = model.parameter.ToDictionary(key => key.field, value => value.value.IsNotEmptyOrNull() ? value.value : value.defaultValue);
await _dataInterfaceService.GetResponseByType(model.interfaceId, 3, model.TenantId, null, parameters);
}
return string.Empty;
}
catch (Exception ex)
{
return ex.Message;
}
}
#endregion
#region PrivateMethod
/// <summary>
/// 详情.
/// </summary>
/// <param name="id">主键.</param>
/// <returns></returns>
private async Task<TimeTaskEntity> GetInfo(string id)
{
return await _repository.GetFirstAsync(x => x.Id == id && x.DeleteMark == null);
}
/// <summary>
/// 新增定时任务.
/// </summary>
/// <param name="input"></param>
private async void AddTimerJob(TimeTaskEntity input)
{
Action<SpareTimer, long>? action = null;
ContentModel? comtentModel = input.ExecuteContent.ToObject<ContentModel>();
input.ExecuteCycleJson = comtentModel.cron;
switch (input.ExecuteType)
{
case "3":
// 查询符合条件的任务方法
TaskMethodInfo? taskMethod = GetTaskMethods()?.Result.FirstOrDefault(m => m.id == comtentModel.localHostTaskId);
if (taskMethod == null) break;
// 创建任务对象
object? typeInstance = Activator.CreateInstance(taskMethod.DeclaringType);
// 创建委托
action = (Action<SpareTimer, long>)Delegate.CreateDelegate(typeof(Action<SpareTimer, long>), typeInstance, taskMethod.MethodName);
break;
default:
action = async (timer, count) =>
{
var msg = await PerformJob(input);
};
break;
}
if (action == null) return;
//SpareTime.Do(comtentModel.cron, action, input.Id, comtentModel.ConnectionConfig.ToJsonString(), true, executeType: SpareTimeExecuteTypes.Parallel);
var starTime = comtentModel.startTime?.TimeStampToDateTime();
var endTime = comtentModel.endTime?.TimeStampToDateTime();
var interval = 1;
if (DateTime.Now < starTime)
{
interval = (starTime.ParseToDateTime() - DateTime.Now).TotalMilliseconds.ParseToInt();
}
SpareTime.DoOnce(interval, action, "Once_" + input.Id);
SpareTime.Do(
() =>
{
var isRun = comtentModel.endTime.IsNullOrEmpty() ? DateTime.Now >= starTime : DateTime.Now >= starTime && DateTime.Now < endTime;
if (isRun)
{
return SpareTime.GetCronNextOccurrence(comtentModel.cron);
}
else
{
return null;
}
},
action, input.Id, comtentModel.ConnectionConfig.ToJsonString(), true, executeType: SpareTimeExecuteTypes.Parallel, cancelInNoneNextTime: false);
}
/// <summary>
/// 获取所有本地任务.
/// </summary>
/// <returns></returns>
private async Task<List<TaskMethodInfo>> GetTaskMethods()
{
var taskMethods = await _cacheManager.GetAsync<List<TaskMethodInfo>>(CommonConst.CACHEKEYTIMERJOB);
if (taskMethods != null) return taskMethods;
// 获取所有本地任务方法必须有spareTimeAttribute特性
taskMethods = App.EffectiveTypes
.Where(u => u.IsClass && !u.IsInterface && !u.IsAbstract && typeof(ISpareTimeWorker).IsAssignableFrom(u))
.SelectMany(u => u.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(m => m.IsDefined(typeof(SpareTimeAttribute), false) &&
m.GetParameters().Length == 2 &&
m.GetParameters()[0].ParameterType == typeof(SpareTimer) &&
m.GetParameters()[1].ParameterType == typeof(long) && m.ReturnType == typeof(void))
.Select(m =>
{
// 默认获取第一条任务特性
var spareTimeAttribute = m.GetCustomAttribute<SpareTimeAttribute>();
return new TaskMethodInfo
{
id = $"{m.DeclaringType.Name}/{m.Name}",
fullName = spareTimeAttribute.WorkerName,
RequestUrl = $"{m.DeclaringType.Name}/{m.Name}",
cron = spareTimeAttribute.CronExpression,
DoOnce = spareTimeAttribute.DoOnce,
ExecuteType = spareTimeAttribute.ExecuteType,
Interval = (int)spareTimeAttribute.Interval / 1000,
StartNow = spareTimeAttribute.StartNow,
RequestType = RequestTypeEnum.Run,
Remark = spareTimeAttribute.Description,
TimerType = string.IsNullOrEmpty(spareTimeAttribute.CronExpression) ? SpareTimeTypes.Interval : SpareTimeTypes.Cron,
MethodName = m.Name,
DeclaringType = m.DeclaringType
};
})).ToList();
await _cacheManager.SetAsync(CommonConst.CACHEKEYTIMERJOB, taskMethods);
return taskMethods;
}
#endregion
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)\common.props" />
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\Tnb.Common.Core\Tnb.Common.Core.csproj" />
<ProjectReference Include="..\..\system\Tnb.Systems.Interfaces\Tnb.Systems.Interfaces.csproj" />
<ProjectReference Include="..\Tnb.TaskScheduler.Interfaces\Tnb.TaskScheduler.Interfaces.csproj" />
</ItemGroup>
</Project>