Browse Source

Merge pull request #457 from colinin/task-management

Add Module Task management
pull/474/head
yx lin 4 years ago
committed by GitHub
parent
commit
ecf15e8562
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 78
      apps/vue/src/api/task-management/backgroundJobInfo.ts
  2. 32
      apps/vue/src/api/task-management/backgroundJobLog.ts
  3. 89
      apps/vue/src/api/task-management/model/backgroundJobInfoModel.ts
  4. 22
      apps/vue/src/api/task-management/model/backgroundJobLogModel.ts
  5. 338
      apps/vue/src/views/task-management/background-jobs/components/BackgroundJobInfoModal.vue
  6. 167
      apps/vue/src/views/task-management/background-jobs/components/BackgroundJobInfoTable.vue
  7. 154
      apps/vue/src/views/task-management/background-jobs/datas/ModalData.ts
  8. 125
      apps/vue/src/views/task-management/background-jobs/datas/TableData.ts
  9. 43
      apps/vue/src/views/task-management/background-jobs/datas/typing.ts
  10. 15
      apps/vue/src/views/task-management/background-jobs/index.vue
  11. 2
      apps/vue/types/abp.d.ts
  12. 7
      aspnet-core/.editorconfig
  13. 2
      aspnet-core/LINGYUN.MicroService.All.sln
  14. 111
      aspnet-core/LINGYUN.MicroService.TaskManagement.sln
  15. 21
      aspnet-core/modules/platform/LINGYUN.Abp.UI.Navigation.VueVbenAdmin/LINGYUN/Abp/UI/Navigation/VueVbenAdmin/AbpUINavigationVueVbenAdminNavigationDefinitionProvider.cs
  16. 3
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/FodyWeavers.xml
  17. 30
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/FodyWeavers.xsd
  18. 15
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN.Abp.BackgroundTasks.Abstractions.csproj
  19. 42
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTaskConcurrentException.cs
  20. 33
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksAbstractionsModule.cs
  21. 84
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksOptions.cs
  22. 44
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/AbpJobExecutionException.cs
  23. 22
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/IJobEvent.cs
  24. 11
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/IJobRunnable.cs
  25. 58
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobEventBase.cs
  26. 17
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobEventContext.cs
  27. 90
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobEventData.cs
  28. 104
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobInfo.cs
  29. 15
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobPriority.cs
  30. 26
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobRunnableContext.cs
  31. 85
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobRunnableContextExtensions.cs
  32. 29
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobStatus.cs
  33. 19
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobType.cs
  34. 3
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/FodyWeavers.xml
  35. 30
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/FodyWeavers.xsd
  36. 22
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN.Abp.BackgroundTasks.Jobs.csproj
  37. 27
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/AbpBackgroundTasksJobsModule.cs
  38. 15
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/ConsoleJob.cs
  39. 29
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/DefaultJobNames.cs
  40. 86
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/HttpRequestJob.cs
  41. 26
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/SendEmailJob.cs
  42. 38
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/SendSmsJob.cs
  43. 261
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/ServiceInvocationJob.cs
  44. 16
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/SleepJob.cs
  45. 3
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/FodyWeavers.xml
  46. 30
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/FodyWeavers.xsd
  47. 19
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN.Abp.BackgroundTasks.Quartz.csproj
  48. 19
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/AbpBackgroundTasksQuartzModule.cs
  49. 12
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/IQuartzJobExecutorProvider.cs
  50. 14
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobConcurrentAdapter.cs
  51. 112
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobExecutorProvider.cs
  52. 149
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobListener.cs
  53. 163
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobScheduler.cs
  54. 35
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobSimpleAdapter.cs
  55. 27
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/Quartz/IJobExecutionContextExtensions.cs
  56. 16
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/README.md
  57. 3
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/FodyWeavers.xml
  58. 30
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/FodyWeavers.xsd
  59. 22
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN.Abp.BackgroundTasks.csproj
  60. 23
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksModule.cs
  61. 46
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundJobAdapter.cs
  62. 94
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundJobManager.cs
  63. 15
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobEventProvider.cs
  64. 12
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobRunnableExecuter.cs
  65. 72
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobScheduler.cs
  66. 27
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobStore.cs
  67. 20
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundCleaningJob.cs
  68. 27
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundKeepAliveJob.cs
  69. 34
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundPollingJob.cs
  70. 109
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/DefaultBackgroundWorker.cs
  71. 87
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/InMemoryJobStore.cs
  72. 41
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobEventProvider.cs
  73. 78
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobExecutedEvent.cs
  74. 27
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobLogEvent.cs
  75. 64
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/JobRunnableExecuter.cs
  76. 118
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/README.md
  77. 3
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/FodyWeavers.xml
  78. 30
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/FodyWeavers.xsd
  79. 20
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN.Abp.TaskManagement.Application.Contracts.csproj
  80. 36
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoCreateDto.cs
  81. 56
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoCreateOrUpdateDto.cs
  82. 104
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoDto.cs
  83. 65
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoGetListInput.cs
  84. 8
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoUpdateDto.cs
  85. 14
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobLogDto.cs
  86. 37
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobLogGetListInput.cs
  87. 22
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/IBackgroundJobInfoAppService.cs
  88. 12
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/IBackgroundJobLogAppService.cs
  89. 64
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/Permissions/TaskManagementPermissionDefinitionProvider.cs
  90. 24
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/Permissions/TaskManagementPermissions.cs
  91. 10
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/TaskManagementApplicationContractsModule.cs
  92. 8
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/TaskManagementRemoteServiceConsts.cs
  93. 3
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/FodyWeavers.xml
  94. 30
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/FodyWeavers.xsd
  95. 20
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN.Abp.TaskManagement.Application.csproj
  96. 176
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/BackgroundJobInfoAppService.cs
  97. 53
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/BackgroundJobLogAppService.cs
  98. 12
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/TaskManagementApplicationMapperProfile.cs
  99. 22
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/TaskManagementApplicationModule.cs
  100. 13
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/TaskManagementApplicationService.cs

78
apps/vue/src/api/task-management/backgroundJobInfo.ts

@ -0,0 +1,78 @@
import { defAbpHttp } from '/@/utils/http/abp';
import {
BackgroundJobInfo,
BackgroundJobInfoCreate,
BackgroundJobInfoUpdate,
BackgroundJobInfoGetListInput,
} from './model/backgroundJobInfoModel';
import { format } from '/@/utils/strings';
import { PagedResultDto } from '../model/baseModel';
enum Api {
GetById = '/api/task-management/background-jobs/{id}',
GetList = '/api/task-management/background-jobs',
Create = '/api/task-management/background-jobs',
Update = '/api/task-management/background-jobs/{id}',
Delete = '/api/task-management/background-jobs/{id}',
Pause = '/api/task-management/background-jobs/{id}/pause',
Resume = '/api/task-management/background-jobs/{id}/resume',
Trigger = '/api/task-management/background-jobs/{id}/trigger',
Stop = '/api/task-management/background-jobs/{id}/stop',
}
export const getById = (id: string) => {
return defAbpHttp.get<BackgroundJobInfo>({
url: format(Api.GetById, { id: id }),
});
};
export const getList = (input: BackgroundJobInfoGetListInput) => {
return defAbpHttp.get<PagedResultDto<BackgroundJobInfo>>({
url: Api.GetList,
params: input,
});
};
export const create = (input: BackgroundJobInfoCreate) => {
return defAbpHttp.post<BackgroundJobInfo>({
url: Api.Create,
data: input,
});
};
export const update = (id: string, input: BackgroundJobInfoUpdate) => {
return defAbpHttp.put<BackgroundJobInfo>({
url: format(Api.Update, { id: id }),
data: input,
});
};
export const deleteById = (id: string) => {
return defAbpHttp.delete<void>({
url: format(Api.Delete, { id: id }),
});
};
export const pause = (id: string) => {
return defAbpHttp.put<void>({
url: format(Api.Pause, { id: id }),
});
};
export const resume = (id: string) => {
return defAbpHttp.put<void>({
url: format(Api.Resume, { id: id }),
});
};
export const trigger = (id: string) => {
return defAbpHttp.put<void>({
url: format(Api.Trigger, { id: id }),
});
};
export const stop = (id: string) => {
return defAbpHttp.put<void>({
url: format(Api.Stop, { id: id }),
});
};

32
apps/vue/src/api/task-management/backgroundJobLog.ts

@ -0,0 +1,32 @@
import { defAbpHttp } from '/@/utils/http/abp';
import {
BackgroundJobLog,
BackgroundJobLogGetListInput,
} from './model/backgroundJobLogModel';
import { format } from '/@/utils/strings';
import { PagedResultDto } from '../model/baseModel';
enum Api {
GetById = '/api/task-management/background-jobs/logs/{id}',
GetList = '/api/task-management/background-jobs/logs',
Delete = '/api/task-management/background-jobs/logs/{id}',
}
export const getById = (id: string) => {
return defAbpHttp.get<BackgroundJobLog>({
url: format(Api.GetById, { id: id }),
});
};
export const getList = (input: BackgroundJobLogGetListInput) => {
return defAbpHttp.get<PagedResultDto<BackgroundJobLog>>({
url: Api.GetList,
params: input,
});
};
export const deleteById = (id: string) => {
return defAbpHttp.delete<void>({
url: format(Api.Delete, { id: id }),
});
};

89
apps/vue/src/api/task-management/model/backgroundJobInfoModel.ts

@ -0,0 +1,89 @@
import { ExtensibleAuditedEntity, IHasConcurrencyStamp, PagedAndSortedResultRequestDto } from "../../model/baseModel";
export enum JobStatus {
None = -1,
Completed = 0,
Running = 10,
FailedRetry = 15,
Paused = 20,
Stopped = 30,
}
export enum JobType {
Once,
Period,
Persistent,
}
export enum JobPriority {
Low = 5,
BelowNormal = 10,
Normal = 0xF,
AboveNormal = 20,
High = 25
}
export interface BackgroundJobInfo extends ExtensibleAuditedEntity<string>, IHasConcurrencyStamp {
isEnabled: boolean;
name: string;
group: string;
type: string;
result: string;
args: ExtraPropertyDictionary,
description?: string;
beginTime: Date;
endTime?: Date;
lastRunTime?: Date;
nextRunTime?: Date;
jobType: JobType;
cron: string;
triggerCount: number;
tryCount: number;
maxTryCount: number;
maxCount: number;
isAbandoned: boolean;
interval: number;
priority: JobPriority;
lockTimeOut: number;
}
interface BackgroundJobInfoCreateOrUpdate {
isEnabled: boolean;
args: ExtraPropertyDictionary;
description?: string;
beginTime: Date;
endTime?: Date;
jobType: JobType;
cron: string;
maxCount: number;
interval: number;
priority: JobPriority;
lockTimeOut: number;
}
export interface BackgroundJobInfoCreate extends BackgroundJobInfoCreateOrUpdate {
name: string;
group: string;
type: string;
}
export interface BackgroundJobInfoUpdate extends BackgroundJobInfoCreateOrUpdate, IHasConcurrencyStamp {
}
export interface BackgroundJobInfoGetListInput extends PagedAndSortedResultRequestDto {
filter?: string;
name?: string;
group?: string;
type?: string;
status?: JobStatus;
beginTime?: Date;
endTime?: Date;
beginLastRunTime?: Date;
endLastRunTime?: Date;
beginCreationTime?: Date;
endCreationTime?: Date;
isAbandoned?: boolean;
jobType?: JobType;
priority?: JobPriority;
}

22
apps/vue/src/api/task-management/model/backgroundJobLogModel.ts

@ -0,0 +1,22 @@
import { PagedAndSortedResultRequestDto } from '/@/api/model/baseModel';
export interface BackgroundJobLog {
id: number;
jobName: string;
jobGroup: string;
jobType: string;
message: string;
runTime: Date;
exception?: string;
}
export interface BackgroundJobLogGetListInput extends PagedAndSortedResultRequestDto {
jobId?: string;
filter?: string;
hasExceptions?: boolean;
name?: string;
group?: string;
type?: string;
beginRunTime?: Date;
endRunTime?: Date;
}

338
apps/vue/src/views/task-management/background-jobs/components/BackgroundJobInfoModal.vue

@ -0,0 +1,338 @@
<template>
<BasicModal
@register="registerModal"
:width="800"
:height="400"
:title="modalTitle"
:help-message="modalTips"
:mask-closable="false"
@ok="handleSubmit"
>
<Form
ref="formElRef"
:colon="false"
label-align="left"
layout="horizontal"
:model="modelRef"
:rules="modelRules"
>
<Tabs v-model:activeKey="activeKey">
<TabPane key="basic" :tab="L('BasicInfo')">
<FormItem name="isEnabled" :labelCol="{ span: 4 }" :wrapperCol="{ span: 18 }" :label="L('DisplayName:IsEnabled')">
<Checkbox v-model:checked="modelRef.isEnabled">{{ L('DisplayName:IsEnabled') }}</Checkbox>
</FormItem>
<FormItem name="group" required :label="L('DisplayName:Group')">
<Input :disabled="isEditModal" v-model:value="modelRef.group" autocomplete="off" />
</FormItem>
<FormItem name="name" required :label="L('DisplayName:Name')">
<Input :disabled="isEditModal" v-model:value="modelRef.name" autocomplete="off" />
</FormItem>
<FormItem name="type" required :label="L('DisplayName:Type')" :extra="L('Description:Type')">
<Textarea :disabled="isEditModal" v-model:value="modelRef.type" :auto-size="{ minRows: 3, maxRows: 6 }" />
</FormItem>
<FormItem name="beginTime" :label="L('DisplayName:BeginTime')">
<DatePicker style="width: 100%;" v-model:value="modelRef.beginTime" />
</FormItem>
<FormItem name="endTime" :label="L('DisplayName:EndTime')">
<DatePicker style="width: 100%;" v-model:value="modelRef.endTime" />
</FormItem>
<FormItem name="jobType" :label="L('DisplayName:JobType')" :help="L('Description:JobType')">
<Select :options="jopTypeOptions" v-model:value="modelRef.jobType" :default-value="JobType.Once" />
</FormItem>
<FormItem name="cron" v-if="modelRef.jobType === JobType.Period" :label="L('DisplayName:Cron')" :help="L('Description:Cron')">
<Input v-model:value="modelRef.cron" autocomplete="off" />
</FormItem>
<FormItem name="interval" v-if="modelRef.jobType !== JobType.Period" :label="L('DisplayName:Interval')" :help="L('Description:Interval')">
<InputNumber style="width: 100%;" v-model:value="modelRef.interval" />
</FormItem>
<FormItem name="maxCount" :label="L('DisplayName:MaxCount')" :help="L('Description:MaxCount')">
<InputNumber style="width: 100%;" v-model:value="modelRef.maxCount" />
</FormItem>
<FormItem name="maxTryCount" :label="L('DisplayName:MaxTryCount')" :help="L('Description:MaxTryCount')">
<InputNumber style="width: 100%;" v-model:value="modelRef.maxTryCount" />
</FormItem>
<FormItem name="priority" :label="L('DisplayName:Priority')" :help="L('Description:Priority')">
<Select :options="jobPriorityOptions" v-model:value="modelRef.priority" :default-value="JobPriority.Normal" />
</FormItem>
<FormItem name="lockTimeOut" :label="L('DisplayName:LockTimeOut')" :help="L('Description:LockTimeOut')">
<InputNumber style="width: 100%;" v-model:value="modelRef.lockTimeOut" />
</FormItem>
<FormItem name="description" :label="L('DisplayName:Description')">
<Textarea v-model:value="modelRef.description" :row="3" />
</FormItem>
<FormItem :label="L('DisplayName:Result')">
<Textarea readonly v-model:value="modelRef.result" :auto-size="{ minRows: 6, maxRows: 12 }" />
</FormItem>
</TabPane>
<TabPane key="paramters" :tab="L('Paramters')">
<BasicTable @register="registerTable" :data-source="jobArgs">
<template #toolbar>
<Button type="primary" @click="handleAddNewArg">{{ L('BackgroundJobs:AddNewArg') }}
</Button>
</template>
<template #action="{ record }">
<TableAction
:stop-button-propagation="true"
:actions="[
{
label: L('Edit'),
icon: 'ant-design:edit-outlined',
onClick: handleEditParam.bind(null, record),
},
{
color: 'error',
label: L('Delete'),
icon: 'ant-design:delete-outlined',
onClick: handleDeleteParam.bind(null, record),
},
]"
/>
</template>
</BasicTable>
</TabPane>
</Tabs>
</Form>
<BasicModal
:title="L('BackgroundJobs:Paramter')"
@register="registerParamModal"
@ok="handleSaveParam"
>
<BasicForm @register="registerParamForm" />
</BasicModal>
</BasicModal>
</template>
<script lang="ts" setup>
import { computed, ref, reactive, unref, nextTick } from 'vue';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useValidation } from '/@/hooks/abp/useValidation';
import { useMessage } from '/@/hooks/web/useMessage';
import {
Button,
Checkbox,
DatePicker,
Form,
Select,
Tabs,
Input,
InputNumber,
Textarea,
} from 'ant-design-vue';
import { BasicForm, useForm } from '/@/components/Form';
import { BasicModal, useModal, useModalInner } from '/@/components/Modal';
import { BasicTable, BasicColumn, TableAction, useTable } from '/@/components/Table';
import { getById, create, update } from '/@/api/task-management/backgroundJobInfo';
import { JobType, JobPriority, BackgroundJobInfo } from '/@/api/task-management/model/backgroundJobInfoModel';
import { JobTypeMap, JobPriorityMap } from '../datas/typing';
const FormItem = Form.Item;
const TabPane = Tabs.TabPane;
const emit = defineEmits(['change', 'register']);
const { L } = useLocalization('TaskManagement');
const { ruleCreator } = useValidation();
const { createMessage } = useMessage();
const formElRef = ref<any>();
const activeKey = ref('basic');
const modelRef = ref<BackgroundJobInfo>({
id: '',
isEnabled: true,
priority: JobPriority.Normal,
jobType: JobType.Once,
args: {},
} as BackgroundJobInfo);
const [registerModal, { closeModal, changeOkLoading }] = useModalInner((model) => {
activeKey.value = 'basic';
fetchModel(model.id);
});
const [registerParamModal, { openModal: openParamModal, closeModal: closeParamModal }] = useModal();
const [registerParamForm, { resetFields: resetParamFields, setFieldsValue: setParamFields, validate }] = useForm({
labelAlign: 'left',
labelWidth: 120,
schemas: [
{
field: 'key',
component: 'Input',
label: L('DisplayName:Key'),
required: true,
colProps: { span: 24 },
componentProps: {
autocomplete: "off"
},
},
{
field: 'value',
component: 'InputTextArea',
label: L('DisplayName:Value'),
required: true,
colProps: { span: 24 },
},
],
showActionButtonGroup: false,
});
const columns: BasicColumn[] = [
{
title: L('DisplayName:Key'),
dataIndex: 'key',
align: 'left',
width: 200,
sorter: true,
},
{
title: L('DisplayName:Value'),
dataIndex: 'value',
align: 'left',
width: 300,
sorter: true,
},
];
const [registerTable] = useTable({
rowKey: 'key',
columns: columns,
pagination: false,
maxHeight: 300,
actionColumn: {
width: 180,
title: L('Actions'),
dataIndex: 'action',
slots: { customRender: 'action' },
},
});
const isEditModal = computed(() => {
if (modelRef.value.id) {
return true;
}
return false;
});
const modalTitle = computed(() => {
if (!isEditModal.value) {
return L('BackgroundJobs:AddNew');
}
if (modelRef.value.isAbandoned) {
return `${L('BackgroundJobs:Edit')} - ${L('DisplayName:IsAbandoned')}`;
}
return L('BackgroundJobs:Edit');
});
const modalTips = computed(() => {
if (modelRef.value.isAbandoned) {
return L('Description:IsAbandoned');
}
return '';
});
const modelRules = reactive({
group: ruleCreator.fieldRequired({
name: 'Group',
resourceName: 'TaskManagement',
prefix: 'DisplayName',
}),
name: ruleCreator.fieldRequired({
name: 'Name',
resourceName: 'TaskManagement',
prefix: 'DisplayName',
}),
type: ruleCreator.fieldRequired({
name: 'Type',
resourceName: 'TaskManagement',
prefix: 'DisplayName',
}),
beginTime: ruleCreator.fieldRequired({
name: 'BeginTime',
resourceName: 'TaskManagement',
prefix: 'DisplayName',
type: 'date',
}),
});
const jopTypeOptions = reactive([
{ label: JobTypeMap[JobType.Once], value: JobType.Once },
{ label: JobTypeMap[JobType.Period], value: JobType.Period },
{ label: JobTypeMap[JobType.Persistent], value: JobType.Persistent },
]);
const jobPriorityOptions = reactive([
{ label: JobPriorityMap[JobPriority.Low], value: JobPriority.Low },
{ label: JobPriorityMap[JobPriority.BelowNormal], value: JobPriority.BelowNormal },
{ label: JobPriorityMap[JobPriority.Normal], value: JobPriority.Normal },
{ label: JobPriorityMap[JobPriority.AboveNormal], value: JobPriority.AboveNormal },
{ label: JobPriorityMap[JobPriority.High], value: JobPriority.High },
]);
const jobArgs = computed(() => {
const model = unref(modelRef);
if (!model.args) return [];
return Object.keys(model.args).map((key) => {
return {
key: key,
value: model.args[key],
};
});
});
function fetchModel(id: string) {
if (!id) {
resetFields();
return;
}
getById(id).then((res) => {
modelRef.value = res;
});
}
function resetFields() {
nextTick(() => {
modelRef.value = {
id: '',
isEnabled: true,
priority: JobPriority.Normal,
jobType: JobType.Once,
args: {},
} as BackgroundJobInfo;
});
}
function handleSubmit() {
const formEl = unref(formElRef);
formEl?.validate().then(() => {
changeOkLoading(true);
const model = unref(modelRef);
const api = isEditModal.value
? update(model.id, Object.assign(model))
: create(Object.assign(model));
api.then(() => {
createMessage.success(L('Successful'));
formEl?.resetFields();
closeModal();
emit('change');
}).finally(() => {
changeOkLoading(false);
});
});
}
function handleAddNewArg() {
openParamModal(true);
nextTick(() => {
resetParamFields();
});
}
function handleSaveParam() {
validate().then((input) => {
const model = unref(modelRef);
model.args ??= {};
model.args[input.key] = input.value;
resetParamFields();
closeParamModal();
});
}
function handleEditParam(record) {
openParamModal(true);
nextTick(() => {
setParamFields(record);
});
}
function handleDeleteParam(record) {
const model = unref(modelRef);
model.args ??= {};
delete model.args[record.key]
}
</script>

