Browse Source

Merge pull request #463 from colinin/task-management-multi-tenancy

Task management multi tenancy
pull/474/head 5.0.0
yx lin 4 years ago
committed by GitHub
parent
commit
6e4b2fed8b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      apps/vue/src/api/auditing/model/auditLogModel.ts
  2. 67
      apps/vue/src/api/task-management/backgroundJobInfo.ts
  3. 50
      apps/vue/src/components/Permission/src/PermissionModal.vue
  4. 12
      apps/vue/src/components/Permission/src/hooks/usePermissions.ts
  5. 1
      apps/vue/src/components/Permission/src/types/permission.ts
  6. 3
      apps/vue/src/components/Permission/src/utils/helper.ts
  7. 1
      apps/vue/src/store/modules/abp.ts
  8. 4
      apps/vue/src/views/auditing/hooks/useAuditLog.ts
  9. 56
      apps/vue/src/views/task-management/background-jobs/components/BackgroundJobInfoTable.vue
  10. 6
      apps/vue/src/views/task-management/background-jobs/datas/TableData.ts
  11. 1
      aspnet-core/Directory.Build.props
  12. 39
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobInfo.cs
  13. 12
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Abstractions/LINGYUN/Abp/BackgroundTasks/JobRunnableContextExtensions.cs
  14. 19
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Hangfire/LINGYUN.Abp.BackgroundTasks.Hangfire.csproj
  15. 18
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Hangfire/LINGYUN/Abp/BackgroundTasks/Hangfire/AbpBackgroundTasksHangfireModule.cs
  16. 96
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Hangfire/LINGYUN/Abp/BackgroundTasks/Hangfire/HangfireJobExecutedAttribute.cs
  17. 122
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Hangfire/LINGYUN/Abp/BackgroundTasks/Hangfire/HangfireJobScheduler.cs
  18. 32
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Hangfire/LINGYUN/Abp/BackgroundTasks/Hangfire/HangfireJobSimpleAdapter.cs
  19. 4
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/AbpBackgroundTasksQuartzModule.cs
  20. 10
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/IQuartzKeyBuilder.cs
  21. 42
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobExecutorProvider.cs
  22. 23
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobScheduler.cs
  23. 35
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzKeyBuilder.cs
  24. 5
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundJobManager.cs
  25. 15
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundCleaningJob.cs
  26. 2
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundKeepAliveJob.cs
  27. 28
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundPollingJob.cs
  28. 11
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/DefaultBackgroundWorker.cs
  29. 98
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobExecutedEvent.cs
  30. 8
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobLogEvent.cs
  31. 9
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoBatchInput.cs
  32. 14
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/IBackgroundJobInfoAppService.cs
  33. 37
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/Permissions/TaskManagementPermissionDefinitionProvider.cs
  34. 1
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/Permissions/TaskManagementPermissions.cs
  35. 92
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/BackgroundJobInfoAppService.cs
  36. 8
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/Resources/en.json
  37. 8
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/Resources/zh-Hans.json
  38. 8
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfo.cs
  39. 8
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobLog.cs
  40. 47
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobManager.cs
  41. 133
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobStore.cs
  42. 2
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/EfCoreBackgroundJobInfoRepository.cs
  43. 55
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.HttpApi/LINGYUN/Abp/TaskManagement/BackgroundJobInfoController.cs
  44. 140
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/EventBus/Handlers/TenantSynchronizer.cs
  45. 7
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj
  46. 201
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Migrations/20220112093435_Add-Support-Multi-Tenancy-With-Background-Jobs.Designer.cs
  47. 38
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Migrations/20220112093435_Add-Support-Multi-Tenancy-With-Background-Jobs.cs
  48. 8
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Migrations/TaskManagementMigrationsDbContextModelSnapshot.cs
  49. 50
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.Configure.cs
  50. 6
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.cs
  51. 13
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/appsettings.Development.json

2
apps/vue/src/api/auditing/model/auditLogModel.ts

@ -17,7 +17,7 @@ export interface PropertyChange {
export interface EntityChange {
id: string;
changeTime?: Date;
changeType?: ChangeType;
changeType: ChangeType;
entityTenantId?: string;
entityId?: string;
entityTypeFullName?: string;

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

@ -17,7 +17,14 @@ enum Api {
Pause = '/api/task-management/background-jobs/{id}/pause',
Resume = '/api/task-management/background-jobs/{id}/resume',
Trigger = '/api/task-management/background-jobs/{id}/trigger',
Start = '/api/task-management/background-jobs/{id}/start',
Stop = '/api/task-management/background-jobs/{id}/stop',
BulkPause = '/api/task-management/background-jobs/bulk-pause',
BulkResume = '/api/task-management/background-jobs/bulk-resume',
BulkTrigger = '/api/task-management/background-jobs/bulk-trigger',
BulkStart = '/api/task-management/background-jobs/bulk-start',
BulkStop = '/api/task-management/background-jobs/bulk-stop',
BulkDelete = '/api/task-management/background-jobs/bulk-delete',
}
export const getById = (id: string) => {
@ -71,8 +78,68 @@ export const trigger = (id: string) => {
});
};
export const start = (id: string) => {
return defAbpHttp.put<void>({
url: format(Api.Stop, { id: id }),
});
};
export const stop = (id: string) => {
return defAbpHttp.put<void>({
url: format(Api.Stop, { id: id }),
});
};
export const bulkPause = (ids: string[]) => {
return defAbpHttp.put<void>({
url: Api.BulkPause,
data: {
jobIds: ids,
},
});
};
export const bulkResume = (ids: string[]) => {
return defAbpHttp.put<void>({
url: Api.BulkResume,
data: {
jobIds: ids,
},
});
};
export const bulkTrigger = (ids: string[]) => {
return defAbpHttp.put<void>({
url: Api.BulkTrigger,
data: {
jobIds: ids,
},
});
};
export const bulkStart = (ids: string[]) => {
return defAbpHttp.put<void>({
url: Api.BulkStart,
data: {
jobIds: ids,
},
});
};
export const bulkStop = (ids: string[]) => {
return defAbpHttp.put<void>({
url: Api.BulkStop,
data: {
jobIds: ids,
},
});
};
export const bulkDelete = (ids: string[]) => {
return defAbpHttp.put<void>({
url: Api.BulkDelete,
data: {
jobIds: ids,
},
});
};

50
apps/vue/src/components/Permission/src/PermissionModal.vue

@ -6,6 +6,7 @@
:width="800"
:min-height="600"
@ok="handleSubmit"
@visible-change="handleVisibleChange"
>
<Row>
<Col :span="24">
@ -35,7 +36,6 @@
<BasicTree
:checkable="true"
:checkStrictly="true"
:selectable="false"
:disabled="permissionTreeDisabled"
:treeData="permission.children"
:replaceFields="{
@ -104,10 +104,28 @@
} = usePermissions({
getPropsRef: model,
});
const [registerModal, { closeModal, setModalProps }] = useModalInner((val) => {
const [registerModal, { closeModal, changeOkLoading }] = useModalInner((val) => {
model.value = val;
});
function handleVisibleChange(visible: boolean) {
if (!visible) {
model.value.providerKey = '';
}
}
function handleSubmit() {
changeOkLoading(true);
handleSavePermission()
.then(() => {
message.success(L('Successful'));
closeModal();
})
.finally(() => {
changeOkLoading(false);
});
}
return {
L,
activeKey,
@ -119,36 +137,12 @@
permissionTreeCheckState,
permissionTreeDisabled,
handlePermissionGranted,
handleSavePermission,
handleGrantAllPermission,
handleGrantPermissions,
registerModal,
closeModal,
setModalProps,
handleSubmit,
handleVisibleChange,
};
},
methods: {
handleSubmit() {
this.setModalProps({
loading: true,
confirmLoading: true,
showCancelBtn: false,
closable: false,
});
this.handleSavePermission()
.then(() => {
message.success(this.L('Successful'));
this.closeModal();
})
.finally(() => {
this.setModalProps({
loading: false,
confirmLoading: false,
showCancelBtn: true,
closable: true,
});
});
},
},
});
</script>

12
apps/vue/src/components/Permission/src/hooks/usePermissions.ts

