Browse Source

feat(tasks): perfect job management interface support

pull/457/head
cKey 4 years ago
parent
commit
97071b9562
  1. 78
      apps/vue/src/api/task-management/backgroundJobInfo.ts
  2. 32
      apps/vue/src/api/task-management/backgroundJobLog.ts
  3. 88
      apps/vue/src/api/task-management/model/backgroundJobInfoModel.ts
  4. 22
      apps/vue/src/api/task-management/model/backgroundJobLogModel.ts
  5. 325
      apps/vue/src/views/task-management/background-jobs/components/BackgroundJobInfoModal.vue
  6. 164
      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. 41
      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. 4
      aspnet-core/LINGYUN.MicroService.TaskManagement.sln
  13. 5
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN.Abp.BackgroundTasks.Jobs.csproj
  14. 4
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/AbpBackgroundTasksJobsModule.cs
  15. 4
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/ConsoleJob.cs
  16. 14
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/HttpRequestJob.cs
  17. 13
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/SendSmsJob.cs
  18. 228
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Jobs/LINGYUN/Abp/BackgroundTasks/Jobs/ServiceInvocationJob.cs
  19. 11
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN/Abp/BackgroundTasks/Quartz/QuartzJobScheduler.cs
  20. 9
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundJobAdapter.cs
  21. 22
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/BackgroundJobManager.cs
  22. 1
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundKeepAliveJob.cs
  23. 3
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/BackgroundPollingJob.cs
  24. 4
      aspnet-core/modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN/Abp/BackgroundTasks/Internal/JobExecutedEvent.cs
  25. 21
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoCreateDto.cs
  26. 11
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoCreateOrUpdateDto.cs
  27. 9
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoDto.cs
  28. 4
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/BackgroundJobInfoGetListInput.cs
  29. 2
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/IBackgroundJobInfoAppService.cs
  30. 4
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/Permissions/TaskManagementPermissionDefinitionProvider.cs
  31. 1
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN/Abp/TaskManagement/Permissions/TaskManagementPermissions.cs
  32. 22
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Application/LINGYUN/Abp/TaskManagement/BackgroundJobInfoAppService.cs
  33. 2
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/BackgroundJobInfoConsts.cs
  34. 63
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/Resources/en.json
  35. 63
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/Resources/zh-Hans.json
  36. 2
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/TaskManagementErrorCodes.cs
  37. 5
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfo.cs
  38. 2
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfoFilter.cs
  39. 27
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobManager.cs
  40. 6
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobStore.cs
  41. 16
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/EfCoreBackgroundJobInfoRepository.cs
  42. 8
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.HttpApi/LINGYUN/Abp/TaskManagement/BackgroundJobInfoController.cs
  43. 193
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Migrations/20220110104046_Reset-Field-Type-Length-With-Background-Job-Info.Designer.cs
  44. 65
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Migrations/20220110104046_Reset-Field-Type-Length-With-Background-Job-Info.cs
  45. 8
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Migrations/TaskManagementMigrationsDbContextModelSnapshot.cs
  46. 2
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Properties/launchSettings.json
  47. 35
      gateways/internal/LINGYUN.MicroService.Internal.ApiGateway/src/LINGYUN.MicroService.Internal.ApiGateway/ocelot.Development.json
  48. 35
      gateways/internal/LINGYUN.MicroService.Internal.ApiGateway/src/LINGYUN.MicroService.Internal.ApiGateway/ocelot.platform.json

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

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

@ -0,0 +1,88 @@
import { ExtensibleAuditedEntity, IHasConcurrencyStamp, PagedAndSortedResultRequestDto } from "../../model/baseModel";
export enum JobStatus {
None = -1,
Completed = 0,
Running = 10,
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;
}

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

@ -0,0 +1,325 @@
<template>
<BasicModal
@register="registerModal"
:width="800"
:height="400"
:title="modalTitle"
: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 :readonly="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(() => {
return isEditModal.value ? L('BackgroundJobs:Edit') : L('BackgroundJobs:AddNew');
});
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>

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

@ -0,0 +1,164 @@
<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 }">
<Tag :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: record.status === JobStatus.Running,
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].includes(record.status),
onClick: handleTrigger.bind(null, record),
},
{
auth: 'TaskManagement.BackgroundJobs.Stop',
label: L('BackgroundJobs:Stop'),
ifShow: record.status === JobStatus.Running,
onClick: handleStop.bind(null, record),
},
]"
/>
</template>
</BasicTable>
<BackgroundJobInfoModal @change="handleChange" @register="registerModal" />
</div>
</template>
<script lang="ts" setup>
import { Switch, Modal, Tag, 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,
},
];
}

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

@ -0,0 +1,41 @@
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.Paused]: L('DisplayName:Paused'),
[JobStatus.Stopped]: L('DisplayName:Stopped'),
}
export const JobStatusColor = {
[JobStatus.None]: '',
[JobStatus.Completed]: '#339933',
[JobStatus.Running]: '#3399CC',
[JobStatus.Paused]: '#CC6633',
[JobStatus.Stopped]: '#FF6600',
}
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;

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