167
apps/vue/src/views/task-management/background-jobs/components/BackgroundJobInfoTable.vue

@ -0,0 +1,167 @@
<template>
<div class="content">
<BasicTable @register="registerTable">
<template #toolbar>
<a-button
v-if="hasPermission('TaskManagement.BackgroundJobs.Create')"
type="primary"
@click="handleAddNew"
>{{ L('BackgroundJobs:AddNew') }}</a-button
>
</template>
<template #enable="{ record }">
<Switch :checked="record.isEnabled" disabled />
</template>
<template #status="{ record }">
<Tooltip v-if="record.isAbandoned" color="orange" :title="L('Description:IsAbandoned')">
<Tag :color="JobStatusColor[record.status]">{{ JobStatusMap[record.status] }}</Tag>
</Tooltip>
<Tag v-else :color="JobStatusColor[record.status]">{{ JobStatusMap[record.status] }}</Tag>
</template>
<template #type="{ record }">
<Tag color="blue">{{ JobTypeMap[record.jobType] }}</Tag>
</template>
<template #priority="{ record }">
<Tag :color="JobPriorityColor[record.priority]">{{ JobPriorityMap[record.priority] }}</Tag>
</template>
<template #action="{ record }">
<TableAction
:stop-button-propagation="true"
:actions="[
{
auth: 'TaskManagement.BackgroundJobs.Update',
label: L('Edit'),
icon: 'ant-design:edit-outlined',
onClick: handleEdit.bind(null, record),
},
{
auth: 'TaskManagement.BackgroundJobs.Delete',
color: 'error',
label: L('Delete'),
icon: 'ant-design:delete-outlined',
onClick: handleDelete.bind(null, record),
},
]"
:dropDownActions="[
{
auth: 'TaskManagement.BackgroundJobs.Pause',
label: L('BackgroundJobs:Pause'),
ifShow: [JobStatus.Running, JobStatus.FailedRetry].includes(record.status),
onClick: handlePause.bind(null, record),
},
{
auth: 'TaskManagement.BackgroundJobs.Resume',
label: L('BackgroundJobs:Resume'),
ifShow: [JobStatus.Paused, JobStatus.Stopped].includes(record.status),
onClick: handleResume.bind(null, record),
},
{
auth: 'TaskManagement.BackgroundJobs.Trigger',
label: L('BackgroundJobs:Trigger'),
ifShow: [JobStatus.Running, JobStatus.Completed, JobStatus.FailedRetry].includes(record.status),
onClick: handleTrigger.bind(null, record),
},
{
auth: 'TaskManagement.BackgroundJobs.Stop',
label: L('BackgroundJobs:Stop'),
ifShow: [JobStatus.Running, JobStatus.FailedRetry].includes(record.status),
onClick: handleStop.bind(null, record),
},
]"
/>
</template>
</BasicTable>
<BackgroundJobInfoModal @change="handleChange" @register="registerModal" />
</div>
</template>
<script lang="ts" setup>
import { Switch, Modal, Tag, Tooltip, message } from 'ant-design-vue';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { usePermission } from '/@/hooks/web/usePermission';
import { useModal } from '/@/components/Modal';
import { BasicTable, TableAction, useTable } from '/@/components/Table';
import { formatPagedRequest } from '/@/utils/http/abp/helper';
import { getList, deleteById, pause, resume, trigger, stop } from '/@/api/task-management/backgroundJobInfo';
import { JobStatus } from '/@/api/task-management/model/backgroundJobInfoModel';
import { getDataColumns } from '../datas/TableData';
import { getSearchFormSchemas } from '../datas/ModalData';
import { JobStatusMap, JobStatusColor, JobTypeMap, JobPriorityMap, JobPriorityColor } from '../datas/typing';
import BackgroundJobInfoModal from './BackgroundJobInfoModal.vue';
const { L } = useLocalization('TaskManagement');
const { hasPermission } = usePermission();
const [registerModal, { openModal }] = useModal();
const [registerTable, { reload }] = useTable({
rowKey: 'id',
title: L('BackgroundJobs'),
columns: getDataColumns(),
api: getList,
beforeFetch: formatPagedRequest,
pagination: true,
striped: false,
useSearchForm: true,
showTableSetting: true,
bordered: true,
showIndexColumn: false,
canResize: false,
immediate: true,
rowSelection: { type: 'radio' },
formConfig: getSearchFormSchemas(),
actionColumn: {
width: 220,
title: L('Actions'),
dataIndex: 'action',
slots: { customRender: 'action' },
},
});
function handleChange() {
reload();
}
function handleAddNew() {
openModal(true, { id: null });
}
function handleEdit(record) {
openModal(true, record);
}
function handlePause(record) {
pause(record.id).then(() => {
message.success(L('Successful'));
});
}
function handleResume(record) {
resume(record.id).then(() => {
message.success(L('Successful'));
});
}
function handleTrigger(record) {
trigger(record.id).then(() => {
message.success(L('Successful'));
});
}
function handleStop(record) {
stop(record.id).then(() => {
message.success(L('Successful'));
});
}
function handleDelete(record) {
Modal.warning({
title: L('AreYouSure'),
content: L('ItemWillBeDeletedMessage'),
okCancel: true,
onOk: () => {
deleteById(record.id).then(() => {
reload();
});
},
});
}
</script>

154
apps/vue/src/views/task-management/background-jobs/datas/ModalData.ts

@ -0,0 +1,154 @@
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { FormProps } from '/@/components/Form';
import { JobStatus, JobType, JobPriority } from '/@/api/task-management/model/backgroundJobInfoModel';
import { JobStatusMap, JobTypeMap, JobPriorityMap } from './typing';
const { L } = useLocalization('TaskManagement', 'AbpUi');
export function getSearchFormSchemas(): Partial<FormProps> {
return {
labelWidth: 100,
schemas: [
{
field: 'group',
component: 'Input',
label: L('DisplayName:Group'),
colProps: { span: 6 },
},
{
field: 'name',
component: 'Input',
label: L('DisplayName:Name'),
colProps: { span: 6 },
},
{
field: 'type',
component: 'Input',
label: L('DisplayName:Type'),
colProps: { span: 12 },
},
{
field: 'status',
component: 'Select',
label: L('DisplayName:Status'),
colProps: { span: 6 },
componentProps: {
options: [
{ label: JobStatusMap[JobStatus.None], value: JobStatus.None },
{ label: JobStatusMap[JobStatus.Running], value: JobStatus.Running },
{ label: JobStatusMap[JobStatus.Completed], value: JobStatus.Completed },
{ label: JobStatusMap[JobStatus.Paused], value: JobStatus.Paused },
{ label: JobStatusMap[JobStatus.Stopped], value: JobStatus.Stopped },
],
},
},
{
field: 'beginTime',
component: 'DatePicker',
label: L('DisplayName:BeginTime'),
colProps: { span: 9 },
componentProps: {
style: {
width: '100%',
},
},
},
{
field: 'endTime',
component: 'DatePicker',
label: L('DisplayName:EndTime'),
colProps: { span: 9 },
componentProps: {
style: {
width: '100%',
},
},
},
{
field: 'jobType',
component: 'Select',
label: L('DisplayName:JobType'),
colProps: { span: 6 },
componentProps: {
options: [
{ label: JobTypeMap[JobType.Once], value: JobType.Once },
{ label: JobTypeMap[JobType.Period], value: JobType.Period },
{ label: JobTypeMap[JobType.Persistent], value: JobType.Persistent },
],
},
},
{
field: 'beginLastRunTime',
component: 'DatePicker',
label: L('DisplayName:BeginLastRunTime'),
colProps: { span: 9 },
componentProps: {
style: {
width: '100%',
},
},
},
{
field: 'endLastRunTime',
component: 'DatePicker',
label: L('DisplayName:EndLastRunTime'),
colProps: { span: 9 },
componentProps: {
style: {
width: '100%',
},
},
},
{
field: 'priority',
component: 'Select',
label: L('DisplayName:Priority'),
colProps: { span: 6 },
componentProps: {
options: [
{ label: JobPriorityMap[JobPriority.Low], value: JobPriority.Low },
{ label: JobPriorityMap[JobPriority.BelowNormal], value: JobPriority.BelowNormal },
{ label: JobPriorityMap[JobPriority.Normal], value: JobPriority.Normal },
{ label: JobPriorityMap[JobPriority.AboveNormal], value: JobPriority.AboveNormal },
{ label: JobPriorityMap[JobPriority.High], value: JobPriority.High },
],
},
},
{
field: 'beginCreationTime',
component: 'DatePicker',
label: L('DisplayName:BeginCreationTime'),
colProps: { span: 9 },
componentProps: {
style: {
width: '100%',
},
},
},
{
field: 'endCreationTime',
component: 'DatePicker',
label: L('DisplayName:EndCreationTime'),
colProps: { span: 9 },
componentProps: {
style: {
width: '100%',
},
},
},
{
field: 'isAbandoned',
component: 'Checkbox',
label: L('DisplayName:IsAbandoned'),
colProps: { span: 4 },
renderComponentContent: L('DisplayName:IsAbandoned'),
},
{
field: 'filter',
component: 'Input',
label: L('Search'),
colProps: { span: 20 },
},
],
};
}

125
apps/vue/src/views/task-management/background-jobs/datas/TableData.ts

@ -0,0 +1,125 @@
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { BasicColumn } from '/@/components/Table';
import { formatToDateTime } from '/@/utils/dateUtil';
const { L } = useLocalization('TaskManagement');
export function getDataColumns(): BasicColumn[] {
return [
{
title: 'id',
dataIndex: 'id',
width: 1,
ifShow: false,
},
{
title: L('DisplayName:Group'),
dataIndex: 'group',
align: 'left',
width: 150,
sorter: true,
},
{
title: L('DisplayName:Name'),
dataIndex: 'name',
align: 'left',
width: 300,
sorter: true,
},
{
title: L('DisplayName:Type'),
dataIndex: 'type',
align: 'left',
width: 350,
sorter: true,
},
{
title: L('DisplayName:CreationTime'),
dataIndex: 'creationTime',
align: 'left',
width: 150,
sorter: true,
format: (text) => {
return formatToDateTime(text);
},
},
{
title: L('DisplayName:Status'),
dataIndex: 'status',
align: 'left',
width: 100,
sorter: true,
slots: {
customRender: 'status',
}
},
{
title: L('DisplayName:Result'),
dataIndex: 'result',
align: 'left',
width: 200,
sorter: true,
},
{
title: L('DisplayName:LastRunTime'),
dataIndex: 'lastRunTime',
align: 'left',
width: 150,
sorter: true,
format: (text) => {
return text ? formatToDateTime(text) : '';
},
},
{
title: L('DisplayName:NextRunTime'),
dataIndex: 'nextRunTime',
align: 'left',
width: 150,
sorter: true,
format: (text) => {
return text ? formatToDateTime(text) : '';
},
},
{
title: L('DisplayName:JobType'),
dataIndex: 'jobType',
align: 'left',
width: 150,
sorter: true,
slots: {
customRender: 'type',
}
},
{
title: L('DisplayName:Priority'),
dataIndex: 'priority',
align: 'left',
width: 150,
sorter: true,
slots: {
customRender: 'priority',
}
},
{
title: L('DisplayName:Cron'),
dataIndex: 'cron',
align: 'left',
width: 150,
sorter: true,
},
{
title: L('DisplayName:TriggerCount'),
dataIndex: 'triggerCount',
align: 'left',
width: 100,
sorter: true,
},
{
title: L('DisplayName:TryCount'),
dataIndex: 'tryCount',
align: 'left',
width: 100,
sorter: true,
},
];
}

43
apps/vue/src/views/task-management/background-jobs/datas/typing.ts

@ -0,0 +1,43 @@
import { JobStatus, JobType, JobPriority } from '/@/api/task-management/model/backgroundJobInfoModel';
import { useLocalization } from '/@/hooks/abp/useLocalization';
const { L } = useLocalization('TaskManagement');
export const JobStatusMap = {
[JobStatus.None]: L('DisplayName:None'),
[JobStatus.Completed]: L('DisplayName:Completed'),
[JobStatus.Running]: L('DisplayName:Running'),
[JobStatus.FailedRetry]: L('DisplayName:FailedRetry'),
[JobStatus.Paused]: L('DisplayName:Paused'),
[JobStatus.Stopped]: L('DisplayName:Stopped'),
}
export const JobStatusColor = {
[JobStatus.None]: '',
[JobStatus.Completed]: '#339933',
[JobStatus.Running]: '#3399CC',
[JobStatus.FailedRetry]: '#FF6600',
[JobStatus.Paused]: '#CC6633',
[JobStatus.Stopped]: '#F00000',
}
export const JobTypeMap = {
[JobType.Once]: L('DisplayName:Once'),
[JobType.Period]: L('DisplayName:Period'),
[JobType.Persistent]: L('DisplayName:Persistent'),
}
export const JobPriorityMap = {
[JobPriority.Low]: L('DisplayName:Low'),
[JobPriority.BelowNormal]: L('DisplayName:BelowNormal'),
[JobPriority.Normal]: L('DisplayName:Normal'),
[JobPriority.AboveNormal]: L('DisplayName:AboveNormal'),
[JobPriority.High]: L('DisplayName:High'),
}
export const JobPriorityColor = {
[JobPriority.Low]: 'purple',
[JobPriority.BelowNormal]: 'cyan',
[JobPriority.Normal]: 'blue',
[JobPriority.AboveNormal]: 'orange',
[JobPriority.High]: 'red',
}

15
apps/vue/src/views/task-management/background-jobs/index.vue

@ -0,0 +1,15 @@
<template>
<BackgroundJobInfoTable />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import BackgroundJobInfoTable from './components/BackgroundJobInfoTable.vue';
export default defineComponent({
components: {
BackgroundJobInfoTable,
},
setup() {},
});
</script>

2
apps/vue/types/abp.d.ts