@ -2,7 +2,7 @@ import type { Ref } from 'vue';
import type { PermissionProps, PermissionTree } from '../types/permission';
import { computed, watch, unref, ref } from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { get, update } from '/@/api/permission-management/permission';
import { PermissionProvider } from '/@/api/permission-management/model/permissionModel';
import {
@ -24,7 +24,7 @@ interface UsePermission {
}
export function usePermissions({ getPropsRef }: UsePermission) {
const { t } = useI18n();
const { L } = useLocalization('AbpPermissionManagement');
/** 弹出层标题 */
const title = ref('');
/** 权限树 */
@ -36,7 +36,7 @@ export function usePermissions({ getPropsRef }: UsePermission) {
*/
function handleGetPermission(name: string, key?: string) {
get({ providerName: name, providerKey: key }).then((res) => {
title.value = `${t('AbpPermissionManagement.Permissions')} - ${res.entityDisplayName}`;
title.value = `${L('Permissions')} - ${res.entityDisplayName}`;
permissionTree.value = generatePermissionTree(res.groups);
});
}
@ -145,8 +145,10 @@ export function usePermissions({ getPropsRef }: UsePermission) {
() => unref(getPropsRef).providerKey,
(key) => {
permissionTree.value = [];
const props = unref(getPropsRef);
handleGetPermission(props.providerName, key);
if (key) {
const props = unref(getPropsRef);
handleGetPermission(props.providerName, key);
}
},
);

1
apps/vue/src/components/Permission/src/types/permission.ts

@ -5,6 +5,7 @@ export interface PermissionProps {
}
export interface PermissionTree {
isRoot: boolean;
/** 权限标识 */
name: string;
/** 显示名称 */

3
apps/vue/src/components/Permission/src/utils/helper.ts

@ -7,6 +7,7 @@ export function generatePermissionTree(permissionGroups: PermissionGroup[]) {
const trees: PermissionTree[] = [];
permissionGroups.forEach((g) => {
const tree: PermissionTree = {
isRoot: true,
name: g.name,
displayName: g.displayName,
disabled: false,
@ -93,7 +94,7 @@ export function getParentList(children: PermissionTree[], name: string) {
export function toPermissionList(treeList: PermissionTree[]) {
const permissions: IPermission[] = [];
for (let index = 0; index < treeList.length; index++) {
if (treeList[index].isGranted !== undefined) {
if (!treeList[index].isRoot && treeList[index].isGranted !== undefined) {
permissions.push({
name: treeList[index].name,
isGranted: treeList[index].isGranted === true,

1
apps/vue/src/store/modules/abp.ts

@ -12,7 +12,6 @@ import {
Auth,
CurrentUser,
Localization,
Setting,
} from '/@/api/abp/model/appModel';
import { ApplicationApiDescriptionModel } from '/@/api/abp/model/apiDefinition';

4
apps/vue/src/views/auditing/hooks/useAuditLog.ts

@ -18,10 +18,10 @@ export function useAuditLog() {
['PATCH']: 'pink',
};
const entityChangeTypeColor = computed(() => {
return (changeType?: ChangeType) => (changeType ? changeTypeColorMap[changeType].color : '');
return (changeType: ChangeType) => changeTypeColorMap[changeType].color;
});
const entityChangeType = computed(() => {
return (changeType?: ChangeType) => (changeType ? changeTypeColorMap[changeType].value : '');
return (changeType: ChangeType) => changeTypeColorMap[changeType].value;
});
const httpMethodColor = computed(() => {
return (method?: string) => {

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

@ -2,6 +2,20 @@
<div class="content">
<BasicTable @register="registerTable">
<template #toolbar>
<a-button
v-if="hasPermission('TaskManagement.BackgroundJobs.Start')"
:disabled="!isMultiSelected"
@click="handleStart"
>{{ L('BackgroundJobs:Start') }}</a-button
>
<a-button
v-if="hasPermission('TaskManagement.BackgroundJobs.Stop')"
type="primary"
danger
:disabled="!isMultiSelected"
@click="handleStop"
>{{ L('BackgroundJobs:Stop') }}</a-button
>
<a-button
v-if="hasPermission('TaskManagement.BackgroundJobs.Create')"
type="primary"
@ -61,12 +75,6 @@
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>
@ -76,13 +84,14 @@
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
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 { getList, pause, resume, trigger, deleteById, bulkStop, bulkStart } from '/@/api/task-management/backgroundJobInfo';
import { JobStatus } from '/@/api/task-management/model/backgroundJobInfoModel';
import { getDataColumns } from '../datas/TableData';
import { getSearchFormSchemas } from '../datas/ModalData';
@ -92,7 +101,7 @@
const { L } = useLocalization('TaskManagement');
const { hasPermission } = usePermission();
const [registerModal, { openModal }] = useModal();
const [registerTable, { reload }] = useTable({
const [registerTable, { reload, getSelectRowKeys }] = useTable({
rowKey: 'id',
title: L('BackgroundJobs'),
columns: getDataColumns(),
@ -106,8 +115,12 @@
showIndexColumn: false,
canResize: false,
immediate: true,
rowSelection: { type: 'radio' },
clickToRowSelect: false,
formConfig: getSearchFormSchemas(),
rowSelection: {
type: 'checkbox',
onChange: handleSelectChange,
},
actionColumn: {
width: 220,
title: L('Actions'),
@ -115,6 +128,14 @@
slots: { customRender: 'action' },
},
});
const selectedRowKeys = ref<string[]>([]);
const isMultiSelected = computed(() => {
return selectedRowKeys.value.length > 0;
});
function handleSelectChange(keys) {
selectedRowKeys.value = keys;
}
function handleChange() {
reload();
@ -149,8 +170,17 @@
});
}
function handleStop(record) {
stop(record.id).then(() => {
function handleStart() {
const selectKeys = getSelectRowKeys();
bulkStart(selectKeys).then(() => {
message.success(L('Successful'));
reload();
});
}
function handleStop() {
const selectKeys = getSelectRowKeys();
bulkStop(selectKeys).then(() => {
message.success(L('Successful'));
reload();
});
@ -159,10 +189,10 @@
function handleDelete(record) {
Modal.warning({
title: L('AreYouSure'),
content: L('ItemWillBeDeletedMessage'),
content: L('MultipleSelectJobsWillBeDeletedMessage'),
okCancel: true,
onOk: () => {
deleteById(record.id).then(() => {
deleteById(record).then(() => {
reload();
});
},

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

@ -18,6 +18,7 @@ export function getDataColumns(): BasicColumn[] {
align: 'left',
width: 150,
sorter: true,
fixed: 'left',
},
{
title: L('DisplayName:Name'),
@ -25,10 +26,11 @@ export function getDataColumns(): BasicColumn[] {
align: 'left',
width: 300,
sorter: true,
fixed: 'left',
},
{
title: L('DisplayName:Type'),
dataIndex: 'type',
title: L('DisplayName:Description'),
dataIndex: 'description',
align: 'left',
width: 350,
sorter: true,

1
aspnet-core/Directory.Build.props

@ -12,6 +12,7 @@
<HangfireMySqlStoragePackageVersion>2.0.3</HangfireMySqlStoragePackageVersion>
<HangfireMSSQLStoragePackageVersion>1.7.27</HangfireMSSQLStoragePackageVersion>
<NESTPackageVersion>7.15.1</NESTPackageVersion>
<QuartzNETPackageVersion>3.3.3</QuartzNETPackageVersion>
<StackExchangeRedisPackageVersion>2.0.593</StackExchangeRedisPackageVersion>
<SerilogPackageVersion>2.10.0</SerilogPackageVersion>
<SerilogAspNetCorePackageVersion>4.1.0</SerilogAspNetCorePackageVersion>

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

@ -10,6 +10,10 @@ public class JobInfo
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// 租户标识
/// </summary>
public Guid? TenantId { get; set; }
/// <summary>
/// 任务名称
/// </summary>
public string Name { get; set; }
@ -101,4 +105,39 @@ public class JobInfo
/// 0或更小不生效
/// </summary>
public int LockTimeOut { get; set; }
public int GetCanBeTriggered()
{
// 无限次
var maxCount = -1;
// 剩余次数
if (MaxCount > 0)
{
maxCount = MaxCount - TriggerCount;
if (maxCount < 0)
{
maxCount = 0;
}
}
// 一次
if (JobType == JobType.Once)
{
maxCount = 1;
}
// 重试
if (Status == JobStatus.FailedRetry)
{
maxCount = MaxTryCount - TryCount;
if (maxCount < 0)
{
maxCount = 0;
}
}
return maxCount;
}
}

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

@ -41,6 +41,18 @@ public static class JobRunnableContextExtensions
return false;
}
public static bool TryGetMultiTenantId(this JobRunnableContext context, out Guid? tenantId)
{
tenantId = null;
if (context.TryGetString(nameof(JobInfo.TenantId), out var tenantUUIdString) &&
Guid.TryParse(tenantUUIdString, out var tenantUUId))
{
tenantId = tenantUUId;
return true;
}
return false;
}
public static T GetJobData<T>(this JobRunnableContext context, string key) where T : struct
{
var value = context.GetJobData(key);

19
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Hangfire/LINGYUN.Abp.BackgroundTasks.Hangfire.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.Hangfire" Version="$(VoloAbpPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.BackgroundTasks\LINGYUN.Abp.BackgroundTasks.csproj" />
</ItemGroup>
</Project>

18
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Hangfire/LINGYUN/Abp/BackgroundTasks/Hangfire/AbpBackgroundTasksHangfireModule.cs

@ -0,0 +1,18 @@
using Hangfire;
using Volo.Abp.Hangfire;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.BackgroundTasks.Hangfire;
[DependsOn(typeof(AbpBackgroundTasksModule))]
[DependsOn(typeof(AbpHangfireModule))]
public class AbpBackgroundTasksHangfireModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddHangfire((provider, configuration) =>
{
configuration.UseFilter(new HangfireJobExecutedAttribute(provider));
});
}
}

96
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Hangfire/LINGYUN/Abp/BackgroundTasks/Hangfire/HangfireJobExecutedAttribute.cs

@ -0,0 +1,96 @@
using Hangfire.Common;
using Hangfire.Server;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace LINGYUN.Abp.BackgroundTasks.Hangfire;
public class HangfireJobExecutedAttribute : JobFilterAttribute, IServerFilter
{
public ILogger<HangfireJobExecutedAttribute> Logger { protected get; set; }
public IServiceProvider ServiceProvider { get; set; }
public HangfireJobExecutedAttribute()
{
Logger = NullLogger<HangfireJobExecutedAttribute>.Instance;
}
public HangfireJobExecutedAttribute(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
Logger = NullLogger<HangfireJobExecutedAttribute>.Instance;
}
public async void OnPerformed(PerformedContext filterContext)
{
if (Guid.TryParse(filterContext.BackgroundJob.Id, out var jobUUId))
{
try
{
var jobEventProvider = ServiceProvider.GetRequiredService<IJobEventProvider>();
var jobEventList = jobEventProvider.GetAll();
if (!jobEventList.Any())
{
return;
}
using var scope = ServiceProvider.CreateScope();
var jobGroup = filterContext.Connection
.GetJobParameter(filterContext.BackgroundJob.Id, nameof(JobInfo.Group));
var jobName = filterContext.Connection
.GetJobParameter(filterContext.BackgroundJob.Id, nameof(JobInfo.Name));
var jobEventData = new JobEventData(
jobUUId,
filterContext.BackgroundJob.Job.Type,
jobGroup,
jobName)
{
Result = filterContext.Result?.ToString()
};
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 async void OnPerforming(PerformingContext filterContext)
{
var lockTime = filterContext.Connection
.GetJobParameter(filterContext.BackgroundJob.Id, nameof(JobInfo.LockTimeOut));
if (!lockTime.IsNullOrWhiteSpace() && int.TryParse(lockTime, out var time) && time > 0)
{
var jobLockProvider = ServiceProvider.GetRequiredService<IJobLockProvider>();
if (!await jobLockProvider.TryLockAsync(
filterContext.BackgroundJob.Id,
time,
filterContext.CancellationToken.ShutdownToken))
{
filterContext.Canceled = true;
}
}
}
}

122
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Hangfire/LINGYUN/Abp/BackgroundTasks/Hangfire/HangfireJobScheduler.cs

@ -0,0 +1,122 @@
using Hangfire;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.BackgroundTasks.Hangfire;
[Dependency(ReplaceServices = true)]
public class HangfireJobScheduler : IJobScheduler, ISingletonDependency
{
public ILogger<HangfireJobScheduler> Logger { protected get; set; }
protected AbpBackgroundTasksOptions Options { get; }
protected JobStorage JobStorage { get; }
protected IRecurringJobManager RecurringJobManager { get; }
public HangfireJobScheduler(
JobStorage jobStorage,
IOptions<AbpBackgroundTasksOptions> options)
{
Options = options.Value;
JobStorage = jobStorage;
RecurringJobManager = new RecurringJobManager(jobStorage);
Logger = NullLogger<HangfireJobScheduler>.Instance;
}
public Task<bool> ExistsAsync(JobInfo job)
{
var monitor = JobStorage.GetMonitoringApi();
monitor.JobDetails(job.);
}
public Task PauseAsync(JobInfo job)
{
throw new NotImplementedException();
}
public virtual Task<bool> QueueAsync(JobInfo job)
{
var jobType = Options.JobProviders.GetOrDefault(job.Type) ?? Type.GetType(job.Type, false);
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 Task.FromResult(false);
}
var jobData = job.Args;
jobData[nameof(JobInfo.Id)] = job.Id;
jobData[nameof(JobInfo.Group)] = job.Group;
jobData[nameof(JobInfo.Name)] = job.Name;
switch (job.JobType)
{
case JobType.Once:
var jobId = BackgroundJob.Schedule<HangfireJobSimpleAdapter>(
adapter => adapter.ExecuteAsync(jobType, jobData.ToImmutableDictionary()),
TimeSpan.FromSeconds(job.Interval));
job.Args["hangfire"] = jobId;
break;
case JobType.Persistent:
var minuteInterval = job.Interval / 60;
if (minuteInterval < 1)
{
minuteInterval = 1;
}
RecurringJob.AddOrUpdate<HangfireJobSimpleAdapter>(
adapter => adapter.ExecuteAsync(jobType, jobData.ToImmutableDictionary()),
Cron.MinuteInterval(minuteInterval));
break;
case JobType.Period:
RecurringJob.AddOrUpdate<HangfireJobSimpleAdapter>(
adapter => adapter.ExecuteAsync(jobType, jobData.ToImmutableDictionary()),
job.Cron,
queue: job.Group);
break;
}
return Task.FromResult(true);
}
public Task QueuesAsync(IEnumerable<JobInfo> jobs)
{
throw new NotImplementedException();
}
public Task<bool> RemoveAsync(JobInfo job)
{
throw new NotImplementedException();
}
public Task ResumeAsync(JobInfo job)
{
throw new NotImplementedException();
}
public Task<bool> ShutdownAsync()
{
throw new NotImplementedException();
}
public Task<bool> StartAsync()
{
throw new NotImplementedException();
}
public Task<bool> StopAsync()
{
throw new NotImplementedException();
}
public Task TriggerAsync(JobInfo job)
{
throw new NotImplementedException();
}
}

32
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Hangfire/LINGYUN/Abp/BackgroundTasks/Hangfire/HangfireJobSimpleAdapter.cs

@ -0,0 +1,32 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace LINGYUN.Abp.BackgroundTasks.Hangfire;
public class HangfireJobSimpleAdapter
{
protected IServiceProvider ServiceProvider { get; }
public HangfireJobSimpleAdapter(
IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
public async virtual Task<object> ExecuteAsync(Type jobRunnableType, IReadOnlyDictionary<string, object> jobData)
{
using var scope = ServiceProvider.CreateScope();
var jobExecuter = scope.ServiceProvider.GetRequiredService<IJobRunnableExecuter>();
var jobContext = new JobRunnableContext(
jobRunnableType,
ServiceProvider,
jobData);
await jobExecuter.ExecuteAsync(jobContext);
return jobContext.Result;
}
}

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

@ -1,5 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Quartz;
using System.Collections.Specialized;
using Volo.Abp;
using Volo.Abp.Modularity;
using Volo.Abp.Quartz;

10
aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/IQuartzKeyBuilder.cs

@ -0,0 +1,10 @@
using Quartz;
namespace LINGYUN.Abp.BackgroundTasks.Quartz;
public interface IQuartzKeyBuilder
{
JobKey CreateJobKey(JobInfo jobInfo);
TriggerKey CreateTriggerKey(JobInfo jobInfo);
}

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

@ -15,20 +15,22 @@ public class QuartzJobExecutorProvider : IQuartzJobExecutorProvider, ISingletonD
protected IClock Clock { get; }
protected AbpBackgroundTasksOptions Options { get; }
protected IQuartzKeyBuilder KeyBuilder { get; }
public QuartzJobExecutorProvider(
IClock clock,
IQuartzKeyBuilder keyBuilder,
IOptions<AbpBackgroundTasksOptions> options)
{
Clock = clock;
Options = options.Value;
KeyBuilder = keyBuilder;
Logger = NullLogger<QuartzJobExecutorProvider>.Instance;
}
public IJobDetail CreateJob(JobInfo job)
{
var jobType = Type.GetType(job.Type) ?? Options.JobProviders.GetOrDefault(job.Type);
var jobType = Options.JobProviders.GetOrDefault(job.Type) ?? Type.GetType(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.");
@ -48,13 +50,18 @@ public class QuartzJobExecutorProvider : IQuartzJobExecutorProvider, ISingletonD
jobType = adapterType.MakeGenericType(jobType);
}
// 改为 JobId作为名称
var jobBuilder = JobBuilder.Create(jobType)
.WithIdentity(job.Name, job.Group)
.WithIdentity(KeyBuilder.CreateJobKey(job))
.WithDescription(job.Description);
jobBuilder.UsingJobData(nameof(JobInfo.Id), job.Id);
jobBuilder.UsingJobData(nameof(JobInfo.LockTimeOut), job.LockTimeOut);
jobBuilder.UsingJobData(new JobDataMap(job.Args));
if (job.TenantId.HasValue)
{
jobBuilder.UsingJobData(nameof(JobInfo.TenantId), job.TenantId.ToString());
}
return jobBuilder.Build();
}
@ -71,11 +78,16 @@ public class QuartzJobExecutorProvider : IQuartzJobExecutorProvider, ISingletonD
Logger.LogWarning($"The task: {job.Group} - {job.Name} periodic task Cron expression was invalid and the task trigger could not be created.");
return null;
}
if (job.GetCanBeTriggered() == 0)
{
Logger.LogWarning($"The task: {job.Group} - {job.Name} reached trigger peak and the task trigger could not be created.");
return null;
}
triggerBuilder
.WithIdentity(job.Name, job.Group)
.WithIdentity(KeyBuilder.CreateTriggerKey(job))
.WithDescription(job.Description)
.EndAt(job.EndTime)
.ForJob(job.Name, job.Group)
.ForJob(KeyBuilder.CreateJobKey(job))
.WithPriority((int)job.Priority)
.WithCronSchedule(job.Cron);
if (job.BeginTime > Clock.Now)
@ -86,22 +98,26 @@ public class QuartzJobExecutorProvider : IQuartzJobExecutorProvider, ISingletonD
case JobType.Once:
case JobType.Persistent:
default:
// Quartz 需要减一位
var maxCount = job.MaxCount <= 0 ? -1 : job.MaxCount - 1;
if (job.JobType == JobType.Once)
var maxCount = job.GetCanBeTriggered();
if (maxCount == 0)
{
maxCount = 0;
Logger.LogWarning($"The task: {job.Group} - {job.Name} reached trigger peak and the task trigger could not be created.");
return null;
}
if (job.Status == JobStatus.FailedRetry && job.TryCount < job.MaxTryCount)
// Quartz 需要减一位
maxCount -= 1;
if (maxCount < -1)
{
maxCount = job.MaxTryCount <= 0 ? -1 : job.MaxTryCount - 1;
maxCount = -1;
}
triggerBuilder
.WithIdentity(job.Name, job.Group)
.WithIdentity(KeyBuilder.CreateTriggerKey(job))
.WithDescription(job.Description)
.StartAt(Clock.Now.AddSeconds(job.Interval))
.EndAt(job.EndTime)
.ForJob(job.Name, job.Group)
.ForJob(KeyBuilder.CreateJobKey(job))
.WithPriority((int)job.Priority)
.WithSimpleSchedule(x =>
x.WithIntervalInSeconds(job.Interval)

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

@ -10,27 +10,29 @@ public class QuartzJobScheduler : IJobScheduler, ISingletonDependency
{
protected IJobStore JobStore { get; }
protected IScheduler Scheduler { get; }
protected IQuartzKeyBuilder KeyBuilder { get; }
protected IQuartzJobExecutorProvider QuartzJobExecutor { get; }
public QuartzJobScheduler(
IJobStore jobStore,
IScheduler scheduler,
IQuartzKeyBuilder keyBuilder,
IQuartzJobExecutorProvider quartzJobExecutor)
{
JobStore = jobStore;
Scheduler = scheduler;
KeyBuilder = keyBuilder;
QuartzJobExecutor = quartzJobExecutor;
}
public virtual async Task<bool> ExistsAsync(JobInfo job)
{
var jobKey = new JobKey(job.Name, job.Group);
return await Scheduler.CheckExists(jobKey);
return await Scheduler.CheckExists(BuildJobKey(job));
}
public virtual async Task PauseAsync(JobInfo job)
{
var jobKey = new JobKey(job.Name, job.Group);
var jobKey = BuildJobKey(job);
if (await Scheduler.CheckExists(jobKey))
{
var triggers = await Scheduler.GetTriggersOfJob(jobKey);
@ -43,7 +45,7 @@ public class QuartzJobScheduler : IJobScheduler, ISingletonDependency
public virtual async Task<bool> QueueAsync(JobInfo job)
{
var jobKey = new JobKey(job.Name, job.Group);
var jobKey = BuildJobKey(job);
if (await Scheduler.CheckExists(jobKey))
{
return false;
@ -86,12 +88,12 @@ public class QuartzJobScheduler : IJobScheduler, ISingletonDependency
jobDictionary[jobDetail] = new ITrigger[] { jobTrigger };
}
await Scheduler.ScheduleJobs(jobDictionary, false);
await Scheduler.ScheduleJobs(jobDictionary, true);
}
public virtual async Task<bool> RemoveAsync(JobInfo job)
{
var jobKey = new JobKey(job.Name, job.Group);
var jobKey = BuildJobKey(job);
if (!await Scheduler.CheckExists(jobKey))
{
return false;
@ -109,7 +111,7 @@ public class QuartzJobScheduler : IJobScheduler, ISingletonDependency
public virtual async Task ResumeAsync(JobInfo job)
{
var jobKey = new JobKey(job.Name, job.Group);
var jobKey = BuildJobKey(job);
if (await Scheduler.CheckExists(jobKey))
{
var triggers = await Scheduler.GetTriggersOfJob(jobKey);
@ -150,7 +152,7 @@ public class QuartzJobScheduler : IJobScheduler, ISingletonDependency
public virtual async Task TriggerAsync(JobInfo job)
{
var jobKey = new JobKey(job.Name, job.Group);
var jobKey = BuildJobKey(job);
if (!await Scheduler.CheckExists(jobKey))
{
await QueueAsync(job);
@ -160,4 +162,9 @@ public class QuartzJobScheduler : IJobScheduler, ISingletonDependency
await Scheduler.TriggerJob(jobKey);
}
}
private JobKey BuildJobKey(JobInfo job)
{
return KeyBuilder.CreateJobKey(job);
}
}

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

@ -0,0 +1,35 @@
using Quartz;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.BackgroundTasks.Quartz;
public class QuartzKeyBuilder : IQuartzKeyBuilder, ISingletonDependency
{
protected ICurrentTenant CurrentTenant { get; }
public QuartzKeyBuilder(ICurrentTenant currentTenant)
{
CurrentTenant = currentTenant;
}
public JobKey CreateJobKey(JobInfo jobInfo)
{
var name = jobInfo.Id.ToString();
var group = CurrentTenant.IsAvailable
? $"{CurrentTenant.Id}:{jobInfo.Group}"
: $"Default:{jobInfo.Group}";
return new JobKey(name, group);
}
public TriggerKey CreateTriggerKey(JobInfo jobInfo)
{
var name = jobInfo.Id.ToString();
var group = CurrentTenant.IsAvailable
? $"{CurrentTenant.Id}:{jobInfo.Group}"
: $"Default:{jobInfo.Group}";
return new TriggerKey(name, group);
}
}

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

@ -6,6 +6,7 @@ using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.Json;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Timing;
namespace LINGYUN.Abp.BackgroundTasks;
@ -16,6 +17,7 @@ public class BackgroundJobManager : IBackgroundJobManager, ITransientDependency
protected IClock Clock { get; }
protected IJobStore JobStore { get; }
protected IJobScheduler JobScheduler { get; }
protected ICurrentTenant CurrentTenant { get; }
protected IGuidGenerator GuidGenerator { get; }
protected IJsonSerializer JsonSerializer { get; }
protected AbpBackgroundJobOptions Options { get; }
@ -23,6 +25,7 @@ public class BackgroundJobManager : IBackgroundJobManager, ITransientDependency
IClock clock,
IJobStore jobStore,
IJobScheduler jobScheduler,
ICurrentTenant currentTenant,
IGuidGenerator guidGenerator,
IJsonSerializer jsonSerializer,
IOptions<AbpBackgroundJobOptions> options)
@ -30,6 +33,7 @@ public class BackgroundJobManager : IBackgroundJobManager, ITransientDependency
Clock = clock;
JobStore = jobStore;
JobScheduler = jobScheduler;
CurrentTenant = currentTenant;
GuidGenerator = guidGenerator;
JsonSerializer = jsonSerializer;
Options = options.Value;
@ -57,6 +61,7 @@ public class BackgroundJobManager : IBackgroundJobManager, ITransientDependency
var jobInfo = new JobInfo
{
Id = jobId,
TenantId = CurrentTenant.Id,
Name = jobId.ToString(),
Group = "BackgroundJobs",
Priority = ConverForm(priority),

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

@ -2,19 +2,26 @@
using Microsoft.Extensions.Options;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.BackgroundTasks.Internal;
[DisableAuditing]
internal class BackgroundCleaningJob : IJobRunnable
public class BackgroundCleaningJob : IJobRunnable
{
public virtual async Task ExecuteAsync(JobRunnableContext context)
{
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundTasksOptions>>().Value;
var store = context.ServiceProvider.GetRequiredService<IJobStore>();
var currentTenant = context.ServiceProvider.GetRequiredService<ICurrentTenant>();
await store.CleanupAsync(
options.MaxJobCleanCount,
options.JobExpiratime);
context.TryGetMultiTenantId(out var tenantId);
using (currentTenant.Change(tenantId))
{
await store.CleanupAsync(
options.MaxJobCleanCount,
options.JobExpiratime);
}
}
}

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

@ -6,7 +6,7 @@ using Volo.Abp.Auditing;
namespace LINGYUN.Abp.BackgroundTasks.Internal;
[DisableAuditing]
internal class BackgroundKeepAliveJob : IJobRunnable
public class BackgroundKeepAliveJob : IJobRunnable
{
public virtual async Task ExecuteAsync(JobRunnableContext context)
{

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

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

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

@ -25,9 +25,12 @@ internal class DefaultBackgroundWorker : BackgroundService
protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{
// 仅轮询宿主端
await QueuePollingJob();
await QueueKeepAliveJob();
await QueueCleaningJob();
// 周期性任务改为手动入队
// await QueueKeepAliveJob();
}
private async Task QueueKeepAliveJob()
@ -54,7 +57,7 @@ internal class DefaultBackgroundWorker : BackgroundService
{
Id = Guid.Parse("8F50C5D9-5691-4B99-A52B-CABD91D93C89"),
Name = nameof(BackgroundKeepAliveJob),
Group = "Default",
Group = "KeepAlive",
Description = "Add periodic tasks",
Args = new Dictionary<string, object>(),
Status = JobStatus.Running,
@ -74,7 +77,7 @@ internal class DefaultBackgroundWorker : BackgroundService
{
Id = Guid.Parse("C51152E9-F0B8-4252-8352-283BE46083CC"),
Name = nameof(BackgroundPollingJob),
Group = "Default",
Group = "Polling",
Description = "Polling tasks to be executed",
Args = new Dictionary<string, object>(),
Status = JobStatus.Running,
@ -94,7 +97,7 @@ internal class DefaultBackgroundWorker : BackgroundService
{
Id = Guid.Parse("AAAF8783-FA06-4CF9-BDCA-11140FB2478F"),
Name = nameof(BackgroundCleaningJob),
Group = "Default",
Group = "Cleaning",
Description = "Cleaning tasks to be executed",
Args = new Dictionary<string, object>(),
Status = JobStatus.Running,

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

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

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

@ -2,7 +2,7 @@
using System.Threading.Tasks;
using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Uow;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.BackgroundTasks.Internal;
@ -21,7 +21,11 @@ public class JobLogEvent : JobEventBase<JobLogEvent>, ITransientDependency
return;
}
var store = context.ServiceProvider.GetRequiredService<IJobStore>();
var currentTenant = context.ServiceProvider.GetRequiredService<ICurrentTenant>();
await store.StoreLogAsync(context.EventData);
using (currentTenant.Change(context.EventData.TenantId))
{
await store.StoreLogAsync(context.EventData);
}
}
}

9
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoBatchInput.cs

@ -0,0 +1,9 @@
using System;
using System.Collections.Generic;
namespace LINGYUN.Abp.TaskManagement;
public class BackgroundJobInfoBatchInput
{
public List<Guid> JobIds { get; set; } = new List<Guid>();
}

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

@ -19,4 +19,18 @@ public interface IBackgroundJobInfoAppService :
Task ResumeAsync(Guid id);
Task StopAsync(Guid id);
Task StartAsync(Guid id);
Task BulkDeleteAsync(BackgroundJobInfoBatchInput input);
Task BulkStopAsync(BackgroundJobInfoBatchInput input);
Task BulkStartAsync(BackgroundJobInfoBatchInput input);
Task BulkTriggerAsync(BackgroundJobInfoBatchInput input);
Task BulkResumeAsync(BackgroundJobInfoBatchInput input);
Task BulkPauseAsync(BackgroundJobInfoBatchInput input);
}

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

@ -1,7 +1,6 @@
using LINGYUN.Abp.TaskManagement.Localization;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.TaskManagement.Permissions;
@ -11,50 +10,42 @@ public class TaskManagementPermissionDefinitionProvider : PermissionDefinitionPr
{
var group = context.AddGroup(
TaskManagementPermissions.GroupName,
L("Permissions:TaskManagement"),
MultiTenancySides.Host);
L("Permissions:TaskManagement"));
var backgroundJobs = group.AddPermission(
TaskManagementPermissions.BackgroundJobs.Default,
L("Permissions:BackgroundJobs"),
MultiTenancySides.Host);
L("Permissions:BackgroundJobs"));
backgroundJobs.AddChild(
TaskManagementPermissions.BackgroundJobs.Create,
L("Permissions:CreateJob"),
MultiTenancySides.Host);
L("Permissions:CreateJob"));
backgroundJobs.AddChild(
TaskManagementPermissions.BackgroundJobs.Update,
L("Permissions:UpdateJob"),
MultiTenancySides.Host);
L("Permissions:UpdateJob"));
backgroundJobs.AddChild(
TaskManagementPermissions.BackgroundJobs.Delete,
L("Permissions:DeleteJob"),
MultiTenancySides.Host);
L("Permissions:DeleteJob"));
backgroundJobs.AddChild(
TaskManagementPermissions.BackgroundJobs.Trigger,
L("Permissions:TriggerJob"),
MultiTenancySides.Host);
L("Permissions:TriggerJob"));
backgroundJobs.AddChild(
TaskManagementPermissions.BackgroundJobs.Pause,
L("Permissions:PauseJob"),
MultiTenancySides.Host);
L("Permissions:PauseJob"));
backgroundJobs.AddChild(
TaskManagementPermissions.BackgroundJobs.Resume,
L("Permissions:ResumeJob"),
MultiTenancySides.Host);
L("Permissions:ResumeJob"));
backgroundJobs.AddChild(
TaskManagementPermissions.BackgroundJobs.Start,
L("Permissions:StartJob"));
backgroundJobs.AddChild(
TaskManagementPermissions.BackgroundJobs.Stop,
L("Permissions:StopJob"),
MultiTenancySides.Host);
L("Permissions:StopJob"));
var backgroundJobLogs = group.AddPermission(
TaskManagementPermissions.BackgroundJobLogs.Default,
L("Permissions:BackgroundJobLogs"),
MultiTenancySides.Host);
L("Permissions:BackgroundJobLogs"));
backgroundJobLogs.AddChild(
TaskManagementPermissions.BackgroundJobLogs.Delete,
L("Permissions:DeleteJobLogs"),
MultiTenancySides.Host);
L("Permissions:DeleteJobLogs"));
}
private ILocalizableString L(string name)

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

@ -13,6 +13,7 @@ public static class TaskManagementPermissions
public const string Trigger = Default + ".Trigger";
public const string Pause = Default + ".Pause";
public const string Resume = Default + ".Resume";
public const string Start = Default + ".Start";
public const string Stop = Default + ".Stop";
}

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

@ -3,6 +3,7 @@ using LINGYUN.Abp.TaskManagement.Permissions;
using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
@ -44,7 +45,8 @@ public class BackgroundJobInfoAppService : TaskManagementApplicationService, IBa
input.EndTime,
input.Priority,
input.MaxCount,
input.MaxTryCount);
input.MaxTryCount,
CurrentTenant.Id);
UpdateByInput(backgroundJobInfo, input);
@ -132,6 +134,14 @@ public class BackgroundJobInfoAppService : TaskManagementApplicationService, IBa
await BackgroundJobManager.StopAsync(backgroundJobInfo);
}
[Authorize(TaskManagementPermissions.BackgroundJobs.Start)]
public virtual async Task StartAsync(Guid id)
{
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id);
await BackgroundJobManager.QueueAsync(backgroundJobInfo);
}
[Authorize(TaskManagementPermissions.BackgroundJobs.Update)]
public virtual async Task<BackgroundJobInfoDto> UpdateAsync(Guid id, BackgroundJobInfoUpdateDto input)
{
@ -148,6 +158,86 @@ public class BackgroundJobInfoAppService : TaskManagementApplicationService, IBa
return ObjectMapper.Map<BackgroundJobInfo, BackgroundJobInfoDto>(backgroundJobInfo);
}
[Authorize(TaskManagementPermissions.BackgroundJobs.Delete)]
public virtual async Task BulkDeleteAsync(BackgroundJobInfoBatchInput input)
{
if (!input.JobIds.Any())
{
return;
}
var jobs = await GetListAsync(input);
await BackgroundJobManager.BulkDeleteAsync(jobs);
}
[Authorize(TaskManagementPermissions.BackgroundJobs.Stop)]
public virtual async Task BulkStopAsync(BackgroundJobInfoBatchInput input)
{
if (!input.JobIds.Any())
{
return;
}
var jobs = await GetListAsync(input);
await BackgroundJobManager.BulkStopAsync(jobs);
}
[Authorize(TaskManagementPermissions.BackgroundJobs.Start)]
public virtual async Task BulkStartAsync(BackgroundJobInfoBatchInput input)
{
if (!input.JobIds.Any())
{
return;
}
var jobs = await GetListAsync(input);
await BackgroundJobManager.BulkQueueAsync(jobs);
}
[Authorize(TaskManagementPermissions.BackgroundJobs.Trigger)]
public virtual async Task BulkTriggerAsync(BackgroundJobInfoBatchInput input)
{
if (!input.JobIds.Any())
{
return;
}
var jobs = await GetListAsync(input);
await BackgroundJobManager.BulkTriggerAsync(jobs);
}
[Authorize(TaskManagementPermissions.BackgroundJobs.Resume)]
public virtual async Task BulkResumeAsync(BackgroundJobInfoBatchInput input)
{
if (!input.JobIds.Any())
{
return;
}
var jobs = await GetListAsync(input);
await BackgroundJobManager.BulkResumeAsync(jobs);
}
[Authorize(TaskManagementPermissions.BackgroundJobs.Pause)]
public virtual async Task BulkPauseAsync(BackgroundJobInfoBatchInput input)
{
if (!input.JobIds.Any())
{
return;
}
var jobs = await GetListAsync(input);
await BackgroundJobManager.BulkPauseAsync(jobs);
}
protected virtual async Task<IEnumerable<BackgroundJobInfo>> GetListAsync(BackgroundJobInfoBatchInput input)
{
var quaryble = await BackgroundJobInfoRepository.GetQueryableAsync();
quaryble = quaryble.Where(x => input.JobIds.Contains(x.Id));
return await AsyncExecuter.ToListAsync(quaryble);
}
protected virtual void UpdateByInput(BackgroundJobInfo backgroundJobInfo, BackgroundJobInfoCreateOrUpdateDto input)
{
backgroundJobInfo.IsEnabled = input.IsEnabled;

8
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/Resources/en.json

@ -9,6 +9,7 @@
"Permissions:TriggerJob": "Trigger Job",
"Permissions:PauseJob": "Pause Job",
"Permissions:ResumeJob": "Resume Job",
"Permissions:StartJob": "Start Job",
"Permissions:StopJob": "Stop Job",
"Permissions:BackgroundJobLogs": "BackgroundJobs Logs",
"Permissions:DeleteJobLogs": "Delete Job Logs",
@ -72,8 +73,11 @@
"BackgroundJobs:Pause": "Pause",
"BackgroundJobs:Resume": "Resume",
"BackgroundJobs:Trigger": "Trigger",
"BackgroundJobs:Stop": "Stop",
"BackgroundJobs:Start": "Start Jobs",
"BackgroundJobs:Stop": "Stop Jobs",
"BackgroundJobs:Delete": "Delete",
"BasicInfo": "Basic",
"Paramters": "Paramters"
"Paramters": "Paramters",
"MultipleSelectJobsWillBeDeletedMessage": "Multiple jobs selected will be deleted!"
}
}

8
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/Resources/zh-Hans.json

@ -9,6 +9,7 @@
"Permissions:TriggerJob": "触发作业",
"Permissions:PauseJob": "暂停作业",
"Permissions:ResumeJob": "恢复作业",
"Permissions:StartJob": "启动作业",
"Permissions:StopJob": "停止作业",
"Permissions:BackgroundJobLogs": "日志管理",
"Permissions:DeleteJobLogs": "删除作业日志",
@ -72,8 +73,11 @@
"BackgroundJobs:Pause": "暂停",
"BackgroundJobs:Resume": "恢复",
"BackgroundJobs:Trigger": "触发",
"BackgroundJobs:Stop": "停止",
"BackgroundJobs:Start": "启动作业",
"BackgroundJobs:Stop": "停止作业",
"BackgroundJobs:Delete": "删除",
"BasicInfo": "基本信息",
"Paramters": "参数列表"
"Paramters": "参数列表",
"MultipleSelectJobsWillBeDeletedMessage": "选择的多个作业将被删除!"
}
}

8
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfo.cs

@ -4,11 +4,13 @@ using System.Collections.Generic;
using Volo.Abp;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.TaskManagement;
public class BackgroundJobInfo : AuditedAggregateRoot<Guid>
public class BackgroundJobInfo : AuditedAggregateRoot<Guid>, IMultiTenant
{
public virtual Guid? TenantId { get; protected set; }
/// <summary>
/// 任务名称
/// </summary>
@ -114,7 +116,8 @@ public class BackgroundJobInfo : AuditedAggregateRoot<Guid>
DateTime? endTime = null,
JobPriority priority = JobPriority.Normal,
int maxCount = 0,
int maxTryCount = 50) : base(id)
int maxTryCount = 50,
Guid? tenantId = null) : base(id)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name), BackgroundJobInfoConsts.MaxNameLength);
Group = Check.NotNullOrWhiteSpace(group, nameof(group), BackgroundJobInfoConsts.MaxGroupLength);
@ -125,6 +128,7 @@ public class BackgroundJobInfo : AuditedAggregateRoot<Guid>
MaxCount = maxCount;
MaxTryCount = maxTryCount;
TenantId = tenantId;
Status = JobStatus.Running;

8
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobLog.cs

@ -1,11 +1,13 @@
using System;
using Volo.Abp;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.TaskManagement;
public class BackgroundJobLog : Entity<long>
public class BackgroundJobLog : Entity<long>, IMultiTenant
{
public virtual Guid? TenantId { get; protected set; }
public virtual Guid? JobId { get; set; }
public virtual string JobName { get; protected set; }
public virtual string JobGroup { get; protected set; }
@ -18,12 +20,14 @@ public class BackgroundJobLog : Entity<long>
string type,
string group,
string name,
DateTime runTime)
DateTime runTime,
Guid? tenantId = null)
{
JobType = Check.NotNullOrWhiteSpace(type, nameof(type), BackgroundJobInfoConsts.MaxTypeLength);
JobGroup = Check.NotNullOrWhiteSpace(group, nameof(group), BackgroundJobInfoConsts.MaxGroupLength);
JobName = Check.NotNullOrWhiteSpace(name, nameof(name), BackgroundJobInfoConsts.MaxNameLength);
RunTime = runTime;
TenantId = tenantId;
}
public BackgroundJobLog SetMessage(string message, Exception ex)

47
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobManager.cs

@ -1,5 +1,6 @@
using LINGYUN.Abp.BackgroundTasks;
using System.Threading.Tasks;
using System.Collections.Generic;
using Volo.Abp;
using Volo.Abp.Domain.Services;
using Volo.Abp.ObjectMapping;
@ -83,12 +84,26 @@ public class BackgroundJobManager : DomainService
});
}
public virtual async Task BulkDeleteAsync(IEnumerable<BackgroundJobInfo> jobInfos)
{
foreach (var jobInfo in jobInfos)
{
await DeleteAsync(jobInfo);
}
}
public virtual async Task QueueAsync(BackgroundJobInfo jobInfo)
{
var job = ObjectMapper.Map<BackgroundJobInfo, JobInfo>(jobInfo);
await JobScheduler.QueueAsync(job);
}
public virtual async Task BulkQueueAsync(IEnumerable<BackgroundJobInfo> jobInfos)
{
var jobs = ObjectMapper.Map<IEnumerable<BackgroundJobInfo>, List<JobInfo>>(jobInfos);
await JobScheduler.QueuesAsync(jobs);
}
public virtual async Task TriggerAsync(BackgroundJobInfo jobInfo)
{
var job = ObjectMapper.Map<BackgroundJobInfo, JobInfo>(jobInfo);
@ -99,6 +114,14 @@ public class BackgroundJobManager : DomainService
await JobScheduler.TriggerAsync(job);
}
public virtual async Task BulkTriggerAsync(IEnumerable<BackgroundJobInfo> jobInfos)
{
foreach (var jobInfo in jobInfos)
{
await TriggerAsync(jobInfo);
}
}
public virtual async Task PauseAsync(BackgroundJobInfo jobInfo)
{
var job = ObjectMapper.Map<BackgroundJobInfo, JobInfo>(jobInfo);
@ -110,6 +133,14 @@ public class BackgroundJobManager : DomainService
await BackgroundJobInfoRepository.UpdateAsync(jobInfo);
}
public virtual async Task BulkPauseAsync(IEnumerable<BackgroundJobInfo> jobInfos)
{
foreach (var jobInfo in jobInfos)
{
await PauseAsync(jobInfo);
}
}
public virtual async Task ResumeAsync(BackgroundJobInfo jobInfo)
{
var job = ObjectMapper.Map<BackgroundJobInfo, JobInfo>(jobInfo);
@ -122,6 +153,14 @@ public class BackgroundJobManager : DomainService
await BackgroundJobInfoRepository.UpdateAsync(jobInfo);
}
public virtual async Task BulkResumeAsync(IEnumerable<BackgroundJobInfo> jobInfos)
{
foreach (var jobInfo in jobInfos)
{
await ResumeAsync(jobInfo);
}
}
public virtual async Task StopAsync(BackgroundJobInfo jobInfo)
{
var job = ObjectMapper.Map<BackgroundJobInfo, JobInfo>(jobInfo);
@ -132,4 +171,12 @@ public class BackgroundJobManager : DomainService
await BackgroundJobInfoRepository.UpdateAsync(jobInfo);
}
public virtual async Task BulkStopAsync(IEnumerable<BackgroundJobInfo> jobInfos)
{
foreach (var jobInfo in jobInfos)
{
await StopAsync(jobInfo);
}
}
}

133
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobStore.cs

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Uow;
@ -13,15 +14,18 @@ namespace LINGYUN.Abp.TaskManagement;
public class BackgroundJobStore : IJobStore, ITransientDependency
{
protected IObjectMapper ObjectMapper { get; }
protected ICurrentTenant CurrentTenant { get; }
protected IBackgroundJobInfoRepository JobInfoRepository { get; }
protected IBackgroundJobLogRepository JobLogRepository { get; }
public BackgroundJobStore(
IObjectMapper objectMapper,
ICurrentTenant currentTenant,
IBackgroundJobInfoRepository jobInfoRepository,
IBackgroundJobLogRepository jobLogRepository)
{
ObjectMapper = objectMapper;
CurrentTenant = currentTenant;
JobInfoRepository = jobInfoRepository;
JobLogRepository = jobLogRepository;
}
@ -50,78 +54,85 @@ public class BackgroundJobStore : IJobStore, ITransientDependency
[UnitOfWork]
public async virtual Task StoreAsync(JobInfo jobInfo)
{
var backgroundJobInfo = await JobInfoRepository.FindAsync(jobInfo.Id);
if (backgroundJobInfo != null)
using (CurrentTenant.Change(jobInfo.TenantId))
{
backgroundJobInfo.SetNextRunTime(jobInfo.NextRunTime);
backgroundJobInfo.SetLastRunTime(jobInfo.LastRunTime);
backgroundJobInfo.SetStatus(jobInfo.Status);
backgroundJobInfo.SetResult(jobInfo.Result);
backgroundJobInfo.TriggerCount = jobInfo.TriggerCount;
backgroundJobInfo.TryCount = jobInfo.TryCount;
backgroundJobInfo.IsAbandoned = jobInfo.IsAbandoned;
await JobInfoRepository.UpdateAsync(backgroundJobInfo);
}
else
{
backgroundJobInfo = new BackgroundJobInfo(
jobInfo.Id,
jobInfo.Name,
jobInfo.Group,
jobInfo.Type,
jobInfo.Args,
jobInfo.BeginTime,
jobInfo.EndTime,
jobInfo.Priority,
jobInfo.MaxCount,
jobInfo.MaxTryCount)
var backgroundJobInfo = await JobInfoRepository.FindAsync(jobInfo.Id);
if (backgroundJobInfo != null)
{
IsEnabled = true,
TriggerCount = jobInfo.TriggerCount,
IsAbandoned = jobInfo.IsAbandoned,
TryCount = jobInfo.TryCount,
LockTimeOut = jobInfo.LockTimeOut,
Description = jobInfo.Description
};
backgroundJobInfo.SetNextRunTime(jobInfo.NextRunTime);
backgroundJobInfo.SetLastRunTime(jobInfo.LastRunTime);
backgroundJobInfo.SetStatus(jobInfo.Status);
backgroundJobInfo.SetResult(jobInfo.Result);
switch (jobInfo.JobType)
backgroundJobInfo.SetNextRunTime(jobInfo.NextRunTime);
backgroundJobInfo.SetLastRunTime(jobInfo.LastRunTime);
backgroundJobInfo.SetStatus(jobInfo.Status);
backgroundJobInfo.SetResult(jobInfo.Result);
backgroundJobInfo.TriggerCount = jobInfo.TriggerCount;
backgroundJobInfo.TryCount = jobInfo.TryCount;
backgroundJobInfo.IsAbandoned = jobInfo.IsAbandoned;
await JobInfoRepository.UpdateAsync(backgroundJobInfo);
}
else
{
case JobType.Once:
backgroundJobInfo.SetOnceJob(jobInfo.Interval);
break;
case JobType.Persistent:
backgroundJobInfo.SetPersistentJob(jobInfo.Interval);
break;
case JobType.Period:
backgroundJobInfo.SetPeriodJob(jobInfo.Cron);
break;
backgroundJobInfo = new BackgroundJobInfo(
jobInfo.Id,
jobInfo.Name,
jobInfo.Group,
jobInfo.Type,
jobInfo.Args,
jobInfo.BeginTime,
jobInfo.EndTime,
jobInfo.Priority,
jobInfo.MaxCount,
jobInfo.MaxTryCount)
{
IsEnabled = true,
TriggerCount = jobInfo.TriggerCount,
IsAbandoned = jobInfo.IsAbandoned,
TryCount = jobInfo.TryCount,
LockTimeOut = jobInfo.LockTimeOut,
Description = jobInfo.Description
};
backgroundJobInfo.SetNextRunTime(jobInfo.NextRunTime);
backgroundJobInfo.SetLastRunTime(jobInfo.LastRunTime);
backgroundJobInfo.SetStatus(jobInfo.Status);
backgroundJobInfo.SetResult(jobInfo.Result);
switch (jobInfo.JobType)
{
case JobType.Once:
backgroundJobInfo.SetOnceJob(jobInfo.Interval);
break;
case JobType.Persistent:
backgroundJobInfo.SetPersistentJob(jobInfo.Interval);
break;
case JobType.Period:
backgroundJobInfo.SetPeriodJob(jobInfo.Cron);
break;
}
await JobInfoRepository.InsertAsync(backgroundJobInfo);
}
await JobInfoRepository.InsertAsync(backgroundJobInfo);
}
}
[UnitOfWork]
public async virtual Task StoreLogAsync(JobEventData eventData)
{
var jogLog = new BackgroundJobLog(
eventData.Type.Name,
using (CurrentTenant.Change(eventData.TenantId))
{
var jogLog = new BackgroundJobLog(
eventData.Type.Name,
eventData.Group,
eventData.Name,
eventData.RunTime)
{
JobId = eventData.Key
};
eventData.RunTime,
eventData.TenantId)
{
JobId = eventData.Key
};
jogLog.SetMessage(
eventData.Exception == null ? eventData.Result ?? "OK" : "Failed",
eventData.Exception);
jogLog.SetMessage(
eventData.Exception == null ? eventData.Result ?? "OK" : "Failed",
eventData.Exception);
await JobLogRepository.InsertAsync(jogLog);
await JobLogRepository.InsertAsync(jogLog);
}
}
[UnitOfWork]
@ -131,9 +142,9 @@ public class BackgroundJobStore : IJobStore, ITransientDependency
CancellationToken cancellationToken = default)
{
var jobs = await JobInfoRepository.GetExpiredJobsAsync(
maxResultCount,
jobExpiratime,
cancellationToken);
maxResultCount,
jobExpiratime,
cancellationToken);
await JobInfoRepository.DeleteManyAsync(jobs, cancellationToken: cancellationToken);
}

2
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/EfCoreBackgroundJobInfoRepository.cs

@ -102,7 +102,7 @@ public class EfCoreBackgroundJobInfoRepository :
.WhereIf(filter.EndTime.HasValue, x => filter.EndTime.Value.CompareTo(x.EndTime) >= 0)
.WhereIf(filter.BeginCreationTime.HasValue, x => x.CreationTime.CompareTo(filter.BeginCreationTime.Value) >= 0)
.WhereIf(filter.EndCreationTime.HasValue, x => x.CreationTime.CompareTo(filter.EndCreationTime.Value) <= 0)
.OrderBy(sorting ?? nameof(BackgroundJobInfo.CreationTime))
.OrderBy(sorting ?? $"{nameof(BackgroundJobInfo.CreationTime)} DESC")
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}

55
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.HttpApi/LINGYUN/Abp/TaskManagement/BackgroundJobInfoController.cs

@ -89,4 +89,59 @@ public class BackgroundJobInfoController : TaskManagementController, IBackground
{
return BackgroundJobInfoAppService.UpdateAsync(id, input);
}
[HttpPut]
[Route("{id}/start")]
[Authorize(TaskManagementPermissions.BackgroundJobs.Start)]
public Task StartAsync(Guid id)
{
return BackgroundJobInfoAppService.StartAsync(id);
}
[HttpPut]
[Route("bulk-stop")]
[Authorize(TaskManagementPermissions.BackgroundJobs.Stop)]
public Task BulkStopAsync(BackgroundJobInfoBatchInput input)
{
return BackgroundJobInfoAppService.BulkStopAsync(input);
}
[HttpPut]
[Route("bulk-start")]
[Authorize(TaskManagementPermissions.BackgroundJobs.Start)]
public Task BulkStartAsync(BackgroundJobInfoBatchInput input)
{
return BackgroundJobInfoAppService.BulkStartAsync(input);
}
[HttpPut]
[Route("bulk-trigger")]
[Authorize(TaskManagementPermissions.BackgroundJobs.Trigger)]
public Task BulkTriggerAsync(BackgroundJobInfoBatchInput input)
{
return BackgroundJobInfoAppService.BulkTriggerAsync(input);
}
[HttpPut]
[Route("bulk-resume")]
[Authorize(TaskManagementPermissions.BackgroundJobs.Resume)]
public Task BulkResumeAsync(BackgroundJobInfoBatchInput input)
{
return BackgroundJobInfoAppService.BulkResumeAsync(input);
}
[HttpPut]
[Route("bulk-pause")]
[Authorize(TaskManagementPermissions.BackgroundJobs.Pause)]
public Task BulkPauseAsync(BackgroundJobInfoBatchInput input)
{
return BackgroundJobInfoAppService.BulkPauseAsync(input);
}
[HttpPut]
[Route("bulk-delete")]
public Task BulkDeleteAsync(BackgroundJobInfoBatchInput input)
{
return BackgroundJobInfoAppService.BulkDeleteAsync(input);
}
}

140
aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/EventBus/Handlers/TenantSynchronizer.cs

@ -0,0 +1,140 @@
using LINGYUN.Abp.BackgroundTasks;
using LINGYUN.Abp.BackgroundTasks.Internal;
using LINGYUN.Abp.Data.DbMigrator;
using LINGYUN.Abp.MultiTenancy;
using LY.MicroService.TaskManagement.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.MultiTenancy;
using Volo.Abp.TenantManagement;
using Volo.Abp.Uow;
namespace LY.MicroService.TaskManagement.EventBus.Handlers
{
public class TenantSynchronizer :
IDistributedEventHandler<CreateEventData>,
IDistributedEventHandler<EntityDeletedEto<TenantEto>>,
ITransientDependency
{
protected ILogger<TenantSynchronizer> Logger { get; }
protected ICurrentTenant CurrentTenant { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected IDbSchemaMigrator DbSchemaMigrator { get; }
protected AbpBackgroundTasksOptions Options { get; }
protected IJobScheduler JobScheduler { get; }
public TenantSynchronizer(
ICurrentTenant currentTenant,
IUnitOfWorkManager unitOfWorkManager,
IDbSchemaMigrator dbSchemaMigrator,
IOptions<AbpBackgroundTasksOptions> options,
IJobScheduler jobScheduler,
ILogger<TenantSynchronizer> logger)
{
CurrentTenant = currentTenant;
UnitOfWorkManager = unitOfWorkManager;
DbSchemaMigrator = dbSchemaMigrator;
JobScheduler = jobScheduler;
Options = options.Value;
Logger = logger;
}
public async Task HandleEventAsync(EntityDeletedEto<TenantEto> eventData)
{
// 租户删除时移除轮询作业
var pollingJob = BuildPollingJobInfo(eventData.Entity.Id, eventData.Entity.Name);
await JobScheduler.RemoveAsync(pollingJob);
var cleaningJob = BuildCleaningJobInfo(eventData.Entity.Id, eventData.Entity.Name);
await JobScheduler.RemoveAsync(cleaningJob);
}
public async Task HandleEventAsync(CreateEventData eventData)
{
await MigrateAsync(eventData);
// 持久层介入之后提供对于租户的后台工作者轮询作业
await QueueBackgroundJobAsync(eventData);
}
private async Task QueueBackgroundJobAsync(CreateEventData eventData)
{
var pollingJob = BuildPollingJobInfo(eventData.Id, eventData.Name);
await JobScheduler.QueueAsync(pollingJob);
var cleaningJob = BuildCleaningJobInfo(eventData.Id, eventData.Name);
await JobScheduler.QueueAsync(cleaningJob);
}
private async Task MigrateAsync(CreateEventData eventData)
{
using (var unitOfWork = UnitOfWorkManager.Begin())
{
using (CurrentTenant.Change(eventData.Id, eventData.Name))
{
Logger.LogInformation("Migrating the new tenant database with localization..");
// 迁移租户数据
await DbSchemaMigrator.MigrateAsync<TaskManagementMigrationsDbContext>(
(connectionString, builder) =>
{
builder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
return new TaskManagementMigrationsDbContext(builder.Options);
});
await unitOfWork.SaveChangesAsync();
Logger.LogInformation("Migrated the new tenant database with localization.");
}
}
}
private JobInfo BuildPollingJobInfo(Guid tenantId, string tenantName)
{
return new JobInfo
{
Id = tenantId,
Name = nameof(BackgroundPollingJob),
Group = "Polling",
Description = "Polling tasks to be executed",
Args = new Dictionary<string, object>() { { nameof(JobInfo.TenantId), tenantId } },
Status = JobStatus.Running,
BeginTime = DateTime.Now,
CreationTime = DateTime.Now,
Cron = Options.JobFetchCronExpression,
JobType = JobType.Period,
Priority = JobPriority.High,
LockTimeOut = Options.JobFetchLockTimeOut,
TenantId = tenantId,
Type = typeof(BackgroundPollingJob).AssemblyQualifiedName,
};
}
private JobInfo BuildCleaningJobInfo(Guid tenantId, string tenantName)
{
return new JobInfo
{
Id = tenantId,
Name = nameof(BackgroundCleaningJob),
Group = "Cleaning",
Description = "Cleaning tasks to be executed",
Args = new Dictionary<string, object>() { { nameof(JobInfo.TenantId), tenantId } },
Status = JobStatus.Running,
BeginTime = DateTime.Now,
CreationTime = DateTime.Now,
Cron = Options.JobCleanCronExpression,
JobType = JobType.Period,
Priority = JobPriority.High,
TenantId = tenantId,
Type = typeof(BackgroundCleaningJob).AssemblyQualifiedName,
};
}
}
}

7
aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<TypeScriptCompileOnSaveEnabled>False</TypeScriptCompileOnSaveEnabled>
@ -19,12 +19,16 @@
<ItemGroup>
<PackageReference Include="AgileConfig.Client" Version="$(AgileConfigClientPackageVersion)" />
<PackageReference Include="DotNetCore.CAP.Dashboard" Version="$(DotNetCoreCAPPackageVersion)" />
<PackageReference Include="DotNetCore.CAP.MySql" Version="$(DotNetCoreCAPPackageVersion)" />
<PackageReference Include="DotNetCore.CAP.RabbitMQ" Version="$(DotNetCoreCAPPackageVersion)" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="$(MicrosoftPackageVersion)">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="$(MicrosoftPackageVersion)" />
<PackageReference Include="DistributedLock.Redis" Version="$(DistributedLockRedisPackageVersion)" />
<PackageReference Include="Quartz.Serialization.Json" Version="$(QuartzNETPackageVersion)" />
<PackageReference Include="Serilog.AspNetCore" Version="$(SerilogAspNetCorePackageVersion)" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="$(SerilogEnrichersEnvironmentPackageVersion)" />
<PackageReference Include="Serilog.Enrichers.Assembly" Version="$(SerilogEnrichersAssemblyPackageVersion)" />
@ -50,6 +54,7 @@
<ItemGroup>
<ProjectReference Include="..\..\modules\common\LINGYUN.Abp.Data.DbMigrator\LINGYUN.Abp.Data.DbMigrator.csproj" />
<ProjectReference Include="..\..\modules\common\LINGYUN.Abp.EventBus.CAP\LINGYUN.Abp.EventBus.CAP.csproj" />
<ProjectReference Include="..\..\modules\common\LINGYUN.Abp.ExceptionHandling.Emailing\LINGYUN.Abp.ExceptionHandling.Emailing.csproj" />
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.BackgroundTasks.ExceptionHandling\LINGYUN.Abp.BackgroundTasks.ExceptionHandling.csproj" />
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.BackgroundTasks.Jobs\LINGYUN.Abp.BackgroundTasks.Jobs.csproj" />

201
aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Migrations/20220112093435_Add-Support-Multi-Tenancy-With-Background-Jobs.Designer.cs

@ -0,0 +1,201 @@
// <auto-generated />
using System;
using LY.MicroService.TaskManagement.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Volo.Abp.EntityFrameworkCore;
#nullable disable
namespace LY.MicroService.TaskManagement.Migrations
{
[DbContext(typeof(TaskManagementMigrationsDbContext))]
[Migration("20220112093435_Add-Support-Multi-Tenancy-With-Background-Jobs")]
partial class AddSupportMultiTenancyWithBackgroundJobs
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.MySql)
.HasAnnotation("ProductVersion", "6.0.1")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("LINGYUN.Abp.TaskManagement.BackgroundJobInfo", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("Args")
.HasColumnType("longtext")
.HasColumnName("Args");
b.Property<DateTime>("BeginTime")
.HasColumnType("datetime(6)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasMaxLength(40)
.HasColumnType("varchar(40)")
.HasColumnName("ConcurrencyStamp");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime(6)")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("char(36)")
.HasColumnName("CreatorId");
b.Property<string>("Cron")
.HasMaxLength(50)
.HasColumnType("varchar(50)")
.HasColumnName("Cron");
b.Property<string>("Description")
.HasMaxLength(255)
.HasColumnType("varchar(255)")
.HasColumnName("Description");
b.Property<DateTime?>("EndTime")
.HasColumnType("datetime(6)");
b.Property<string>("ExtraProperties")
.HasColumnType("longtext")
.HasColumnName("ExtraProperties");
b.Property<string>("Group")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)")
.HasColumnName("Group");
b.Property<int>("Interval")
.HasColumnType("int");
b.Property<bool>("IsAbandoned")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsEnabled")
.HasColumnType("tinyint(1)");
b.Property<int>("JobType")
.HasColumnType("int");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime(6)")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("char(36)")
.HasColumnName("LastModifierId");
b.Property<DateTime?>("LastRunTime")
.HasColumnType("datetime(6)");
b.Property<int>("LockTimeOut")
.HasColumnType("int");
b.Property<int>("MaxCount")
.HasColumnType("int");
b.Property<int>("MaxTryCount")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)")
.HasColumnName("Name");
b.Property<DateTime?>("NextRunTime")
.HasColumnType("datetime(6)");
b.Property<int>("Priority")
.HasColumnType("int");
b.Property<string>("Result")
.HasMaxLength(1000)
.HasColumnType("varchar(1000)")
.HasColumnName("Result");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<Guid?>("TenantId")
.HasColumnType("char(36)")
.HasColumnName("TenantId");
b.Property<int>("TriggerCount")
.HasColumnType("int");
b.Property<int>("TryCount")
.HasColumnType("int");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("varchar(1000)")
.HasColumnName("Type");
b.HasKey("Id");
b.HasIndex("Name", "Group");
b.ToTable("TK_BackgroundJobs", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.TaskManagement.BackgroundJobLog", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<string>("Exception")
.HasMaxLength(2000)
.HasColumnType("varchar(2000)")
.HasColumnName("Exception");
b.Property<string>("JobGroup")
.HasMaxLength(50)
.HasColumnType("varchar(50)")
.HasColumnName("JobGroup");
b.Property<Guid?>("JobId")
.HasColumnType("char(36)");
b.Property<string>("JobName")
.HasMaxLength(100)
.HasColumnType("varchar(100)")
.HasColumnName("JobName");
b.Property<string>("JobType")
.HasMaxLength(1000)
.HasColumnType("varchar(1000)")
.HasColumnName("JobType");
b.Property<string>("Message")
.HasMaxLength(1000)
.HasColumnType("varchar(1000)")
.HasColumnName("Message");
b.Property<DateTime>("RunTime")
.HasColumnType("datetime(6)");
b.Property<Guid?>("TenantId")
.HasColumnType("char(36)")
.HasColumnName("TenantId");
b.HasKey("Id");
b.HasIndex("JobGroup", "JobName");
b.ToTable("TK_BackgroundJobLogs", (string)null);
});
#pragma warning restore 612, 618
}
}
}

38
aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Migrations/20220112093435_Add-Support-Multi-Tenancy-With-Background-Jobs.cs

@ -0,0 +1,38 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LY.MicroService.TaskManagement.Migrations
{
public partial class AddSupportMultiTenancyWithBackgroundJobs : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "TenantId",
table: "TK_BackgroundJobs",
type: "char(36)",
nullable: true,
collation: "ascii_general_ci");
migrationBuilder.AddColumn<Guid>(
name: "TenantId",
table: "TK_BackgroundJobLogs",
type: "char(36)",
nullable: true,
collation: "ascii_general_ci");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TenantId",
table: "TK_BackgroundJobs");
migrationBuilder.DropColumn(
name: "TenantId",
table: "TK_BackgroundJobLogs");
}
}
}