@ -34,9 +34,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.BackgroundTasks
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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.BackgroundTasks.Abstractions", "modules\task-management\LINGYUN.Abp.BackgroundTasks.Abstractions\LINGYUN.Abp.BackgroundTasks.Abstractions.csproj", "{4A049C32-55F2-4A5F-954A-C8A977C2D87F}"
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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.BackgroundTasks.Jobs", "modules\task-management\LINGYUN.Abp.BackgroundTasks.Jobs\LINGYUN.Abp.BackgroundTasks.Jobs.csproj", "{4C59F590-AA6C-4C46-8060-DA65D8305980}"
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

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

@ -4,7 +4,7 @@
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
@ -15,7 +15,8 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.BackgroundTasks.Abstractions\LINGYUN.Abp.BackgroundTasks.Abstractions.csproj" />
<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>

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

@ -1,4 +1,5 @@
using Volo.Abp.Emailing;
using LINGYUN.Abp.Dapr.Client;
using Volo.Abp.Emailing;
using Volo.Abp.Http.Client;
using Volo.Abp.Modularity;
using Volo.Abp.Sms;
@ -8,6 +9,7 @@ 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)

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

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

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

@ -59,12 +59,20 @@ public class HttpRequestJob : IJobRunnable
Encoding.UTF8,
contentType?.ToString() ?? MimeTypes.Application.Json);
}
if (context.TryGetJobData(PropertyHeaders, out var headers) &&
headers is IDictionary<string, string> headersDic)
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);
httpRequestMesasge.Headers.Add(header.Key, header.Value.ToString());
}
}
// TODO: 和 headers 一起?

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

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Json;
using Volo.Abp.Sms;
namespace LINGYUN.Abp.BackgroundTasks.Jobs;
@ -16,9 +17,17 @@ public class SendSmsJob : IJobRunnable
var message = context.GetString(PropertyMessage);
var smsMessage = new SmsMessage(phoneNumber, message);
if (context.TryGetJobData(PropertyProperties, out var data) &&
data is IDictionary<string, object> properties)
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);
}

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

@ -1,4 +1,5 @@
using Microsoft.Extensions.Options;
using LINGYUN.Abp.Dapr.Client.DynamicProxying;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Globalization;
@ -11,15 +12,31 @@ 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)
{
@ -29,63 +46,101 @@ public class ServiceInvocationJob : IJobRunnable
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);
}
using (CultureHelper.Use(culture ?? CultureInfo.CurrentCulture.Name))
Guid? tenantId = null;
if (context.TryGetString(PropertyTenant, out var tenant) &&
Guid.TryParse(tenant, out var converTenant))
{
// 反射所必须的参数
var callRequestMethod = nameof(DynamicHttpProxyInterceptorClientProxy<object>.CallRequestAsync);
var clientProxyType = typeof(DynamicHttpProxyInterceptorClientProxy<>).MakeGenericType(serviceType);
var clientProxy = context.GetRequiredService(clientProxyType);
var clientProxyMethod = typeof(DynamicHttpProxyInterceptorClientProxy<>).GetMethod(callRequestMethod);
// 调用远程服务发现端点
var actionApiDescription = await GetActionApiDescriptionModel(context, serviceType, serviceMethod);
// 拼接调用参数
var invokeParameters = new Dictionary<string, object>();
var methodParameters = serviceMethod.GetParameters();
foreach (var parameter in methodParameters)
tenantId = converTenant;
}
var currentTenant = context.GetRequiredService<ICurrentTenant>();
using (currentTenant.Change(tenantId))
{
using (CultureHelper.Use(culture ?? CultureInfo.CurrentCulture.Name))
{
if (context.TryGetJobData(parameter.Name, out var value))
switch (provider)
{
invokeParameters.Add(parameter.Name, value);
case "http":
await ExecuteWithHttpProxy(context, serviceType, serviceMethod);
break;
case "dapr":
await ExecuteWithDaprProxy(context, serviceType, serviceMethod);
break;
}
}
}
}
// 构造服务代理上下文
var clientProxyRequestContext = new ClientProxyRequestContext(
actionApiDescription,
invokeParameters,
serviceType);
#region HttpClient
if (serviceMethod.ReturnType.GenericTypeArguments.IsNullOrEmpty())
{
// 直接调用
var taskProxy = (Task)clientProxyMethod.Invoke(clientProxy, new object[] { clientProxyRequestContext });
await taskProxy;
}
else
{
// 有返回值的调用
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);
var callRequestAsyncMethod = typeof(DynamicHttpProxyInterceptor<object>)
.GetMethods(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 returnType = serviceMethod.ReturnType.GenericTypeArguments[0];
var result = (Task)callRequestAsyncMethod
.MakeGenericMethod(returnType)
.Invoke(this, new object[] { context });
// 调用远程服务发现端点
var actionApiDescription = await GetActionApiDescriptionModel(context, serviceType, serviceMethod);
context.SetResult(await GetResultAsync(result, returnType));
}
// 调用参数
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 method)
MethodInfo serviceMethod)
{
var clientOptions = context.GetRequiredService<IOptions<AbpHttpClientOptions>>().Value;
var remoteServiceConfigurationProvider = context.GetRequiredService<IRemoteServiceConfigurationProvider>();
@ -101,10 +156,99 @@ public class ServiceInvocationJob : IJobRunnable
client,
remoteServiceConfig.BaseUrl,
serviceType,
method
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;

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

@ -152,13 +152,12 @@ public class QuartzJobScheduler : IJobScheduler, ISingletonDependency
public virtual async Task TriggerAsync(JobInfo job)
{
var jobKey = new JobKey(job.Name, job.Group);
if (await Scheduler.CheckExists(jobKey))
{
await Scheduler.TriggerJob(jobKey);
}
else
if (!await Scheduler.CheckExists(jobKey))
{
throw new AbpException("This task could not be found in task scheduler, please confirm that it is enabled?");
job.JobType = JobType.Once;
await QueueAsync(job);
}
await Scheduler.TriggerJob(jobKey);
}
}

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