@ -3,6 +3,8 @@ declare interface LocalizableStringInfo {
name: string;
}
declare type ExtraPropertyDictionary = { [key: string]: any };
declare interface ISelectionStringValueItem {
value: string;
displayText: LocalizableStringInfo;

7
aspnet-core/.editorconfig

@ -1,4 +1,5 @@
# Rules in this file were initially inferred by Visual Studio IntelliCode from the D:\Projects\MicroService\CRM\Vue\abp-next-admin\aspnet-core codebase based on best match to current usage at 2021-12-27
# Rules in this file were initially inferred by Visual Studio IntelliCode from the D:\Projects\MicroService\CRM\Vue\abp-next-admin\aspnet-core codebase based on best match to current usage at 2022-01-07
# There already existed an .editorconfig file in this directory. Copy rules from this .editorconfig.inferred file to the existing .editorconfig file as desired to have them take effect at this location.
# You can modify the rules from these initially generated values to suit your own policies
# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
[*.cs]
@ -26,8 +27,8 @@ csharp_new_line_before_else = true
csharp_new_line_before_members_in_anonymous_types = true
#require members of object initializers to be on the same line
csharp_new_line_before_members_in_object_initializers = false
#require braces to be on a new line for anonymous_types, control_blocks, types, lambdas, object_collection_array_initializers, methods, and anonymous_methods (also known as "Allman" style)
csharp_new_line_before_open_brace = anonymous_types, control_blocks, types, lambdas, object_collection_array_initializers, methods, anonymous_methods
#require braces to be on a new line for methods, control_blocks, types, lambdas, object_collection_array_initializers, anonymous_methods, and anonymous_types (also known as "Allman" style)
csharp_new_line_before_open_brace = methods, control_blocks, types, lambdas, object_collection_array_initializers, anonymous_methods, anonymous_types
#Formatting - organize using options

2
aspnet-core/LINGYUN.MicroService.All.sln

@ -378,7 +378,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Sms.Tencent", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.BlobStoring.Tencent", "modules\cloud-tencent\LINGYUN.Abp.BlobStoring.Tencent\LINGYUN.Abp.BlobStoring.Tencent.csproj", "{A4B972EC-9F0B-4405-9965-766FABC9B07E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.OssManagement.Tencent", "modules\oss-management\LINGYUN.Abp.OssManagement.Tencent\LINGYUN.Abp.OssManagement.Tencent.csproj", "{31E60E23-FD98-4D5E-A137-2B3F2968BA09}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.OssManagement.Tencent", "modules\oss-management\LINGYUN.Abp.OssManagement.Tencent\LINGYUN.Abp.OssManagement.Tencent.csproj", "{31E60E23-FD98-4D5E-A137-2B3F2968BA09}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

111
aspnet-core/LINGYUN.MicroService.TaskManagement.sln

@ -0,0 +1,111 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "host", "host", "{8DA8A2EE-0B26-487E-A6C4-518906E92B1B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C38EB7EF-BAE9-4129-862A-71C652B81775}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{77341F31-F54C-436A-AF8D-F78D91303C45}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.TaskManagement.Domain.Shared", "modules\task-management\LINGYUN.Abp.TaskManagement.Domain.Shared\LINGYUN.Abp.TaskManagement.Domain.Shared.csproj", "{691CD138-9FFA-4988-BAC4-A32F0DAE1090}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.TaskManagement.Domain", "modules\task-management\LINGYUN.Abp.TaskManagement.Domain\LINGYUN.Abp.TaskManagement.Domain.csproj", "{8873A651-4F83-43B3-A34E-7AAA8A3ED4CF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.TaskManagement.Application.Contracts", "modules\task-management\LINGYUN.Abp.TaskManagement.Application.Contracts\LINGYUN.Abp.TaskManagement.Application.Contracts.csproj", "{D5EC4CA0-7E16-4D17-B08E-E162EF332C77}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.TaskManagement.Application", "modules\task-management\LINGYUN.Abp.TaskManagement.Application\LINGYUN.Abp.TaskManagement.Application.csproj", "{09039DDF-5B0E-4670-8055-CC0BE82D4D2C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.TaskManagement.HttpApi", "modules\task-management\LINGYUN.Abp.TaskManagement.HttpApi\LINGYUN.Abp.TaskManagement.HttpApi.csproj", "{283B0039-F67C-41F7-B554-DF5EE9178C4A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.TaskManagement.EntityFrameworkCore", "modules\task-management\LINGYUN.Abp.TaskManagement.EntityFrameworkCore\LINGYUN.Abp.TaskManagement.EntityFrameworkCore.csproj", "{B8AB5E9B-9711-470E-8072-7444579EC5F6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C99728F6-FB3C-4D26-8917-1D30725209B9}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "provider", "provider", "{385578CC-C0F1-4377-A7A2-682B8F416234}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.BackgroundTasks", "modules\task-management\LINGYUN.Abp.BackgroundTasks\LINGYUN.Abp.BackgroundTasks.csproj", "{AC0B4342-9C9B-41E6-9646-E505C763EE77}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.BackgroundTasks.Quartz", "modules\task-management\LINGYUN.Abp.BackgroundTasks.Quartz\LINGYUN.Abp.BackgroundTasks.Quartz.csproj", "{7051C251-11D0-4971-B13E-F6929AE6DE89}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LY.MicroService.TaskManagement.HttpApi.Host", "services\LY.MicroService.TaskManagement.HttpApi.Host\LY.MicroService.TaskManagement.HttpApi.Host.csproj", "{E8022994-A19F-4540-B9D1-7EF4AA85D18A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.BackgroundTasks.Abstractions", "modules\task-management\LINGYUN.Abp.BackgroundTasks.Abstractions\LINGYUN.Abp.BackgroundTasks.Abstractions.csproj", "{4A049C32-55F2-4A5F-954A-C8A977C2D87F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.BackgroundTasks.Jobs", "modules\task-management\LINGYUN.Abp.BackgroundTasks.Jobs\LINGYUN.Abp.BackgroundTasks.Jobs.csproj", "{4C59F590-AA6C-4C46-8060-DA65D8305980}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{691CD138-9FFA-4988-BAC4-A32F0DAE1090}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{691CD138-9FFA-4988-BAC4-A32F0DAE1090}.Debug|Any CPU.Build.0 = Debug|Any CPU
{691CD138-9FFA-4988-BAC4-A32F0DAE1090}.Release|Any CPU.ActiveCfg = Release|Any CPU
{691CD138-9FFA-4988-BAC4-A32F0DAE1090}.Release|Any CPU.Build.0 = Release|Any CPU
{8873A651-4F83-43B3-A34E-7AAA8A3ED4CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8873A651-4F83-43B3-A34E-7AAA8A3ED4CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8873A651-4F83-43B3-A34E-7AAA8A3ED4CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8873A651-4F83-43B3-A34E-7AAA8A3ED4CF}.Release|Any CPU.Build.0 = Release|Any CPU
{D5EC4CA0-7E16-4D17-B08E-E162EF332C77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D5EC4CA0-7E16-4D17-B08E-E162EF332C77}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D5EC4CA0-7E16-4D17-B08E-E162EF332C77}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D5EC4CA0-7E16-4D17-B08E-E162EF332C77}.Release|Any CPU.Build.0 = Release|Any CPU
{09039DDF-5B0E-4670-8055-CC0BE82D4D2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{09039DDF-5B0E-4670-8055-CC0BE82D4D2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{09039DDF-5B0E-4670-8055-CC0BE82D4D2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{09039DDF-5B0E-4670-8055-CC0BE82D4D2C}.Release|Any CPU.Build.0 = Release|Any CPU
{283B0039-F67C-41F7-B554-DF5EE9178C4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{283B0039-F67C-41F7-B554-DF5EE9178C4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{283B0039-F67C-41F7-B554-DF5EE9178C4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{283B0039-F67C-41F7-B554-DF5EE9178C4A}.Release|Any CPU.Build.0 = Release|Any CPU
{B8AB5E9B-9711-470E-8072-7444579EC5F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B8AB5E9B-9711-470E-8072-7444579EC5F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B8AB5E9B-9711-470E-8072-7444579EC5F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B8AB5E9B-9711-470E-8072-7444579EC5F6}.Release|Any CPU.Build.0 = Release|Any CPU
{AC0B4342-9C9B-41E6-9646-E505C763EE77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AC0B4342-9C9B-41E6-9646-E505C763EE77}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AC0B4342-9C9B-41E6-9646-E505C763EE77}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AC0B4342-9C9B-41E6-9646-E505C763EE77}.Release|Any CPU.Build.0 = Release|Any CPU
{7051C251-11D0-4971-B13E-F6929AE6DE89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7051C251-11D0-4971-B13E-F6929AE6DE89}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7051C251-11D0-4971-B13E-F6929AE6DE89}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7051C251-11D0-4971-B13E-F6929AE6DE89}.Release|Any CPU.Build.0 = Release|Any CPU
{E8022994-A19F-4540-B9D1-7EF4AA85D18A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E8022994-A19F-4540-B9D1-7EF4AA85D18A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8022994-A19F-4540-B9D1-7EF4AA85D18A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8022994-A19F-4540-B9D1-7EF4AA85D18A}.Release|Any CPU.Build.0 = Release|Any CPU
{4A049C32-55F2-4A5F-954A-C8A977C2D87F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A049C32-55F2-4A5F-954A-C8A977C2D87F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A049C32-55F2-4A5F-954A-C8A977C2D87F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4A049C32-55F2-4A5F-954A-C8A977C2D87F}.Release|Any CPU.Build.0 = Release|Any CPU
{4C59F590-AA6C-4C46-8060-DA65D8305980}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4C59F590-AA6C-4C46-8060-DA65D8305980}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C59F590-AA6C-4C46-8060-DA65D8305980}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4C59F590-AA6C-4C46-8060-DA65D8305980}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{691CD138-9FFA-4988-BAC4-A32F0DAE1090} = {C38EB7EF-BAE9-4129-862A-71C652B81775}
{8873A651-4F83-43B3-A34E-7AAA8A3ED4CF} = {C38EB7EF-BAE9-4129-862A-71C652B81775}
{D5EC4CA0-7E16-4D17-B08E-E162EF332C77} = {C38EB7EF-BAE9-4129-862A-71C652B81775}
{09039DDF-5B0E-4670-8055-CC0BE82D4D2C} = {C38EB7EF-BAE9-4129-862A-71C652B81775}
{283B0039-F67C-41F7-B554-DF5EE9178C4A} = {C38EB7EF-BAE9-4129-862A-71C652B81775}
{B8AB5E9B-9711-470E-8072-7444579EC5F6} = {C38EB7EF-BAE9-4129-862A-71C652B81775}
{AC0B4342-9C9B-41E6-9646-E505C763EE77} = {C38EB7EF-BAE9-4129-862A-71C652B81775}
{7051C251-11D0-4971-B13E-F6929AE6DE89} = {385578CC-C0F1-4377-A7A2-682B8F416234}
{E8022994-A19F-4540-B9D1-7EF4AA85D18A} = {8DA8A2EE-0B26-487E-A6C4-518906E92B1B}
{4A049C32-55F2-4A5F-954A-C8A977C2D87F} = {C38EB7EF-BAE9-4129-862A-71C652B81775}
{4C59F590-AA6C-4C46-8060-DA65D8305980} = {C38EB7EF-BAE9-4129-862A-71C652B81775}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E1FD1F4C-D344-408B-97CF-B6F1F6D7D293}
EndGlobalSection
EndGlobal

21
aspnet-core/modules/platform/LINGYUN.Abp.UI.Navigation.VueVbenAdmin/LINGYUN/Abp/UI/Navigation/VueVbenAdmin/AbpUINavigationVueVbenAdminNavigationDefinitionProvider.cs

@ -14,6 +14,7 @@ namespace LINGYUN.Abp.UI.Navigation.VueVbenAdmin
context.Add(GetApiGateway());
context.Add(GetLocalization());
context.Add(GetOssManagement());
context.Add(GetTaskManagement());
}
private static NavigationDefinition GetDashboard()
@ -360,5 +361,25 @@ namespace LINGYUN.Abp.UI.Navigation.VueVbenAdmin
return new NavigationDefinition(oss);
}
private static NavigationDefinition GetTaskManagement()
{
var task = new ApplicationMenu(
name: "TaskManagement",
displayName: "任务调度平台",
url: "/task-management",
component: "",
description: "任务调度平台",
icon: "");
task.AddItem(
new ApplicationMenu(
name: "BackgroundJobs",
displayName: "任务管理",
url: "/task-management/background-jobs",
component: "/task-management/background-jobs/index",
description: "任务管理"));
return new NavigationDefinition(task);
}
}
}

3
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

15
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN.Abp.BackgroundTasks.Abstractions.csproj

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Core" Version="$(VoloAbpPackageVersion)" />
</ItemGroup>
</Project>

42
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTaskConcurrentException.cs

@ -0,0 +1,42 @@
using System;
namespace LINGYUN.Abp.BackgroundTasks;
public class AbpBackgroundTaskConcurrentException : AbpJobExecutionException
{
/// <summary>
/// Creates a new <see cref="AbpBackgroundTaskConcurrentException"/> object.
/// </summary>
/// <param name="innerException">Inner exception</param>
public AbpBackgroundTaskConcurrentException(Type jobType)
: base(
jobType,
$"This job {jobType.Name} cannot be performed because it has been locked by another performer",
null)
{
}
/// <summary>
/// Creates a new <see cref="AbpBackgroundTaskConcurrentException"/> object.
/// </summary>
/// <param name="jobType">Execute job type</param>
/// <param name="innerException">Inner exception</param>
public AbpBackgroundTaskConcurrentException(Type jobType, Exception innerException)
: base(
jobType,
$"This job {jobType.Name} cannot be performed because it has been locked by another performer",
innerException)
{
}
/// <summary>
/// Creates a new <see cref="AbpBackgroundTaskConcurrentException"/> object.
/// </summary>
/// <param name="jobType">Execute job type</param>
/// <param name="message">Exception message</param>
/// <param name="innerException">Inner exception</param>
public AbpBackgroundTaskConcurrentException(Type jobType, string message, Exception innerException)
: base(jobType, message, innerException)
{
}
}

33
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksAbstractionsModule.cs

@ -0,0 +1,33 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using Volo.Abp.Modularity;
using Volo.Abp.Reflection;
namespace LINGYUN.Abp.BackgroundTasks;
public class AbpBackgroundTasksAbstractionsModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
AutoAddJobMonitors(context.Services);
}
private static void AutoAddJobMonitors(IServiceCollection services)
{
var jobMonitors = new List<Type>();
services.OnRegistred(context =>
{
if (ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, typeof(JobEventBase<>)))
{
jobMonitors.Add(context.ImplementationType);
}
});
services.Configure<AbpBackgroundTasksOptions>(options =>
{
options.JobMonitors.AddIfNotContains(jobMonitors);
});
}
}

84
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksOptions.cs

@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Collections;
namespace LINGYUN.Abp.BackgroundTasks;
public class AbpBackgroundTasksOptions
{
/// <summary>
/// 任务监听类型列表
/// </summary>
/// <remarks>
/// 用户可以实现事件监听实现自定义逻辑
/// </remarks>
public ITypeList<IJobEvent> JobMonitors { get; }
/// <summary>
/// 作业提供者列表
/// </summary>
/// <remarks>
/// 用户实现的作业可以添加在集合中
/// </remarks>
public IDictionary<string, Type> JobProviders { get; }
/// <summary>
/// 任务过期时间
/// 默认: 15 days
/// </summary>
/// <remarks>
/// 任务过期时间,超出时间段清理
/// </remarks>
public TimeSpan JobExpiratime { get; set; }
/// <summary>
/// 每次清理任务批次大小
/// 默认: 1000
/// </summary>
public int MaxJobCleanCount { get; set; }
/// <summary>
/// 清理过期任务批次Cron表达式
/// 默认: 600秒(0 0/10 * * * ? * )
/// </summary>
/// <remarks>
/// Cron表达式
/// </remarks>
public string JobCleanCronExpression { get; set; }
/// <summary>
/// 每次轮询任务批次大小
/// 默认: 1000
/// </summary>
public int MaxJobFetchCount { get; set; }
/// <summary>
/// 轮询任务批次Cron表达式
/// 默认: 30秒(0/30 * * * * ? )
/// </summary>
/// <remarks>
/// Cron表达式
/// </remarks>
public string JobFetchCronExpression { get; set; }
/// <summary>
/// 轮询任务批次时锁定任务超时时长(秒)
/// 默认:120
/// </summary>
/// <remarks>
/// 轮询任务也属于一个后台任务, 需要对每一次轮询加锁,防止重复任务入库
/// </remarks>
public int JobFetchLockTimeOut { get; set; }
public AbpBackgroundTasksOptions()
{
MaxJobFetchCount = 1000;
JobFetchLockTimeOut = 120;
JobFetchCronExpression = "0/30 * * * * ? ";
MaxJobCleanCount = 1000;
JobExpiratime = TimeSpan.FromDays(15d);
JobCleanCronExpression = "0 0/10 * * * ? *";
JobMonitors = new TypeList<IJobEvent>();
JobProviders = new Dictionary<string, Type>();
}
public void AddProvider<TJobRunnable>(string name)
where TJobRunnable : IJobRunnable
{
JobProviders[name] = typeof(TJobRunnable);
}
}

44
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/AbpJobExecutionException.cs

@ -0,0 +1,44 @@
using System;
using Volo.Abp;
namespace LINGYUN.Abp.BackgroundTasks;
public class AbpJobExecutionException : AbpException
{
public Type JobType { get; }
/// <summary>
/// Creates a new <see cref="AbpJobExecutionException"/> object.
/// </summary>
/// <param name="innerException">Inner exception</param>
public AbpJobExecutionException(Type jobType)
: this(
jobType,
$"Unable to execute job {jobType.Name}.",
null)
{
}
/// <summary>
/// Creates a new <see cref="AbpJobExecutionException"/> object.
/// </summary>
/// <param name="jobType">Execute job type</param>
/// <param name="innerException">Inner exception</param>
public AbpJobExecutionException(Type jobType, Exception innerException)
: this(
jobType,
$"Unable to execute job {jobType.Name} because it: {innerException.Message}",
innerException)
{
}
/// <summary>
/// Creates a new <see cref="AbpJobExecutionException"/> object.
/// </summary>
/// <param name="jobType">Execute job type</param>
/// <param name="message">Exception message</param>
/// <param name="innerException">Inner exception</param>
public AbpJobExecutionException(Type jobType, string message, Exception innerException)
: base(message, innerException)
{
JobType = jobType;
}
}

22
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/IJobEvent.cs