8
aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Migrations/TaskManagementMigrationsDbContextModelSnapshot.cs

@ -123,6 +123,10 @@ namespace LY.MicroService.TaskManagement.Migrations
b.Property<int>("Status")
.HasColumnType("int");
b.Property<Guid?>("TenantId")
.HasColumnType("char(36)")
.HasColumnName("TenantId");
b.Property<int>("TriggerCount")
.HasColumnType("int");
@ -179,6 +183,10 @@ namespace LY.MicroService.TaskManagement.Migrations
b.Property<DateTime>("RunTime")
.HasColumnType("datetime(6)");
b.Property<Guid?>("TenantId")
.HasColumnType("char(36)")
.HasColumnName("TenantId");
b.HasKey("Id");
b.HasIndex("JobGroup", "JobName");

50
aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.Configure.cs

@ -12,6 +12,8 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using Volo.Abp;
@ -22,7 +24,10 @@ using Volo.Abp.Json;
using Volo.Abp.Json.SystemTextJson;
using Volo.Abp.Localization;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Quartz;
using Volo.Abp.VirtualFileSystem;
using Quartz;
using DotNetCore.CAP;
namespace LY.MicroService.TaskManagement;
@ -41,6 +46,51 @@ public partial class TaskManagementHttpApiHostModule
});
}
private void PreConfigureCAP(IConfiguration configuration)
{
PreConfigure<CapOptions>(options =>
{
options
.UseMySql(mySqlOptions =>
{
configuration.GetSection("CAP:MySql").Bind(mySqlOptions);
})
.UseRabbitMQ(rabbitMQOptions =>
{
configuration.GetSection("CAP:RabbitMQ").Bind(rabbitMQOptions);
})
.UseDashboard();
});
}
private void PreConfigureQuartz(IConfiguration configuration)
{
PreConfigure<AbpQuartzOptions>(options =>
{
// 如果使用持久化存储, 则配置quartz持久层
if (configuration.GetSection("Quartz:UsePersistentStore").Get<bool>())
{
var settings = configuration.GetSection("Quartz:Properties").Get<Dictionary<string, string>>();
if (settings != null)
{
foreach (var setting in settings)
{
options.Properties[setting.Key] = setting.Value;
}
}
options.Configurator += (config) =>
{
config.UsePersistentStore(store =>
{
store.UseProperties = false;
store.UseJsonSerializer();
});
};
}
});
}
private void ConfigureDistributedLock(IServiceCollection services, IConfiguration configuration)
{
var redis = ConnectionMultiplexer.Connect(configuration["DistributedLock:Redis:Configuration"]);

6
aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.cs

@ -3,6 +3,7 @@ using LINGYUN.Abp.BackgroundTasks.ExceptionHandling;
using LINGYUN.Abp.BackgroundTasks.Jobs;
using LINGYUN.Abp.BackgroundTasks.Quartz;
using LINGYUN.Abp.Data.DbMigrator;
using LINGYUN.Abp.EventBus.CAP;
using LINGYUN.Abp.ExceptionHandling.Emailing;
using LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore;
using LINGYUN.Abp.MultiTenancy.DbFinder;
@ -57,6 +58,7 @@ namespace LY.MicroService.TaskManagement;
typeof(AbpSettingManagementEntityFrameworkCoreModule),
typeof(AbpTenantManagementEntityFrameworkCoreModule),
typeof(AbpLocalizationManagementEntityFrameworkCoreModule),
typeof(AbpCAPEventBusModule),
typeof(AbpDataDbMigratorModule),
typeof(AbpCachingStackExchangeRedisModule),
typeof(AbpAspNetCoreMvcModule),
@ -67,7 +69,11 @@ public partial class TaskManagementHttpApiHostModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
PreConfigureApp();
PreConfigureCAP(configuration);
PreConfigureQuartz(configuration);
}
public override void ConfigureServices(ServiceConfigurationContext context)

13
aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/appsettings.Development.json

@ -31,6 +31,19 @@
"VirtualHost": "/"
}
},
"Quartz": {
"UsePersistentStore": false,
"Properties": {
"quartz.jobStore.dataSource": "tkm",
"quartz.jobStore.type": "Quartz.Impl.AdoJobStore.JobStoreTX,Quartz",
"quartz.jobStore.driverDelegateType": "Quartz.Impl.AdoJobStore.MySQLDelegate,Quartz",
"quartz.dataSource.tkm.connectionString": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456",
"quartz.dataSource.tkm.connectionStringName": "TaskManagement",
"quartz.dataSource.tkm.provider": "MySqlConnector",
"quartz.jobStore.clustered": "true",
"quartz.serializer.type": "json"
}
},
"ConnectionStrings": {
"Default": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456",
"TaskManagement": "Server=127.0.0.1;Database=Platform;User Id=root;Password=123456",

Loading…
Cancel
Save