@ -5,6 +5,7 @@ using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.Json;
namespace LINGYUN.Abp.BackgroundTasks;
@ -31,7 +32,13 @@ public class BackgroundJobAdapter<TArgs> : IJobRunnable
public virtual async Task ExecuteAsync(JobRunnableContext context)
{
using var scope = ServiceScopeFactory.CreateScope();
var args = context.JobData.GetOrDefault(nameof(TArgs));
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);

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

@ -5,6 +5,7 @@ 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;
@ -14,17 +15,23 @@ 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;
}
@ -34,7 +41,7 @@ public class BackgroundJobManager : IBackgroundJobManager, ITransientDependency
TimeSpan? delay = null)
{
var jobConfiguration = Options.GetJob<TArgs>();
var interval = 60;
var interval = 0;
if (delay.HasValue)
{
interval = delay.Value.Seconds;
@ -42,14 +49,15 @@ public class BackgroundJobManager : IBackgroundJobManager, ITransientDependency
var jobId = GuidGenerator.Create();
var jobArgs = new Dictionary<string, object>
{
{ nameof(TArgs), args },
{ nameof(TArgs), JsonSerializer.Serialize(args) },
{ "ArgsType", jobConfiguration.ArgsType.AssemblyQualifiedName },
{ "JobType", jobConfiguration.JobType.AssemblyQualifiedName },
{ "JobName", jobConfiguration.JobName },
};
var jobInfo = new JobInfo
{
Id = jobId,
Name = jobConfiguration.JobName,
Name = jobId.ToString(),
Group = "BackgroundJobs",
Priority = ConverForm(priority),
BeginTime = DateTime.Now,
@ -58,13 +66,17 @@ public class BackgroundJobManager : IBackgroundJobManager, ITransientDependency
JobType = JobType.Once,
Interval = interval,
CreationTime = Clock.Now,
Status = JobStatus.Running,
// 确保不会被轮询入队
Status = JobStatus.None,
Type = typeof(BackgroundJobAdapter<TArgs>).AssemblyQualifiedName,
};
// 作为一次性任务持久化
// 存储状态
await JobStore.StoreAsync(jobInfo);
// 手动入队
await JobScheduler.QueueAsync(jobInfo);
return jobId.ToString();
}

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

@ -12,6 +12,7 @@ internal class BackgroundKeepAliveJob : IJobRunnable
{
var store = context.ServiceProvider.GetRequiredService<IJobStore>();
// TODO: 如果积压有大量周期性任务, 可能后面的队列无法被检索到
var periodJobs = await store.GetAllPeriodTasksAsync();
if (!periodJobs.Any())

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

@ -14,6 +14,9 @@ internal class BackgroundPollingJob : IJobRunnable
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundTasksOptions>>().Value;
var store = context.ServiceProvider.GetRequiredService<IJobStore>();
// TODO: 如果积压有大量持续性任务, 可能后面的队列无法被检索到
// 通过 NextRunTime 属性过滤,已入队任务重启应用后将无法再次被检索到
// 需要借助队列提供者来持久化已入队任务
var waitingJobs = await store.GetWaitingListAsync(options.MaxJobFetchCount);
if (!waitingJobs.Any())

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

@ -17,11 +17,13 @@ public class JobExecutedEvent : JobEventBase<JobExecutedEvent>, ITransientDepend
job.NextRunTime = context.EventData.NextRunTime;
job.LastRunTime = context.EventData.RunTime;
job.Result = context.EventData.Result ?? "OK";
job.Status = JobStatus.Running;
// 一次性任务执行一次后标记为已完成
if (job.JobType == JobType.Once)
{
job.Status = JobStatus.Completed;
job.NextRunTime = null;
}
// 任务异常后可重试
@ -47,6 +49,7 @@ public class JobExecutedEvent : JobEventBase<JobExecutedEvent>, ITransientDepend
{
job.Status = JobStatus.Stopped;
job.IsAbandoned = true;
job.NextRunTime = null;
await RemoveJobAsync(context, job);
}
@ -56,6 +59,7 @@ public class JobExecutedEvent : JobEventBase<JobExecutedEvent>, ITransientDepend
if (job.MaxCount > 0 && job.TriggerCount >= job.MaxCount)
{
job.Status = JobStatus.Completed;
job.NextRunTime = null;
await RemoveJobAsync(context, job);
}

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

@ -1,17 +1,36 @@
namespace LINGYUN.Abp.TaskManagement;
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; }
}

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