@ -0,0 +1,22 @@
using System.Threading.Tasks;
namespace LINGYUN.Abp.BackgroundTasks;
/// <summary>
/// 挂载任务事件接口
/// </summary>
public interface IJobEvent
{
/// <summary>
/// 任务启动前事件
/// </summary>
/// <param name="jobEventData"></param>
/// <returns></returns>
Task OnJobBeforeExecuted(JobEventContext context);
/// <summary>
/// 任务完成后事件
/// </summary>
/// <param name="jobEventData"></param>
/// <returns></returns>
Task OnJobAfterExecuted(JobEventContext context);
}

11
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/IJobRunnable.cs

@ -0,0 +1,11 @@
using System.Threading.Tasks;
namespace LINGYUN.Abp.BackgroundTasks;
/// <summary>
/// 任务类需要实现此接口
/// </summary>
public interface IJobRunnable
{
Task ExecuteAsync(JobRunnableContext context);
}

58
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobEventBase.cs

@ -0,0 +1,58 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Threading.Tasks;
namespace LINGYUN.Abp.BackgroundTasks;
public abstract class JobEventBase<TEvent> : IJobEvent
{
public ILogger<TEvent> Logger { protected get; set; }
protected JobEventBase()
{
Logger = NullLogger<TEvent>.Instance;
}
public async Task OnJobAfterExecuted(JobEventContext context)
{
try
{
await OnJobAfterExecutedAsync(context);
}
catch (Exception ex)
{
Logger.LogError("Failed to execute event, error:" + GetSourceException(ex).Message);
}
}
public async Task OnJobBeforeExecuted(JobEventContext context)
{
try
{
await OnJobBeforeExecutedAsync(context);
}
catch (Exception ex)
{
Logger.LogError("Failed to execute preprocessing event, error:" + GetSourceException(ex).Message);
}
}
protected virtual Task OnJobAfterExecutedAsync(JobEventContext context)
{
return Task.CompletedTask;
}
protected virtual Task OnJobBeforeExecutedAsync(JobEventContext context)
{
return Task.CompletedTask;
}
protected virtual Exception GetSourceException(Exception exception)
{
if (exception.InnerException != null)
{
return GetSourceException(exception.InnerException);
}
return exception;
}
}

17
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobEventContext.cs

@ -0,0 +1,17 @@
using System;
namespace LINGYUN.Abp.BackgroundTasks;
public class JobEventContext
{
public IServiceProvider ServiceProvider { get; }
public JobEventData EventData { get; }
public JobEventContext(
IServiceProvider serviceProvider,
JobEventData jobEventData)
{
ServiceProvider = serviceProvider;
EventData = jobEventData;
}
}

90
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobEventData.cs

@ -0,0 +1,90 @@
using System;
namespace LINGYUN.Abp.BackgroundTasks;
public class JobEventData
{
/// <summary>
/// 任务类别
/// </summary>
public Type Type { get; }
/// <summary>
/// 任务组别
/// </summary>
public string Group { get; }
/// <summary>
/// 任务名称
/// </summary>
public string Name { get; }
/// <summary>
/// 任务标识
/// </summary>
public Guid Key { get; }
/// <summary>
/// 任务状态
/// </summary>
public JobStatus Status { get; set; }
/// <summary>
/// 执行者租户
/// </summary>
public Guid? TenantId { get; set; }
/// <summary>
/// 错误明细
/// </summary>
public Exception Exception { get; }
/// <summary>
/// 任务描述
/// </summary>
public string Description { get; set; }
/// <summary>
/// 返回参数
/// </summary>
public string Result { get; set; }
/// <summary>
/// 触发次数
/// </summary>
public int Triggered { get; set; }
/// <summary>
/// 最大可执行次数
/// </summary>
public int RepeatCount { get; set; }
/// <summary>
/// 失败重试上限
/// 默认:50
/// </summary>
public int TryCount { get; set; }
/// <summary>
/// 最大执行次数
/// 默认:0, 无限制
/// </summary>
public int MaxCount { get; set; }
/// <summary>
/// 运行时间
/// </summary>
public DateTime RunTime { get; set; }
/// <summary>
/// 上次运行时间
/// </summary>
public DateTime? LastRunTime { get; set; }
/// <summary>
/// 下次运行时间
/// </summary>
public DateTime? NextRunTime { get; set; }
/// <summary>
/// 连续失败且不会再次执行
/// </summary>
public bool IsAbandoned { get; set; }
public JobEventData(
Guid key,
Type type,
string group,
string name,
Exception exception = null)
{
Key = key;
Type = type;
Group = group;
Name = name;
Exception = exception;
}
}

104
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobInfo.cs

@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
namespace LINGYUN.Abp.BackgroundTasks;
public class JobInfo
{
/// <summary>
/// 任务标识
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// 任务名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 任务分组
/// </summary>
public string Group { get; set; }
/// <summary>
/// 任务类型
/// </summary>
public string Type { get; set; }
/// <summary>
/// 返回参数
/// </summary>
public string Result { get; set; }
/// <summary>
/// 任务参数
/// </summary>
public IDictionary<string, object> Args { get; set; }
/// <summary>
/// 任务状态
/// </summary>
public JobStatus Status { get; set; } = JobStatus.None;
/// <summary>
/// 描述
/// </summary>
public string Description { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreationTime { get; set; }
/// <summary>
/// 开始时间
/// </summary>
public DateTime BeginTime { get; set; }
/// <summary>
/// 结束时间
/// </summary>
public DateTime? EndTime { get; set; }
/// <summary>
/// 上次运行时间
/// </summary>
public DateTime? LastRunTime { get; set; }
/// <summary>
/// 下一次执行时间
/// </summary>
public DateTime? NextRunTime { get; set; }
/// <summary>
/// 任务类别
/// </summary>
public JobType JobType { get; set; } = JobType.Once;
/// <summary>
/// Cron表达式,如果是周期性任务需要指定
/// </summary>
public string Cron { get; set; }
/// <summary>
/// 触发次数
/// </summary>
public int TriggerCount { get; set; }
/// <summary>
/// 失败重试次数
/// </summary>
public int TryCount { get; set; }
/// <summary>
/// 失败重试上限
/// 默认:50
/// </summary>
public int MaxTryCount { get; set; } = 50;
/// <summary>
/// 最大执行次数
/// 默认:0, 无限制
/// </summary>
public int MaxCount { get; set; }
/// <summary>
/// 连续失败且不会再次执行
/// </summary>
public bool IsAbandoned { get; set; }
/// <summary>
/// 间隔时间,单位秒,与Cron表达式冲突
/// 默认: 300
/// </summary>
public int Interval { get; set; } = 300;
/// <summary>
/// 任务优先级
/// </summary>
public JobPriority Priority { get; set; } = JobPriority.Normal;
/// <summary>
/// 任务独占超时时长(秒)
/// 0或更小不生效
/// </summary>
public int LockTimeOut { get; set; }
}

15
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobPriority.cs

@ -0,0 +1,15 @@
namespace LINGYUN.Abp.BackgroundTasks;
/// <summary>
/// 任务优先级
/// </summary>
/// <remarks>
/// 与框架保持一致
/// </remarks>
public enum JobPriority
{
Low = 5,
BelowNormal = 10,
Normal = 0xF,
AboveNormal = 20,
High = 25
}

26
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobRunnableContext.cs

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
namespace LINGYUN.Abp.BackgroundTasks;
public class JobRunnableContext
{
public Type JobType { get; }
public IServiceProvider ServiceProvider { get; }
public IReadOnlyDictionary<string, object> JobData { get; }
public object Result { get; private set; }
public JobRunnableContext(
Type jobType,
IServiceProvider serviceProvider,
IReadOnlyDictionary<string, object> jobData)
{
JobType = jobType;
ServiceProvider = serviceProvider;
JobData = jobData;
}
public void SetResult(object result)
{
Result = result;
}
}

85
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobRunnableContextExtensions.cs

@ -0,0 +1,85 @@
using Microsoft.Extensions.DependencyInjection;
using System;
namespace LINGYUN.Abp.BackgroundTasks;
public static class JobRunnableContextExtensions
{
public static T GetService<T>(this JobRunnableContext context)
{
return context.ServiceProvider.GetService<T>();
}
public static object GetService(this JobRunnableContext context, Type serviceType)
{
return context.ServiceProvider.GetService(serviceType);
}
public static T GetRequiredService<T>(this JobRunnableContext context)
{
return context.ServiceProvider.GetRequiredService<T>();
}
public static object GetRequiredService(this JobRunnableContext context, Type serviceType)
{
return context.ServiceProvider.GetRequiredService(serviceType);
}
public static string GetString(this JobRunnableContext context, string key)
{
return context.GetJobData(key).ToString();
}
public static bool TryGetString(this JobRunnableContext context, string key, out string value)
{
if (context.TryGetJobData(key, out var data) && data != null)
{
value = data.ToString();
return true;
}
value = default;
return false;
}
public static T GetJobData<T>(this JobRunnableContext context, string key) where T : struct
{
var value = context.GetJobData(key);
return value.To<T>();
}
public static bool TryGetJobData<T>(this JobRunnableContext context, string key, out T value) where T : struct
{
if (context.TryGetJobData(key, out var data) && data != null)
{
try
{
value = data.To<T>();
return true;
}
catch
{
}
}
value = default;
return false;
}
public static object GetJobData(this JobRunnableContext context, string key)
{
if (context.TryGetJobData(key, out var value) && value != null)
{
return value;
}
throw new ArgumentException(key + " not specified.");
}
public static bool TryGetJobData(this JobRunnableContext context, string key, out object value)
{
if (context.JobData.TryGetValue(key, out value))
{
return true;
}
return false;
}
}

29
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobStatus.cs

@ -0,0 +1,29 @@
namespace LINGYUN.Abp.BackgroundTasks;
public enum JobStatus
{
/// <summary>
/// 未知
/// </summary>
None = -1,
/// <summary>
/// 已完成
/// </summary>
Completed = 0,
/// <summary>
/// 运行中
/// </summary>
Running = 10,
/// <summary>
/// 失败重试
/// </summary>
FailedRetry = 15,
/// <summary>
/// 已暂停
/// </summary>
Paused = 20,
/// <summary>
/// 已停止
/// </summary>
Stopped = 30
}

19
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobType.cs

@ -0,0 +1,19 @@
namespace LINGYUN.Abp.BackgroundTasks;
/// <summary>
/// 任务类别
/// </summary>
public enum JobType
{
/// <summary>
/// 一次性
/// </summary>
Once,
/// <summary>
/// 周期性
/// </summary>
Period,
/// <summary>
/// 持续性
/// </summary>
Persistent
}

3
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>

30
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

22
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN.Abp.BackgroundTasks.Jobs.csproj

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Emailing" Version="$(VoloAbpPackageVersion)" />
<PackageReference Include="Volo.Abp.Sms" Version="$(VoloAbpPackageVersion)" />
<PackageReference Include="Volo.Abp.Http.Client" Version="$(VoloAbpPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.BackgroundTasks.Abstractions\LINGYUN.Abp.BackgroundTasks.Abstractions.csproj" />
<ProjectReference Include="..\..\dapr\LINGYUN.Abp.Dapr.Client\LINGYUN.Abp.Dapr.Client.csproj" />
</ItemGroup>
</Project>

27
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/AbpBackgroundTasksJobsModule.cs

@ -0,0 +1,27 @@
using LINGYUN.Abp.Dapr.Client;
using Volo.Abp.Emailing;
using Volo.Abp.Http.Client;
using Volo.Abp.Modularity;
using Volo.Abp.Sms;
namespace LINGYUN.Abp.BackgroundTasks.Jobs;
[DependsOn(typeof(AbpEmailingModule))]
[DependsOn(typeof(AbpSmsModule))]
[DependsOn(typeof(AbpHttpClientModule))]
[DependsOn(typeof(AbpDaprClientModule))]
public class AbpBackgroundTasksJobsModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpBackgroundTasksOptions>(options =>
{
options.AddProvider<ConsoleJob>(DefaultJobNames.ConsoleJob);
options.AddProvider<SendEmailJob>(DefaultJobNames.SendEmailJob);
options.AddProvider<SendSmsJob>(DefaultJobNames.SendSmsJob);
options.AddProvider<SleepJob>(DefaultJobNames.SleepJob);
options.AddProvider<ServiceInvocationJob>(DefaultJobNames.ServiceInvocationJob);
options.AddProvider<HttpRequestJob>(DefaultJobNames.HttpRequestJob);
});
}
}

15
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/ConsoleJob.cs

@ -0,0 +1,15 @@
using System;
using System.Threading.Tasks;
namespace LINGYUN.Abp.BackgroundTasks.Jobs;
public class ConsoleJob : IJobRunnable
{
public const string PropertyMessage = "message";
public Task ExecuteAsync(JobRunnableContext context)
{
context.TryGetString(PropertyMessage, out var message);
Console.WriteLine($"This message: {message ?? "None"} comes from the job: {GetType()}");
return Task.CompletedTask;
}
}

29
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/DefaultJobNames.cs

@ -0,0 +1,29 @@
namespace LINGYUN.Abp.BackgroundTasks.Jobs;
public static class DefaultJobNames
{
/// <summary>
/// 发送邮件
/// </summary>
public const string SendEmailJob = "SendEmail";
/// <summary>
/// 发送短信
/// </summary>
public const string SendSmsJob = "SendSms";
/// <summary>
/// 控制台输出
/// </summary>
public const string ConsoleJob = "Console";
/// <summary>
/// 休眠
/// </summary>
public const string SleepJob = "Sleep";
/// <summary>
/// 服务间调用
/// </summary>
public const string ServiceInvocationJob = "ServiceInvocation";
/// <summary>
/// Http请求
/// </summary>
public const string HttpRequestJob = "HttpRequest";
}

86
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/HttpRequestJob.cs

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Http;
using Volo.Abp.Json;
namespace LINGYUN.Abp.BackgroundTasks.Jobs;
public class HttpRequestJob : IJobRunnable
{
public const string PropertyUrl = "url";
public const string PropertyMethod = "method";
public const string PropertyData = "data";
public const string PropertyContentType = "contentType";
public const string PropertyHeaders = "headers";
public const string PropertyToken = "token";
public virtual async Task ExecuteAsync(JobRunnableContext context)
{
var clientFactory = context.GetRequiredService<IHttpClientFactory>();
var client = clientFactory.CreateClient();
var requestMessage = BuildRequestMessage(context);
var response = await client.SendAsync(
requestMessage,
HttpCompletionOption.ResponseHeadersRead);
var stringContent = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode && stringContent.IsNullOrWhiteSpace())
{
context.SetResult($"HttpStatusCode: {(int)response.StatusCode}, Reason: {response.ReasonPhrase}");
return;
}
context.SetResult(stringContent);
}
protected virtual HttpRequestMessage BuildRequestMessage(JobRunnableContext context)
{
var url = context.GetString(PropertyUrl);
var method = context.GetString(PropertyMethod);
context.TryGetJobData(PropertyData, out var data);
context.TryGetJobData(PropertyContentType, out var contentType);
var jsonSerializer = context.GetRequiredService<IJsonSerializer>();
var httpRequestMesasge = new HttpRequestMessage(new HttpMethod(method), url);
if (data != null)
{
// TODO: 需要支持表单类型
// application/json 支持
httpRequestMesasge.Content = new StringContent(
jsonSerializer.Serialize(data),
Encoding.UTF8,
contentType?.ToString() ?? MimeTypes.Application.Json);
}
if (context.TryGetJobData(PropertyHeaders, out var headers))
{
var headersDic = new Dictionary<string, object>();
if (headers is string headerString)
{
try
{
headersDic = jsonSerializer.Deserialize<Dictionary<string, object>>(headerString);
}
catch { }
}
foreach (var header in headersDic)
{
httpRequestMesasge.Headers.Add(header.Key, header.Value.ToString());
}
}
// TODO: 和 headers 一起?
if (context.TryGetString(PropertyToken, out var accessToken))
{
httpRequestMesasge.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
}
return httpRequestMesasge;
}
}

26
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/SendEmailJob.cs

@ -0,0 +1,26 @@
using System.Threading.Tasks;
using Volo.Abp.Emailing;
namespace LINGYUN.Abp.BackgroundTasks.Jobs;
public class SendEmailJob : IJobRunnable
{
public const string PropertyFrom = "from";
public const string PropertyTo = "to";
public const string PropertySubject = "subject";
public const string PropertyBody = "body";
public const string PropertyIsBodyHtml = "isBodyHtml";
public virtual async Task ExecuteAsync(JobRunnableContext context)
{
context.TryGetString(PropertyFrom, out var from);
var to = context.GetString(PropertyTo);
var subject = context.GetString(PropertySubject);
var body = context.GetString(PropertyBody);
context.TryGetJobData<bool>(PropertyIsBodyHtml, out var isBodyHtml);
var emailSender = context.GetRequiredService<IEmailSender>();
await emailSender.QueueAsync(from, to, subject, body, isBodyHtml);
}
}

38
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/SendSmsJob.cs

@ -0,0 +1,38 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Json;
using Volo.Abp.Sms;
namespace LINGYUN.Abp.BackgroundTasks.Jobs;
public class SendSmsJob : IJobRunnable
{
public const string PropertyPhoneNumber = "phoneNumber";
public const string PropertyMessage = "message";
public const string PropertyProperties = "properties";
public virtual async Task ExecuteAsync(JobRunnableContext context)
{
var phoneNumber = context.GetString(PropertyPhoneNumber);
var message = context.GetString(PropertyMessage);
var smsMessage = new SmsMessage(phoneNumber, message);
if (context.TryGetString(PropertyProperties, out var data))
{
var properties = new Dictionary<string, object>();
try
{
var jsonSerializer = context.GetRequiredService<IJsonSerializer>();
properties = jsonSerializer.Deserialize<Dictionary<string, object>>(data);
}
catch { }
smsMessage.Properties.AddIfNotContains(properties);
}
var smsSender = context.GetRequiredService<ISmsSender>();
await smsSender.SendAsync(smsMessage);
}
}

261
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/ServiceInvocationJob.cs

@ -0,0 +1,261 @@
using LINGYUN.Abp.Dapr.Client.DynamicProxying;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Http.Client;
using Volo.Abp.Http.Client.ClientProxying;
using Volo.Abp.Http.Client.DynamicProxying;
using Volo.Abp.Http.Client.Proxying;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Json;
using Volo.Abp.Localization;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.BackgroundTasks.Jobs;
public class ServiceInvocationJob : IJobRunnable
{
// 必须, 接口类型
public const string PropertyService = "service";
// 必须, 接口方法名称
public const string PropertyMethod = "method";
// 可选, 请求数据, 传递 Dictionary<string, object> 类型的字符串参数
public const string PropertyData = "data";
// 可选, 请求时指定区域性
public const string PropertyCulture = "culture";
// 可选, 请求时指定租户
public const string PropertyTenant = "tenant";
// 可选, 提供者名称, http、dapr, 无此参数默认 http
public const string PropertyProvider = "provider";
// Dapr 调用所必须的参数
public const string PropertyAppId = "appId";
// TODO: support grpc
private readonly static string[] Providers = new string[] { "http", "dapr" };
public virtual async Task ExecuteAsync(JobRunnableContext context)
{
// 获取参数列表
var type = context.GetString(PropertyService);
var method = context.GetString(PropertyMethod);
var serviceType = Type.GetType(type, true);
var serviceMethod = serviceType.GetMethod(method);
context.TryGetString(PropertyCulture, out var culture);
context.TryGetString(PropertyProvider, out var provider);
provider ??= "http";
if (!Providers.Contains(provider))
{
throw new AbpJobExecutionException(
GetType(),
$"Invalid inter-service invocation provider: {provider}",
null);
}
Guid? tenantId = null;
if (context.TryGetString(PropertyTenant, out var tenant) &&
Guid.TryParse(tenant, out var converTenant))
{
tenantId = converTenant;
}
var currentTenant = context.GetRequiredService<ICurrentTenant>();
using (currentTenant.Change(tenantId))
{
using (CultureHelper.Use(culture ?? CultureInfo.CurrentCulture.Name))
{
switch (provider)
{
case "http":
await ExecuteWithHttpProxy(context, serviceType, serviceMethod);
break;
case "dapr":
await ExecuteWithDaprProxy(context, serviceType, serviceMethod);
break;
}
}
}
}
#region HttpClient
protected readonly static string CallRequestMethod = nameof(DynamicHttpProxyInterceptorClientProxy<object>.CallRequestAsync);
protected readonly static MethodInfo ClientProxyMethod = typeof(DynamicHttpProxyInterceptorClientProxy<>)
.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.First(m => m.Name == CallRequestMethod && !m.IsGenericMethodDefinition);
protected readonly static MethodInfo CallRequestAsyncMethod = typeof(DynamicHttpProxyInterceptor<object>)
.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.First(m => m.Name == CallRequestMethod && m.IsGenericMethodDefinition);
protected virtual async Task ExecuteWithHttpProxy(
JobRunnableContext context,
Type serviceType,
MethodInfo serviceMethod)
{
// 反射所必须的参数
var clientProxyType = typeof(DynamicHttpProxyInterceptorClientProxy<>).MakeGenericType(serviceType);
var clientProxy = context.GetRequiredService(clientProxyType);
// 调用远程服务发现端点
var actionApiDescription = await GetActionApiDescriptionModel(context, serviceType, serviceMethod);
// 调用参数
var invokeParameters = new Dictionary<string, object>();
if (context.TryGetString(PropertyData, out var data))
{
var jsonSerializer = context.GetRequiredService<IJsonSerializer>();
invokeParameters = jsonSerializer.Deserialize<Dictionary<string, object>>(data);
}
// 构造服务代理上下文
var clientProxyRequestContext = new ClientProxyRequestContext(
actionApiDescription,
invokeParameters,
serviceType);
if (serviceMethod.ReturnType.GenericTypeArguments.IsNullOrEmpty())
{
// 直接调用
var taskProxy = (Task)ClientProxyMethod
.Invoke(clientProxy, new object[] { clientProxyRequestContext });
await taskProxy;
}
else
{
// 有返回值的调用
var returnType = serviceMethod.ReturnType.GenericTypeArguments[0];
var result = (Task)CallRequestAsyncMethod
.MakeGenericMethod(returnType)
.Invoke(this, new object[] { context });
context.SetResult(await GetResultAsync(result, returnType));
}
}
protected virtual async Task<ActionApiDescriptionModel> GetActionApiDescriptionModel(
JobRunnableContext context,
Type serviceType,
MethodInfo serviceMethod)
{
var clientOptions = context.GetRequiredService<IOptions<AbpHttpClientOptions>>().Value;
var remoteServiceConfigurationProvider = context.GetRequiredService<IRemoteServiceConfigurationProvider>();
var proxyHttpClientFactory = context.GetRequiredService<IProxyHttpClientFactory>();
var apiDescriptionFinder = context.GetRequiredService<IApiDescriptionFinder>();
var clientConfig = clientOptions.HttpClientProxies.GetOrDefault(serviceType) ??
throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {serviceType.FullName}.");
var remoteServiceConfig = await remoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName);
var client = proxyHttpClientFactory.Create(clientConfig.RemoteServiceName);
return await apiDescriptionFinder.FindActionAsync(
client,
remoteServiceConfig.BaseUrl,
serviceType,
serviceMethod
);
}
#endregion
#region DaprClient
protected readonly static string DaprCallRequestMethod = nameof(DynamicDaprProxyInterceptorClientProxy<object>.CallRequestAsync);
protected readonly static MethodInfo DaprClientProxyMethod = typeof(DynamicDaprProxyInterceptorClientProxy<>)
.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.First(m => m.Name == DaprCallRequestMethod && !m.IsGenericMethodDefinition);
protected readonly static MethodInfo DaprCallRequestAsyncMethod = typeof(DynamicDaprProxyInterceptorClientProxy<object>)
.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.First(m => m.Name == DaprCallRequestMethod && m.IsGenericMethodDefinition);
protected virtual async Task ExecuteWithDaprProxy(
JobRunnableContext context,
Type serviceType,
MethodInfo serviceMethod)
{
var appid = context.GetString(PropertyAppId);
var clientOptions = context.GetRequiredService<IOptions<AbpHttpClientOptions>>().Value;
var clientConfig = clientOptions.HttpClientProxies.GetOrDefault(serviceType) ??
throw new AbpJobExecutionException(
GetType(),
$"Could not get DynamicHttpClientProxyConfig for {serviceType.FullName}.",
null);
// 反射所必须的参数
var clientProxyType = typeof(DynamicDaprProxyInterceptorClientProxy<>).MakeGenericType(serviceType);
var clientProxy = context.GetRequiredService(clientProxyType);
// 调用远程服务发现端点
var actionApiDescription = await GetActionApiDescriptionModel(
context,
clientConfig.RemoteServiceName,
appid,
serviceType,
serviceMethod);
// 调用参数
var invokeParameters = new Dictionary<string, object>();
if (context.TryGetString(PropertyData, out var data))
{
var jsonSerializer = context.GetRequiredService<IJsonSerializer>();
invokeParameters = jsonSerializer.Deserialize<Dictionary<string, object>>(data);
}
// 构造服务代理上下文
var clientProxyRequestContext = new ClientProxyRequestContext(
actionApiDescription,
invokeParameters,
serviceType);
if (serviceMethod.ReturnType.GenericTypeArguments.IsNullOrEmpty())
{
// 直接调用
var taskProxy = (Task)DaprClientProxyMethod
.Invoke(clientProxy, new object[] { clientProxyRequestContext });
await taskProxy;
}
else
{
// 有返回值的调用
var returnType = serviceMethod.ReturnType.GenericTypeArguments[0];
var result = (Task)DaprCallRequestAsyncMethod
.MakeGenericMethod(returnType)
.Invoke(this, new object[] { context });
context.SetResult(await GetResultAsync(result, returnType));
}
}
protected virtual async Task<ActionApiDescriptionModel> GetActionApiDescriptionModel(
JobRunnableContext context,
string service,
string appId,
Type serviceType,
MethodInfo serviceMethod)
{
var apiDescriptionFinder = context.GetRequiredService<IDaprApiDescriptionFinder>();
return await apiDescriptionFinder.FindActionAsync(
service,
appId,
serviceType,
serviceMethod);
}
#endregion
protected virtual async Task<object> GetResultAsync(Task task, Type resultType)
{
await task;
var resultProperty = typeof(Task<>)
.MakeGenericType(resultType)
.GetProperty(nameof(Task<object>.Result), BindingFlags.Instance | BindingFlags.Public);
Check.NotNull(resultProperty, nameof(resultProperty));
return resultProperty.GetValue(task);
}
}

16
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/SleepJob.cs

@ -0,0 +1,16 @@
using System;
using System.Threading.Tasks;
namespace LINGYUN.Abp.BackgroundTasks.Jobs;
public class SleepJob : IJobRunnable
{
public async Task ExecuteAsync(JobRunnableContext context)
{
context.JobData.TryGetValue("Delay", out var sleep);
Console.WriteLine($"Sleep {sleep ?? 20000} milliseconds.");
await Task.Delay(sleep?.To<int>() ?? 20000);
}
}

3
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

19
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN.Abp.BackgroundTasks.Quartz.csproj

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Quartz" Version="$(VoloAbpPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.BackgroundTasks\LINGYUN.Abp.BackgroundTasks.csproj" />
</ItemGroup>
</Project>

19
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/AbpBackgroundTasksQuartzModule.cs

@ -0,0 +1,19 @@
using Microsoft.Extensions.DependencyInjection;
using Quartz;
using Volo.Abp;
using Volo.Abp.Modularity;
using Volo.Abp.Quartz;
namespace LINGYUN.Abp.BackgroundTasks.Quartz;
[DependsOn(typeof(AbpBackgroundTasksModule))]
[DependsOn(typeof(AbpQuartzModule))]
public class AbpBackgroundTasksQuartzModule : AbpModule
{
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var _scheduler = context.ServiceProvider.GetRequiredService<IScheduler>();
_scheduler.ListenerManager.AddJobListener(context.ServiceProvider.GetRequiredService<QuartzJobListener>());
}
}

12
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/IQuartzJobExecutorProvider.cs

@ -0,0 +1,12 @@
using Quartz;
namespace LINGYUN.Abp.BackgroundTasks.Quartz;
public interface IQuartzJobExecutorProvider
{
#nullable enable
IJobDetail? CreateJob(JobInfo job);
ITrigger CreateTrigger(JobInfo job);
#nullable disable
}

14
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobConcurrentAdapter.cs

@ -0,0 +1,14 @@
using System;
using Quartz;
namespace LINGYUN.Abp.BackgroundTasks.Quartz;
[DisallowConcurrentExecution]
public class QuartzJobConcurrentAdapter<TJobRunnable> : QuartzJobSimpleAdapter<TJobRunnable>
where TJobRunnable : IJobRunnable
{
public QuartzJobConcurrentAdapter(IServiceProvider serviceProvider)
: base(serviceProvider)
{
}
}

112
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobExecutorProvider.cs

@ -0,0 +1,112 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Quartz;
using System;
using System.Collections.Generic;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Timing;
namespace LINGYUN.Abp.BackgroundTasks.Quartz;
public class QuartzJobExecutorProvider : IQuartzJobExecutorProvider, ISingletonDependency
{
public ILogger<QuartzJobExecutorProvider> Logger { protected get; set; }
protected IClock Clock { get; }
protected AbpBackgroundTasksOptions Options { get; }
public QuartzJobExecutorProvider(
IClock clock,
IOptions<AbpBackgroundTasksOptions> options)
{
Clock = clock;
Options = options.Value;
Logger = NullLogger<QuartzJobExecutorProvider>.Instance;
}
public IJobDetail CreateJob(JobInfo job)
{
var jobType = Type.GetType(job.Type) ?? Options.JobProviders.GetOrDefault(job.Type);
if (jobType == null)
{
Logger.LogWarning($"The task: {job.Group} - {job.Name}: {job.Type} is not registered and cannot create an instance of the performer type.");
return null;
}
var adapterType = typeof(QuartzJobSimpleAdapter<>);
if (job.LockTimeOut > 0)
{
adapterType = typeof(QuartzJobConcurrentAdapter<>);
}
if (!typeof(IJob).IsAssignableFrom(jobType))
{
jobType = adapterType.MakeGenericType(jobType);
}
var jobBuilder = JobBuilder.Create(jobType)
.WithIdentity(job.Name, job.Group)
.WithDescription(job.Description);
jobBuilder.UsingJobData(nameof(JobInfo.Id), job.Id);
jobBuilder.UsingJobData(nameof(JobInfo.LockTimeOut), job.LockTimeOut);
jobBuilder.UsingJobData(new JobDataMap(job.Args));
return jobBuilder.Build();
}
public ITrigger CreateTrigger(JobInfo job)
{
var triggerBuilder = TriggerBuilder.Create();
switch (job.JobType)
{
case JobType.Period:
if (!CronExpression.IsValidExpression(job.Cron))
{
Logger.LogWarning($"The task: {job.Group} - {job.Name} periodic task Cron expression was invalid and the task trigger could not be created.");
return null;
}
triggerBuilder
.WithIdentity(job.Name, job.Group)
.WithDescription(job.Description)
.EndAt(job.EndTime)
.ForJob(job.Name, job.Group)
.WithPriority((int)job.Priority)
.WithCronSchedule(job.Cron);
if (job.BeginTime > Clock.Now)
{
triggerBuilder = triggerBuilder.StartAt(job.BeginTime);
}
break;
case JobType.Once:
case JobType.Persistent:
default:
// Quartz 需要减一位
var maxCount = job.MaxCount <= 0 ? -1 : job.MaxCount - 1;
if (job.JobType == JobType.Once)
{
maxCount = 0;
}
if (job.Status == JobStatus.FailedRetry && job.TryCount < job.MaxTryCount)
{
maxCount = job.MaxTryCount <= 0 ? -1 : job.MaxTryCount - 1;
}
triggerBuilder
.WithIdentity(job.Name, job.Group)
.WithDescription(job.Description)
.StartAt(Clock.Now.AddSeconds(job.Interval))
.EndAt(job.EndTime)
.ForJob(job.Name, job.Group)
.WithPriority((int)job.Priority)
.WithSimpleSchedule(x =>
x.WithIntervalInSeconds(job.Interval)
.WithRepeatCount(maxCount));
break;
}
return triggerBuilder.Build();
}
}

149
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobListener.cs

@ -0,0 +1,149 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Quartz;
using Quartz.Listener;
using System;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
namespace LINGYUN.Abp.BackgroundTasks.Quartz;
public class QuartzJobListener : JobListenerSupport, ISingletonDependency
{
public ILogger<QuartzJobListener> Logger { protected get; set; }
public override string Name => "QuartzJobListener";
protected IJobEventProvider EventProvider { get; }
protected IServiceProvider ServiceProvider { get; }
public QuartzJobListener(
IServiceProvider serviceProvider,
IJobEventProvider eventProvider)
{
ServiceProvider = serviceProvider;
EventProvider = eventProvider;
Logger = NullLogger<QuartzJobListener>.Instance;
}
public override Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default)
{
var jobType = context.JobDetail.JobType;
if (jobType.IsGenericType)
{
jobType = jobType.GetGenericTypeDefinition();
}
Logger.LogInformation($"The task {jobType.Name} could not be performed...");
return Task.FromResult(-1);
}
public override async Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default)
{
var jobId = context.GetString(nameof(JobInfo.Id));
if (Guid.TryParse(jobId, out var jobUUId))
{
try
{
using var scope = ServiceProvider.CreateScope();
var jobEventData = new JobEventData(
jobUUId,
context.JobDetail.JobType,
context.JobDetail.Key.Group,
context.JobDetail.Key.Name)
{
Result = context.Result?.ToString()
};
var jobEventList = EventProvider.GetAll();
var eventContext = new JobEventContext(
scope.ServiceProvider,
jobEventData);
var index = 0;
var taskList = new Task[jobEventList.Count];
foreach (var jobEvent in jobEventList)
{
taskList[index] = jobEvent.OnJobBeforeExecuted(eventContext);
index++;
}
await Task.WhenAll(taskList);
}
catch (Exception ex)
{
Logger.LogError($"The event before the task execution is abnormal:{ex}");
}
}
}
public override async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException, CancellationToken cancellationToken = default)
{
try
{
using var scope = ServiceProvider.CreateScope();
var jobId = context.GetString(nameof(JobInfo.Id));
if (Guid.TryParse(jobId, out var jobUUId))
{
var jobType = context.JobDetail.JobType;
if (jobType.IsGenericType)
{
jobType = jobType.GetGenericArguments()[0];
}
var jobEventData = new JobEventData(
jobUUId,
jobType,
context.JobDetail.Key.Group,
context.JobDetail.Key.Name,
jobException)
{
Status = JobStatus.Running
};
if (context.Trigger is ISimpleTrigger simpleTrigger)
{
jobEventData.Triggered = simpleTrigger.TimesTriggered;
jobEventData.RepeatCount = simpleTrigger.RepeatCount;
}
jobEventData.Description = context.JobDetail.Description;
jobEventData.RunTime = context.FireTimeUtc.LocalDateTime;
jobEventData.LastRunTime = context.PreviousFireTimeUtc?.LocalDateTime;
jobEventData.NextRunTime = context.NextFireTimeUtc?.LocalDateTime;
if (context.Result != null)
{
jobEventData.Result = context.Result?.ToString();
}
var tenantIdString = context.GetString(nameof(IMultiTenant.TenantId));
if (Guid.TryParse(tenantIdString, out var tenantId))
{
jobEventData.TenantId = tenantId;
}
var jobEventList = EventProvider.GetAll();
var eventContext = new JobEventContext(
scope.ServiceProvider,
jobEventData);
var index = 0;
var taskList = new Task[jobEventList.Count];
foreach (var jobEvent in jobEventList)
{
taskList[index] = jobEvent.OnJobAfterExecuted(eventContext);
index++;
}
await Task.WhenAll(taskList);
}
}
catch (Exception ex)
{
Logger.LogError($"The event is abnormal after the task is executed:{ex}");
}
}
}

163
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobScheduler.cs