@ -1,6 +1,7 @@
using LINGYUN.Abp.BackgroundTasks;
using System;
using Volo.Abp.Data;
using Volo.Abp.Validation;
namespace LINGYUN.Abp.TaskManagement;
@ -17,22 +18,16 @@ public abstract class BackgroundJobInfoCreateOrUpdateDto
/// <summary>
/// 描述
/// </summary>
[DynamicStringLength(typeof(BackgroundJobInfoConsts), nameof(BackgroundJobInfoConsts.MaxDescriptionLength))]
public string Description { get; set; }
/// <summary>
/// 开始时间
/// </summary>
public DateTime BeginTime { get; set; }
/// <summary>
/// 结束时间
/// </summary>
public DateTime? EndTime { get; set; }
/// <summary>
/// 任务类别
/// </summary>
public JobType JobType { get; set; }
/// <summary>
/// Cron表达式,如果是持续任务需要指定
/// </summary>
[DynamicStringLength(typeof(BackgroundJobInfoConsts), nameof(BackgroundJobInfoConsts.MaxCronLength))]
public string Cron { get; set; }
/// <summary>
/// 失败重试上限

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

@ -1,13 +1,14 @@
using LINGYUN.Abp.BackgroundTasks;
using System;
using System.Collections.Generic;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
namespace LINGYUN.Abp.TaskManagement;
public class BackgroundJobInfoDto : ExtensibleAuditedEntityDto<Guid>
public class BackgroundJobInfoDto : ExtensibleAuditedEntityDto<Guid>, IHasConcurrencyStamp
{
public string ConcurrencyStamp { get; set; }
/// <summary>
/// 任务名称
/// </summary>
@ -83,6 +84,10 @@ public class BackgroundJobInfoDto : ExtensibleAuditedEntityDto<Guid>
/// </summary>
public bool IsAbandoned { get; set; }
/// <summary>
/// 是否启用
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// 间隔时间,单位秒,与Cron表达式冲突
/// 默认: 300
/// </summary>

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

@ -55,9 +55,9 @@ public class BackgroundJobInfoGetListInput: PagedAndSortedResultRequestDto
/// </summary>
public bool? IsAbandoned { get; set; }
/// <summary>
/// 是否持续性任务
/// 任务类型
/// </summary>
public bool? IsPeriod { get; set; }
public JobType? JobType { get; set; }
/// <summary>
/// 优先级
/// </summary>

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

@ -17,4 +17,6 @@ public interface IBackgroundJobInfoAppService :
Task PauseAsync(Guid id);
Task ResumeAsync(Guid id);
Task StopAsync(Guid id);
}

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

@ -42,6 +42,10 @@ public class TaskManagementPermissionDefinitionProvider : PermissionDefinitionPr
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,

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 Stop = Default + ".Stop";
}
public static class BackgroundJobLogs

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