@ -0,0 +1,163 @@
using Quartz;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.BackgroundTasks.Quartz;
[Dependency(ReplaceServices = true)]
public class QuartzJobScheduler : IJobScheduler, ISingletonDependency
{
protected IJobStore JobStore { get; }
protected IScheduler Scheduler { get; }
protected IQuartzJobExecutorProvider QuartzJobExecutor { get; }
public QuartzJobScheduler(
IJobStore jobStore,
IScheduler scheduler,
IQuartzJobExecutorProvider quartzJobExecutor)
{
JobStore = jobStore;
Scheduler = scheduler;
QuartzJobExecutor = quartzJobExecutor;
}
public virtual async Task<bool> ExistsAsync(JobInfo job)
{
var jobKey = new JobKey(job.Name, job.Group);
return await Scheduler.CheckExists(jobKey);
}
public virtual async Task PauseAsync(JobInfo job)
{
var jobKey = new JobKey(job.Name, job.Group);
if (await Scheduler.CheckExists(jobKey))
{
var triggers = await Scheduler.GetTriggersOfJob(jobKey);
foreach (var trigger in triggers)
{
await Scheduler.PauseTrigger(trigger.Key);
}
}
}
public virtual async Task<bool> QueueAsync(JobInfo job)
{
var jobKey = new JobKey(job.Name, job.Group);
if (await Scheduler.CheckExists(jobKey))
{
return false;
}
var jobDetail = QuartzJobExecutor.CreateJob(job);
if (jobDetail == null)
{
return false;
}
var jobTrigger = QuartzJobExecutor.CreateTrigger(job);
if (jobTrigger == null)
{
return false;
}
await Scheduler.ScheduleJob(jobDetail, jobTrigger);
return await Scheduler.CheckExists(jobTrigger.Key);
}
public virtual async Task QueuesAsync(IEnumerable<JobInfo> jobs)
{
var jobDictionary = new Dictionary<IJobDetail, IReadOnlyCollection<ITrigger>>();
foreach (var job in jobs)
{
var jobDetail = QuartzJobExecutor.CreateJob(job);
if (jobDetail == null)
{
continue;
}
var jobTrigger = QuartzJobExecutor.CreateTrigger(job);
if (jobTrigger == null)
{
continue;
}
jobDictionary.Add(jobDetail, new ITrigger[] { jobTrigger });
}
await Scheduler.ScheduleJobs(jobDictionary, false);
}
public virtual async Task<bool> RemoveAsync(JobInfo job)
{
var jobKey = new JobKey(job.Name, job.Group);
if (!await Scheduler.CheckExists(jobKey))
{
return false;
}
var triggers = await Scheduler.GetTriggersOfJob(jobKey);
foreach (var trigger in triggers)
{
await Scheduler.PauseTrigger(trigger.Key);
}
await Scheduler.DeleteJob(jobKey);
return !await Scheduler.CheckExists(jobKey);
}
public virtual async Task ResumeAsync(JobInfo job)
{
var jobKey = new JobKey(job.Name, job.Group);
if (await Scheduler.CheckExists(jobKey))
{
var triggers = await Scheduler.GetTriggersOfJob(jobKey);
foreach (var trigger in triggers)
{
await Scheduler.ResumeTrigger(trigger.Key);
}
}
}
public virtual async Task<bool> ShutdownAsync()
{
await StopAsync();
await Scheduler.Shutdown(true);
return Scheduler.IsShutdown;
}
public virtual async Task<bool> StartAsync()
{
if (Scheduler.InStandbyMode)
{
await Scheduler.Start();
}
return Scheduler.InStandbyMode;
}
public virtual async Task<bool> StopAsync()
{
if (!Scheduler.InStandbyMode)
{
//等待任务运行完成
await Scheduler.Standby();
}
return !Scheduler.InStandbyMode;
}
public virtual async Task TriggerAsync(JobInfo job)
{
var jobKey = new JobKey(job.Name, job.Group);
if (!await Scheduler.CheckExists(jobKey))
{
job.JobType = JobType.Once;
await QueueAsync(job);
}
await Scheduler.TriggerJob(jobKey);
}
}

35
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobSimpleAdapter.cs

@ -0,0 +1,35 @@
using Microsoft.Extensions.DependencyInjection;
using Quartz;
using System;
using System.Collections.Immutable;
using System.Threading.Tasks;
namespace LINGYUN.Abp.BackgroundTasks.Quartz;
public class QuartzJobSimpleAdapter<TJobRunnable> : IJob
where TJobRunnable : IJobRunnable
{
protected IServiceProvider ServiceProvider { get; }
public QuartzJobSimpleAdapter(
IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
public async virtual Task Execute(IJobExecutionContext context)
{
// 任务已经在一个作用域中
// using var scope = ServiceProvider.CreateScope();
var jobExecuter = ServiceProvider.GetRequiredService<IJobRunnableExecuter>();
var jobContext = new JobRunnableContext(
typeof(TJobRunnable),
ServiceProvider,
context.MergedJobDataMap.ToImmutableDictionary());
await jobExecuter.ExecuteAsync(jobContext);
context.Result = jobContext.Result;
}
}

27
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/Quartz/IJobExecutionContextExtensions.cs

@ -0,0 +1,27 @@
using System;
namespace Quartz;
public static class IJobExecutionContextExtensions
{
public static TValue GetData<TValue>(this IJobExecutionContext context, string key)
{
var value = context.MergedJobDataMap.GetString(key);
return (TValue)Convert.ChangeType(value, typeof(TValue));
}
public static string GetString(this IJobExecutionContext context, string key)
{
var value = context.MergedJobDataMap.Get(key);
return value != null ? value.ToString() : "";
}
public static int GetInt(this IJobExecutionContext context, string key)
{
var value = context.MergedJobDataMap.GetInt(key);
return value;
}
}

16
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/README.md

@ -0,0 +1,16 @@
# LINGYUN.Abp.BackgroundTasks.Quartz
后台任务(队列)模块的Quartz实现.
并添加一个监听器用于通知管理者任务状态
## 配置使用
模块按需引用,具体配置参考Volo.Abp.Quartz模块
```csharp
[DependsOn(typeof(AbpBackgroundTasksQuartzModule))]
public class YouProjectModule : AbpModule
{
// other
}
```

3
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

22
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN.Abp.BackgroundTasks.csproj

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Auditing" Version="$(VoloAbpPackageVersion)" />
<PackageReference Include="Volo.Abp.BackgroundJobs.Abstractions" Version="$(VoloAbpPackageVersion)" />
<PackageReference Include="Volo.Abp.DistributedLocking.Abstractions" Version="$(VoloAbpPackageVersion)" />
<PackageReference Include="Volo.Abp.Guids" Version="$(VoloAbpPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.BackgroundTasks.Abstractions\LINGYUN.Abp.BackgroundTasks.Abstractions.csproj" />
</ItemGroup>
</Project>

23
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/AbpBackgroundTasksModule.cs

@ -0,0 +1,23 @@
using LINGYUN.Abp.BackgroundTasks.Internal;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Auditing;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.BackgroundTasks;
[DependsOn(typeof(AbpAuditingModule))]
[DependsOn(typeof(AbpBackgroundTasksAbstractionsModule))]
[DependsOn(typeof(AbpBackgroundJobsAbstractionsModule))]
[DependsOn(typeof(AbpDistributedLockingAbstractionsModule))]
[DependsOn(typeof(AbpGuidsModule))]
public class AbpBackgroundTasksModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddTransient(typeof(BackgroundJobAdapter<>));
context.Services.AddHostedService<DefaultBackgroundWorker>();
}
}

46
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundJobAdapter.cs

@ -0,0 +1,46 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.Json;
namespace LINGYUN.Abp.BackgroundTasks;
public class BackgroundJobAdapter<TArgs> : IJobRunnable
{
public ILogger<BackgroundJobAdapter<TArgs>> Logger { protected get; set; }
protected AbpBackgroundJobOptions Options { get; }
protected IServiceScopeFactory ServiceScopeFactory { get; }
protected IBackgroundJobExecuter JobExecuter { get; }
public BackgroundJobAdapter(
IOptions<AbpBackgroundJobOptions> options,
IBackgroundJobExecuter jobExecuter,
IServiceScopeFactory serviceScopeFactory)
{
JobExecuter = jobExecuter;
ServiceScopeFactory = serviceScopeFactory;
Options = options.Value;
Logger = NullLogger<BackgroundJobAdapter<TArgs>>.Instance;
}
public virtual async Task ExecuteAsync(JobRunnableContext context)
{
using var scope = ServiceScopeFactory.CreateScope();
object args = null;
if (context.TryGetString(nameof(TArgs), out var argsJson))
{
var jsonSerializer = context.GetRequiredService<IJsonSerializer>();
args = jsonSerializer.Deserialize<TArgs>(argsJson);
}
var jobType = Options.GetJob(typeof(TArgs)).JobType;
var jobContext = new JobExecutionContext(scope.ServiceProvider, jobType, args);
await JobExecuter.ExecuteAsync(jobContext);
}
}

94
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundJobManager.cs

@ -0,0 +1,94 @@
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.Json;
using Volo.Abp.Timing;
namespace LINGYUN.Abp.BackgroundTasks;
[Dependency(ReplaceServices = true)]
public class BackgroundJobManager : IBackgroundJobManager, ITransientDependency
{
protected IClock Clock { get; }
protected IJobStore JobStore { get; }
protected IJobScheduler JobScheduler { get; }
protected IGuidGenerator GuidGenerator { get; }
protected IJsonSerializer JsonSerializer { get; }
protected AbpBackgroundJobOptions Options { get; }
public BackgroundJobManager(
IClock clock,
IJobStore jobStore,
IJobScheduler jobScheduler,
IGuidGenerator guidGenerator,
IJsonSerializer jsonSerializer,
IOptions<AbpBackgroundJobOptions> options)
{
Clock = clock;
JobStore = jobStore;
JobScheduler = jobScheduler;
GuidGenerator = guidGenerator;
JsonSerializer = jsonSerializer;
Options = options.Value;
}
public virtual async Task<string> EnqueueAsync<TArgs>(
TArgs args,
BackgroundJobPriority priority = BackgroundJobPriority.Normal,
TimeSpan? delay = null)
{
var jobConfiguration = Options.GetJob<TArgs>();
var interval = 0;
if (delay.HasValue)
{
interval = delay.Value.Seconds;
}
var jobId = GuidGenerator.Create();
var jobArgs = new Dictionary<string, object>
{
{ nameof(TArgs), JsonSerializer.Serialize(args) },
{ "ArgsType", jobConfiguration.ArgsType.AssemblyQualifiedName },
{ "JobType", jobConfiguration.JobType.AssemblyQualifiedName },
{ "JobName", jobConfiguration.JobName },
};
var jobInfo = new JobInfo
{
Id = jobId,
Name = jobId.ToString(),
Group = "BackgroundJobs",
Priority = ConverForm(priority),
BeginTime = DateTime.Now,
Args = jobArgs,
Description = "From the framework background jobs",
JobType = JobType.Once,
Interval = interval,
CreationTime = Clock.Now,
// 确保不会被轮询入队
Status = JobStatus.None,
Type = typeof(BackgroundJobAdapter<TArgs>).AssemblyQualifiedName,
};
// 存储状态
await JobStore.StoreAsync(jobInfo);
// 手动入队
await JobScheduler.QueueAsync(jobInfo);
return jobId.ToString();
}
private JobPriority ConverForm(BackgroundJobPriority priority)
{
return priority switch
{
BackgroundJobPriority.Low => JobPriority.Low,
BackgroundJobPriority.High => JobPriority.High,
BackgroundJobPriority.BelowNormal => JobPriority.BelowNormal,
BackgroundJobPriority.AboveNormal => JobPriority.AboveNormal,
_ => JobPriority.Normal,
};
}
}

15
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobEventProvider.cs

@ -0,0 +1,15 @@
using System.Collections.Generic;
namespace LINGYUN.Abp.BackgroundTasks;
/// <summary>
/// 任务事件提供者
/// </summary>
public interface IJobEventProvider
{
/// <summary>
/// 返回所有任务事件注册接口
/// </summary>
/// <returns></returns>
IReadOnlyCollection<IJobEvent> GetAll();
}

12
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobRunnableExecuter.cs

@ -0,0 +1,12 @@
using System.Threading.Tasks;
namespace LINGYUN.Abp.BackgroundTasks;
/// <summary>
/// 定义任务执行者接口
/// 可以通过它实现一些限制(例如分布式锁)
/// </summary>
public interface IJobRunnableExecuter
{
Task ExecuteAsync(JobRunnableContext context);
}

72
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobScheduler.cs

@ -0,0 +1,72 @@
using System.Threading.Tasks;
using System.Collections.Generic;
namespace LINGYUN.Abp.BackgroundTasks;
/// <summary>
/// 作业调度接口
/// </summary>
public interface IJobScheduler
{
/// <summary>
/// 任务入队
/// </summary>
/// <param name="job"></param>
/// <returns></returns>
Task<bool> QueueAsync(JobInfo job);
/// <summary>
/// 任务入队
/// </summary>
/// <param name="jobs"></param>
/// <returns></returns>
Task QueuesAsync(IEnumerable<JobInfo> jobs);
/// <summary>
/// 任务是否存在
/// </summary>
/// <param name="group"></param>
/// <param name="name"></param>
/// <returns></returns>
Task<bool> ExistsAsync(JobInfo job);
/// <summary>
/// 触发任务
/// </summary>
/// <param name="group"></param>
/// <param name="name"></param>
/// <returns></returns>
Task TriggerAsync(JobInfo job);
/// <summary>
/// 暂停任务
/// </summary>
/// <param name="group"></param>
/// <param name="name"></param>
/// <returns></returns>
Task PauseAsync(JobInfo job);
/// <summary>
/// 恢复暂停的任务
/// </summary>
/// <param name="group"></param>
/// <param name="name"></param>
/// <returns></returns>
Task ResumeAsync(JobInfo job);
/// <summary>
/// 移除任务
/// </summary>
/// <param name="group"></param>
/// <param name="name"></param>
/// <returns></returns>
Task<bool> RemoveAsync(JobInfo job);
/// <summary>
/// 启动任务协调器
/// </summary>
/// <returns></returns>
Task<bool> StartAsync();
/// <summary>
/// 停止任务协调器
/// </summary>
/// <returns></returns>
Task<bool> StopAsync();
/// <summary>
/// 释放任务协调器
/// </summary>
/// <returns></returns>
Task<bool> ShutdownAsync();
}

27
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/IJobStore.cs

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.BackgroundTasks;
public interface IJobStore
{
Task<List<JobInfo>> GetWaitingListAsync(
int maxResultCount,
CancellationToken cancellationToken = default);
Task<List<JobInfo>> GetAllPeriodTasksAsync(
CancellationToken cancellationToken = default);
Task<JobInfo> FindAsync(Guid jobId);
Task StoreAsync(JobInfo jobInfo);
Task StoreLogAsync(JobEventData eventData);
Task CleanupAsync(
int maxResultCount,
TimeSpan jobExpiratime,
CancellationToken cancellationToken = default);
}

20
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundCleaningJob.cs

@ -0,0 +1,20 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
namespace LINGYUN.Abp.BackgroundTasks.Internal;
[DisableAuditing]
internal class BackgroundCleaningJob : IJobRunnable
{
public virtual async Task ExecuteAsync(JobRunnableContext context)
{
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundTasksOptions>>().Value;
var store = context.ServiceProvider.GetRequiredService<IJobStore>();
await store.CleanupAsync(
options.MaxJobCleanCount,
options.JobExpiratime);
}
}

27
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundKeepAliveJob.cs

@ -0,0 +1,27 @@
using Microsoft.Extensions.DependencyInjection;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
namespace LINGYUN.Abp.BackgroundTasks.Internal;
[DisableAuditing]
internal class BackgroundKeepAliveJob : IJobRunnable
{
public virtual async Task ExecuteAsync(JobRunnableContext context)
{
var store = context.ServiceProvider.GetRequiredService<IJobStore>();
// TODO: 如果积压有大量周期性任务, 可能后面的队列无法被检索到
var periodJobs = await store.GetAllPeriodTasksAsync();
if (!periodJobs.Any())
{
return;
}
var jobScheduler = context.ServiceProvider.GetRequiredService<IJobScheduler>();
await jobScheduler.QueuesAsync(periodJobs);
}
}

34
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundPollingJob.cs

@ -0,0 +1,34 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
namespace LINGYUN.Abp.BackgroundTasks.Internal;
[DisableAuditing]
internal class BackgroundPollingJob : IJobRunnable
{
public virtual async Task ExecuteAsync(JobRunnableContext context)
{
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundTasksOptions>>().Value;
var store = context.ServiceProvider.GetRequiredService<IJobStore>();
// TODO: 如果积压有大量持续性任务, 可能后面的队列无法被检索到
// 尽量让任务重复次数在可控范围内
// 需要借助队列提供者来持久化已入队任务
var waitingJobs = await store.GetWaitingListAsync(options.MaxJobFetchCount);
if (!waitingJobs.Any())
{
return;
}
var jobScheduler = context.ServiceProvider.GetRequiredService<IJobScheduler>();
foreach (var job in waitingJobs)
{
await jobScheduler.QueueAsync(job);
}
}
}

109
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/DefaultBackgroundWorker.cs

@ -0,0 +1,109 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.BackgroundTasks.Internal;
internal class DefaultBackgroundWorker : BackgroundService
{
private readonly IJobStore _jobStore;
private readonly IJobScheduler _jobScheduler;
private readonly AbpBackgroundTasksOptions _options;
public DefaultBackgroundWorker(
IJobStore jobStore,
IJobScheduler jobScheduler,
IOptions<AbpBackgroundTasksOptions> options)
{
_jobStore = jobStore;
_jobScheduler = jobScheduler;
_options = options.Value;
}
protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{
await QueuePollingJob();
await QueueKeepAliveJob();
await QueueCleaningJob();
}
private async Task QueueKeepAliveJob()
{
var keepAliveJob = BuildKeepAliveJobInfo();
await _jobScheduler.QueueAsync(keepAliveJob);
}
private async Task QueuePollingJob()
{
var pollingJob = BuildPollingJobInfo();
await _jobScheduler.QueueAsync(pollingJob);
}
private async Task QueueCleaningJob()
{
var cleaningJob = BuildCleaningJobInfo();
await _jobScheduler.QueueAsync(cleaningJob);
}
private JobInfo BuildKeepAliveJobInfo()
{
return new JobInfo
{
Id = Guid.Parse("8F50C5D9-5691-4B99-A52B-CABD91D93C89"),
Name = nameof(BackgroundKeepAliveJob),
Group = "Default",
Description = "Add periodic tasks",
Args = new Dictionary<string, object>(),
Status = JobStatus.Running,
BeginTime = DateTime.Now,
CreationTime = DateTime.Now,
JobType = JobType.Once,
Priority = JobPriority.High,
MaxCount = 1,
Interval = 30,
Type = typeof(BackgroundKeepAliveJob).AssemblyQualifiedName,
};
}
private JobInfo BuildPollingJobInfo()
{
return new JobInfo
{
Id = Guid.Parse("C51152E9-F0B8-4252-8352-283BE46083CC"),
Name = nameof(BackgroundPollingJob),
Group = "Default",
Description = "Polling tasks to be executed",
Args = new Dictionary<string, object>(),
Status = JobStatus.Running,
BeginTime = DateTime.Now,
CreationTime = DateTime.Now,
Cron = _options.JobFetchCronExpression,
JobType = JobType.Period,
Priority = JobPriority.High,
LockTimeOut = _options.JobFetchLockTimeOut,
Type = typeof(BackgroundPollingJob).AssemblyQualifiedName,
};
}
private JobInfo BuildCleaningJobInfo()
{
return new JobInfo
{
Id = Guid.Parse("AAAF8783-FA06-4CF9-BDCA-11140FB2478F"),
Name = nameof(BackgroundCleaningJob),
Group = "Default",
Description = "Cleaning tasks to be executed",
Args = new Dictionary<string, object>(),
Status = JobStatus.Running,
BeginTime = DateTime.Now,
CreationTime = DateTime.Now,
Cron = _options.JobCleanCronExpression,
JobType = JobType.Period,
Priority = JobPriority.High,
Type = typeof(BackgroundCleaningJob).AssemblyQualifiedName,
};
}
}