@ -27,7 +27,7 @@ public class BackgroundJobInfoAppService : TaskManagementApplicationService, IBa
[Authorize(TaskManagementPermissions.BackgroundJobs.Create)]
public virtual async Task<BackgroundJobInfoDto> CreateAsync(BackgroundJobInfoCreateDto input)
{
if (await BackgroundJobInfoRepository.CheckNameAsync(input.Name, input.Group))
if (await BackgroundJobInfoRepository.CheckNameAsync(input.Group, input.Name))
{
throw new BusinessException(TaskManagementErrorCodes.JobNameAlreadyExists)
.WithData("Group", input.Group)
@ -78,7 +78,7 @@ public class BackgroundJobInfoAppService : TaskManagementApplicationService, IBa
var filter = new BackgroundJobInfoFilter
{
IsAbandoned = input.IsAbandoned,
IsPeriod = input.IsPeriod,
JobType = input.JobType,
BeginCreationTime = input.BeginCreationTime,
EndCreationTime = input.EndCreationTime,
BeginLastRunTime = input.BeginLastRunTime,
@ -124,6 +124,14 @@ public class BackgroundJobInfoAppService : TaskManagementApplicationService, IBa
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)
{
@ -145,6 +153,16 @@ public class BackgroundJobInfoAppService : TaskManagementApplicationService, IBa
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));
foreach (var arg in input.Args)
{
backgroundJobInfo.SetProperty(arg.Key, arg.Value);
}
backgroundJobInfo.SetPriority(input.Priority);
switch (input.JobType)
{
case JobType.Once:

2
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/BackgroundJobInfoConsts.cs

@ -5,7 +5,7 @@ public static class BackgroundJobInfoConsts
public static int MaxCronLength { get; set; } = 50;
public static int MaxNameLength { get; set; } = 100;
public static int MaxGroupLength { get; set; } = 50;
public static int MaxTypeLength { get; set; } = 200;
public static int MaxTypeLength { get; set; } = 1000;
public static int MaxDescriptionLength { get; set; } = 255;
public static int MaxResultLength { get; set; } = 1000;
}

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

@ -9,8 +9,69 @@
"Permissions:TriggerJob": "Trigger Job",
"Permissions:PauseJob": "Pause Job",
"Permissions:ResumeJob": "Resume Job",
"Permissions:StopJob": "Stop Job",
"Permissions:BackgroundJobLogs": "BackgroundJobs Logs",
"Permissions:DeleteJobLogs": "Delete Job Logs",
"TaskManagement:01000": "A job named {Name} already exists in the Group {Group}!"
"TaskManagement:01000": "A job named {Name} already exists in the Group {Group}!",
"TaskManagement:01001": "The queue did not find the job named {Name} in Group {Group}. Please join the job first!",
"DisplayName:IsEnabled": "IsEnabled",
"DisplayName:Group": "Group",
"DisplayName:Name": "Name",
"DisplayName:Description": "Description",
"DisplayName:Type": "Type",
"Description:Type": "Type of job implementer, format: type full name, assembly, example: System.String, mscorlib",
"DisplayName:Status": "Status",
"DisplayName:BeginTime": "BeginTime",
"DisplayName:EndTime": "EndTime",
"DisplayName:JobType": "JobType",
"Description:JobType": "One-off jobs are executed only once, periodic jobs are executed periodically based on the given conditions, and persistent jobs are executed repeatedly based on the given conditions",
"DisplayName:Interval": "Interval (it seconds)",
"Description:Interval": "One-off jobs are triggered after a given period of time, and persistent jobs are triggered at intervals",
"DisplayName:BeginLastRunTime": "From LastRunTime",
"DisplayName:EndLastRunTime": "To LastRunTime",
"DisplayName:Priority": "Priority",
"Description:Priority": "The priority of a job execution affects the execution sequence of the job",
"DisplayName:LockTimeOut": "Lock timeout (it seconds)",
"Description:LockTimeOut": "If the job is not allowed to be fired at the same time, it will work, allowing other jobs to proceed after a certain number of seconds",
"DisplayName:BeginCreationTime": "From CreationTime",
"DisplayName:EndCreationTime": "To CreationTime",
"DisplayName:IsAbandoned": "Is Abandoned",
"DisplayName:CreationTime": "CreationTime",
"DisplayName:Result": "Last Execution Result",
"DisplayName:LastRunTime": "LastRunTime",
"DisplayName:NextRunTime": "NextRunTime",
"DisplayName:Cron": "Cron expression",
"Description:Cron": "Category is valid when periodic job, reference: https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/crontrigger.html#introduction",
"DisplayName:MaxCount": "Max Count",
"Description:MaxCount": "The maximum number of triggers allowed to stop the job",
"DisplayName:TriggerCount": "Trigger Count",
"DisplayName:TryCount": "Try Count",
"DisplayName:MaxTryCount": "Maximum retry count",
"Description:MaxTryCount": "The maximum number of retries when an error occurs during job execution, at which job retries are stopped and marked abandoned",
"DisplayName:Key": "Key",
"DisplayName:Value": "Value",
"DisplayName:None": "None",
"DisplayName:Completed": "Completed",
"DisplayName:Running": "Running",
"DisplayName:Paused": "Paused",
"DisplayName:Stopped": "Stopped",
"DisplayName:Once": "Once",
"DisplayName:Period": "Period",
"DisplayName:Persistent": "Persistent",
"DisplayName:Low": "Low",
"DisplayName:BelowNormal": "Below Normal",
"DisplayName:Normal": "Normal",
"DisplayName:AboveNormal": "Above Normal",
"DisplayName:High": "High",
"BackgroundJobs": "Jobs",
"BackgroundJobs:AddNew": "New Job",
"BackgroundJobs:Edit": "Edit Job",
"BackgroundJobs:AddNewArg": "New Args",
"BackgroundJobs:Pause": "Pause",
"BackgroundJobs:Resume": "Resume",
"BackgroundJobs:Trigger": "Trigger",
"BackgroundJobs:Stop": "Stop",
"BasicInfo": "Basic",
"Paramters": "Paramters"
}
}

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

@ -9,8 +9,69 @@
"Permissions:TriggerJob": "触发作业",
"Permissions:PauseJob": "暂停作业",
"Permissions:ResumeJob": "恢复作业",
"Permissions:StopJob": "停止作业",
"Permissions:BackgroundJobLogs": "日志管理",
"Permissions:DeleteJobLogs": "删除作业日志",
"TaskManagement:01000": "分组 {Group} 中已经存在一个名称为 {Name} 的作业!"
"TaskManagement:01000": "分组 {Group} 中已经存在一个名称为 {Name} 的作业!",
"TaskManagement:01001": "队列没有找到分组 {Group} 中名称为 {Name} 的作业, 请先将作业入队!",
"DisplayName:IsEnabled": "是否启用",
"DisplayName:Group": "分组",
"DisplayName:Name": "名称",
"DisplayName:Description": "描述",
"DisplayName:Type": "类型",
"Description:Type": "作业实现者的类型,格式: 类型全名, 程序集名称, 示例: System.String, mscorlib",
"DisplayName:Status": "状态",
"DisplayName:BeginTime": "开始时间",
"DisplayName:EndTime": "结束时间",
"DisplayName:JobType": "类别",
"Description:JobType": "一次性作业只会执行一次, 周期性作业按照给定条件周期性运行, 持续性任务按照给定条件重复运行",
"DisplayName:Interval": "时间间隔(秒)",
"Description:Interval": "一次性作业在给定时间段后触发,持续性作业按照间隔时间触发",
"DisplayName:BeginLastRunTime": "起始运行时间",
"DisplayName:EndLastRunTime": "截止运行时间",
"DisplayName:Priority": "优先级",
"Description:Priority": "作业执行的优先级, 优先级高低影响作业的执行顺序",
"DisplayName:LockTimeOut": "锁定超时时间(秒)",
"Description:LockTimeOut": "如果不允许作业被同时触发,它将会起作用,在一定数值秒之后允许其他作业进行",
"DisplayName:BeginCreationTime": "起始创建时间",
"DisplayName:EndCreationTime": "截止创建时间",
"DisplayName:IsAbandoned": "是否放弃",
"DisplayName:CreationTime": "创建时间",
"DisplayName:Result": "上次执行结果",
"DisplayName:LastRunTime": "上次执行时间",
"DisplayName:NextRunTime": "下次执行时间",
"DisplayName:Cron": "Cron表达式",
"Description:Cron": "类别为周期性作业时有效, 参考: https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/crontrigger.html#introduction",
"DisplayName:MaxCount": "最大触发次数",
"Description:MaxCount": "允许的最大触发次数,达到此数值将停止作业运行",
"DisplayName:TriggerCount": "触发次数",
"DisplayName:TryCount": "尝试触发次数",
"DisplayName:MaxTryCount": "最大重试次数",
"Description:MaxTryCount": "当作业执行中出现错误时的最大重试次数,达到此数值时将停止作业重试并标记为已放弃",
"DisplayName:Key": "参数名",
"DisplayName:Value": "参数值",
"DisplayName:None": "未定义",
"DisplayName:Completed": "已完成",
"DisplayName:Running": "运行中",
"DisplayName:Paused": "已暂停",
"DisplayName:Stopped": "已停止",
"DisplayName:Once": "一次性",
"DisplayName:Period": "周期性",
"DisplayName:Persistent": "持续性",
"DisplayName:Low": "低",
"DisplayName:BelowNormal": "低于正常",
"DisplayName:Normal": "正常",
"DisplayName:AboveNormal": "高于正常",
"DisplayName:High": "高",
"BackgroundJobs": "作业列表",
"BackgroundJobs:AddNew": "新建作业",
"BackgroundJobs:Edit": "编辑作业",
"BackgroundJobs:AddNewArg": "添加参数",
"BackgroundJobs:Pause": "暂停",
"BackgroundJobs:Resume": "恢复",
"BackgroundJobs:Trigger": "触发",
"BackgroundJobs:Stop": "停止",
"BasicInfo": "基本信息",
"Paramters": "参数列表"
}
}

2
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/TaskManagementErrorCodes.cs

@ -5,5 +5,7 @@
public const string Namespace = "TaskManagement";
public const string JobNameAlreadyExists = Namespace + ":01000";
public const string JobNotFoundInQueue = Namespace + ":01001";
}
}

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

@ -180,4 +180,9 @@ public class BackgroundJobInfo : AuditedAggregateRoot<Guid>
{
Status = status;
}
public void SetPriority(JobPriority priority)
{
Priority = priority;
}
}

2
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain/LINGYUN/Abp/TaskManagement/BackgroundJobInfoFilter.cs

@ -59,7 +59,7 @@ public class BackgroundJobInfoFilter
/// <summary>
/// 是否持续性任务
/// </summary>
public bool? IsPeriod { get; set; }
public JobType? JobType { get; set; }
/// <summary>
/// 优先级
/// </summary>

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

@ -92,6 +92,13 @@ public class BackgroundJobManager : DomainService
public virtual async Task TriggerAsync(BackgroundJobInfo jobInfo)
{
var job = ObjectMapper.Map<BackgroundJobInfo, JobInfo>(jobInfo);
//if (!await JobScheduler.ExistsAsync(job))
//{
// throw new BusinessException(TaskManagementErrorCodes.JobNotFoundInQueue)
// .WithData("Group", job.Group)
// .WithData("Name", job.Name);
//}
await JobScheduler.TriggerAsync(job);
}
@ -99,11 +106,31 @@ public class BackgroundJobManager : DomainService
{
var job = ObjectMapper.Map<BackgroundJobInfo, JobInfo>(jobInfo);
await JobScheduler.PauseAsync(job);
jobInfo.SetStatus(JobStatus.Paused);
jobInfo.SetNextRunTime(null);
await BackgroundJobInfoRepository.UpdateAsync(jobInfo);
}
public virtual async Task ResumeAsync(BackgroundJobInfo jobInfo)
{
var job = ObjectMapper.Map<BackgroundJobInfo, JobInfo>(jobInfo);
await JobScheduler.ResumeAsync(job);
jobInfo.SetStatus(JobStatus.Running);
await BackgroundJobInfoRepository.UpdateAsync(jobInfo);
}
public virtual async Task StopAsync(BackgroundJobInfo jobInfo)
{
var job = ObjectMapper.Map<BackgroundJobInfo, JobInfo>(jobInfo);
await JobScheduler.RemoveAsync(job);
jobInfo.SetStatus(JobStatus.Stopped);
jobInfo.SetNextRunTime(null);
await BackgroundJobInfoRepository.UpdateAsync(jobInfo);
}
}

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