87
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/InMemoryJobStore.cs

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.BackgroundTasks.Internal;
[Dependency(TryRegister = true)]
internal class InMemoryJobStore : IJobStore, ISingletonDependency
{
private readonly List<JobInfo> _memoryJobStore;
public InMemoryJobStore()
{
_memoryJobStore = new List<JobInfo>();
}
public Task<List<JobInfo>> GetAllPeriodTasksAsync(CancellationToken cancellationToken = default)
{
var jobs = _memoryJobStore
.Where(x => x.JobType == JobType.Period && x.Status == JobStatus.Running)
.OrderByDescending(x => x.Priority)
.ToList();
return Task.FromResult(jobs);
}
public Task<List<JobInfo>> GetWaitingListAsync(int maxResultCount, CancellationToken cancellationToken = default)
{
var now = DateTime.Now;
var jobs = _memoryJobStore
.Where(x => !x.IsAbandoned && x.JobType != JobType.Period && x.Status == JobStatus.Running)
.OrderByDescending(x => x.Priority)
.ThenBy(x => x.TryCount)
.ThenBy(x => x.NextRunTime)
.Take(maxResultCount)
.ToList();
return Task.FromResult(jobs);
}
public Task<JobInfo> FindAsync(Guid jobId)
{
var job = _memoryJobStore.FirstOrDefault(x => x.Id.Equals(jobId));
return Task.FromResult(job);
}
public Task StoreAsync(JobInfo jobInfo)
{
var job = _memoryJobStore.FirstOrDefault(x => x.Id.Equals(jobInfo.Id));
if (job != null)
{
job.NextRunTime = jobInfo.NextRunTime;
job.LastRunTime = jobInfo.LastRunTime;
job.Status = jobInfo.Status;
job.TriggerCount = jobInfo.TriggerCount;
job.TryCount = jobInfo.TryCount;
job.IsAbandoned = jobInfo.IsAbandoned;
}
else
{
_memoryJobStore.Add(jobInfo);
}
return Task.CompletedTask;
}
public Task StoreLogAsync(JobEventData eventData)
{
return Task.CompletedTask;
}
public Task CleanupAsync(int maxResultCount, TimeSpan jobExpiratime, CancellationToken cancellationToken = default)
{
var expiratime = DateTime.Now - jobExpiratime;
var expriaJobs = _memoryJobStore.Where(
x => x.Status == JobStatus.Completed &&
expiratime.CompareTo(x.LastRunTime ?? x.EndTime ?? x.CreationTime) <= 0)
.Take(maxResultCount);
_memoryJobStore.RemoveAll(expriaJobs);
return Task.CompletedTask;
}
}

41
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobEventProvider.cs

@ -0,0 +1,41 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.BackgroundTasks.Internal;
internal class JobEventProvider : IJobEventProvider, ISingletonDependency
{
private readonly Lazy<List<IJobEvent>> _lazyEvents;
private List<IJobEvent> _events => _lazyEvents.Value;
private readonly IServiceProvider _serviceProvider;
private readonly AbpBackgroundTasksOptions _options;
public JobEventProvider(
IOptions<AbpBackgroundTasksOptions> options,
IServiceProvider serviceProvider)
{
_options = options.Value;
_serviceProvider = serviceProvider;
_lazyEvents = new Lazy<List<IJobEvent>>(CreateJobEvents);
}
public IReadOnlyCollection<IJobEvent> GetAll()
{
return _events.ToImmutableList();
}
private List<IJobEvent> CreateJobEvents()
{
var jobEvents = _options
.JobMonitors
.Select(p => _serviceProvider.GetRequiredService(p) as IJobEvent)
.ToList();
return jobEvents;
}
}

78
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobExecutedEvent.cs

@ -0,0 +1,78 @@
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.BackgroundTasks.Internal;
public class JobExecutedEvent : JobEventBase<JobExecutedEvent>, ITransientDependency
{
protected override async Task OnJobAfterExecutedAsync(JobEventContext context)
{
var store = context.ServiceProvider.GetRequiredService<IJobStore>();
var job = await store.FindAsync(context.EventData.Key);
if (job != null)
{
job.TriggerCount += 1;
job.LastRunTime = context.EventData.RunTime;
job.NextRunTime = context.EventData.NextRunTime;
job.Result = context.EventData.Result ?? "OK";
job.Status = JobStatus.Running;
// 一次性任务执行一次后标记为已完成
if (job.JobType == JobType.Once)
{
job.Status = JobStatus.Completed;
}
// 任务异常后可重试
if (context.EventData.Exception != null)
{
job.TryCount += 1;
job.IsAbandoned = false;
// 将任务标记为运行中, 会被轮询重新进入队列
job.Status = JobStatus.FailedRetry;
job.Result = context.EventData.Exception.Message;
// 多次异常后需要重新计算优先级
if (job.TryCount <= (job.MaxTryCount / 2) &&
job.TryCount > (job.MaxTryCount / 3))
{
job.Priority = JobPriority.BelowNormal;
}
else if (job.TryCount > (job.MaxTryCount / 1.5))
{
job.Priority = JobPriority.Low;
}
if (job.TryCount > job.MaxTryCount)
{
job.Status = JobStatus.Stopped;
job.IsAbandoned = true;
job.NextRunTime = null;
await RemoveJobAsync(context, job);
}
}
else
{
// 所有任务达到上限则标记已完成
if (job.MaxCount > 0 && job.TriggerCount >= job.MaxCount)
{
job.Status = JobStatus.Completed;
job.NextRunTime = null;
await RemoveJobAsync(context, job);
}
}
await store.StoreAsync(job);
}
}
private async Task RemoveJobAsync(JobEventContext context, JobInfo jobInfo)
{
var jobScheduler = context.ServiceProvider.GetRequiredService<IJobScheduler>();
await jobScheduler.RemoveAsync(jobInfo);
}
}

27
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobLogEvent.cs

@ -0,0 +1,27 @@
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Uow;
namespace LINGYUN.Abp.BackgroundTasks.Internal;
/// <summary>
/// 存储任务日志
/// </summary>
/// <remarks>
/// 任务类型标记了<see cref="DisableAuditingAttribute"/> 特性则不会记录日志
/// </remarks>
public class JobLogEvent : JobEventBase<JobLogEvent>, ITransientDependency
{
protected async override Task OnJobAfterExecutedAsync(JobEventContext context)
{
if (context.EventData.Type.IsDefined(typeof(DisableAuditingAttribute), true))
{
return;
}
var store = context.ServiceProvider.GetRequiredService<IJobStore>();
await store.StoreLogAsync(context.EventData);
}
}

64
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/JobRunnableExecuter.cs

@ -0,0 +1,64 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DistributedLocking;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.BackgroundTasks;
public class JobRunnableExecuter : IJobRunnableExecuter, ISingletonDependency
{
protected const string LockKeyFormat = "p:{0},job:{1},key:{2}";
public async virtual Task ExecuteAsync(JobRunnableContext context)
{
Guid? tenantId = null;
if (context.JobData.TryGetValue(nameof(IMultiTenant.TenantId), out var tenant) &&
Guid.TryParse(tenant?.ToString(), out var tid))
{
tenantId = tid;
}
var currentTenant = context.ServiceProvider.GetRequiredService<ICurrentTenant>();
using (currentTenant.Change(tenantId))
{
context.JobData.TryGetValue(nameof(JobInfo.LockTimeOut), out var lockTime);
// 某些提供者如果无法保证锁一致性, 那么需要用分布式锁
if (lockTime != null && (lockTime is int time && time > 0))
{
var jobId = context.JobData.GetOrDefault(nameof(JobInfo.Id));
var jobLockKey = string.Format(LockKeyFormat, tenantId?.ToString() ?? "Default", context.JobType.Name, jobId);
var distributedLock = context.ServiceProvider.GetRequiredService<IAbpDistributedLock>();
var handle = await distributedLock.TryAcquireAsync(jobLockKey, TimeSpan.FromSeconds(time));
if (handle == null)
{
// 抛出异常 通过监听器使其重试
throw new AbpBackgroundTaskConcurrentException(context.JobType);
}
await using (handle)
{
await InternalExecuteAsync(context);
}
}
else
{
await InternalExecuteAsync(context);
}
}
}
private async Task InternalExecuteAsync(JobRunnableContext context)
{
var jobRunnable = context.ServiceProvider.GetService(context.JobType);
if (jobRunnable == null)
{
jobRunnable = Activator.CreateInstance(context.JobType);
}
await ((IJobRunnable)jobRunnable).ExecuteAsync(context);
}
}

118
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/README.md

@ -0,0 +1,118 @@
# LINGYUN.Abp.BackgroundTasks
后台任务(队列)模块,Abp提供的后台作业与后台工作者不支持Cron表达式, 提供可管理的后台任务(队列)功能.
实现了**Volo.Abp.BackgroundJobs.IBackgroundJobManager**, 意味着您也能通过框架后台作业接口添加新作业.
## 任务类别
* JobType.Once: 一次性任务, 此类型只会被执行一次, 适用于邮件通知等场景
* JobType.Period: 周期性任务, 此类型任务会根据Cron表达式来决定运行方式, 适用于报表分析等场景
* JobType.Persistent: 持续性任务, 此类型任务按照给定重复次数、重复间隔运行, 适用于接口压测等场景
## 配置使用
模块按需引用
```csharp
[DependsOn(typeof(AbpBackgroundTasksModule))]
public class YouProjectModule : AbpModule
{
// other
}
```
```csharp
public class DemoClass
{
protected IServiceProvider ServiceProvider { get; }
public DemoClass(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
public async Task Some()
{
var scheduler = ServiceProvider.GetRequiredService<IJobScheduler>();
// 将周期性(5秒一次)任务添加到队列
await scheduler.QueueAsync(new JobInfo
{
Type = typeof(ConsoleJob).AssemblyQualifiedName,
Args = new Dictionary<string, object>(),
Name = "Test-Console-Period",
Group = "Test",
Description = "Test-Console",
Id = Guid.NewGuid(),
JobType = JobType.Period,
Priority = Volo.Abp.BackgroundJobs.BackgroundJobPriority.Low,
Cron = "0/5 * * * * ? ",
TryCount = 10,
Status = JobStatus.Running,
// 定义此字段处理并发
LockTimeOut = 120,
});
// 将一次性任务添加到队列, 将在10(Interval)秒后被执行
await scheduler.QueueAsync(new JobInfo
{
Type = typeof(ConsoleJob).AssemblyQualifiedName,
Args = new Dictionary<string, object>(),
Name = "Test-Console-Once",
Group = "Test",
Description = "Test-Console",
Id = Guid.NewGuid(),
JobType = JobType.Once,
Priority = Volo.Abp.BackgroundJobs.BackgroundJobPriority.Low,
Interval = 10,
TryCount = 10,
Status = JobStatus.Running,
});
// 将持续性任务添加到队列, 将在10(Interval)秒后被执行, 最大执行5(MaxCount)次
await scheduler.QueueAsync(new JobInfo
{
Type = typeof(ConsoleJob).AssemblyQualifiedName,
Args = new Dictionary<string, object>(),
Name = "Test-Console-Persistent",
Group = "Test",
Description = "Test-Console",
Id = Guid.NewGuid(),
JobType = JobType.Persistent,
Priority = Volo.Abp.BackgroundJobs.BackgroundJobPriority.Low,
Interval = 10,
TryCount = 10,
MaxCount = 5,
Status = JobStatus.Running,
});
// 同样可以把框架后台作业添加到作业调度器中, 不需要更改使用习惯
var backgroundJobManager = ServiceProvider.GetRequiredService<IBackgroundJobManager>();
await jobManager.EnqueueAsync(
new SmsJobArgs
{
PhoneNumber = "13800138000",
Message = "来自框架的后台工作者"
},
BackgroundJobPriority.High,
TimeSpan.FromSeconds(10));
}
}
public class SmsJobArgs
{
public string PhoneNumber { get; set; }
public string Message { get; set; }
}
public class SmsJob : AsyncBackgroundJob<SmsJobArgs>, ITransientDependency
{
public override Task ExecuteAsync(SmsJobArgs args)
{
Console.WriteLine($"Send sms message: {args.Message}");
return Task.CompletedTask;
}
}
```

3
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

20
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN.Abp.TaskManagement.Application.Contracts.csproj

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Authorization" Version="$(VoloAbpPackageVersion)" />
<PackageReference Include="Volo.Abp.Ddd.Application.Contracts" Version="$(VoloAbpPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.TaskManagement.Domain.Shared\LINGYUN.Abp.TaskManagement.Domain.Shared.csproj" />
</ItemGroup>
</Project>

36
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoCreateDto.cs

@ -0,0 +1,36 @@
using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Validation;
namespace LINGYUN.Abp.TaskManagement;
public class BackgroundJobInfoCreateDto : BackgroundJobInfoCreateOrUpdateDto
{
/// <summary>
/// 任务名称
/// </summary>
[Required]
[DynamicStringLength(typeof(BackgroundJobInfoConsts), nameof(BackgroundJobInfoConsts.MaxNameLength))]
public string Name { get; set; }
/// <summary>
/// 任务分组
/// </summary>
[Required]
[DynamicStringLength(typeof(BackgroundJobInfoConsts), nameof(BackgroundJobInfoConsts.MaxGroupLength))]
public string Group { get; set; }
/// <summary>
/// 任务类型
/// </summary>
[Required]
[DynamicStringLength(typeof(BackgroundJobInfoConsts), nameof(BackgroundJobInfoConsts.MaxTypeLength))]
public string Type { get; set; }
/// <summary>
/// 开始时间
/// </summary>
[Required]
public DateTime BeginTime { get; set; }
/// <summary>
/// 结束时间
/// </summary>
public DateTime? EndTime { get; set; }
}

56
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoCreateOrUpdateDto.cs

@ -0,0 +1,56 @@
using LINGYUN.Abp.BackgroundTasks;
using System;
using Volo.Abp.Data;
using Volo.Abp.Validation;
namespace LINGYUN.Abp.TaskManagement;
public abstract class BackgroundJobInfoCreateOrUpdateDto
{
/// <summary>
/// 是否启用
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// 任务参数
/// </summary>
public ExtraPropertyDictionary Args { get; set; }
/// <summary>
/// 描述
/// </summary>
[DynamicStringLength(typeof(BackgroundJobInfoConsts), nameof(BackgroundJobInfoConsts.MaxDescriptionLength))]
public string Description { get; set; }
/// <summary>
/// 任务类别
/// </summary>
public JobType JobType { get; set; }
/// <summary>
/// Cron表达式,如果是持续任务需要指定
/// </summary>
[DynamicStringLength(typeof(BackgroundJobInfoConsts), nameof(BackgroundJobInfoConsts.MaxCronLength))]
public string Cron { get; set; }
/// <summary>
/// 失败重试上限
/// 默认:50
/// </summary>
public int MaxTryCount { get; set; }
/// <summary>
/// 最大执行次数
/// 默认:0, 无限制
/// </summary>
public int MaxCount { get; set; }
/// <summary>
/// 间隔时间,单位秒,与Cron表达式冲突
/// 默认: 300
/// </summary>
public int Interval { get; set; } = 300;
/// <summary>
/// 任务优先级
/// </summary>
public JobPriority Priority { get; set; }
/// <summary>
/// 任务独占超时时长(秒)
/// 0或更小不生效
/// </summary>
public int LockTimeOut { get; set; }
}

104
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoDto.cs

@ -0,0 +1,104 @@
using LINGYUN.Abp.BackgroundTasks;
using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
namespace LINGYUN.Abp.TaskManagement;
public class BackgroundJobInfoDto : ExtensibleAuditedEntityDto<Guid>, IHasConcurrencyStamp
{
public string ConcurrencyStamp { get; set; }
/// <summary>
/// 任务名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 任务分组
/// </summary>
public string Group { get; set; }
/// <summary>
/// 任务类型
/// </summary>
public string Type { get; set; }
/// <summary>
/// 返回参数
/// </summary>
public string Result { get; set; }
/// <summary>
/// 任务参数
/// </summary>
public ExtraPropertyDictionary Args { get; set; }
/// <summary>
/// 任务状态
/// </summary>
public JobStatus Status { get; set; }
/// <summary>
/// 描述
/// </summary>
public string Description { get; set; }
/// <summary>
/// 开始时间
/// </summary>
public DateTime BeginTime { get; set; }
/// <summary>
/// 结束时间
/// </summary>
public DateTime? EndTime { get; set; }
/// <summary>
/// 上次运行时间
/// </summary>
public DateTime? LastRunTime { get; set; }
/// <summary>
/// 下一次执行时间
/// </summary>
public DateTime? NextRunTime { get; set; }
/// <summary>
/// 任务类别
/// </summary>
public JobType JobType { get; set; }
/// <summary>
/// Cron表达式,如果是持续任务需要指定
/// </summary>
public string Cron { get; set; }
/// <summary>
/// 触发次数
/// </summary>
public int TriggerCount { get; set; }
/// <summary>
/// 失败重试次数
/// </summary>
public int TryCount { get; set; }
/// <summary>
/// 失败重试上限
/// 默认:50
/// </summary>
public int MaxTryCount { get; set; }
/// <summary>
/// 最大执行次数
/// 默认:0, 无限制
/// </summary>
public int MaxCount { get; set; }
/// <summary>
/// 连续失败且不会再次执行
/// </summary>
public bool IsAbandoned { get; set; }
/// <summary>
/// 是否启用
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// 间隔时间,单位秒,与Cron表达式冲突
/// 默认: 300
/// </summary>
public int Interval { get; set; } = 300;
/// <summary>
/// 任务优先级
/// </summary>
public JobPriority Priority { get; set; }
/// <summary>
/// 任务独占超时时长(秒)
/// 0或更小不生效
/// </summary>
public int LockTimeOut { get; set; }
}