@ -75,8 +75,10 @@ public class BackgroundJobStore : IJobStore, ITransientDependency
jobInfo.EndTime,
jobInfo.Priority,
jobInfo.MaxCount,
jobInfo.MaxTryCount);
jobInfo.MaxTryCount)
{
IsEnabled = true
};
backgroundJobInfo.SetNextRunTime(jobInfo.NextRunTime);
backgroundJobInfo.SetLastRunTime(jobInfo.LastRunTime);
backgroundJobInfo.SetStatus(jobInfo.Status);

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

@ -55,8 +55,9 @@ public class EfCoreBackgroundJobInfoRepository :
{
return await (await GetDbSetAsync())
.Where(x => x.IsEnabled && !x.IsAbandoned)
.Where(x => x.JobType == JobType.Period && x.Status == JobStatus.Running)
.Where(x => x.TriggerCount < x.MaxCount && x.TryCount < x.MaxTryCount)
.Where(x => x.JobType == JobType.Period && x.Status == JobStatus.Running && !x.NextRunTime.HasValue)
.Where(x => x.MaxCount == 0 || x.TriggerCount < x.MaxCount)
.Where(x => x.MaxTryCount == 0 || x.TryCount < x.MaxTryCount)
.OrderByDescending(x => x.Priority)
.ToListAsync(GetCancellationToken(cancellationToken));
}
@ -69,8 +70,7 @@ public class EfCoreBackgroundJobInfoRepository :
.WhereIf(!filter.Name.IsNullOrWhiteSpace(), x => x.Name.Equals(filter.Name))
.WhereIf(!filter.Filter.IsNullOrWhiteSpace(), x => x.Name.Contains(filter.Filter) ||
x.Group.Contains(filter.Filter) || x.Type.Contains(filter.Filter) || x.Description.Contains(filter.Filter))
.WhereIf(filter.IsPeriod.HasValue && filter.IsPeriod.Value, x => x.JobType == JobType.Period)
.WhereIf(filter.IsPeriod.HasValue && !filter.IsPeriod.Value, x => x.JobType != JobType.Period)
.WhereIf(filter.JobType.HasValue, x => x.JobType == filter.JobType)
.WhereIf(filter.Priority.HasValue, x => x.Priority == filter.Priority.Value)
.WhereIf(filter.Status.HasValue, x => x.Status == filter.Status.Value)
.WhereIf(filter.IsAbandoned.HasValue, x => x.IsAbandoned == filter.IsAbandoned.Value)
@ -91,8 +91,7 @@ public class EfCoreBackgroundJobInfoRepository :
.WhereIf(!filter.Name.IsNullOrWhiteSpace(), x => x.Name.Equals(filter.Name))
.WhereIf(!filter.Filter.IsNullOrWhiteSpace(), x => x.Name.Contains(filter.Filter) ||
x.Group.Contains(filter.Filter) || x.Type.Contains(filter.Filter) || x.Description.Contains(filter.Filter))
.WhereIf(filter.IsPeriod.HasValue && filter.IsPeriod.Value, x => !string.IsNullOrWhiteSpace(x.Cron))
.WhereIf(filter.IsPeriod.HasValue && !filter.IsPeriod.Value, x => string.IsNullOrWhiteSpace(x.Cron))
.WhereIf(filter.JobType.HasValue, x => x.JobType == filter.JobType)
.WhereIf(filter.Status.HasValue, x => x.Status == filter.Status.Value)
.WhereIf(filter.Priority.HasValue, x => x.Priority == filter.Priority.Value)
.WhereIf(filter.IsAbandoned.HasValue, x => x.IsAbandoned == filter.IsAbandoned.Value)
@ -113,8 +112,9 @@ public class EfCoreBackgroundJobInfoRepository :
return await (await GetDbSetAsync())
.Where(x => x.IsEnabled && !x.IsAbandoned)
.Where(x => x.JobType != JobType.Period && x.Status == JobStatus.Running)
.Where(x => x.TriggerCount < x.MaxCount && x.TryCount < x.MaxTryCount)
.Where(x => x.JobType != JobType.Period && x.Status == JobStatus.Running && !x.NextRunTime.HasValue)
.Where(x => x.MaxCount == 0 || x.TriggerCount < x.MaxCount)
.Where(x => x.MaxTryCount == 0 || x.TryCount < x.MaxTryCount)
.OrderByDescending(x => x.Priority)
.ThenBy(x => x.TryCount)
.ThenBy(x => x.NextRunTime)

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

@ -74,6 +74,14 @@ public class BackgroundJobInfoController : TaskManagementController, IBackground
return BackgroundJobInfoAppService.TriggerAsync(id);
}
[HttpPut]
[Route("{id}/stop")]
[Authorize(TaskManagementPermissions.BackgroundJobs.Stop)]
public Task StopAsync(Guid id)
{
return BackgroundJobInfoAppService.StopAsync(id);
}
[HttpPut]
[Route("{id}")]
[Authorize(TaskManagementPermissions.BackgroundJobs.Update)]

193
aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Migrations/20220110104046_Reset-Field-Type-Length-With-Background-Job-Info.Designer.cs

@ -0,0 +1,193 @@
// <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("20220110104046_Reset-Field-Type-Length-With-Background-Job-Info")]
partial class ResetFieldTypeLengthWithBackgroundJobInfo
{
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<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.HasKey("Id");
b.HasIndex("JobGroup", "JobName");
b.ToTable("TK_BackgroundJobLogs", (string)null);
});
#pragma warning restore 612, 618
}
}
}

65
aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Migrations/20220110104046_Reset-Field-Type-Length-With-Background-Job-Info.cs

@ -0,0 +1,65 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LY.MicroService.TaskManagement.Migrations
{
public partial class ResetFieldTypeLengthWithBackgroundJobInfo : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Type",
table: "TK_BackgroundJobs",
type: "varchar(1000)",
maxLength: 1000,
nullable: false,
oldClrType: typeof(string),
oldType: "varchar(200)",
oldMaxLength: 200)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "JobType",
table: "TK_BackgroundJobLogs",
type: "varchar(1000)",
maxLength: 1000,
nullable: true,
oldClrType: typeof(string),
oldType: "varchar(200)",
oldMaxLength: 200,
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Type",
table: "TK_BackgroundJobs",
type: "varchar(200)",
maxLength: 200,
nullable: false,
oldClrType: typeof(string),
oldType: "varchar(1000)",
oldMaxLength: 1000)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "JobType",
table: "TK_BackgroundJobLogs",
type: "varchar(200)",
maxLength: 200,
nullable: true,
oldClrType: typeof(string),
oldType: "varchar(1000)",
oldMaxLength: 1000,
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
}
}
}

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

@ -131,8 +131,8 @@ namespace LY.MicroService.TaskManagement.Migrations
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("varchar(200)")
.HasMaxLength(1000)
.HasColumnType("varchar(1000)")
.HasColumnName("Type");
b.HasKey("Id");
@ -167,8 +167,8 @@ namespace LY.MicroService.TaskManagement.Migrations
.HasColumnName("JobName");
b.Property<string>("JobType")
.HasMaxLength(200)
.HasColumnType("varchar(200)")
.HasMaxLength(1000)
.HasColumnType("varchar(1000)")
.HasColumnName("JobType");
b.Property<string>("Message")

2
aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Properties/launchSettings.json

@ -12,7 +12,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://127.0.0.1:30040",
"applicationUrl": "http://0.0.0.0:30040",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

35
gateways/internal/LINGYUN.MicroService.Internal.ApiGateway/src/LINGYUN.MicroService.Internal.ApiGateway/ocelot.Development.json

@ -1370,6 +1370,41 @@
"UseTracing": true
}
},
{
"DownstreamPathTemplate": "/api/task-management/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "127.0.0.1",
"Port": 30040
}
],
"UpstreamPathTemplate": "/api/task-management/{everything}",
"UpstreamHttpMethod": [
"GET",
"POST",
"PUT",
"DELETE"
],
"LoadBalancerOptions": {
"Type": "RoundRobin"
},
"RateLimitOptions": {
"ClientWhitelist": [],
"EnableRateLimiting": true,
"Period": "1s",
"PeriodTimespan": 1,
"Limit": 5
},
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 10,
"DurationOfBreak": 1000,
"TimeoutValue": 10000
},
"HttpHandlerOptions": {
"UseTracing": true
}
},
{
"DownstreamPathTemplate": "/api/setting-management/oss-management/by-current-tenant",
"DownstreamScheme": "http",

35
gateways/internal/LINGYUN.MicroService.Internal.ApiGateway/src/LINGYUN.MicroService.Internal.ApiGateway/ocelot.platform.json

@ -180,6 +180,41 @@
"UseTracing": true
}
},
{
"DownstreamPathTemplate": "/api/task-management/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "127.0.0.1",
"Port": 30040
}
],
"UpstreamPathTemplate": "/api/task-management/{everything}",
"UpstreamHttpMethod": [
"GET",
"POST",
"PUT",
"DELETE"
],
"LoadBalancerOptions": {
"Type": "RoundRobin"
},
"RateLimitOptions": {
"ClientWhitelist": [],
"EnableRateLimiting": true,
"Period": "1s",
"PeriodTimespan": 1,
"Limit": 5
},
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 10,
"DurationOfBreak": 1000,
"TimeoutValue": 10000
},
"HttpHandlerOptions": {
"UseTracing": true
}
},
//
{
"DownstreamPathTemplate": "/api/setting-management/oss-management/by-current-tenant",

Loading…
Cancel
Save