65
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoGetListInput.cs

@ -0,0 +1,65 @@
using LINGYUN.Abp.BackgroundTasks;
using System;
using Volo.Abp.Application.Dtos;
namespace LINGYUN.Abp.TaskManagement;
public class BackgroundJobInfoGetListInput: PagedAndSortedResultRequestDto
{
/// <summary>
/// 其他过滤条件
/// </summary>
public string Filter { get; set; }
/// <summary>
/// 任务名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 任务分组
/// </summary>
public string Group { get; set; }
/// <summary>
/// 任务类型
/// </summary>
public string Type { get; set; }
/// <summary>
/// 任务状态
/// </summary>
public JobStatus? Status { get; set; }
/// <summary>
/// 开始时间
/// </summary>
public DateTime? BeginTime { get; set; }
/// <summary>
/// 结束时间
/// </summary>
public DateTime? EndTime { get; set; }
/// <summary>
/// 上次起始触发时间
/// </summary>
public DateTime? BeginLastRunTime { get; set; }
/// <summary>
/// 上次截止触发时间
/// </summary>
public DateTime? EndLastRunTime { get; set; }
/// <summary>
/// 起始创建时间
/// </summary>
public DateTime? BeginCreationTime { get; set; }
/// <summary>
/// 截止创建时间
/// </summary>
public DateTime? EndCreationTime { get; set; }
/// <summary>
/// 是否已放弃任务
/// </summary>
public bool? IsAbandoned { get; set; }
/// <summary>
/// 任务类型
/// </summary>
public JobType? JobType { get; set; }
/// <summary>
/// 优先级
/// </summary>
public JobPriority? Priority { get; set; }
}

8
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoUpdateDto.cs

@ -0,0 +1,8 @@
using Volo.Abp.Domain.Entities;
namespace LINGYUN.Abp.TaskManagement;
public class BackgroundJobInfoUpdateDto : BackgroundJobInfoCreateOrUpdateDto, IHasConcurrencyStamp
{
public string ConcurrencyStamp { get; set; }
}

14
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobLogDto.cs

@ -0,0 +1,14 @@
using System;
using Volo.Abp.Application.Dtos;
namespace LINGYUN.Abp.TaskManagement;
public class BackgroundJobLogDto : EntityDto<long>
{
public string JobName { get; set; }
public string JobGroup { get; set; }
public string JobType { get; set; }
public string Message { get; set; }
public DateTime RunTime { get; set; }
public string Exception { get; set; }
}

37
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobLogGetListInput.cs

@ -0,0 +1,37 @@
using System;
using Volo.Abp.Application.Dtos;
namespace LINGYUN.Abp.TaskManagement;
public class BackgroundJobLogGetListInput : PagedAndSortedResultRequestDto
{
public Guid? JobId { get; set; }
/// <summary>
/// 其他过滤条件
/// </summary>
public string Filter { get; set; }
/// <summary>
/// 存在异常
/// </summary>
public bool? HasExceptions { get; set; }
/// <summary>
/// 任务名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 任务分组
/// </summary>
public string Group { get; set; }
/// <summary>
/// 任务类型
/// </summary>
public string Type { get; set; }
/// <summary>
/// 开始触发时间
/// </summary>
public DateTime? BeginRunTime { get; set; }
/// <summary>
/// 结束触发时间
/// </summary>
public DateTime? EndRunTime { get; set; }
}

22
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/IBackgroundJobInfoAppService.cs

@ -0,0 +1,22 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace LINGYUN.Abp.TaskManagement;
public interface IBackgroundJobInfoAppService :
ICrudAppService<
BackgroundJobInfoDto,
Guid,
BackgroundJobInfoGetListInput,
BackgroundJobInfoCreateDto,
BackgroundJobInfoUpdateDto>
{
Task TriggerAsync(Guid id);
Task PauseAsync(Guid id);
Task ResumeAsync(Guid id);
Task StopAsync(Guid id);
}

12
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/IBackgroundJobLogAppService.cs

@ -0,0 +1,12 @@
using Volo.Abp.Application.Services;
namespace LINGYUN.Abp.TaskManagement;
public interface IBackgroundJobLogAppService :
IReadOnlyAppService<
BackgroundJobLogDto,
long,
BackgroundJobLogGetListInput>,
IDeleteAppService<long>
{
}

64
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/Permissions/TaskManagementPermissionDefinitionProvider.cs

@ -0,0 +1,64 @@
using LINGYUN.Abp.TaskManagement.Localization;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.TaskManagement.Permissions;
public class TaskManagementPermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var group = context.AddGroup(
TaskManagementPermissions.GroupName,
L("Permissions:TaskManagement"),
MultiTenancySides.Host);
var backgroundJobs = group.AddPermission(
TaskManagementPermissions.BackgroundJobs.Default,
L("Permissions:BackgroundJobs"),
MultiTenancySides.Host);
backgroundJobs.AddChild(
TaskManagementPermissions.BackgroundJobs.Create,
L("Permissions:CreateJob"),
MultiTenancySides.Host);
backgroundJobs.AddChild(
TaskManagementPermissions.BackgroundJobs.Update,
L("Permissions:UpdateJob"),
MultiTenancySides.Host);
backgroundJobs.AddChild(
TaskManagementPermissions.BackgroundJobs.Delete,
L("Permissions:DeleteJob"),
MultiTenancySides.Host);
backgroundJobs.AddChild(
TaskManagementPermissions.BackgroundJobs.Trigger,
L("Permissions:TriggerJob"),
MultiTenancySides.Host);
backgroundJobs.AddChild(
TaskManagementPermissions.BackgroundJobs.Pause,
L("Permissions:PauseJob"),
MultiTenancySides.Host);
backgroundJobs.AddChild(
TaskManagementPermissions.BackgroundJobs.Resume,
L("Permissions:ResumeJob"),
MultiTenancySides.Host);
backgroundJobs.AddChild(
TaskManagementPermissions.BackgroundJobs.Stop,
L("Permissions:StopJob"),
MultiTenancySides.Host);
var backgroundJobLogs = group.AddPermission(
TaskManagementPermissions.BackgroundJobLogs.Default,
L("Permissions:BackgroundJobLogs"),
MultiTenancySides.Host);
backgroundJobLogs.AddChild(
TaskManagementPermissions.BackgroundJobLogs.Delete,
L("Permissions:DeleteJobLogs"),
MultiTenancySides.Host);
}
private ILocalizableString L(string name)
{
return LocalizableString.Create<TaskManagementResource>(name);
}
}

24
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/Permissions/TaskManagementPermissions.cs

@ -0,0 +1,24 @@
namespace LINGYUN.Abp.TaskManagement.Permissions;
public static class TaskManagementPermissions
{
public const string GroupName = "TaskManagement";
public static class BackgroundJobs
{
public const string Default = GroupName + ".BackgroundJobs";
public const string Create = Default + ".Create";
public const string Update = Default + ".Update";
public const string Delete = Default + ".Delete";
public const string Trigger = Default + ".Trigger";
public const string Pause = Default + ".Pause";
public const string Resume = Default + ".Resume";
public const string Stop = Default + ".Stop";
}
public static class BackgroundJobLogs
{
public const string Default = GroupName + ".BackgroundJobLogs";
public const string Delete = Default + ".Delete";
}
}

10
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/TaskManagementApplicationContractsModule.cs

@ -0,0 +1,10 @@
using Volo.Abp.Application;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.TaskManagement;
[DependsOn(typeof(TaskManagementDomainSharedModule))]
[DependsOn(typeof(AbpDddApplicationContractsModule))]
public class TaskManagementApplicationContractsModule : AbpModule
{
}

8
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/TaskManagementRemoteServiceConsts.cs

@ -0,0 +1,8 @@
namespace LINGYUN.Abp.TaskManagement;
public static class TaskManagementRemoteServiceConsts
{
public const string RemoteServiceName = "TaskManagement";
public const string ModuleName = "task-management";
}

3
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

20
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN.Abp.TaskManagement.Application.csproj

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Application" Version="$(VoloAbpPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.TaskManagement.Application.Contracts\LINGYUN.Abp.TaskManagement.Application.Contracts.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.TaskManagement.Domain\LINGYUN.Abp.TaskManagement.Domain.csproj" />
</ItemGroup>
</Project>

176
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/BackgroundJobInfoAppService.cs

@ -0,0 +1,176 @@
using LINGYUN.Abp.BackgroundTasks;
using LINGYUN.Abp.TaskManagement.Permissions;
using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Data;
namespace LINGYUN.Abp.TaskManagement;
[Authorize(TaskManagementPermissions.BackgroundJobs.Default)]
public class BackgroundJobInfoAppService : TaskManagementApplicationService, IBackgroundJobInfoAppService
{
protected BackgroundJobManager BackgroundJobManager { get; }
protected IBackgroundJobInfoRepository BackgroundJobInfoRepository { get; }
public BackgroundJobInfoAppService(
BackgroundJobManager backgroundJobManager,
IBackgroundJobInfoRepository backgroundJobInfoRepository)
{
BackgroundJobManager = backgroundJobManager;
BackgroundJobInfoRepository = backgroundJobInfoRepository;
}
[Authorize(TaskManagementPermissions.BackgroundJobs.Create)]
public virtual async Task<BackgroundJobInfoDto> CreateAsync(BackgroundJobInfoCreateDto input)
{
if (await BackgroundJobInfoRepository.CheckNameAsync(input.Group, input.Name))
{
throw new BusinessException(TaskManagementErrorCodes.JobNameAlreadyExists)
.WithData("Group", input.Group)
.WithData("Name", input.Name);
}
var backgroundJobInfo = new BackgroundJobInfo(
GuidGenerator.Create(),
input.Name,
input.Group,
input.Type,
input.Args,
input.BeginTime,
input.EndTime,
input.Priority,
input.MaxCount,
input.MaxTryCount);
UpdateByInput(backgroundJobInfo, input);
await BackgroundJobInfoRepository.InsertAsync(backgroundJobInfo, autoSave: true);
if (backgroundJobInfo.IsEnabled && backgroundJobInfo.JobType == JobType.Period)
{
await BackgroundJobManager.QueueAsync(backgroundJobInfo);
}
return ObjectMapper.Map<BackgroundJobInfo, BackgroundJobInfoDto>(backgroundJobInfo);
}
[Authorize(TaskManagementPermissions.BackgroundJobs.Delete)]
public virtual async Task DeleteAsync(Guid id)
{
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id);
await BackgroundJobManager.DeleteAsync(backgroundJobInfo);
}
public virtual async Task<BackgroundJobInfoDto> GetAsync(Guid id)
{
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id);
return ObjectMapper.Map<BackgroundJobInfo, BackgroundJobInfoDto>(backgroundJobInfo);
}
public virtual async Task<PagedResultDto<BackgroundJobInfoDto>> GetListAsync(BackgroundJobInfoGetListInput input)
{
var filter = new BackgroundJobInfoFilter
{
IsAbandoned = input.IsAbandoned,
JobType = input.JobType,
BeginCreationTime = input.BeginCreationTime,
EndCreationTime = input.EndCreationTime,
BeginLastRunTime = input.BeginLastRunTime,
EndLastRunTime = input.EndLastRunTime,
BeginTime = input.BeginTime,
EndTime = input.EndTime,
Filter = input.Filter,
Group = input.Group,
Name = input.Name,
Priority = input.Priority,
Status = input.Status,
Type = input.Type
};
var totalCount = await BackgroundJobInfoRepository.GetCountAsync(filter);
var backgroundJobInfos = await BackgroundJobInfoRepository.GetListAsync(
filter, input.Sorting, input.MaxResultCount, input.SkipCount);
return new PagedResultDto<BackgroundJobInfoDto>(totalCount,
ObjectMapper.Map<List<BackgroundJobInfo>, List<BackgroundJobInfoDto>>(backgroundJobInfos));
}
[Authorize(TaskManagementPermissions.BackgroundJobs.Pause)]
public virtual async Task PauseAsync(Guid id)
{
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id);
await BackgroundJobManager.PauseAsync(backgroundJobInfo);
}
[Authorize(TaskManagementPermissions.BackgroundJobs.Resume)]
public virtual async Task ResumeAsync(Guid id)
{
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id);
await BackgroundJobManager.ResumeAsync(backgroundJobInfo);
}
[Authorize(TaskManagementPermissions.BackgroundJobs.Trigger)]
public virtual async Task TriggerAsync(Guid id)
{
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id);
await BackgroundJobManager.TriggerAsync(backgroundJobInfo);
}
[Authorize(TaskManagementPermissions.BackgroundJobs.Stop)]
public virtual async Task StopAsync(Guid id)
{
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id);
await BackgroundJobManager.StopAsync(backgroundJobInfo);
}
[Authorize(TaskManagementPermissions.BackgroundJobs.Update)]
public virtual async Task<BackgroundJobInfoDto> UpdateAsync(Guid id, BackgroundJobInfoUpdateDto input)
{
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id);
var resetJob = backgroundJobInfo.JobType == input.JobType;
UpdateByInput(backgroundJobInfo, input);
backgroundJobInfo.SetConcurrencyStampIfNotNull(input.ConcurrencyStamp);
await BackgroundJobManager.UpdateAsync(backgroundJobInfo, resetJob);
return ObjectMapper.Map<BackgroundJobInfo, BackgroundJobInfoDto>(backgroundJobInfo);
}
protected virtual void UpdateByInput(BackgroundJobInfo backgroundJobInfo, BackgroundJobInfoCreateOrUpdateDto input)
{
backgroundJobInfo.IsEnabled = input.IsEnabled;
backgroundJobInfo.LockTimeOut = input.LockTimeOut;
backgroundJobInfo.Description = input.Description;
backgroundJobInfo.MaxCount = input.MaxCount;
backgroundJobInfo.MaxTryCount = input.MaxTryCount;
backgroundJobInfo.Args.RemoveAll(x => !input.Args.ContainsKey(x.Key));
backgroundJobInfo.Args.AddIfNotContains(input.Args);
backgroundJobInfo.SetPriority(input.Priority);
switch (input.JobType)
{
case JobType.Once:
backgroundJobInfo.SetOnceJob(input.Interval);
break;
case JobType.Persistent:
backgroundJobInfo.SetPersistentJob(input.Interval);
break;
case JobType.Period:
backgroundJobInfo.SetPeriodJob(input.Cron);
break;
}
}
}

53
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/BackgroundJobLogAppService.cs

@ -0,0 +1,53 @@
using LINGYUN.Abp.TaskManagement.Permissions;
using Microsoft.AspNetCore.Authorization;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
namespace LINGYUN.Abp.TaskManagement;
[Authorize(TaskManagementPermissions.BackgroundJobLogs.Default)]
public class BackgroundJobLogAppService : TaskManagementApplicationService, IBackgroundJobLogAppService
{
protected IBackgroundJobLogRepository BackgroundJobLogRepository { get; }
public BackgroundJobLogAppService(
IBackgroundJobLogRepository backgroundJobLogRepository)
{
BackgroundJobLogRepository = backgroundJobLogRepository;
}
[Authorize(TaskManagementPermissions.BackgroundJobLogs.Delete)]
public virtual Task DeleteAsync(long id)
{
return BackgroundJobLogRepository.DeleteAsync(id);
}
public virtual async Task<BackgroundJobLogDto> GetAsync(long id)
{
var backgroundJobLog = await BackgroundJobLogRepository.GetAsync(id);
return ObjectMapper.Map<BackgroundJobLog, BackgroundJobLogDto>(backgroundJobLog);
}
public virtual async Task<PagedResultDto<BackgroundJobLogDto>> GetListAsync(BackgroundJobLogGetListInput input)
{
var filter = new BackgroundJobLogFilter
{
BeginRunTime = input.BeginRunTime,
EndRunTime = input.EndRunTime,
HasExceptions = input.HasExceptions,
Filter = input.Filter,
Group = input.Group,
Name = input.Name,
Type = input.Type
};
var totalCount = await BackgroundJobLogRepository.GetCountAsync(filter, input.JobId);
var backgroundJobLogs = await BackgroundJobLogRepository.GetListAsync(
filter, input.JobId, input.Sorting, input.MaxResultCount, input.SkipCount);
return new PagedResultDto<BackgroundJobLogDto>(totalCount,
ObjectMapper.Map<List<BackgroundJobLog>, List<BackgroundJobLogDto>>(backgroundJobLogs));
}
}

12
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/TaskManagementApplicationMapperProfile.cs

@ -0,0 +1,12 @@
using AutoMapper;
namespace LINGYUN.Abp.TaskManagement;
public class TaskManagementApplicationMapperProfile : Profile
{
public TaskManagementApplicationMapperProfile()
{
CreateMap<BackgroundJobInfo, BackgroundJobInfoDto>();
CreateMap<BackgroundJobLog, BackgroundJobLogDto>();
}
}

22
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/TaskManagementApplicationModule.cs

@ -0,0 +1,22 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Application;
using Volo.Abp.AutoMapper;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.TaskManagement;
[DependsOn(typeof(TaskManagementApplicationContractsModule))]
[DependsOn(typeof(TaskManagementDomainModule))]
[DependsOn(typeof(AbpDddApplicationModule))]
public class TaskManagementApplicationModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAutoMapperObjectMapper<TaskManagementApplicationModule>();
Configure<AbpAutoMapperOptions>(options =>
{
options.AddProfile<TaskManagementApplicationMapperProfile>(validate: true);
});
}
}

13
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/TaskManagementApplicationService.cs

@ -0,0 +1,13 @@
using LINGYUN.Abp.TaskManagement.Localization;
using Volo.Abp.Application.Services;
namespace LINGYUN.Abp.TaskManagement;
public abstract class TaskManagementApplicationService : ApplicationService
{
protected TaskManagementApplicationService()
{
LocalizationResource = typeof(TaskManagementResource);
ObjectMapperContext = typeof(TaskManagementApplicationModule);
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save